第 12 章

数学形态学

本章共 9 个小节 · 腐蚀 · 膨胀 · 开/闭运算 · 梯度 · 礼帽/黑帽 · 核函数
数学形态学常用于二值图像和灰度图像的结构处理。它通过结构元素,也就是内核,改变白色前景区域的大小、连接关系或轮廓。 本章先讲腐蚀和膨胀,再用 morphologyEx() 组合出开运算、闭运算、形态学梯度、礼帽和黑帽等操作,最后说明建立结构元素的核函数。
12-1

腐蚀 Erosion

12-1-1 理论基础

腐蚀会让白色前景区域缩小。以二值图像为例,当结构元素覆盖在影像上时,只有内核覆盖区域都满足前景条件,中心位置才保留为白色; 否则会被腐蚀成黑色。因此腐蚀可以消除小白点、断开细连接线,也会让物体边界向内收缩。

腐蚀理论示意
腐蚀理论示意图
结构元素完整落在白色前景内时,中心点才保留为白色。

12-1-2 腐蚀函数 erode()

dst = cv2.erode(src, kernel, anchor, iterations, borderType, borderValue)
参数说明
src来源影像,常见为二值图。
kernel结构元素,可用 Numpy 阵列或 getStructuringElement() 产生。
anchor锚点位置,默认 (-1, -1) 表示内核中心。
iterations执行次数,默认 1;次数越多,腐蚀越明显。

程序实例 ch12_1.py:用 3x3 内核理解腐蚀操作

# ch12_1.py import cv2 import numpy as np src = np.zeros((7, 7), np.uint8) src[1:6, 1:6] = 1 kernel = np.ones((3, 3), np.uint8) dst = cv2.erode(src, kernel) print(f"src =\n{src}") print(f"kernel =\n{kernel}") print(f"erosion =\n{dst}")
阵列执行结果
ch12_1.py 腐蚀阵列结果
3x3 内核必须完整落在前景内,中心点才保留为 1,参考原书第 12-4 页。

程序实例 ch12_2.py:使用不同大小内核腐蚀二值图

# ch12_2.py import cv2 import numpy as np src = cv2.imread("bw.jpg") kernel5 = np.ones((5, 5), np.uint8) kernel11 = np.ones((11, 11), np.uint8) dst1 = cv2.erode(src, kernel5) dst2 = cv2.erode(src, kernel11) cv2.imshow("src", src) cv2.imshow("after erosion 5 x 5", dst1) cv2.imshow("after erosion 11 x 11", dst2) cv2.waitKey(0) cv2.destroyAllWindows()
腐蚀图像结果
ch12_2.py 腐蚀不同内核结果
内核越大,白色前景缩得越明显;带噪声图也可用腐蚀去除小白点,参考原书第 12-5 页。

程序实例 ch12_3.py:腐蚀去除二值图噪声

# ch12_3.py import cv2 import numpy as np src = cv2.imread("bw_noise.jpg") kernel = np.ones((5, 5), np.uint8) dst = cv2.erode(src, kernel) cv2.imshow("src", src) cv2.imshow("after erosion", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch12_4.py:彩色影像腐蚀

# ch12_4.py import cv2 import numpy as np src = cv2.imread("color.jpg") kernel = np.ones((5, 5), np.uint8) dst = cv2.erode(src, kernel) cv2.imshow("src", src) cv2.imshow("after erosion", dst) cv2.waitKey(0) cv2.destroyAllWindows()

ch12_3.py 将腐蚀用于带噪声的二值图,观察小白点被移除;ch12_4.py 把同样观念用在彩色影像上, 可以看到暗色区域扩大、亮色区域缩小。

腐蚀延伸结果
ch12_3.py 腐蚀去噪结果
腐蚀可去除部分小型白色噪声,参考原书第 12-5 页。
ch12_4.py 彩色影像腐蚀结果
彩色影像执行腐蚀后的比较,参考原书第 12-6 页。
12-2

膨胀 Dilation

12-2-1 理论基础

膨胀和腐蚀相反,会让白色前景区域扩大。只要结构元素覆盖范围内存在前景,中心位置就可能被设置为白色。 膨胀可以补强断裂线条、扩大亮区,常和腐蚀搭配使用。

膨胀理论示意
膨胀理论示意图
结构元素范围内只要存在白色前景,中心点就可能扩张为白色。

12-2-2 膨胀函数 dilate()

dst = cv2.dilate(src, kernel, anchor, iterations, borderType, borderValue)

程序实例 ch12_5.py:用 3x3 内核理解膨胀操作

