形态学操作
形态学,即数学形态学(Mathematical Morphology),是图像处理过程中一个非常重要的研 究方向。形态学主要从图像内提取分量信息,该分量信息通常对于表达和描绘图像的形状具有 重要意义,通常是图像理解时所使用的最本质的形状特征。例如,在识别手写数字时,能够通 过形态学运算得到其骨架信息,在具体识别时,仅针对其骨架进行运算即可。形态学处理在视 觉检测、文字识别、医学图像处理、图像压缩编码等领域都有非常重要的应用。
形态学操作主要包含:腐蚀、膨胀、开运算、闭运算、形态学梯度(Morphological Gradient) 运算、顶帽运算(礼帽运算)、黑帽运算等操作。腐蚀操作和膨胀操作是形态学运算的基础, 将腐蚀和膨胀操作进行结合,就可以实现开运算、闭运算、形态学梯度运算、顶帽运算、黑帽 运算、击中击不中等不同形式的运算。
一,腐蚀
腐蚀是最基本的形态学操作之一,它能够将图像的边界点消除,使图像沿着边界向内收缩, 也可以将小于指定结构体元素的部分去除。
1.原理
整幅图像的背景色是黑色的,前景对象是一个白色的圆形。图像左上角的深色小方块是遍历图像所使用的结构元。在腐蚀过程中,要将该结构元逐个像素地遍历整 幅图像,并根据结构元与被腐蚀图像的关系,来确定腐蚀结果图像中对应结构元中心点位置的 像素点的值。
需要注意的是,腐蚀操作等形态学操作是逐个像素地来决定值的,每次判定的点都是与结构元中心点所对应的点。
- 如果结构元完全处于前景图像中(图 8-3 的左图),就将结构元中心点所对应的腐蚀结 果图像中的像素点处理为前景色(白色,像素点的像素值为 1)。
- 如果结构元未完全处于前景图像中(可能部分在,也可能完全不在,图 8-3 的右图), 就将结构元中心点对应的腐蚀结果图像中的像素点处理为背景色(黑色,像素点的像素 值为 0)。
针对图 8-3 中的图像,腐蚀的结果就是前景色的白色圆直径变小。上述结构元也被称为核。 例如,有需要被腐蚀的图像 img,其值如下,其中 1 表示白色前景,0 表示黑色背景:
核的结构为:
腐蚀其实就是原图像通过核来逐个计算每个像素,最后得到腐蚀图像。包括下文中的膨胀原理也大差不大,你可以简单地将膨胀理解为腐蚀的逆,只不过膨胀对点的处理不同而已。但是核的结构也是一致的。
2.代码示例
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5,5),np.uint8)
img_erosion = cv2.erode(img,kernel,iterations=2)
plt.subplot(331),plt.imshow(img),plt.title("Origin")
plt.subplot(332),plt.imshow(img_erosion),plt.title("Erosion")
plt.show()
cv2.waitKey(0)
对比两幅图片,我们发现腐蚀操作明显将毛刺给过滤腐蚀掉了,但同时我们也发现了字体瘦了一圈,这是腐蚀操作不得不带来的。接下来我们讲解一下上述代码中的函数。
首先我们用imread读取目标图片,通过np.ones生成一个5x5的核,根据原理我们知道里面的值要求为1,接下来调用erode腐蚀函数对图片进行处理,参数基本只要写上述的三个(源,核,迭迭代次数)。接下来就是用matplotlib绘制出来就可以了。当然,它还有个参数borderType,可以参考了解一下:
二,膨胀
膨胀操作是形态学中另外一种基本的操作。膨胀操作和腐蚀操作的作用是相反的,膨胀操 作能对图像的边界进行扩张。膨胀操作将与当前对象(前景)接触到的背景点合并到当前对象 内,从而实现将图像的边界点向外扩张。如果图像内两个对象的距离较近,那么在膨胀的过程 中,两个对象可能会连通在一起。膨胀操作对填补图像分割后图像内所存在的空白相当有帮助。
1.原理
- 如果结构元中任意一点处于前景图像中,就将膨胀结果图像中对应像素点处理为前景色。
- 如果结构元完全处于背景图像外,就将膨胀结果图像中对应像素点处理为背景色。
这就是膨胀和腐蚀之间的差别,其他的都差不多,就是对点的处理不一样。
2.代码示例
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('manolo_1.jpg')
circle = cv2.imread('circle.jpg')
kernel = np.ones((5,5),np.uint8)
img_erosion = cv2.erode(img,kernel,iterations=2)
img_dilate = cv2.dilate(img_erosion,kernel,iterations=2)
circle_dilate_1 = cv2.dilate(circle,kernel=kernel,iterations=1)
circle_dilate_2 = cv2.dilate(circle,kernel=kernel,iterations=3)
circle_dilate_3 = cv2.dilate(circle,kernel=kernel,iterations=10)
plt.subplot(331),plt.imshow(img),plt.title("Origin")
plt.subplot(332),plt.imshow(img_erosion),plt.title("Erosion")
plt.subplot(333),plt.imshow(img_dilate),plt.title("Erosion-Dilate")
plt.subplot(334),plt.imshow(circle_dilate_1),plt.title("Circle_Dilate_1")
plt.subplot(335),plt.imshow(circle_dilate_2),plt.title("Circle_Dilate_2")
plt.subplot(336),plt.imshow(circle_dilate_3),plt.title("Circle_Dilate_3")
plt.show()
cv2.waitKey(0)
我们看到第三幅图像,发现它既保留了原有的大小又除去了毛刺,这是因为我们先使用了腐蚀再使用了膨胀,这个过程我们后续称为开运算。
三,形态学函数
腐蚀操作和膨胀操作是形态学运算的基础,将腐蚀和膨胀操作进行组合,就可以实现开运 算、闭运算(关运算)、形态学梯度(Morphological Gradient)运算、礼帽运算(顶帽运算)、 黑帽运算、击中击不中等多种不同形式的运算。
dst = cv2.morphologyEx( src, op, kernel[, anchor[, iterations[, borderType[, borderValue]]]]] )
形态学函数很好理解:它相当于把我们本章所有内容融合到了一个函数中,我们可以设置op的值来选择我们的形态学操作:比如上文的erode和dilate函数可以直接用morphologyEx()函数调用。当然,也包含我们后续的开运算,闭运算,梯度运算,礼帽运算(顶帽),黑帽运算。
接下来我们用形态学函数来讲解内容。
四,开运算
开运算进行的操作是先将图像腐蚀,再对腐蚀的结果进行膨胀。开运算可以用于去噪、计 数等。
开运算 = erode + dilate
1.代码示例
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img_open = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)#cv2.MORPH_OPEN 开运算:先腐蚀,再膨胀
img_close = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)#cv2.MORPH_CLOSE 闭运算,先膨胀,再腐蚀
#对比
plt.subplot(121),plt.imshow(img_open),plt.title("OPEN")
plt.subplot(122),plt.imshow(img_close),plt.title("CLOSE")
plt.show()
cv2.waitKey(0)
这里由于开运算和闭运算只是顺序不同,所以我把开运算和闭运算结果放在一起了,我们先看开运算,这个结果其实和先腐蚀后膨胀差不多,但是效果差了一点。
五,闭运算
闭运算是先膨胀、后腐蚀的运算,它有助于关闭前景物体内部的小孔,或去除物体上的小 黑点,还可以将不同的前景图像进行连接。
闭运算 = dilate + erode
1.代码示例
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img_open = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)#cv2.MORPH_OPEN 开运算:先腐蚀,再膨胀
img_close = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)#cv2.MORPH_CLOSE 闭运算,先膨胀,再腐蚀
#对比
plt.subplot(121),plt.imshow(img_open),plt.title("OPEN")
plt.subplot(122),plt.imshow(img_close),plt.title("CLOSE")
plt.show()
cv2.waitKey(0)
现在我们看第二幅图像,这明显就没有把毛刺除去,因为先膨胀,毛刺也随之变大,再腐蚀的话毛刺就不会除干净了。
六,梯度运算
形态学梯度运算是用图像的膨胀图像减腐蚀图像的操作,该操作可以获取原始图像中前景图像的边缘。
梯度 = dilate - erode
我们可以想象到图片应该是一个边框对吧?接下来我们来验证一下猜想。
1.代码示例
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img_gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
plt.subplot(131),plt.imshow(img_gradient),plt.title("Gradient")
plt.show()
cv2.waitKey(0)
和我们的猜想差不多,不过比较可惜:毛刺没有去掉。我们可以先调用开运算再来一次梯度运算,应该就可以得到干净的边框吧?
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img = cv2.erode(img,kernel,iterations=2)
img = cv2.dilate(img,kernel,iterations=2)
img_gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
plt.subplot(131),plt.imshow(img_gradient),plt.title("Gradient")
plt.show()
cv2.waitKey(0)
七,礼帽运算
礼帽运算是用原始图像减去其开运算图像的操作。礼帽运算能够获取图像的噪声信息,或者得到比原始图像的边缘更亮的边缘信息。
我们再代码验证前可以想想:开运算图像相当于除去毛刺的原图像吧?所以用原图像减去开运算图像应该只剩毛刺对吗?
1.代码示例
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img = cv2.erode(img,kernel,iterations=2)
img = cv2.dilate(img,kernel,iterations=2)
img_gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
img_tophat = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel)
plt.subplot(131),plt.imshow(img_gradient),plt.title("Gradient")
plt.subplot(132),plt.imshow(img_tophat),plt.title("TOPHAT")
plt.show()
cv2.waitKey(0)
为什么第二幅图像是空的?我们仔细观察代码发现,原来是我们之前已经对原图像进行过开运算操作了,所以相减后没有图像了。我们删除掉开运算处理的代码。
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img_gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
img_tophat = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel)
plt.subplot(131),plt.imshow(img_gradient),plt.title("Gradient")
plt.subplot(132),plt.imshow(img_tophat),plt.title("TOPHAT")
plt.show()
cv2.waitKey(0)
这下和我们的预想是一致的。
八,黑帽运算
黑帽运算是用闭运算图像减去原始图像的操作。黑帽运算能够获取图像内部的小孔,或前景色中的小黑点,或者得到比原始图像的边缘更暗的边缘部分。
1.代码示例
img = cv2.imread('manolo_1.jpg')
kernel = np.ones((5, 5), np.uint8)
img_gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
img_tophat = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel)
img_blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT,kernel)
plt.subplot(131),plt.imshow(img_gradient),plt.title("Gradient")
plt.subplot(132),plt.imshow(img_tophat),plt.title("TOPHAT")
plt.subplot(133),plt.imshow(img_blackhat),plt.title("BLACKHAT")
plt.show()
cv2.waitKey(0)
图像效果并不是很明显,这个方法是用来显示原图像的噪声的,有余力可以用不同图片进行演示。
九,核函数
在进行形态学操作时,必须使用一个特定的核(结构元)。该核可以自定义生成,也可以 通过函数 cv2.getStructuringElement()构造。函数 cv2.getStructuringElement()能够构造并返回一 个用于形态学处理所使用的结构元素。该函数的语法格式为:
retval = cv2.getStructuringElement( shape, ksize[, anchor])
import cv2
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel2 = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
kernel3 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
print("kernel1=\n", kernel1)
print("kernel2=\n", kernel2)
print("kernel3=\n", kernel3)
这部分可以替代np.ones构建核的过程,通过不同的参数来设置不同的核,但是缺点是不能自定义。如果想要自己的卷积核,那还是用np自己构建吧。