车道线检测技术

车道线检测技术是计算机视觉和自动驾驶领域中的关键技术之一,它能够帮助车辆在道路上识别和跟踪车道线,从而实现车道保持、辅助驾驶和自动驾驶等功能。

车道线检测方法总体上可以分为传统方法基于深度学习的方法

车道线数据集

数据集 数量 尺寸 场景 特点
TuSimple 72k 1280x720 高速路,天气晴朗,车道线清晰 车道线以点来标注
CULane 98k 级 1640x590 复杂场景,包含北京城区道路 最多标记 4 条车道线
Caltech 1.2k 640x480 场景比较简单,视角较好
VPGNet 20k 白天、雨天、大雨、夜晚等 包含车道线类型,以及箭头、斑马线等道路标识
BDD100K 约 100k 1280x720 美国多个地区,覆盖白天夜晚和多种天气 包含多类别车道线标注
ApolloScape 140k 3384x2710 车道线以掩码形式标注,包含 2D / 3D 多类别车道线
CurveLanes 135k 2650x1440 S 路、Y 车道、夜间和多车道等复杂场景 采用三次样条曲线手动标注车道线
Comma2k19 LD 2k 1164x874 从 Comma2k19 数据集中抽取高速场景 手动注释
OpenLane: 3D 200k 真实道路场景 大规模 3D lane 数据集

传统方法

传统图像方法通常通过滤波、边缘检测、阈值分割等方式提取车道线候选区域,然后结合霍夫变换、RANSAC、多项式拟合等算法进行车道线检测。这类算法需要人工设计特征和调节参数,工作量较大,且对光照、阴影、雨雾、磨损车道线和复杂道路结构的鲁棒性较差。

滑动窗口法

滑动窗口法是一种传统方法,它在图像中使用滑动窗口逐步搜索可能包含车道线的区域。

相关源代码:

Advanced Lane Detection

Advanced Lane Finding Using Sliding Window Search

相机校准

不同于一般的检测算法,涉及像素坐标系到世界坐标系变换时,通常需要先进行相机校准,得到相机内参和畸变参数。

图像灰度化

若对原始彩色图像直接处理,由于信息量较多,算法可能难以提取有效特征,因此通常先对原图像进行灰度化处理。

\textstyle Gray(x,y) = 0.2989 \times R(x,y) + 0.5870 \times G(x,y) + 0.1140 \times B(x,y)

图像滤波

通过高斯滤波进行图像降噪,可以有效降低噪声干扰。常见做法是使用 5x5 的高斯核,并设置标准差 \textstyle \sigma 为 1 或根据实际图像调整。

图像二值化

图像二值化是分割图像的常见步骤。通过将原图像像素灰度值与预定阈值 S 进行比较,把像素分为两类:大于阈值的置为 1,小于阈值的置为 0。

加入 OTSU 算法进行自适应二值化,可以在一定程度上提高算法对光照变化的鲁棒性。

图像二值化

透视变换

透视变换可以将当前前视视角转换为 BEV 视角,用于可视化前方道路。这样可以减少非车道线信息,并使车道线在变换后近似平行。

透视变换本质上是将输入图像中车道区域边界的四个点映射到目标平面上的四个点,从而生成所需的 BEV 图像。

查找车道线像素

在图像中定位车道线像素点的方法之一,是绘制非零像素的直方图。由于像素值已经二值化,再加上透视变换,直方图峰值可以代表大部分非零像素的位置。直方图中峰值的 \text x 坐标可作为搜索车道线的起点,左右车道各一个。滑动窗口围绕车道线中心点放置,并逐步向上搜索。

查找车道线像素

滑动窗口能够估计每个车道线区域的中心点,也就是中心点的 \text x\text y 坐标。可以使用 search_around_poly() 函数拟合多项式曲线。要注意,这个函数拟合的结果通常是 \text {f(y)} 而不是 \text {f(x)},因为车道线在图像中更接近竖直方向。

边缘检测

Canny 边缘检测是一种经典边缘检测算法,由 John Canny 于 1986 年提出。它能够有效检测图像边缘,并且对噪声具有一定抵抗能力。

高斯滤波

为了降低图像中的噪声,首先对原始图像进行高斯滤波。高斯滤波通过高斯核卷积原始图像来平滑图像,减小图像中的高频噪声。

梯度计算

使用 Roberts、Prewitt、Sobel 等算子计算图像梯度,分别得到图像在水平和垂直方向上的梯度值。梯度方向通常用于确定边缘方向。\textstyle G 表示梯度强度,\textstyle G_x\textstyle G_y 表示水平和垂直方向的一阶导数值,\textstyle \theta 表示梯度方向:

\textstyle G = \sqrt{G_x^2+G_y^2}
\textstyle \theta = \arctan2(G_y, G_x)

NMS 非极大值抑制

计算出像素点 \textstyle p 的梯度和方向后,对梯度幅值图进行非极大值抑制,去除非边缘像素。具体做法是:在梯度方向上,比较每个像素的梯度幅值与相邻两个像素的梯度幅值。如果当前像素不是局部极大值,就将其置 0;否则保留。非极大值抑制能够帮助保留局部最大梯度,抑制其他梯度值,从而保留梯度变化最锐利的位置。

NMS 非极大值抑制

边缘跟踪

这个阶段可以加入双阈值处理方法,即设置一个高阈值和一个低阈值。根据梯度幅值,将像素分为强边缘、弱边缘和非边缘三类。高阈值用于标记强边缘,低阈值用于标记弱边缘。