# ch12_5.py import cv2 import numpy as np src = np.zeros((7, 7), np.uint8) src[2:5, 2:5] = 1 kernel = np.ones((3, 3), np.uint8) dst = cv2.dilate(src, kernel) print(f"src =\n{src}") print(f"kernel =\n{kernel}") print(f"Dilation =\n{dst}")
阵列执行结果
ch12_5.py 膨胀阵列结果
膨胀会让前景向外扩张,参考原书第 12-9 页。

程序实例 ch12_6.py:使用不同大小内核膨胀二值图

# ch12_6.py import cv2 import numpy as np src = cv2.imread("bw_dilate.jpg") kernel5 = np.ones((5, 5), np.uint8) kernel11 = np.ones((11, 11), np.uint8) dst1 = cv2.dilate(src, kernel5) dst2 = cv2.dilate(src, kernel11) cv2.imshow("src", src) cv2.imshow("after dilation 5 x 5", dst1) cv2.imshow("after dilation 11 x 11", dst2) cv2.waitKey(0) cv2.destroyAllWindows()
膨胀图像结果
ch12_6.py 膨胀不同内核结果
内核越大,白色区域扩张越明显,参考原书第 12-9 页。

程序实例 ch12_7.py:字母图膨胀

# ch12_7.py import cv2 import numpy as np src = cv2.imread("a.jpg") kernel3 = np.ones((3, 3), np.uint8) kernel5 = np.ones((5, 5), np.uint8) dst1 = cv2.dilate(src, kernel3) dst2 = cv2.dilate(src, kernel5) cv2.imshow("src", src) cv2.imshow("after dilation 3 x 3", dst1) cv2.imshow("after dilation 5 x 5", dst2) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch12_8.py:彩色影像膨胀

# ch12_8.py import cv2 import numpy as np src = cv2.imread("color.jpg") kernel = np.ones((5, 5), np.uint8) dst = cv2.dilate(src, kernel) cv2.imshow("src", src) cv2.imshow("after dilation", dst) cv2.waitKey(0) cv2.destroyAllWindows()

ch12_7.py 使用字母图观察 3x3 与 5x5 膨胀核的差异;ch12_8.py 把彩色影像的腐蚀程序改写为膨胀程序, 可看到亮色区域扩张。

膨胀延伸结果
ch12_7.py 字母 A 膨胀结果
字母 A 使用不同内核膨胀后的结果,参考原书第 12-10 页。
ch12_6.py 膨胀二值图结果
二值图膨胀对比,参考原书第 12-9 页。
12-3

OpenCV 应用在数学形态学的通用函数

腐蚀和膨胀是基础运算。OpenCV 也提供 morphologyEx(),用同一个函数执行开运算、闭运算、形态学梯度、顶帽、黑帽等复合操作。 结构元素可用 getStructuringElement() 建立,并指定矩形、十字形或椭圆形。

dst = cv2.morphologyEx(src, op, kernel, anchor, iterations, borderType, borderValue)
op 常数作用
cv2.MORPH_OPEN开运算:先腐蚀再膨胀。
cv2.MORPH_CLOSE闭运算:先膨胀再腐蚀。
cv2.MORPH_GRADIENT形态学梯度:膨胀图减腐蚀图,用来取得边界。
cv2.MORPH_TOPHAT顶帽:原图减开运算结果。
cv2.MORPH_BLACKHAT黑帽:闭运算结果减原图。
kernel = cv2.getStructuringElement(shape, ksize, anchor)
shape结构元素
cv2.MORPH_RECT矩形内核。
cv2.MORPH_CROSS十字形内核。
cv2.MORPH_ELLIPSE椭圆形内核。
12-4

开运算 Opening

开运算等于先腐蚀再膨胀。它常用于删除小型白色噪声、断开细连接,同时尽量保留较大的主体区域。 对二值树状图,开运算可以去掉细线连接;对彩色图,效果会表现为局部细节被平滑或移除。

程序实例 ch12_9.py:用开运算处理二值树状图

# ch12_9.py import cv2 import numpy as np src = cv2.imread("tree.jpg") kernel = np.ones((3, 3), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel) cv2.imshow("src", src) cv2.imshow("after Opening 3 x 3", dst) cv2.waitKey(0) cv2.destroyAllWindows()
开运算二值图结果
ch12_9.py 开运算处理树状二值图
开运算会先腐蚀细连接,再通过膨胀恢复主体大小,参考原书第 12-13 页。

程序实例 ch12_10.py:用开运算处理彩色夜景图

# ch12_10.py import cv2 import numpy as np src = cv2.imread("night.jpg") kernel = np.ones((9, 9), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel) cv2.imshow("src", src) cv2.imshow("after Opening 9 x 9", dst) cv2.waitKey(0) cv2.destroyAllWindows()
开运算彩色图结果
ch12_10.py 开运算处理彩色影像
彩色图也可做形态学处理,效果取决于内核大小和影像内容,参考原书第 12-14 页。

