第1章:颜色空间与阈值分割

各位同学好,我是你们的车道线检测实战课讲师。今天咱们来聊聊颜色空间和阈值分割。

说实话,我刚入行那会儿,也以为车道线检测就是找个边缘检测算法就完事了。结果呢?在一条黄昏的乡间小路上,我的算法直接罢工了——因为光线太暗,白色车道线和路面几乎融为一体。那次之后我才明白:颜色空间转换和阈值分割,才是车道线检测的基石。

1.1 为什么需要颜色空间转换?

咱们摄像头拍到的图像,默认是BGR格式。BGR代表蓝、绿、红三个通道。但问题来了——BGR对光照变化特别敏感。

你想想看,同样是白色车道线,晴天和阴天拍出来的BGR值能差好几倍。这怎么设阈值?

所以我们需要一个对光照不那么敏感的颜色空间。HSV就是这样一个好东西。

1.2 BGR与HSV的转换

HSV把颜色拆成三个分量:

  • H(Hue):色调,说白了就是颜色种类。红色是0度,绿色是120度,蓝色是240度。
  • S(Saturation):饱和度,颜色鲜艳程度。0是灰色,255是纯色。
  • V(Value):明度,亮度高低。0是黑色,255是白色。

我个人习惯用OpenCV做转换,代码就一行:

import cv2
import numpy as np

# 读取图像(默认BGR)
img_bgr = cv2.imread('road.jpg')

# 转换到HSV
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

这里有个坑,我提醒一下:OpenCV里的H范围是0-179,不是0-359。很多新手直接拿0-360去设阈值,结果什么都检测不到。我曾经因为这个bug调试了整整一个下午。

1.3 颜色阈值设定

好了,现在图像在HSV空间了。我们要提取白色和黄色车道线,就得知道它们的HSV范围。

我整理了一个常用表,你们可以直接用:

颜色 H范围 S范围 V范围
白色 0-180 0-30 200-255
黄色 15-35 100-255 100-255

注意看,白色的H范围是全的,因为白色没有色调。它的特点是饱和度低、亮度高。黄色的特点是色调在黄绿之间,饱和度和亮度都适中。

代码实现是这样的:

# 定义颜色范围
lower_white = np.array([0, 0, 200])
upper_white = np.array([180, 30, 255])

lower_yellow = np.array([15, 100, 100])
upper_yellow = np.array([35, 255, 255])

# 生成掩膜
mask_white = cv2.inRange(img_hsv, lower_white, upper_white)
mask_yellow = cv2.inRange(img_hsv, lower_yellow, upper_yellow)

# 合并掩膜
mask_combined = cv2.bitwise_or(mask_white, mask_yellow)
小技巧:实际项目中,我建议你准备几张不同光照条件下的道路图片,手动调整阈值。没有万能参数,只有最适合你场景的参数。

1.4 二值化处理

掩膜本身就是二值图像了。但有时候我们需要对灰度图做二值化,比如在夜间或者阴影区域。

常用的二值化方法有三种:

1.4.1 全局阈值(OTSU)

OTSU算法会自动计算最佳阈值。它的原理是让前景和背景的类间方差最大。说白了,就是帮你找到那个「分得最开」的阈值。

# 转灰度
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

# OTSU二值化
_, binary_otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

嗯,这里要注意:OTSU假设图像是双峰分布。如果路面和车道线的亮度分布不均匀,效果会打折扣。我在高速公路上遇到过这种情况——路面反光导致OTSU把路面也当成车道线了。

1.4.2 自适应阈值

自适应阈值把图像分成小块,每块单独算阈值。这样能应对光照不均匀的情况。

# 自适应阈值
binary_adaptive = cv2.adaptiveThreshold(
    gray, 255, 
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
    cv2.THRESH_BINARY, 
    11,  # 块大小,必须是奇数
    2    # 常数,从均值中减去的值
)

块大小和常数需要调参。块太大就失去自适应的意义,块太小又会被噪声干扰。我一般从11开始试,效果不好再调。

1.4.3 三种方法对比

方法 优点 缺点 适用场景
颜色阈值(HSV) 对颜色敏感,抗光照好 需要手动调参 白天、车道线颜色明显
OTSU 自动计算,简单 对光照不均匀敏感 光照均匀的场景
自适应阈值 抗光照不均匀 参数多,容易过分割 阴影、夜间

1.5 实战:提取白色/黄色车道线

好了,理论讲完了,咱们来写一个完整的车道线提取函数。

def extract_lane_lines(img_bgr):
    """
    提取白色和黄色车道线
    返回二值掩膜
    """
    # 1. 转换到HSV
    img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
    
    # 2. 白色阈值
    lower_white = np.array([0, 0, 200])
    upper_white = np.array([180, 30, 255])
    mask_white = cv2.inRange(img_hsv, lower_white, upper_white)
    
    # 3. 黄色阈值
    lower_yellow = np.array([15, 100, 100])
    upper_yellow = np.array([35, 255, 255])
    mask_yellow = cv2.inRange(img_hsv, lower_yellow, upper_yellow)
    
    # 4. 合并
    mask_combined = cv2.bitwise_or(mask_white, mask_yellow)
    
    # 5. 可选:形态学操作去除噪声
    kernel = np.ones((3, 3), np.uint8)
    mask_clean = cv2.morphologyEx(mask_combined, cv2.MORPH_CLOSE, kernel)
    
    return mask_clean
核心思路:先用HSV颜色阈值提取白色和黄色区域,再用形态学操作去除小噪点。这样得到的二值掩膜,就是车道线的候选区域。

我实际测试过,这套方法在晴天、阴天、黄昏都能稳定工作。但遇到大雨天或者积雪覆盖,那就得另想办法了——那些场景需要结合其他特征,比如边缘和纹理。

避坑指南:我曾经在一条有蓝色路标的道路上测试,结果把蓝色路标也当成车道线了。后来加了面积过滤才解决。所以记住:颜色阈值只是第一步,后面还要加几何约束。

1.6 本章知识体系

下面这张图总结了本章的核心逻辑:

颜色空间与阈值分割知识体系 BGR图像输入 BGR → HSV 转换 白色阈值 (H全, S低, V高) 黄色阈值 (H15-35, S100+, V100+) 二值掩膜输出

这张图展示了从BGR图像输入到二值掩膜输出的完整流程。核心就是两步:颜色空间转换 + 阈值分割。白色和黄色分别处理,最后合并。

好了,这一章的内容就到这里。记住:颜色阈值是车道线检测的起点,但不是终点。下一章我们会在此基础上,加入边缘检测和霍夫变换,让检测更鲁棒。

可可精品资料库