3. 图像基础与预处理(下)
好,咱们接着聊。上一节我们把图像的基本概念和简单预处理讲完了,这一节要动真格的了——滤波、边缘检测、形态学操作。这三个东西,说白了就是自动驾驶感知系统的“眼睛清洗剂”和“轮廓笔”。
我刚开始做车道线检测的时候,觉得原始图像直接扔进算法就能出结果。结果呢?噪声大得离谱,车道线边缘跟狗啃的一样。后来才明白,预处理这一步没做好,后面全是白费功夫。
3.1 图像滤波:给图像“去噪”
滤波是什么?你可以把它想象成用一块抹布在图像上擦一遍。抹布擦过的地方,像素值被重新计算,噪声就被抹平了。
3.1.1 高斯滤波
高斯滤波是我个人最常用的滤波器。它的核心思想很简单:离中心点越近的像素,权重越大;越远的,权重越小。权重分布服从高斯函数,也就是正态分布。
为什么用高斯?因为自然界的噪声大多服从高斯分布,用高斯滤波去匹配它,效果最好。我在项目中遇到过一种情况:车道线被路面上的小石子反光干扰,用高斯滤波轻轻一过,那些小亮点就消失了,而车道线本身还保留得很好。
代码实现也很直接:
import cv2
import numpy as np
# 读取图像
img = cv2.imread('lane.jpg')
# 高斯滤波,核大小5x5,标准差1.5
gaussian_blur = cv2.GaussianBlur(img, (5, 5), 1.5)
# 显示结果
cv2.imshow('Gaussian Blur', gaussian_blur)
cv2.waitKey(0)
3.1.2 中值滤波
中值滤波跟高斯滤波的思路完全不同。它不搞加权平均那一套,而是把窗口内的像素值排序,取中间那个值作为新像素值。
你想想看,如果图像里有椒盐噪声(就是那种黑白点),高斯滤波只能把它变淡,但中值滤波能直接把它干掉。为什么?因为椒盐噪声的像素值要么极大要么极小,排序后肯定在两端,中间值根本轮不到它。
我曾经在一个雨夜场景里测试车道线检测,图像上全是雨水反光造成的亮点。高斯滤波搞不定,中值滤波一上,效果立竿见影。
# 中值滤波,核大小5
median_blur = cv2.medianBlur(img, 5)
3.2 边缘检测:找到图像的“轮廓”
滤波做完了,图像干净了。接下来要干嘛?找边缘。车道线本质上就是图像中的边缘——路面和车道线之间的灰度突变。
3.2.1 Sobel算子
Sobel算子是最基础的边缘检测方法。它用两个3x3的卷积核,一个检测水平方向的变化,一个检测垂直方向的变化。
说白了,Sobel就是在算梯度。图像中灰度变化剧烈的地方,梯度值就大,那就是边缘。
# Sobel边缘检测
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 水平方向
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 垂直方向
# 合并两个方向
sobel_combined = cv2.magnitude(sobel_x, sobel_y)
嗯,这里要注意:Sobel对噪声比较敏感。所以我的习惯是——先高斯滤波,再做Sobel。顺序不能反。
3.2.2 Canny边缘检测
Canny是边缘检测里的“扛把子”。它比Sobel复杂,但效果也更好。Canny的流程大致是:
- 高斯滤波去噪
- 计算梯度幅值和方向
- 非极大值抑制(只保留梯度方向上的最大值)
- 双阈值检测(高阈值确定强边缘,低阈值连接弱边缘)
为什么Canny效果好?因为它做了“非极大值抑制”,把那些模糊的、不清晰的边缘都去掉了,只留下最锐利的线条。我在做车道线检测时,Canny几乎是标配。
# Canny边缘检测
# 参数:低阈值50,高阈值150
edges = cv2.Canny(img, 50, 150)
3.3 形态学操作:修整边缘
边缘检测完了,你以为就结束了?不,车道线边缘可能断断续续的,或者有毛刺。这时候就需要形态学操作来“修一修”。
3.3.1 膨胀与腐蚀
膨胀和腐蚀是形态学操作的基础。它们都用一个结构元素(比如3x3的矩形)在图像上滑动。
- 腐蚀: 结构元素覆盖的区域中,只要有一个像素是0,中心点就变成0。说白了就是“瘦身”,把边缘往里缩。
- 膨胀: 结构元素覆盖的区域中,只要有一个像素是1,中心点就变成1。说白了就是“增肥”,把边缘往外扩。
我记得有一次,车道线因为路面磨损变得很细,Canny检测出来断断续续的。我用膨胀操作把线条加粗,断开的点就连上了。
# 定义结构元素
kernel = np.ones((3,3), np.uint8)
# 腐蚀
eroded = cv2.erode(edges, kernel, iterations=1)
# 膨胀
dilated = cv2.dilate(edges, kernel, iterations=1)
3.3.2 开运算与闭运算
开运算 = 先腐蚀后膨胀。闭运算 = 先膨胀后腐蚀。
开运算用来去除小噪点。比如图像上有些孤立的白点,先腐蚀把它们消掉,再膨胀恢复原来的大小。闭运算用来填充小空洞。比如车道线中间有个小缺口,先膨胀填上,再腐蚀恢复原状。
我建议你记住这个口诀:开运算去噪点,闭运算填空洞。
# 开运算
opening = cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
3.4 本章知识体系
下面这张图把本章的核心逻辑串起来了。你可以把它当作一个处理流水线:
你看,整个流程是串行的:原始图像 → 滤波去噪 → 边缘检测 → 形态学修整。每一步都依赖上一步的结果。我在实际项目中,经常在这个流水线上来回调试参数,直到车道线边缘清晰、连续、没有噪声为止。
好了,这一节的内容就到这儿。记住一句话:预处理做得好,后面算法跑得顺。别嫌麻烦,多调几次参数,你会找到感觉的。