程序实例 ch12_11.py:手动执行开运算流程

# ch12_11.py import cv2 import numpy as np src = cv2.imread("tree.jpg") kernel = np.ones((9, 9), np.uint8) erosion = cv2.erode(src, kernel) dst = cv2.dilate(erosion, kernel) cv2.imshow("src", src) cv2.imshow("after erosion", erosion) cv2.imshow("after Opening", dst) cv2.waitKey(0) cv2.destroyAllWindows()

书中还把开运算拆成 erode() 后接 dilate(),用来验证 MORPH_OPEN 的实际流程。

12-5

闭运算 Closing

闭运算等于先膨胀再腐蚀。它常用于填补白色区域中的小黑洞、连接相近的白色区域,适合修补前景对象中的断裂或空洞。

程序实例 ch12_12.py:用闭运算处理雪人图

# ch12_12.py import cv2 import numpy as np src = cv2.imread("snowman.jpg") kernel = np.ones((10, 10), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_CLOSE, kernel) cv2.imshow("src", src) cv2.imshow("after Closing", dst) cv2.waitKey(0) cv2.destroyAllWindows()
闭运算雪人结果
ch12_12.py 雪人闭运算结果
闭运算可修补白色主体内部的黑色空洞,参考原书第 12-16 页。

程序实例 ch12_13.py:用闭运算修补图形

# ch12_13.py import cv2 import numpy as np src = cv2.imread("snowman1.jpg") kernel = np.ones((10, 10), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_CLOSE, kernel) cv2.imshow("src", src) cv2.imshow("after Closing", dst) cv2.waitKey(0) cv2.destroyAllWindows()
闭运算结果
ch12_13.py 闭运算修补图形结果
闭运算可以填补内部孔洞或连接近距离区域,参考原书第 12-16 页。

程序实例 ch12_14.py:手动执行闭运算流程

# ch12_14.py import cv2 import numpy as np src = cv2.imread("snowman.jpg") kernel = np.ones((10, 10), np.uint8) dilation = cv2.dilate(src, kernel) dst = cv2.erode(dilation, kernel) cv2.imshow("src", src) cv2.imshow("after dilation", dilation) cv2.imshow("after Closing", dst) cv2.waitKey(0) cv2.destroyAllWindows()

ch12_12.py 使用雪人图示范闭运算填补空洞;ch12_14.py 则用先 dilate()erode() 的方式验证闭运算。

闭运算延伸结果
ch12_14.py 手动闭运算流程结果
手动执行膨胀再腐蚀,可验证闭运算流程,参考原书第 12-17 页。
12-6

形态学梯度 Morphological Gradient

形态学梯度是膨胀结果减去腐蚀结果,能突出对象边界。它不像 Canny 那样完整执行多阶段边缘检测, 而是直接用形态学方式取得前景外扩和内缩之间的差异。

dst = cv2.morphologyEx(src, cv2.MORPH_GRADIENT, kernel)

程序实例 ch12_15.py:字母图形态学梯度

# ch12_15.py import cv2 import numpy as np src = cv2.imread("k.jpg") kernel = np.ones((3, 3), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_GRADIENT, kernel) cv2.imshow("src", src) cv2.imshow("morphological gradient", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch12_16.py:用形态学梯度抽出边界

# ch12_16.py import cv2 import numpy as np src = cv2.imread("k.jpg") kernel = np.ones((5, 5), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_GRADIENT, kernel) cv2.imshow("src", src) cv2.imshow("morphological gradient", dst) cv2.waitKey(0) cv2.destroyAllWindows()
形态学梯度结果
ch12_16.py 形态学梯度结果
形态学梯度可抽出白色对象的外缘,参考原书第 12-18 页。

程序实例 ch12_17.py:城市图形态学梯度

# ch12_17.py import cv2 import numpy as np src = cv2.imread("hole.jpg") kernel = np.ones((3, 3), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_GRADIENT, kernel) cv2.imshow("src", src) cv2.imshow("morphological gradient", dst) cv2.waitKey(0) cv2.destroyAllWindows()
形态学梯度延伸结果
ch12_15.py K 字形态学梯度结果
字母图形的形态学梯度结果,参考原书第 12-18 页。
ch12_17.py 城市图形态学梯度结果
城市影像的形态学梯度结果,参考原书第 12-19 页。
12-7

顶帽运算 Tophat

顶帽运算是原始影像减去开运算结果,常用来提取比结构元素小、且比周围区域更亮的局部特征。 如果影像中存在细亮线或小亮点,顶帽运算可以把这些细节凸显出来。

