3. 图像处理基础(二):颜色空间转换、滤波与边缘检测
好,咱们接着聊。上一节我们把图像当成了一堆数字矩阵,这一节要动真格的了——让这些数字“说话”。
在自动驾驶里,摄像头拍到的原始画面,说白了就是一堆RGB值。但你想让车看懂车道线,直接拿RGB去算,效果往往很差。为什么?因为RGB受光照影响太大了。同一个白色车道线,晴天和阴天拍出来数值差好几倍。
所以,我们需要先做颜色空间转换,再做滤波去噪,最后用边缘检测把车道线的轮廓抓出来。这三步,是我做车道线项目时每天都要打交道的流程。
3.1 颜色空间转换:BGR转灰度、HSV
OpenCV默认读图是BGR顺序,不是我们熟悉的RGB。这个坑我刚开始踩过好几次,调试时发现颜色不对,查了半天才发现是通道顺序搞反了。
3.1.1 BGR转灰度图
灰度图就是去掉颜色信息,只保留亮度。每个像素用一个0-255的值表示,0是纯黑,255是纯白。
转换公式其实很简单:
Gray = 0.299 * R + 0.587 * G + 0.114 * B
为什么绿色权重最高?因为人眼对绿色最敏感。OpenCV里一行代码搞定:
import cv2
img = cv2.imread('road.jpg') # BGR格式
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
我个人习惯,在做边缘检测前,一定会先转灰度。因为Canny算子只认单通道,你给它三通道它反而会懵。
3.1.2 BGR转HSV
HSV比RGB更符合人类对颜色的感知。H是色调,S是饱和度,V是明度。
举个例子:你要提取黄色车道线。在RGB空间里,黄色可能是(255,255,0),但光照一变,数值就飘了。而在HSV空间里,黄色的H值稳定在30-60之间,你只要设定这个范围,就能稳定提取。
我在项目中遇到过这样的情况:白天和黄昏的黄色车道线,在RGB下差异很大,但转到HSV后,H通道几乎不变。这就是HSV的威力。
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 提取黄色区域
lower_yellow = np.array([20, 100, 100])
upper_yellow = np.array([30, 255, 255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
3.2 图像滤波:高斯滤波与中值滤波
摄像头拍到的图像,多少都有噪声。椒盐噪声、高斯噪声……这些噪声如果不处理,边缘检测会检测出一堆假边缘,车道线反而看不清。
滤波,说白了就是“模糊”一下,把噪声抹平。
3.2.1 高斯滤波
高斯滤波用的是一个高斯核,中心权重最大,越往边缘权重越小。这样模糊出来的图像,既去除了噪声,又保留了边缘的轮廓。
为什么用高斯核?因为图像中的噪声往往服从高斯分布,用高斯核去卷积,正好“门当户对”。
blurred = cv2.GaussianBlur(gray, (5, 5), 1.5)
这里(5,5)是核大小,必须是奇数。1.5是标准差σ。σ越大,图像越模糊。
我建议你从(5,5)开始试,σ设1.0-1.5。核太大,车道线边缘也会被模糊掉,得不偿失。
3.2.2 中值滤波
中值滤波对付椒盐噪声特别有效。它的原理很简单:把核内所有像素排序,取中间值替代中心像素。
举个例子,一个3x3区域里有几个白点(椒盐噪声),排序后中间值大概率是正常像素值,噪声就被干掉了。
median = cv2.medianBlur(gray, 5)
中值滤波的核大小也必须是奇数。5x5是比较常用的。
我曾经在一个夜间项目中,图像里全是摄像头传感器产生的椒盐噪声。高斯滤波效果一般,换成中值滤波后,车道线瞬间清晰了。嗯,这里要注意:中值滤波计算量比高斯滤波大,实时性要求高的场景要权衡。
| 滤波方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 高斯滤波 | 高斯噪声、通用去噪 | 平滑效果好,保留边缘 | 对椒盐噪声效果一般 |
| 中值滤波 | 椒盐噪声、脉冲噪声 | 去噪彻底,边缘保持好 | 计算量大,实时性略差 |
3.3 边缘检测:Canny算子
终于到重头戏了。Canny边缘检测,可以说是计算机视觉里最经典的算法之一。它为什么经典?因为它把边缘检测这件事,做得非常“讲究”。
Canny算法分四步走:
- 高斯滤波去噪——先模糊,再找边
- 计算梯度幅值和方向——用Sobel算子算x和y方向的梯度
- 非极大值抑制——只保留梯度方向上的局部最大值,把“胖”边缘变“瘦”
- 双阈值检测——高阈值确定强边缘,低阈值连接弱边缘
第四步是关键。高阈值设高了,边缘会断;设低了,噪声会混进来。我一般设高阈值100-150,低阈值50-75。具体数值要看你的图像对比度。
edges = cv2.Canny(blurred, 50, 150)
你想想看,为什么Canny要搞两个阈值?因为真实世界的边缘,有的很清晰(高梯度),有的很模糊(低梯度)。双阈值让算法既能抓住强边缘,又能通过连通性把弱边缘也捡回来。
最后,给你一个完整的处理流程示例:
import cv2
import numpy as np
def preprocess_lane_image(img):
# 1. 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 高斯滤波去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 1.2)
# 3. Canny边缘检测
edges = cv2.Canny(blurred, 50, 150)
return edges
这个流程,我用了不下上百次。它简单、稳定、高效。当然,实际项目中你还需要做透视变换、车道线拟合等后续处理,但图像预处理这一步,打好基础比什么都重要。
下一节,我们会聊透视变换和ROI提取,把车道线从图像中“抠”出来。到时候你会发现,前面这些滤波和边缘检测的功夫,一点都没白费。