第四章:传统车道线检测(一):颜色空间转换与阈值分割

各位同学,欢迎来到实战环节的第一站。说实话,很多新手一上来就怼深度学习,结果连最基本的车道线都提不出来。我个人习惯是,先把手动特征工程玩明白,再谈端到端。这一章,咱们就聊聊传统方法里最核心的两个东西:颜色空间和梯度阈值。

4.1 为什么要做颜色空间转换?

你想想看,摄像头拍到的原始图像是RGB格式。但RGB有个毛病——它对光照变化特别敏感。同一个黄色车道线,晴天和阴天拍出来,RGB值能差好几倍。我在项目中遇到过,白天跑得好好的模型,一到傍晚就疯狂丢线。后来发现,问题就出在RGB上。

所以我们需要换个颜色空间。说白了,就是把颜色信息从「红绿蓝」的混合表示,拆成更符合人类视觉感知的方式。常用的有两个:HSV 和 LAB。

4.2 HSV颜色空间

HSV把颜色拆成三个分量:

  • H(Hue,色调):0-180度,代表颜色种类。比如黄色在15-35之间。
  • S(Saturation,饱和度):颜色纯度。车道线的饱和度通常较高。
  • V(Value,明度):亮度。这个分量受光照影响最大,我们往往要把它扔掉。

核心思路:利用H和S通道提取车道线颜色,忽略V通道来抵抗光照变化。

嗯,这里要注意:OpenCV里HSV的H范围是0-180,不是0-360。我第一次用的时候直接拿0-360去设阈值,结果啥也提取不出来,折腾了半天才发现这个坑。

4.3 LAB颜色空间

LAB是另一种选择。它把颜色拆成:

  • L(Lightness):亮度,同样受光照影响大。
  • A(绿-红):从绿色到红色的变化。
  • B(蓝-黄):从蓝色到黄色的变化。

我个人更偏爱LAB里的B通道。为什么?因为黄色车道线在B通道上响应特别强,白色车道线在L通道上响应强。你想想看,黄色和白色是路上最常见的两种车道线颜色,LAB刚好能分别处理它们。

颜色空间 优点 缺点 适用场景
HSV 色调分离好,抗光照 饱和度阈值难调 黄色车道线
LAB B通道对黄色敏感 计算稍慢 黄白混合车道线

4.4 颜色阈值分割实战

理论讲完了,直接上代码。这是我在实际项目中用过的阈值配置:

import cv2
import numpy as np

def color_threshold(img):
    # 转HSV
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    # 黄色阈值(H在15-35,S>80,V>50)
    lower_yellow = np.array([15, 80, 50])
    upper_yellow = np.array([35, 255, 255])
    mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
    
    # 转LAB
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    # 白色阈值(L>200,B在130-200)
    lower_white = np.array([200, 0, 130])
    upper_white = np.array([255, 255, 200])
    mask_white = cv2.inRange(lab, lower_white, upper_white)
    
    # 合并
    mask = cv2.bitwise_or(mask_yellow, mask_white)
    return mask

我的经验:阈值不要死记硬背。不同相机、不同光照下,阈值需要微调。我一般会在代码里加一个滑动条调试窗口,实时观察效果。

4.5 梯度阈值(Sobel)

颜色分割搞定了,但还不够。你想想看,如果路面颜色和车道线颜色接近怎么办?比如水泥路上的白色车道线,颜色分割就不好使了。这时候需要梯度信息。

Sobel算子说白了就是求图像的一阶导数。车道线边缘处,像素值变化剧烈,梯度值就大。我们利用这个特性来提取边缘。

Sobel有两个方向:

  • x方向:检测垂直边缘(车道线左右边界)
  • y方向:检测水平边缘(车道线上下边界)

对于车道线,我们主要关注x方向的梯度。因为车道线是竖直的,它的左右边界在x方向变化最明显。