dst = cv2.morphologyEx(src, cv2.MORPH_TOPHAT, kernel)

程序实例 ch12_18.py:礼帽运算

# ch12_18.py import cv2 import numpy as np src = cv2.imread("smiley.jpg") kernel = np.ones((9, 9), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_TOPHAT, kernel) cv2.imshow("src", src) cv2.imshow("tophat", dst) cv2.waitKey(0) cv2.destroyAllWindows()
顶帽运算结果
ch12_18.py 顶帽运算结果
顶帽运算能保留被开运算移除的亮细节,参考原书第 12-19 页。
礼帽运算补充结果
ch12_18.py 笑脸图礼帽运算结果
笑脸图的礼帽运算结果。
ch12_18.py 城市图礼帽运算结果
城市夜景图的礼帽运算结果,参考原书第 12-19 页。
12-8

黑帽运算 Blackhat

黑帽运算是闭运算结果减去原始影像,常用来提取比结构元素小、且比周围区域更暗的局部特征。 它和顶帽运算互补:顶帽找亮细节,黑帽找暗细节。

dst = cv2.morphologyEx(src, cv2.MORPH_BLACKHAT, kernel)

程序实例 ch12_19.py:黑帽运算

# ch12_19.py import cv2 import numpy as np src = cv2.imread("smiley.jpg") kernel = np.ones((9, 9), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_BLACKHAT, kernel) cv2.imshow("src", src) cv2.imshow("blackhat", dst) cv2.waitKey(0) cv2.destroyAllWindows()
黑帽运算结果
ch12_20.py 黑帽运算结果
黑帽运算可凸显暗色局部结构,参考原书第 12-21 页。

程序实例 ch12_20.py:Excel 图黑帽运算

# ch12_20.py import cv2 import numpy as np src = cv2.imread("excel.jpg") kernel = np.ones((9, 9), np.uint8) dst = cv2.morphologyEx(src, cv2.MORPH_BLACKHAT, kernel) cv2.imshow("src", src) cv2.imshow("blackhat", dst) cv2.waitKey(0) cv2.destroyAllWindows()
黑帽运算延伸结果
ch12_19.py 雪人黑帽运算结果
黑帽运算可提取主体内部或周围的暗色细节,参考原书第 12-21 页。
ch12_20.py Excel 图黑帽运算结果
Excel 图黑帽运算对照,参考原书第 12-21 页。
12-9

核函数 getStructuringElement()

前面示例多用 np.ones() 建立矩形内核。OpenCV 还提供 getStructuringElement(), 可以直接建立矩形、椭圆形和十字形结构元素。不同形状的内核会让腐蚀、膨胀和复合形态学运算产生不同结果。

程序实例 ch12_21.py:建立不同形状的结构元素

# ch12_21.py import cv2 kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5)) print("MORPH_RECT =\n", kernel1) print("MORPH_ELLIPSE =\n", kernel2) print("MORPH_CROSS =\n", kernel3)
结构元素输出
ch12_21.py getStructuringElement 内核结果
矩形、椭圆形与十字形结构元素的阵列结果,参考原书第 12-22 页。

程序实例 ch12_22.py:比较不同结构元素对图像的影响

# ch12_22.py import cv2 src = cv2.imread("bw_circle.jpg") kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (39, 39)) kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (39, 39)) kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (39, 39)) dst1 = cv2.dilate(src, kernel1) dst2 = cv2.dilate(src, kernel2) dst3 = cv2.dilate(src, kernel3) cv2.imshow("src", src) cv2.imshow("MORPH_RECT", dst1) cv2.imshow("MORPH_ELLIPSE", dst2) cv2.imshow("MORPH_CROSS", dst3) cv2.waitKey(0) cv2.destroyAllWindows()
结构元素形状对照
ch12_22.py 不同结构元素形状结果
同一图像搭配不同结构元素,腐蚀后的形状会明显不同,参考原书第 12-23 页。
习题

1. 使用原书指定影像,练习腐蚀与膨胀,并比较矩形、椭圆形、十字形结构元素对轮廓的影响。

第 12 章习题 1 参考结果
习题 1 参考结果。

2. 使用原书指定影像,执行开运算或闭运算,观察小噪点、细线和空洞的变化。

第 12 章习题 2 参考结果
习题 2 参考结果。

3. 对寺庙影像执行 MORPH_GRADIENT,观察形态学梯度抽出的边界。

第 12 章寺庙影像形态学梯度习题参考结果
习题 3 参考图,参考原书第 12-24 页。

4. 对书本影像执行 MORPH_BLACKHAT,观察黑帽运算抽出的暗色细节。

第 12 章书本黑帽运算习题参考结果
习题 4 参考图,参考原书第 12-25 页。