第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
我实际测试过,这套方法在晴天、阴天、黄昏都能稳定工作。但遇到大雨天或者积雪覆盖,那就得另想办法了——那些场景需要结合其他特征,比如边缘和纹理。
1.6 本章知识体系
下面这张图总结了本章的核心逻辑:
这张图展示了从BGR图像输入到二值掩膜输出的完整流程。核心就是两步:颜色空间转换 + 阈值分割。白色和黄色分别处理,最后合并。
好了,这一章的内容就到这里。记住:颜色阈值是车道线检测的起点,但不是终点。下一章我们会在此基础上,加入边缘检测和霍夫变换,让检测更鲁棒。
可可精品资料库