3. 车道线检测算法基础:图像预处理与直线检测

各位同学,今天我们进入车道线检测的核心环节。说实话,这部分内容我做了不下上百次实验,踩过的坑比你们想象的多得多。但别担心,我会把那些血泪教训都揉碎了讲给你们听。

车道线检测,说白了就是让摄像头“看懂”路面上的白线或黄线。但摄像头拍到的原始图像,嗯,那叫一个乱——有阴影、有反光、有路面纹理,甚至还有前车的影子。所以,我们得先做图像预处理,把有用的信息提取出来。

3.1 图像灰度化:去掉颜色干扰

为什么要把彩色图变成灰度图?我个人习惯是:颜色信息在车道线检测中其实用处不大。你想想看,车道线无非是白色或黄色,但路面颜色千变万化,反而容易干扰算法。

灰度化的公式很简单:

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

这个公式模拟了人眼对亮度的感知。绿色权重最高,因为人眼对绿色最敏感。我在项目中遇到过一个问题:有些摄像头对红色通道特别敏感,导致灰度图偏亮。后来我调整了权重系数,才解决了这个问题。

小技巧: 如果你用的是工业相机,建议先校准白平衡,否则灰度化后的图像会有偏色,影响后续处理。

3.2 高斯滤波:去除噪声

图像噪声是绕不开的坎。摄像头传感器本身就有热噪声,再加上路面颠簸、光照变化,噪声无处不在。高斯滤波就是用来平滑图像、去除噪声的。

它的原理很简单:用一个高斯核(比如3x3或5x5的矩阵)去卷积图像。每个像素的值被替换为它周围像素的加权平均值,权重由高斯分布决定。

// 5x5高斯核示例
1/273 * [
  1,  4,  7,  4, 1,
  4, 16, 26, 16, 4,
  7, 26, 41, 26, 7,
  4, 16, 26, 16, 4,
  1,  4,  7,  4, 1
]

我曾经在一条乡间小路上测试算法,结果发现车道线边缘全被模糊掉了。后来才意识到,高斯核选太大了。对于车道线这种细长结构,3x3的核就够用了,5x5反而会丢失细节。

注意: 高斯滤波的sigma参数很关键。sigma越大,图像越模糊。我建议sigma取1.0到1.5之间,既能去噪,又不至于把车道线边缘抹平。

3.3 Canny边缘检测:找到边界

好了,现在图像干净了,接下来要找到车道线的边缘。Canny边缘检测是我最常用的方法,没有之一。它分四步走:

  1. 计算梯度:用Sobel算子算出每个像素的梯度强度和方向。
  2. 非极大值抑制:只保留梯度方向上的局部最大值,把其他像素置零。
  3. 双阈值检测:设定高阈值和低阈值,高于高阈值的保留,低于低阈值的丢弃,介于两者之间的看是否与高阈值像素相连。
  4. 边缘连接:把断开的边缘连起来。

这里有个坑,我踩过好几次:双阈值的设定。高阈值太高,车道线边缘会断断续续;低阈值太低,又会引入大量噪声。我的经验是:高阈值取150-200,低阈值取50-80,具体数值要根据实际场景微调。

// OpenCV中的Canny调用示例
cv::Mat edges;
cv::Canny(gray_img, edges, 150, 80, 3);
核心要点: Canny边缘检测的效果,90%取决于双阈值的设定。建议在实车上跑一遍,录一段视频,然后离线调参,找到最适合你场景的参数组合。

3.4 霍夫变换直线检测:从点到线

边缘检测之后,我们得到了一堆像素点。但这些点怎么变成直线呢?霍夫变换就是干这个的。

它的思想很巧妙:把图像空间中的点,映射到参数空间中的曲线。在参数空间中,如果多条曲线交于一点,就说明这些点在图像空间中位于同一条直线上。

常用的霍夫变换有两种:

类型 参数空间 适用场景
标准霍夫变换 (ρ, θ) 检测完整直线
概率霍夫变换 (ρ, θ) + 线段端点 检测线段,速度快

我个人更推荐概率霍夫变换。为什么?因为它只随机采样一部分点,计算量小,而且直接输出线段端点,省去了后续的线段提取步骤。

// 概率霍夫变换示例
std::vector<cv::Vec4i> lines;
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 50, 30, 10);

参数说明:

  • rho:距离分辨率,一般取1像素
  • theta:角度分辨率,一般取1度(即π/180)
  • threshold:累加器阈值,值越大,检测到的直线越少但越可靠
  • minLineLength:最小线段长度,太短的线段可能是噪声
  • maxLineGap:同一条直线上两点之间的最大间隔
避坑指南: 我曾经把threshold设得太低,结果检测出几百条乱七八糟的线,CPU直接跑满。后来我改成50,效果好了很多。记住:宁可漏检,也不要误检。

3.5 车道线拟合:最小二乘法

霍夫变换检测到的直线,往往有多条。我们需要从中筛选出左右两条车道线,然后用最小二乘法拟合出它们的方程。

最小二乘法的原理很简单:找到一条直线,使得所有点到这条直线的垂直距离的平方和最小。数学上,就是求解一个线性方程组。

// 最小二乘法拟合直线
// 输入:点集 (x_i, y_i)
// 输出:斜率 k 和截距 b

double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0;
int n = points.size();

for (int i = 0; i < n; i++) {
    sum_x += points[i].x;
    sum_y += points[i].y;
    sum_xy += points[i].x * points[i].y;
    sum_x2 += points[i].x * points[i].x;
}

double k = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
double b = (sum_y - k * sum_x) / n;

这里有个细节:车道线在图像中通常是斜的,而且越靠近车头越宽。所以,我建议只取图像下半部分的点进行拟合,上半部分的点(比如远处的车道线)误差太大,反而会拉偏拟合结果。

实战经验: 拟合完成后,一定要做合理性检查。比如斜率不能太陡(否则可能是护栏或路沿),截距不能超出图像边界。我见过一个案例,算法把路边的排水沟当成车道线,差点酿成事故。

3.6 完整流程总结

好了,我们把整个流程串起来:

  1. 读取摄像头图像
  2. 灰度化:去掉颜色信息
  3. 高斯滤波:去除噪声
  4. Canny边缘检测:提取边缘
  5. 霍夫变换:检测直线
  6. 筛选左右车道线:根据斜率和位置
  7. 最小二乘法拟合:得到车道线方程

这个流程看起来简单,但每个环节都有坑。我建议你们先在离线数据集上跑通,再移植到嵌入式平台上。嗯,说到嵌入式平台,下一章我会讲如何在资源受限的MCU上优化这些算法,敬请期待。

最后提醒: 车道线检测只是ADAS系统的一小部分。真正的挑战在于:雨天、夜晚、弯道、遮挡等复杂场景。这些内容我们会在后续章节逐一攻克。