4. 图像预处理:图像滤波、边缘检测与形态学操作
各位同学,欢迎来到第四讲。上一章我们聊了图像的基本操作,今天咱们进入一个更核心的话题——图像预处理。
说实话,在真实的交通标志检测项目里,你拿到的原始图像往往「脏」得不行。光照不均、镜头污渍、运动模糊……这些噪声如果不处理,后面的模型再牛也白搭。我自己的经验是,预处理花的时间,有时候比调模型还多。
今天我们就来搞定三件事:图像滤波(去噪)、边缘检测(找轮廓)、形态学操作(修形状)。
4.1 图像滤波:给图像「洗把脸」
滤波说白了就是去噪。你想想看,摄像头拍到的交通标志,上面可能沾了灰尘,或者因为光线太强出现了椒盐噪声(就是那种黑白小点)。这时候就需要滤波来「平滑」一下。
4.1.1 高斯滤波
高斯滤波是我最常用的滤波器。它的原理很简单:用一个高斯核(就是一个权重矩阵)去扫描整张图,每个像素的新值由它周围像素的加权平均决定。离中心越近,权重越大。
为什么用高斯?因为自然界很多噪声是服从高斯分布的。说白了,高斯滤波对高斯噪声的抑制效果最好。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('traffic_sign.jpg')
# 高斯滤波
# (5,5) 是核大小,必须为奇数;0 表示自动计算 sigma
gaussian_blur = cv2.GaussianBlur(img, (5, 5), 0)
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Gaussian Blur', gaussian_blur)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.1.2 中值滤波
中值滤波跟高斯滤波的思路完全不同。它不是算平均,而是取邻域内所有像素的中位数作为新值。
这招对付椒盐噪声特别管用。为什么?因为椒盐噪声是极端值(要么特别亮要么特别暗),取中位数就能完美避开这些异常点。
# 中值滤波
# 参数是核大小,这里用 5
median_blur = cv2.medianBlur(img, 5)
对比一下两种滤波:高斯滤波适合处理高斯噪声(比如传感器热噪声),中值滤波适合处理椒盐噪声。实际项目中,我经常两个都试一下,看哪个效果更好。
4.2 边缘检测:Canny 算法
边缘检测是计算机视觉的「老本行」。你想识别交通标志,首先得知道标志的轮廓在哪。Canny 算法是业界公认的「黄金标准」。
Canny 算法其实是一套组合拳,包含四个步骤:
- 去噪:先用高斯滤波平滑图像
- 计算梯度:用 Sobel 算子算出每个像素的梯度强度和方向
- 非极大值抑制:只保留梯度方向上的局部最大值,把「胖」边缘变「瘦」
- 双阈值检测:用高低两个阈值,确定哪些是强边缘、哪些是弱边缘
这里最关键的就是双阈值。高阈值决定哪些边缘是「铁定算数」的,低阈值决定哪些边缘「可能算数」。介于两者之间的,如果跟强边缘相连,就保留;否则丢弃。
# Canny 边缘检测
# 参数:低阈值 50,高阈值 150
edges = cv2.Canny(img, 50, 150)
# 显示结果
cv2.imshow('Canny Edges', edges)
我记得有一次做高速公路限速牌检测,因为图像里有大量树影,Canny 检测出了无数条「假边缘」。后来我把低阈值从 30 提到 80,效果立竿见影。所以,阈值不是死的,要根据实际场景调。
4.3 形态学操作:膨胀与腐蚀
形态学操作,说白了就是「修形状」。边缘检测完,你得到的可能是一堆断断续续的线条,或者有一些小噪点。这时候就需要膨胀和腐蚀来「整形」。
4.3.1 腐蚀
腐蚀就像「削铅笔」。它会消除物体边界上的像素,让物体「瘦」一圈。对于去除小白点噪声特别有效。
# 定义结构元素(核)
kernel = np.ones((5,5), np.uint8)
# 腐蚀
erosion = cv2.erode(img, kernel, iterations=1)
4.3.2 膨胀
膨胀跟腐蚀相反,它会「增肥」。把物体边界向外扩张,填补空洞、连接断裂的线条。
# 膨胀
dilation = cv2.dilate(img, kernel, iterations=1)
# 开运算:先腐蚀后膨胀
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算:先膨胀后腐蚀
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
这里有个小表格,方便大家对比:
| 操作 | 作用 | 典型应用场景 |
|---|---|---|
| 腐蚀 | 消除边界点,使物体缩小 | 去除小白点噪声 |
| 膨胀 | 扩大边界,使物体增大 | 填补空洞,连接断裂 |
| 开运算 | 先腐蚀后膨胀 | 去噪点,保持物体大小 |
| 闭运算 | 先膨胀后腐蚀 | 填补内部空洞 |
4.4 实战小案例:预处理流水线
最后,我给大家搭一个完整的预处理流水线。假设我们要检测一个限速 60 的标志:
import cv2
import numpy as np
# 1. 读取图像
img = cv2.imread('speed_limit_60.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)
# 5. 显示结果
cv2.imshow('Pipeline Result', closed)
cv2.waitKey(0)
cv2.destroyAllWindows()
这个流水线我用了很多次,效果稳定。当然,具体参数要根据你的图像质量微调。比如光线暗的时候,Canny 的阈值要降低;噪声大的时候,滤波核要加大。
好了,这一章的内容就到这里。下一章我们会讲图像分割与轮廓提取,到时候这些预处理的结果就能派上大用场了。大家先动手试试今天的代码,有问题随时交流。