然后设置两个阈值 minValmaxVal。强度梯度大于 maxVal 的边缘肯定是边缘,低于 minVal 的边缘肯定是非边缘,因此会被丢弃。位于两个阈值之间的点,则需要看它是否与真正边缘相连接。如果它连接强边缘像素,就被认为是边缘的一部分,否则也会被丢弃。

边缘跟踪

分割

可以使用 Mask 蒙版保留 ROI 区域。一般认为包含车道线的区域在画面下方,因此将主要车道区域的 mask 值设为 1,其他区域设为 0。将 mask 与当前 frame 叠加,便可以得到主要车道区域。

霍夫变换

霍夫变换是一种常用计算机视觉技术,用于在图像中检测几何形状,例如直线、圆和椭圆。

霍夫变换的基本思想,是把图像空间中的几何形状转换到参数空间中进行投票。以直线 \text {y=mx+b} 为例,一条直线可以表示为参数空间中的一个点 \text {(m,b)}。通过对参数空间中的点进行累积投票,可以检测这些几何形状。可以总结为:从图像空间到霍夫空间的转换。

霍夫变换 1

空间坐标系中的直线转换到霍夫空间是点,因为直线参数确定后,参数空间中的位置也确定。空间坐标系中的一个点转换到霍夫空间是一条曲线,因为经过这个点有无穷多条直线,每条直线对应不同参数。

因此,若要在图像空间中寻找一条线,那么在霍夫空间中,要寻找的是相交的曲线或累加器中投票集中的位置。

将边缘检测图像中的每个点都视为霍夫空间中的一条曲线。在霍夫空间中有很多曲线相交的地方,就可以认为找到了一组连续的边缘点,对应图像空间中的一条直线。

霍夫变换 2

为了避免垂直线斜率无限大的情况,可以使用极坐标重新定义直线。

\textstyle \rho 表示直线到原点的垂直距离,\textstyle \theta 表示这条直线的垂线与横轴的夹角。

也就是 \textstyle x_0=\rho_0\cos\theta_0\textstyle y_0=\rho_0\sin\theta_0,由此可以得到:

\textstyle \rho_0 = x_0\cos\theta_0 + y_0\sin\theta_0
霍夫变换 3

假设 \textstyle x_0\textstyle y_0 均为 1,那么 \textstyle \rho_0 = x_0\cos\theta_0 + y_0\sin\theta_0 的图像如下图所示,是一条三角函数曲线。

霍夫变换 4

因此,如果取直线上的 \textstyle (x_1,y_1),\ldots,(x_n,y_n),那么它们会转化为霍夫空间中的 \textstyle n 条正弦曲线。这些正弦曲线在霍夫空间中的交点,即图像空间中直线对应的参数。

霍夫变换 5

RGB -> GRAY

首先将 RGB 格式的图像(三通道)转换为 GRAY(单通道)。

void cvtColor(InputArray src, OutputArray dst, int code, int dstCn = 0);

// src:输入 RGB 图像。
// dst:输出灰度图像。
// code:颜色空间转换代码,例如 cv::COLOR_BGR2GRAY 或 cv::COLOR_RGB2GRAY。
// dstCn:输出图像通道数,默认为 0,表示由转换代码自动确定。

高斯滤波

去除高频噪声。

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT);

// src:输入图像。
// dst:输出图像,用于存储滤波结果。
// ksize:高斯内核大小,可以使用 Size(width, height) 指定。
// sigmaX:X 方向高斯核标准差。
// sigmaY:Y 方向高斯核标准差。如未指定,默认为 0,表示与 sigmaX 相同。
// borderType:边界像素处理方式,默认为 BORDER_DEFAULT。

Canny 边缘检测

Canny 边缘检测算法可以寻找与图像边缘相关的点。它会在尽量保留主要边缘信息的情况下,将灰度图转换为边缘二值图像。

void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false);

MASK 掩码

Mask 可以降低计算代价,只在 ROI 区域进行运算。下图中的 height 和 width 分别代表图像的高与宽。

MASK 掩码

霍夫变换

void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0);

// image:输入图像,可以是 Canny 算法输出的边缘检测结果。
// lines:输出参数,检测到的直线线段集合。
// rho:极坐标中的距离分辨率。
// theta:极坐标中的角度分辨率。
// threshold:直线检测阈值,累加器中的值大于等于阈值时才会被认为是一条直线。
// minLineLength:最小线段长度,小于此长度的线段将被忽略。
// maxLineGap:最大线段间隙,如果两条线段之间的间隙小于此值,则将它们视为一条线段。

Lane 绘制

直接使用霍夫变换得到的车道线结果,一般会包含多条相邻线段,而不是一条完整车道线。因此还需要进行如下处理:

  • 对每条直线求取斜率。
  • 分别归入左右车道线列表,并计算斜率平均值 \textstyle m_0 和最高点 A。
  • 根据平均斜率 \textstyle m_0 与最高点,计算线段与图像下方的交点 B 坐标。
  • 连接最高点 A 与交点 B。

Lane 和原始图像的融合

将拟合得到的车道线绘制到空白图像上,再与原始图像进行加权融合,可以得到最终的车道线检测可视化结果。


参考文章

[1] 传统车道线检测方法综述

[2] 车道线检测传统算法

[3] Advanced Lane Detection

[4] 基于自适应滑动窗口检测的车道线识别

[5] 霍夫变换原理

[6] Canny 边缘检测

[7] OpenCV 霍夫变换