4、OpenCV基础回顾(三):图像滤波、边缘检测与形态学操作
好,咱们继续。前两节我们把图像读写、颜色空间转换和几何变换捋了一遍。这一节,我打算聊聊图像预处理里最常用的三板斧:滤波、边缘检测和形态学操作。
说实话,这三个东西在实战中几乎天天见。你想想看,不管你是做目标检测还是图像分类,原始图像里总有噪声、细节干扰或者形状不完整的问题。怎么处理?就是靠它们。
4.1 图像滤波:让图像“干净”一点
滤波,说白了就是“模糊”或者“平滑”。为什么要做这个?因为摄像头采集的数据,尤其是工业相机或者低光照环境下的图像,噪声特别多。我当年做安防项目时,晚上监控画面全是雪花点,不滤波根本没法做后续分析。
4.1.1 高斯滤波
高斯滤波是我个人最常用的。它用高斯核去卷积图像,对每个像素点做加权平均。离中心越近的像素,权重越大。
为什么好?因为它能保留边缘信息的同时,把高斯噪声去掉。嗯,这里要注意:它适合处理正态分布的噪声。
核心参数:
ksize:核大小,必须是奇数。比如 (5,5) 或 (3,3)。越大越模糊。sigmaX:X方向标准差。一般设为0,函数会自动计算。
import cv2
import numpy as np
img = cv2.imread('noisy.jpg')
# 高斯滤波,核大小5x5
blurred = cv2.GaussianBlur(img, (5, 5), 0)
cv2.imshow('Gaussian Blur', blurred)
cv2.waitKey(0)
我在项目中遇到过一个问题:核大小设太大,人脸特征都没了,检测器直接罢工。后来我习惯先试 (3,3),不行再往上加。别一上来就搞 (9,9),那叫“糊图”不叫滤波。
4.1.2 中值滤波
中值滤波,顾名思义,用邻域内所有像素的中位数代替中心像素值。
它对付椒盐噪声特别有效。什么是椒盐噪声?就是图像上随机出现的黑白点,像撒了盐和胡椒一样。我做过一个OCR项目,扫描文档上全是这种点,用高斯滤波根本去不掉,换成中值滤波,效果立竿见影。
小技巧:中值滤波的核大小也必须是奇数。常用的是3或5。核越大,图像越平滑,但细节丢失也越严重。
# 中值滤波,核大小3
median = cv2.medianBlur(img, 3)
我曾经犯过一个错:把核大小设成偶数,结果程序直接报错。记住,OpenCV里很多函数都要求核是奇数,这是硬性规定。
4.2 边缘检测:Canny算子
边缘检测,是计算机视觉里最基础也最重要的操作之一。你想想看,人眼识别物体靠什么?靠轮廓。机器也一样。
Canny边缘检测,是业界公认的“黄金标准”。它不像Sobel那样只算梯度,而是做了四步处理:去噪、计算梯度、非极大值抑制、双阈值检测。
Canny函数原型:
edges = cv2.Canny(image, threshold1, threshold2)
threshold1:低阈值threshold2:高阈值
一般建议 threshold2 = 3 * threshold1 或 2 * threshold1。
img = cv2.imread('lena.jpg', 0) # 灰度图
edges = cv2.Canny(img, 50, 150)
cv2.imshow('Canny Edges', edges)
为什么要有两个阈值?简单说:高于高阈值的,肯定是边缘;低于低阈值的,肯定不是;介于中间的,只有跟高阈值边缘相连才保留。这叫“滞后阈值”。
我记得第一次用Canny时,阈值设得太低,结果整张图全是线,跟蜘蛛网似的。后来我总结了一个经验:先试 (50, 150),如果边缘太多就提高低阈值,如果边缘断断续续就降低高阈值。
避坑指南:我曾经在YOLO检测前用Canny提取边缘,然后直接喂给模型。结果发现检测精度反而下降了。为什么?因为Canny把纹理细节也当成了边缘,干扰了特征提取。后来我改成只在预处理阶段做轻度滤波,边缘检测留给模型自己去学。
4.3 形态学操作:让形状更“规整”
形态学操作,主要针对二值图像。说白了,就是让白色区域(前景)的形状变得更干净、更完整。
常用的有四种:腐蚀、膨胀、开运算、闭运算。
4.3.1 腐蚀与膨胀
腐蚀:让白色区域“瘦”一圈。可以去掉小白点噪声。
膨胀:让白色区域“胖”一圈。可以填补小空洞。
kernel = np.ones((5,5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
dilation = cv2.dilate(img, kernel, iterations=1)
我在做车牌识别时,二值化后的字符经常有断裂。这时候先膨胀一下,把断开的笔画连起来,再腐蚀回原来的粗细。嗯,这就是开运算和闭运算的思路。
4.3.2 开运算与闭运算
开运算 = 先腐蚀后膨胀。用来去除小白点,同时保持前景大小不变。
闭运算 = 先膨胀后腐蚀。用来填补小空洞,同时保持前景大小不变。
# 开运算
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
个人经验:形态学操作的核大小和迭代次数很关键。我一般先用 (3,3) 核,迭代1次。如果效果不够,再增加迭代次数,而不是直接加大核。因为核太大容易把目标形状都改变了。
4.4 实战串联:滤波 + 边缘检测 + 形态学
最后,我给大家一个完整的例子。假设我们要从一张有噪声的图片里提取出物体的轮廓。
import cv2
import numpy as np
# 1. 读取图像
img = cv2.imread('object.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 高斯滤波去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 3. Canny边缘检测
edges = cv2.Canny(blurred, 50, 150)
# 4. 形态学闭运算,连接断裂边缘
kernel = np.ones((3,3), np.uint8)
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations=2)
# 5. 显示结果
cv2.imshow('Original', img)
cv2.imshow('Edges after processing', closed)
cv2.waitKey(0)
cv2.destroyAllWindows()
这个流程,我在多个项目里都用过。比如工业零件检测,先滤波去噪,再Canny找边缘,最后形态学把断裂的轮廓连起来。效果很稳定。
总结一下:
| 操作 | 用途 | 常用参数 |
|---|---|---|
| 高斯滤波 | 去除高斯噪声 | ksize=(3,3)或(5,5) |
| 中值滤波 | 去除椒盐噪声 | ksize=3或5 |
| Canny边缘检测 | 提取图像边缘 | threshold1=50, threshold2=150 |
| 腐蚀/膨胀 | 缩小/放大前景 | kernel=(3,3), iterations=1 |
| 开运算/闭运算 | 去噪点/填空洞 | kernel=(3,3), iterations=1~2 |
好了,这一节的内容就到这里。下一节我们会把这些操作真正用到YOLO和分类器的串联流程里。到时候你会发现,预处理做得好,模型推理事半功倍。