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和分类器的串联流程里。到时候你会发现,预处理做得好,模型推理事半功倍。