4. 车道线检测基础

各位同学好,今天我们来聊聊车道线检测中最基础、也最经典的一套组合拳——边缘检测、霍夫变换、RANSAC拟合。说实话,虽然现在深度学习很火,但传统方法在嵌入式平台、低算力场景下依然有不可替代的优势。我自己在早期做量产项目时,就是靠这套方法撑过了大半年。

4.1 边缘检测:找到图像中的“突变”

车道线在图像里是什么?说白了,就是路面上一道颜色突变的边界。边缘检测的任务,就是把这些突变点找出来。

我个人最常用的是 Canny 边缘检测。它比单纯的 Sobel 算子要稳定得多。为什么?因为它做了“非极大值抑制”和“双阈值滞后处理”。

核心流程:

  1. 高斯滤波去噪(先模糊,再求导,避免噪声干扰)
  2. 计算梯度幅值和方向(Sobel 算子)
  3. 非极大值抑制(只保留梯度方向上的局部最大值)
  4. 双阈值检测(高阈值确定强边缘,低阈值连接弱边缘)

嗯,这里要注意:双阈值的设置很关键。我见过不少新手把高阈值设得太高,结果车道线断成一段一段的。我的经验是,高阈值取 150-200,低阈值取 50-80,具体要看你的图像对比度。

// C++ 示例:Canny 边缘检测
cv::Mat gray, edges;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(gray, gray, cv::Size(5, 5), 1.5);
cv::Canny(gray, edges, 50, 150);  // 低阈值50,高阈值150

我的小技巧: 在项目里,我习惯先对图像做 ROI 裁剪,只保留车道线可能出现的区域(比如下半部分)。这样能减少大量无关边缘,提升后续处理速度。

4.2 霍夫变换:从边缘点到直线

边缘检测完,你得到的是一个个离散的像素点。怎么把这些点变成一条完整的车道线?霍夫变换就是干这个的。

它的思想很巧妙:把图像空间中的点,映射到参数空间中的曲线。在参数空间中找交点,交点对应的就是图像空间中的直线。

常用的有两种:

  • 标准霍夫变换:用 (ρ, θ) 参数空间,检测直线
  • 概率霍夫变换:只随机采样部分点,效率更高

我在项目中几乎只用概率霍夫变换。为什么?因为标准霍夫变换会把所有可能的直线都找出来,包括那些只有两三个点组成的“伪直线”。概率霍夫变换可以设置最小线段长度和最大间隔,过滤掉这些噪声。

// C++ 示例:概率霍夫变换
std::vector<cv::Vec4i> lines;
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 50, 30, 10);
// 参数:rho=1, theta=1度, 阈值=50, 最小线段长度=30, 最大间隔=10

我曾经踩过的坑: 霍夫变换的阈值设得太低,结果把路边的护栏、阴影都当成车道线了。后来我加了一个角度约束——只保留角度在 30°~150° 之间的线段。因为车道线在图像中基本是斜的,不会出现水平或垂直的线。

4.3 RANSAC 拟合:对抗噪声的利器

霍夫变换出来的线段,往往还是断断续续的。而且受路面纹理、光照变化影响,会有不少离群点。这时候就需要 RANSAC 出场了。

RANSAC 的全称是 Random Sample Consensus,随机抽样一致。它的核心思想是:从数据中随机选一小部分点拟合模型,然后看其他点对这个模型的“支持度”。支持度高的模型,就是我们要的。

你想想看,如果直接用最小二乘法拟合,离群点会把结果拉偏。但 RANSAC 只信任“内点”,所以鲁棒性很强。

方法 优点 缺点
最小二乘法 计算快,数学形式简单 对离群点敏感,一个坏点就能毁掉结果
RANSAC 抗离群点能力强,鲁棒性好 需要迭代,计算量稍大;需要设置阈值
// 伪代码:RANSAC 直线拟合
for (int i = 0; i < maxIterations; i++) {
    // 1. 随机选两个点
    Point p1 = randomPoint(points);
    Point p2 = randomPoint(points);
    
    // 2. 由这两个点确定一条直线
    Line model = fitLine(p1, p2);
    
    // 3. 计算所有点到这条直线的距离
    int inliers = 0;
    for (auto& pt : points) {
        if (distance(pt, model) < threshold) {
            inliers++;
        }
    }
    
    // 4. 如果内点数量超过当前最优,更新模型
    if (inliers > bestInliers) {
        bestModel = model;
        bestInliers = inliers;
    }
}

我的经验: RANSAC 的迭代次数和距离阈值怎么设?我一般先设 100 次迭代,阈值 3-5 个像素。如果场景噪声大(比如雨天、夜晚),我会把迭代次数提到 200,阈值放宽到 8 个像素。另外,记得在拟合完后再用所有内点做一次最小二乘优化,这样精度更高。

4.4 三者如何配合?

这三步是串行工作的:

  1. 边缘检测:把彩色图像变成二值边缘图
  2. 霍夫变换:从边缘图中提取候选线段
  3. RANSAC 拟合:从候选线段中拟合出最终的车道线模型

我画了一张流程图,帮你理清这个流程:

输入图像 灰度图 / ROI裁剪 Canny边缘检测 双阈值 + 非极大值抑制 概率霍夫变换 提取候选线段 RANSAC拟合 迭代优化 + 内点筛选 输出车道线 左右车道线参数 传统车道线检测流程 预处理 边缘检测 线段提取 拟合优化 输出

这个流程看起来简单,但实际工程中坑不少。我举个例子:在隧道里,光照变化剧烈,Canny 的边缘检测结果会很不稳定。我的做法是动态调整 Canny 的阈值——根据图像的平均亮度,自动把阈值调高或调低。

总结一下这套方法的优缺点:

  • 优点:计算量小,实时性好;不需要训练数据;在结构化道路上表现稳定
  • 缺点:对光照、阴影、磨损的车道线敏感;无法处理弯道(只能拟合直线);需要手动调参