形态变化是基于图像中物体的形态进行一些简单变换,通常在二值化的图像上进行,包括侵蚀/扩张/开运算/闭运算等操作,每种操作还可以设置一个形态学的内核,决定各种操作的表现形式,还是来理解一下官方文档的例子吧。
1.侵蚀
侵蚀的基本思想就像土壤侵蚀一样,侵蚀着物体前景色(白色)的边界。形态学处理内核在图像窗口中滑动,只有当内核下的所有像素都为1时,原始图像中的像素(1或0)才会被视为1,否则它将被侵蚀(变为零)。
因此最终的表现就是,所有靠近前景色边界的像素都将而被丢弃,内核尺寸越大丢的越多,即白色的区域会减小。这种操作对于消除小的白色噪声(如我们在色彩空间一章中所看到的)、分离两个连接的对象等非常有用。
下面的例子中,使用一个5x5的内核,来对图像进行侵蚀操作:
def pltShow(img):
dst = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(dst)
def testErosion():
img = cv2.imread('test.png', 0)
kernel = np.ones((5, 5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
plt.subplot(121),pltShow(img),plt.title("Origin")
plt.subplot(122),pltShow(erosion),plt.title("Erosion")
plt.show()
这个操作比较好理解,没啥可说的。
2.膨胀
膨胀的效果正好与侵蚀相反,如果内核下至少有一个像素为“1”,则像素元素为“1”。因此,它增加了图像中的白色区域(前景对象)的大小。
在去除噪音的时候,一般采用的操作是侵蚀之后再膨胀。侵蚀消除了白色的噪音,但它也缩小了物体,所以需要通过膨胀来还原。而噪音已经在侵蚀的过程中消失,膨胀的时候也不会再出现。例子:
3.开运算
开运算就是2节中讲到的先侵蚀再膨胀的操作,可以认为是个语法糖吧。
def testOpen():
img = cv2.imread("test2.png", 0)
kernel = np.ones((5,5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
dilateAfterErosion = cv2.dilate(erosion, kernel, iterations=1)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
plt.subplot(131), pltShow(img), plt.title("Origin")
plt.subplot(132), pltShow(dilateAfterErosion), plt.title("DilateAfterErosion")
plt.subplot(133), pltShow(opening), plt.title("Opening")
plt.show()
4.闭运算
顾名思义,开运算与闭运算是相反的语法糖——先膨胀再侵蚀,其作用也可以想象到了,可以填充上物体内部的杂点。
def testClose():
img = cv2.imread("test3.png", 0)
kernel = np.ones((5,5), np.uint8)
dilate = cv2.dilate(img, kernel, iterations=1)
erosionAfterDilate = cv2.erode(dilate, kernel, iterations=1)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
plt.subplot(131), pltShow(img), plt.title("Origin")
plt.subplot(132), pltShow(erosionAfterDilate), plt.title("ErosionAfterDilate")
plt.subplot(133), pltShow(closing), plt.title("Closing")
plt.show()
5.形态梯度
出现梯度变化的地方前景色保留,其它的去掉,用于检测边缘
def testGradient():
img = cv2.imread('test.png', 0)
kernel = np.ones((2, 2), np.uint8)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
plt.subplot(121), pltShow(img), plt.title("Origin")
plt.subplot(122), pltShow(gradient), plt.title("Gradient")
plt.show()
6.tophat
文档中的描述:𝚍𝚜𝚝=tophat(𝚜𝚛𝚌,𝚎𝚕𝚎𝚖𝚎𝚗𝚝)=𝚜𝚛𝚌−open(𝚜𝚛𝚌,𝚎𝚕𝚎𝚖𝚎𝚗𝚝)
def testTopHat():
img = cv2.imread('test.png', 0)
kernel = np.ones((9, 9), np.uint8)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
plt.subplot(121), pltShow(img), plt.title("Origin")
plt.subplot(122), pltShow(tophat), plt.title("Gradient")
plt.show()
7.black hat
𝚍𝚜𝚝=blackhat(𝚜𝚛𝚌,𝚎𝚕𝚎𝚖𝚎𝚗𝚝)=close(𝚜𝚛𝚌,𝚎𝚕𝚎𝚖𝚎𝚗𝚝)−𝚜𝚛𝚌
def testBlackHat():
img = cv2.imread('test.png', 0)
kernel = np.ones((9, 9), np.uint8)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
plt.subplot(121), pltShow(img), plt.title("Origin")
plt.subplot(122), pltShow(blackhat), plt.title("BlackHat")
plt.show()
8.自定义内核结构
前面的例子中,变换内核矩阵是用numpy手动生成的n*n矩阵,代表规则的矩形内核,opencv提供了一些函数来获取其它形态的内核,如:
# Rectangular Kernel
cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
# Elliptical Kernel
cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)
# Cross-shaped Kernel
cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
一些形态变换的示例 http://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm
本文链接:https://www.zoucz.com/blog/2019/03/12/c517f8d0-44b6-11e9-9947-3d7b79f522a2/