上一章我们学习了仿射与透视变换,完成了文档倾斜矫正。在 HSV 颜色掩码、二值图、轮廓检测的流程里,经常会出现细碎噪点、小空洞、边缘毛刺,直接拿去找轮廓很容易检出大量无效轮廓。形态学操作就是专门针对二值图像做形状修正的预处理工具。
前置说明:形态学一般作用于二值图(黑白图),白色为前景目标,黑色为背景;也可以作用于灰度图。核心都是基于卷积核(结构元 kernel)滑动扫描图像。
一、基础:腐蚀 erode 与膨胀 dilate
腐蚀和膨胀是所有形态学运算的基石,其余运算都是这两个的组合。
结构元 kernel:一个矩形、椭圆或十字形矩阵,我们可以用 cv2.getStructuringElement() 创建。
python
# 生成结构元,ksize 是核大小,越大效果越强
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
1. 腐蚀 cv2.erode
腐蚀:吃掉前景白色区域的边缘,白色区域缩小。
作用:消除细小白色噪点、断开两个挨得很近的目标、磨平毛刺。
原理:核覆盖区域只要有一个像素是黑色,锚点位置就变黑。
python
import cv2
import numpy as np
img = cv2.imread(r"D:\PYTHON\binary_mask.jpg")
if img is None:
print("图片读取失败!")
exit()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#5×5 矩形核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
erode_img = cv2.erode(gray, kernel, iterations=1)
cv2.imshow("原图二值掩码", gray)
cv2.imshow("腐蚀结果", erode_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 膨胀 cv2.dilate
膨胀:扩张前景白色区域,白色区域变大,向外"长胖"。
作用:填补目标内部小孔洞、连接距离很近的断裂线条、把分开的碎片合并。
原理:核覆盖区域只要有一个像素是白色,锚点位置就变白。
python
import cv2
import numpy as np
img = cv2.imread(r"D:\PYTHON\binary_mask.jpg")
if img is None:
print("图片读取失败!")
exit()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dilate_img = cv2.dilate(gray, kernel, iterations=1)
cv2.imshow("原图二值掩码", gray)
cv2.imshow("膨胀结果", dilate_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
参数
iterations:迭代次数,相当于重复执行该操作,次数越大效果越强。
二、组合运算:开运算、闭运算(最常用在掩码预处理)
OpenCV 提供 cv2.morphologyEx() 统一执行复合形态学操作,不用手动连续调用 erode、dilate。
1. 开运算 MORPH_OPEN
流程:先腐蚀,后膨胀。
核心用途:去掉前景外面细小的白色噪点,目标主体大小基本不变。
适合场景:HSV 颜色掩码里散落的小白噪点、孤立碎斑。
python
import cv2
import numpy as np
img = cv2.imread(r"D:\PYTHON\mask_noise.jpg")
if img is None:
print("图片读取失败!")
exit()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
open_img = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel)
cv2.imshow("带噪掩码", gray)
cv2.imshow("开运算(去外部小白噪点)", open_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 闭运算 MORPH_CLOSE
流程:先膨胀,后腐蚀。
核心用途:填充前景目标内部的黑色小洞、缝隙,目标外轮廓基本不变。
适合场景:掩码目标里面有孔洞、文字笔画断裂、色块内部镂空。
python
import cv2
import numpy as np
img = cv2.imread(r"D:\PYTHON\mask_hole.jpg")
if img is None:
print("图片读取失败!")
exit()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
close_img = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
cv2.imshow("带孔洞掩码", gray)
cv2.imshow("闭运算(填充内部孔洞)", close_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
实操搭配口诀:外噪点开运算,内孔洞闭运算,HSV 掩码预处理经常开运算 + 闭运算连用。
三、进阶形态学:梯度、顶帽、黑帽
1. 形态学梯度 MORPH_GRADIENT
梯度 = 膨胀图 − 腐蚀图。
作用:提取物体轮廓边缘,得到目标的描边。
python
gradient_img = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)
2. 顶帽(礼帽)MORPH_TOPHAT
顶帽 = 原图 − 开运算图。
作用:提取比核尺寸更小的亮区域,用来提取暗背景上细小亮纹理、微弱亮线条。
3. 黑帽 MORPH_BLACKHAT
黑帽 = 闭运算图 − 原图。
作用:提取比核尺寸更小的暗区域,提取亮目标内部细小暗缝隙、黑点。
四、六种形态学操作完整对比代码
下面把六种操作放在一起运行,方便直观对比效果差异。
python
import cv2
import numpy as np
img = cv2.imread(r"D:\PYTHON\binary.jpg")
if img is None:
print("图片读取失败!")
exit()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
erode = cv2.erode(gray, kernel)
dilate = cv2.dilate(gray, kernel)
open_op = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel)
close_op = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
gradient = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, kernel)
blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow("erode 腐蚀", erode)
cv2.imshow("dilate 膨胀", dilate)
cv2.imshow("open 开运算", open_op)
cv2.imshow("close 闭运算", close_op)
cv2.imshow("gradient 梯度", gradient)
cv2.imshow("tophat 顶帽", tophat)
cv2.imshow("blackhat 黑帽", blackhat)
cv2.waitKey(0)
cv2.destroyAllWindows()
把同一张二值图依次跑完这六种操作,效果差异其实非常直观:腐蚀 让白色前景整体收缩一圈,细小噪点直接消失;膨胀 让白色前景向外扩张,小孔洞和断裂缝隙被填上;开运算 先腐蚀后膨胀,只去掉前景外部的零散小白噪点,主体大小基本不变;闭运算 先膨胀后腐蚀,只填补前景内部的黑色小洞,外轮廓基本不变;形态学梯度 输出的是膨胀图与腐蚀图之差,得到的是目标的一圈描边;顶帽 输出原图与开运算图之差,突出比核更小的亮区域;黑帽输出闭运算图与原图之差,突出比核更小的暗区域。
建议读者准备一张同时包含外部小噪点、内部小孔洞、细线条和边缘毛刺的二值图,用同一份代码依次运行六种操作,把七张窗口并排观察。这样比分别用不同图片测试更容易看出每种操作到底改变了什么,也更容易理解"开运算去外噪、闭运算填内洞"这类口诀背后的实际画面。
结构元尺寸是影响效果强弱的关键变量,建议固定同一张图,把 ksize 从 3×3 逐步调到 15×15,重点观察以下几点:
- 3×3:效果最轻微,只对单个像素级的噪点和毛刺起作用,目标主体形状几乎不变,适合作为保底参数。
- 5×5:日常掩码预处理的常用值,能清掉大部分细小噪点和小孔洞,同时不明显破坏目标轮廓。
- 7×7 到 9×9:去噪和填洞能力明显增强,但腐蚀会让细笔画、细线条变细甚至断开,膨胀会让相邻目标粘连成一片。
- 11×11 到 15×15:效果最强,小目标可能被整体腐蚀掉,大目标边缘也会明显变形;此时梯度描边会变粗,顶帽和黑帽提取出的区域也会明显减少。
- 形状差异:矩形核在水平和垂直方向作用均匀,椭圆核边缘过渡更平滑,十字核只沿水平和垂直方向作用,处理方向性纹理时要优先考虑非正方形核。
一句话总结调参思路:先用小核确认效果方向,再逐步加大核尺寸观察目标是否被破坏,找到"噪点去干净、主体还完整"的那个临界尺寸,通常就是这张图最合适的结构元大小。
五、六种操作速查表
| 操作 | OpenCV 常量 | 等价组合 | 主要作用 |
|---|---|---|---|
| 腐蚀 | cv2.erode |
--- | 收缩前景,去小噪点 |
| 膨胀 | cv2.dilate |
--- | 扩张前景,填小孔洞 |
| 开运算 | cv2.MORPH_OPEN |
先腐蚀后膨胀 | 去外部噪点,分离目标 |
| 闭运算 | cv2.MORPH_CLOSE |
先膨胀后腐蚀 | 填内部孔洞,连接断裂 |
| 形态学梯度 | cv2.MORPH_GRADIENT |
膨胀 − 腐蚀 | 提取轮廓边缘 |
| 顶帽 | cv2.MORPH_TOPHAT |
原图 − 开运算 | 提取亮小区域 |
| 黑帽 | cv2.MORPH_BLACKHAT |
闭运算 − 原图 | 提取暗小区域 |
六、综合实战:车牌区域粗定位
下面用一个完整示例演示形态学操作在车牌粗定位中的应用:先灰度化、边缘检测,再用闭运算连接字符,最后用开运算去噪。
python
import cv2
import numpy as np
img = cv2.imread("car.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#1. 高斯模糊去噪
blur = cv2.GaussianBlur(gray, (5, 5), 0)
#2. Sobel 水平边缘
sobel_x = cv2.Sobel(blur, cv2.CV_16S, 1, 0, ksize=3)
abs_x = cv2.convertScaleAbs(sobel_x)
#3. 二值化
_, binary = cv2.threshold(abs_x, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
#4. 闭运算连接字符
kernel_close = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5))
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel_close)
#5. 开运算去除小噪点
kernel_open = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel_open)
#6. 查找轮廓
contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
ratio = w / float(h)
area = cv2.contourArea(cnt)
# 车牌大致比例 2.5 ~ 5.5,面积过滤
if 2.5 < ratio < 5.5 and area > 1000:
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
这个例子中,闭运算的横向结构元素(17×5)专门用于把横向排列的字符连成一块,开运算则负责清理残留噪点。结构元素的形状和尺寸选择,往往比操作本身更关键。
七、性能与调参建议
- 优先使用
cv2.morphologyEx:它内部对开闭运算做了优化,比手动先腐蚀再膨胀更快。 - 结构元素尽量小:3×3 或 5×5 通常足够,过大容易破坏目标形状。
- 迭代次数替代大核 :需要更强效果时,可增加
iterations,但要注意计算量。 - 方向性结构元素:处理横向或纵向纹理时,使用非正方形核(如 1×15、15×1)效果更好。
- 彩色图像先转灰度:形态学操作通常作用于单通道图像,彩色图需先转换或分通道处理。
八、本章小结
本章系统梳理了 OpenCV 中六种核心形态学操作:腐蚀、膨胀、开运算、闭运算、形态学梯度、顶帽与黑帽。它们本质上都是结构元素与图像局部区域的集合运算,理解"先腐蚀后膨胀""先膨胀后腐蚀"的组合逻辑,就能灵活应对去噪、填洞、连断、提边、提取亮暗小目标等常见需求。
下一章将进入图像轮廓检测与特征分析,继续把形态学处理后的结果转化为可量化的几何信息。