def sobel_threshold(img, sobel_kernel=3):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # x方向Sobel
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    # 取绝对值并归一化到0-255
    abs_sobelx = np.absolute(sobelx)
    scaled_sobel = np.uint8(255 * abs_sobelx / np.max(abs_sobelx))
    # 阈值分割
    grad_binary = np.zeros_like(scaled_sobel)
    grad_binary[(scaled_sobel >= 20) & (scaled_sobel <= 100)] = 1
    return grad_binary

注意:Sobel核大小一般用3或5。核越大,对噪声越不敏感,但边缘会变粗。我曾经在高速场景用7x7的核,结果车道线边缘糊成一团,检测精度直线下降。

4.6 方向滤波

光有梯度大小还不够。你想想看,路面上有各种边缘:路沿、阴影、车辆轮廓……它们都有梯度。怎么区分哪些是车道线?

答案是:看方向。车道线的梯度方向大致是水平的(因为边缘是竖直的)。我们可以计算梯度方向,只保留那些方向接近水平的点。

def direction_threshold(img, sobel_kernel=3):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    # 计算梯度方向
    abs_grad_dir = np.arctan2(np.absolute(sobely), np.absolute(sobelx))
    # 保留方向在45-135度之间的点(接近竖直边缘)
    dir_binary = np.zeros_like(abs_grad_dir)
    dir_binary[(abs_grad_dir >= 0.7) & (abs_grad_dir <= 1.3)] = 1
    return dir_binary

这里0.7和1.3是弧度值,对应大约40度和75度。为什么是这个范围?因为车道线不完全是竖直的,弯道处会有倾斜。我试过更窄的范围,直道表现很好,但弯道直接丢线。后来放宽到0.7-1.3,弯道也能稳定检测了。

4.7 组合策略:颜色+梯度

现在我们有三个二值图:颜色掩码、梯度掩码、方向掩码。怎么组合?

我的做法是:颜色优先,梯度辅助。具体来说:

  • 颜色分割能提取到的,直接用颜色结果
  • 颜色分割失效的区域(比如阴影下),用梯度+方向来补
def combined_threshold(img):
    color_binary = color_threshold(img)
    grad_binary = sobel_threshold(img)
    dir_binary = direction_threshold(img)
    
    # 组合:颜色或(梯度与方向)
    combined = np.zeros_like(color_binary)
    combined[(color_binary == 1) | ((grad_binary == 1) & (dir_binary == 1))] = 1
    return combined

为什么这样组合? 颜色分割的假阳性少,但召回率低。梯度+方向召回率高,但噪声多。两者取「或」关系,既能保证召回,又能通过方向滤波压制噪声。

4.8 本章知识体系

下面这张图总结了本章的核心逻辑,我画成了流程图,方便你理解整个处理链路:

原始图像 (BGR) 颜色空间转换 梯度计算 (Sobel) HSV 阈值 LAB 阈值 梯度大小 梯度方向 颜色掩码 (color_binary) 梯度+方向掩码 (grad_dir_binary) 最终二值图 (combined)

这张图展示了完整的处理流程:原始图像先分两路,一路做颜色空间转换和阈值分割,另一路做Sobel梯度计算和方向滤波,最后合并成一张二值图。这个二值图就是后续车道线拟合的输入。

4.9 避坑指南

最后分享几个我踩过的坑:

  • 阈值不要写死:不同光照、不同路面材质,阈值差异很大。我建议在代码里留一个配置文件,方便现场调参。
  • Sobel核大小要匹配分辨率:高清图像用3x3核,边缘太细碎;低分辨率图像用5x5核,边缘又太粗。我一般根据图像宽度来选:宽度大于1000用5x5,否则用3x3。
  • 方向滤波的弧度范围要留余量:弯道处车道线方向会变化,范围太窄会丢线。0.7-1.3是我试过比较稳妥的值。
  • 颜色和梯度要互补:不要只依赖一种方法。颜色在强光下容易饱和,梯度在弱纹理路面容易失效。两者结合才是王道。

好了,这一章的内容就到这里。颜色和梯度是传统车道线检测的两大基石,下一章我们会在此基础上做透视变换和滑动窗口搜索。记住,先把基础打牢,后面的路才好走。

可可精品资料库