形态变化是基于图像中物体的形态进行一些简单变换,通常在二值化的图像上进行,包括侵蚀/扩张/开运算/闭运算等操作,每种操作还可以设置一个形态学的内核,决定各种操作的表现形式,还是来理解一下官方文档的例子吧。

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()

image.png

这个操作比较好理解,没啥可说的。

2.膨胀

膨胀的效果正好与侵蚀相反,如果内核下至少有一个像素为“1”,则像素元素为“1”。因此,它增加了图像中的白色区域(前景对象)的大小。

在去除噪音的时候,一般采用的操作是侵蚀之后再膨胀。侵蚀消除了白色的噪音,但它也缩小了物体,所以需要通过膨胀来还原。而噪音已经在侵蚀的过程中消失,膨胀的时候也不会再出现。例子:


image.png

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()

image.png

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()

image.png

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()

image.png

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()

image.png

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()

image.png

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

☞ 参与评论