图像形态学是根据图像的形状进行的操作,它可以从图像中提取出用于表示图像形状最基本的信息,使得计算机能够更好的理解和识别图像信息。
形态学操作一般针对的是二值图像进行操作。这里简单说下二值图、灰度图、彩色图的区别:
- 二值图:图像中的每个像素值为 0 或者 255,即:每个像素点要不是 0 、要不是 255
- 彩色图:由 R、G、B 三原色组成的图像。即:有 3 个通道,每个通道像素值的取值范围为 0~255
- 灰度图:灰度图只有 1 个通道,每个像素的取值范围为 0 ~ 255
下面是是一段生成三种图像的示例代码:
import numpy as np import matplotlib.pyplot as plt if __name__ == '__main__': # 固定随机数种子 np.random.seed(0) # 生成图像尺寸 size = (16, 16) # 1. 二值图 image1 = np.random.choice([0, 255], size=size) # 2. 灰度图 image2 = np.random.randint(0, 256, size=size) # 3. 彩色图 size += (3,) # 通道数 image3 = np.random.randint(0, 256, size=size) # imshow 函数默认显示 RGB(A) 彩色图,通过指定 cmap='gray' 显示灰度图 for index, (image, title) in enumerate([(image1, '二值图'), (image2, '灰度图'), (image3, '彩色图')]): plt.subplot(131 + index) plt.imshow(image, cmap='gray') plt.title(title) plt.axis('off') plt.show()
程序输出结果:
灰度图和二值图看起来都是黑白图,不同的是灰度图的每个像素点是有明亮变化的。
1. 腐蚀和膨胀
腐蚀: 卷积核对应的原图像的像素值中全部是 1,则中心元素的像素值就是 1,否则为 0。这对于去除白噪声很有用,也可以用来断开两个连在一块的物体等。
膨胀: 卷积核对应的原图像的像素值中只要有一个是 1,则中心元素的像素值就是 1,否则为 0。腐蚀去掉白噪声的同时,也会使前景对象变小。膨胀使前景对象变大,它也可以用来连接两个分开的物体。
import numpy as np import matplotlib.pyplot as plt import cv2 def test(): np.random.seed(0) images = [] # 读取图像 orign_image = cv2.imread('demo.png', cv2.IMREAD_GRAYSCALE) images.append((orign_image, "原图")) # 添加噪声 h, w = orign_image.shape noise_num = 200000 hp = np.random.randint(0, h, size=(noise_num,)) wp = np.random.randint(0, w, size=(noise_num,)) noise_image = np.copy(orign_image) for n1, n2 in zip(hp, wp): noise_image[n1][n2] = 255 images.append((noise_image, "噪声")) # 腐蚀操作 # kernel size 越大腐蚀程度越强 kernel = np.ones((5, 5), np.int8) # iterations 表示重复多少次腐蚀或者膨胀 erode_image = cv2.erode(noise_image, kernel, iterations=2) images.append((erode_image, "腐蚀")) # 膨胀操作 dilate_image = cv2.dilate(erode_image, kernel, iterations=2) images.append((dilate_image, "膨胀")) # 图像显示 for index, (image, title) in enumerate(images): plt.subplot(141 + index) plt.imshow(image, cmap='gray') plt.title(title) plt.axis('off') plt.show() if __name__ == '__main__': test()
程序输出结果:
2. 开闭运算
开运算:先腐蚀再膨胀,它可以被用来去除噪声。例如上面的例子一样,先腐蚀之后去除了噪声,但是对象会变小,再进行膨胀,增大对象,从而去除噪声。
闭运算:先膨胀再腐蚀。它可以被用来填充前景物体中的小洞,或者前景物体上的小黑点。下面的例子演示下这个效果。
import numpy as np import matplotlib.pyplot as plt import cv2 def test(): np.random.seed(0) images = [] # 读取图像 orign_image = cv2.imread('demo.png', cv2.IMREAD_GRAYSCALE) images.append((orign_image, "原图")) # 添加噪声 h, w = orign_image.shape hp, wp = range(0, h), range(0, w) noise_image = np.copy(orign_image) for x in hp: for y in wp: if noise_image[x][y] == 255: if np.random.randint(1, 11) <= 2: noise_image[x][y] = 0 images.append((noise_image, "噪声")) # 闭运算 kernel = np.ones((5, 5), np.int8) # op=cv2.MORPH_OPEN 参数指定开运算 # op=cv2.MORPH_CLOSE 参数指定闭运算 open_image = cv2.morphologyEx(noise_image, op=cv2.MORPH_CLOSE, kernel=kernel, iterations=2) images.append((open_image, "闭运算")) # 图像显示 for index, (image, title) in enumerate(images): plt.subplot(131 + index) plt.imshow(image, cmap='gray') plt.title(title) plt.axis('off') plt.show() if __name__ == '__main__': test()
程序执行结果:
从这里可以看到,开运算可以去除背景中的噪声,闭运算则可以去除前景中的噪声。
3. 形态学梯度
形态学梯度:图像膨胀与腐蚀的差,结果看上去就像前景物体的轮廓。梯度用于描述目标边界灰度剧烈变化的区域,突出高亮区域的边界。
import cv2 import numpy as np import matplotlib.pyplot as plt def test(): images = [] image = cv2.imread('demo.png', cv2.IMREAD_GRAYSCALE) images.append((image, '原图')) kernel = np.ones(shape=(5, 5)) image = cv2.morphologyEx(image, op=cv2.MORPH_GRADIENT, kernel=kernel, iterations=1) images.append((image, '梯度')) # 图像显示 for index, (image, title) in enumerate(images): plt.subplot(121 + index) plt.imshow(image, cmap='gray') plt.title(title) plt.axis('off') plt.show() if __name__ == '__main__': test()
程序输出结果:
4. 顶帽和黑帽
顶帽:原图与开运算之后得到的差,用于显示开运算忽略的图像信息。
黑帽:原图与闭运算之后得到的差,用于显示闭运算忽略的图像信息。
4.1 顶帽示例
import numpy as np import matplotlib.pyplot as plt import cv2 def test(): np.random.seed(0) images = [] # 读取图像 orign_image = cv2.imread('demo.png', cv2.IMREAD_GRAYSCALE) # 添加噪声 h, w = orign_image.shape noise_num = 150000 hp = np.random.randint(0, h, size=(noise_num,)) wp = np.random.randint(0, w, size=(noise_num,)) noise_image = np.copy(orign_image) for n1, n2 in zip(hp, wp): noise_image[n1][n2] = 255 images.append((noise_image, "原图")) # 开运算 kernel = np.ones((5, 5), np.int8) open_image = cv2.morphologyEx(noise_image, op=cv2.MORPH_OPEN, kernel=kernel, iterations=2) images.append((open_image, "开运算")) # 顶帽 tophat_image = cv2.morphologyEx(noise_image, op=cv2.MORPH_TOPHAT, kernel=kernel, iterations=2) images.append((tophat_image, "顶帽")) # 图像显示 for index, (image, title) in enumerate(images): plt.subplot(131 + index) plt.imshow(image, cmap='gray') plt.title(title) plt.axis('off') plt.show() if __name__ == '__main__': test()
程序执行结果:
4.2 黑帽示例
import numpy as np import matplotlib.pyplot as plt import cv2 def test(): np.random.seed(0) images = [] # 读取图像 orign_image = cv2.imread('demo.png', cv2.IMREAD_GRAYSCALE) # 添加噪声 h, w = orign_image.shape hp, wp = range(0, h), range(0, w) noise_image = np.copy(orign_image) for x in hp: for y in wp: if noise_image[x][y] == 255: if np.random.randint(1, 11) <= 2: noise_image[x][y] = 0 images.append((noise_image, "原图")) # 闭运算 kernel = np.ones((5, 5), np.int8) close_image = cv2.morphologyEx(noise_image, op=cv2.MORPH_CLOSE, kernel=kernel, iterations=2) images.append((close_image, "闭运算")) # 黑帽 black_image = cv2.morphologyEx(noise_image, op=cv2.MORPH_BLACKHAT, kernel=kernel, iterations=2) images.append((black_image, "黑帽")) # 图像显示 for index, (image, title) in enumerate(images): plt.subplot(131 + index) plt.imshow(image, cmap='gray') plt.title(title) plt.axis('off') plt.show() if __name__ == '__main__': test()
程序执行结果: