4. 图像预处理:图像灰度化、高斯滤波、直方图均衡化、Canny边缘检测

各位同学,咱们今天聊聊图像预处理。说实话,这部分内容在自动驾驶感知里,就像盖房子打地基一样。地基没打好,后面什么车道线检测、可行驶区域计算,全得翻车。

我在项目里见过不少团队,一上来就急着跑模型,结果边缘检测出来全是噪声。为什么?预处理没做到位。今天我就把这四个核心步骤掰开揉碎了讲给你听。

4.1 图像灰度化

摄像头采集到的原始图像,通常是RGB三通道的彩色图。但咱们做边缘检测时,其实不需要颜色信息。为什么?因为边缘是亮度突变的地方,跟颜色关系不大。

灰度化说白了,就是把每个像素的RGB值,按照一个权重公式,算出一个灰度值。常用的公式是:

Gray = 0.299 * R + 0.587 * G + 0.114 * B

这个权重不是随便定的。人眼对绿色最敏感,对蓝色最不敏感。所以绿色权重最高,蓝色最低。嗯,这里要注意,有些库直接用平均值法(R+G+B)/3,效果其实差不少。

我的习惯: 在自动驾驶场景下,我一般用OpenCV的 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)。它内部用的就是上面那个加权公式。别自己手写,容易出错。

4.2 高斯滤波

灰度化之后,图像里还有噪声。比如摄像头传感器的高斯噪声、路面反光造成的椒盐噪声。这些噪声如果不处理,Canny边缘检测会把它当成边缘,结果就是一堆假边缘。

高斯滤波的原理,我简单说一下。它用一个高斯核(一个权重矩阵)去扫过整张图像。每个像素的新值,是它周围像素的加权平均。离中心越近,权重越大。

举个例子,一个5x5的高斯核,中心点的权重最高,边缘的权重低。这样平滑效果自然,不会像均值滤波那样产生块状效应。

import cv2
import numpy as np

# 读取图像
img = cv2.imread('road.jpg', cv2.IMREAD_GRAYSCALE)

# 高斯滤波,核大小5x5,sigma=1.0
blurred = cv2.GaussianBlur(img, (5, 5), 1.0)
避坑指南: 我曾经在一条乡间小路上测试,路面纹理特别粗糙。我用了3x3的核,结果边缘检测出来全是路面纹理。后来换成7x7的核,效果才正常。核大小要根据图像分辨率来调,没有固定值。

4.3 直方图均衡化

你想想看,自动驾驶的车子,一天要经历各种光照条件。大晴天、阴天、黄昏、隧道入口。光照不均匀,图像对比度就会很差。直方图均衡化就是来解决这个问题的。

它的原理很简单:把图像的灰度直方图,从集中在某个小范围,拉伸到整个0-255范围。这样暗的地方变亮,亮的地方变暗,细节就出来了。

我举个例子。你在隧道里拍到的路面,整体偏暗,灰度值集中在30-80之间。均衡化之后,灰度值分布到0-255,路面的纹理、裂缝全都能看清了。

# 直方图均衡化
equalized = cv2.equalizeHist(blurred)

但这里有个坑。直方图均衡化会放大噪声。所以一定要先做高斯滤波,再做均衡化。顺序不能乱。

重要: 在夜间场景下,我建议用CLAHE(对比度受限自适应直方图均衡化)。它把图像分成小块,每块单独做均衡化,效果比全局均衡化好很多。代码是 cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))

4.4 Canny边缘检测

终于到了核心环节。Canny边缘检测,说白了就是找图像中亮度变化剧烈的地方。这些地方,就是道路边界、车道线、路沿。

Canny算法分四步走:

  1. 高斯滤波:去噪声,我们已经做过了。
  2. 计算梯度:用Sobel算子算水平和垂直方向的梯度,得到梯度幅值和方向。
  3. 非极大值抑制:只保留梯度方向上的局部最大值,把其他像素置零。这一步让边缘变细。
  4. 双阈值检测:设定高低两个阈值。高于高阈值的,是强边缘;介于中间的,是弱边缘;低于低阈值的,直接丢弃。弱边缘只有跟强边缘相连,才保留。

为什么Canny这么流行?因为它能检测出单像素宽度的边缘,而且对噪声鲁棒。我在项目中用过很多边缘检测算法,Sobel太粗,Laplacian对噪声太敏感,最后还是Canny最稳。

# Canny边缘检测
# 参数:低阈值50,高阈值150
edges = cv2.Canny(equalized, 50, 150)

# 显示结果
cv2.imshow('Edges', edges)
cv2.waitKey(0)
调参经验: 高阈值和低阈值的比例,我一般设在2:1到3:1之间。比如低阈值50,高阈值100-150。如果边缘太多噪声,就提高低阈值;如果边缘断断续续,就降低低阈值。没有标准答案,全靠试。

4.5 完整流程串联

好了,四个步骤都讲完了。咱们把它们串起来,形成一个完整的预处理流水线:

import cv2

def preprocess(image_path):
    # 1. 读取图像
    img = cv2.imread(image_path)
    
    # 2. 灰度化
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 3. 高斯滤波
    blurred = cv2.GaussianBlur(gray, (5, 5), 1.0)
    
    # 4. 直方图均衡化
    equalized = cv2.equalizeHist(blurred)
    
    # 5. Canny边缘检测
    edges = cv2.Canny(equalized, 50, 150)
    
    return edges

这个流程,我在多个量产项目里都用过。效果稳定,计算量也不大。在嵌入式平台上,一帧640x480的图像,预处理时间能控制在10ms以内。

4.6 常见问题与调试

现象 可能原因 解决办法
边缘太多,全是噪声 高斯滤波核太小,或Canny阈值太低 增大核大小,提高低阈值
边缘断断续续 Canny阈值太高,或图像对比度不足 降低阈值,或加强直方图均衡化
边缘位置偏移 高斯滤波核太大,导致边缘模糊 减小核大小,或改用双边滤波
处理速度慢 图像分辨率太高,或核太大 先降采样,再预处理
我曾经踩过的坑: 有一次在雨天测试,路面有水渍反光。Canny检测出来一堆假边缘,把水渍当成道路边界了。后来我在预处理前加了一步:用形态学操作(开运算)去除小亮点,效果才改善。所以预处理不是固定的,要根据场景灵活调整。

好了,图像预处理这部分就讲到这里。记住,预处理做得好,后面的道路边界检测就成功了一半。下一章咱们聊聊如何从这些边缘中提取出真正的道路边界线。

公众号:蓝海资料掘金营,微信deep3321