4. 车道线检测进阶(深度学习方法)
聊完了传统方法,咱们得进入正题了。说实话,传统方法在结构化道路、光照好的时候确实够用。但一到雨天、夜晚、或者路面有阴影,传统方法就开始「摆烂」了。我自己的项目里,最早也是用传统方法,后来发现鲁棒性实在扛不住,才彻底转向了深度学习。
深度学习做车道线检测,核心思路就一个:让网络学会「看懂」图像中的线条结构。今天咱们重点聊三个方向:语义分割、实例分割与聚类、以及轻量化部署。
4.1 语义分割网络:UNet 与 LaneNet
语义分割,说白了就是给每个像素打标签。车道线像素标为1,背景标为0。这活儿谁干得好?UNet 和 LaneNet 是经典代表。
4.1.1 UNet:从医学图像到车道线
UNet 最早是做医学图像分割的,但结构太经典了,直接拿来做车道线效果也不错。它的核心是「编码-解码」结构,加上跳跃连接。
我习惯把 UNet 理解为:先下采样提取特征,再上采样恢复分辨率,中间用跳跃连接把细节补回来。你想想看,下采样会丢失位置信息,跳跃连接就是「抄近道」把底层细节送到高层。
# 一个简化的 UNet 编码块
import torch.nn as nn
class DownBlock(nn.Module):
def __init__(self, in_ch, out_ch):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=1),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True),
nn.Conv2d(out_ch, out_ch, 3, padding=1),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True)
)
self.pool = nn.MaxPool2d(2)
def forward(self, x):
x = self.conv(x)
skip = x # 跳跃连接保存
x = self.pool(x)
return x, skip
嗯,这里要注意:跳跃连接保存的是卷积后的特征图,不是原始输入。我在项目中踩过这个坑,一开始把原始图像当跳跃连接传过去,结果网络根本学不动。
4.1.2 LaneNet:专为车道线设计
LaneNet 是专门为车道线检测设计的。它有两个分支:分割分支和嵌入分支。分割分支做二分类(车道线/背景),嵌入分支做像素聚类。
我个人觉得 LaneNet 的精髓在于:它把「分割」和「聚类」放在一个网络里端到端训练。分割分支负责找出哪些像素是车道线,嵌入分支负责把这些像素分到不同的车道线实例中。
4.2 实例分割与聚类:把车道线分开
语义分割只能告诉你「哪些像素是车道线」,但分不清「这是第几条车道线」。实例分割要解决的就是这个问题。
常用的方法有两种:
- 基于聚类的后处理:LaneNet 的嵌入分支输出每个像素的嵌入向量,然后用 DBSCAN 或 MeanShift 聚类。我建议用 DBSCAN,因为它不需要预先指定聚类数量。
- 基于注意力机制的端到端方法:比如 CLRNet、LaneATT,直接用 Transformer 或注意力机制预测每条车道线的曲线参数。
我曾经在项目中试过聚类方法,发现一个问题:聚类速度太慢。每帧图像都要跑一次 DBSCAN,在嵌入式设备上根本扛不住。后来换成了基于锚点的回归方法,速度直接提升了 5 倍。
4.3 轻量化模型部署:TensorRT 实战
模型训练好了,得部署到车上。车上的算力有限,不能指望 3090 跟着跑。TensorRT 是 NVIDIA 的推理优化工具,能把模型压缩、量化、加速。
4.3.1 模型转换流程
从 PyTorch 到 TensorRT,我习惯走这条路:
- 导出 ONNX:PyTorch 模型转成 ONNX 格式
- ONNX 转 TensorRT:用 trtexec 或 Python API 转换
- 精度验证:对比 FP32 和 FP16 的输出差异
# PyTorch 导出 ONNX
import torch
dummy_input = torch.randn(1, 3, 288, 800)
torch.onnx.export(
model,
dummy_input,
"lanenet.onnx",
opset_version=11,
input_names=['input'],
output_names=['seg', 'embed']
)
嗯,这里有个坑:ONNX 的 opset 版本要选对。我一开始用的 opset=13,结果 TensorRT 8.x 不支持,折腾了半天。后来统一用 opset=11,兼容性最好。
4.3.2 量化与加速
TensorRT 支持 FP16 和 INT8 量化。FP16 基本无损,INT8 需要校准数据集。
| 精度模式 | 推理速度(ms) | 精度损失 | 适用场景 |
|---|---|---|---|
| FP32 | 15 | 无 | 验证、调试 |
| FP16 | 8 | <0.5% | 实车部署 |
| INT8 | 5 | 1-2% | 资源受限设备 |
我个人建议:实车部署先用 FP16。INT8 虽然快,但精度损失在复杂场景下可能漏检。我遇到过 INT8 量化后,夜间车道线检测率下降了 8%,后来老老实实换回 FP16。
4.3.3 部署优化技巧
除了 TensorRT 本身的优化,还有一些工程技巧:
- 输入尺寸固定:不要用动态尺寸,固定为 288x800 或 360x640,能省不少时间
- 异步推理:用 CUDA Stream 做异步推理,CPU 和 GPU 可以并行工作
- 预处理融合:把归一化、减均值等操作融合到 TensorRT 引擎里,减少 CPU-GPU 数据传输
我记得有一次,光是把预处理从 CPU 挪到 GPU,推理帧率就从 30fps 提到了 45fps。你想想看,这个优化多划算。
4.4 小结
深度学习做车道线检测,说白了就是:用分割网络找出像素,用聚类或回归区分实例,用 TensorRT 加速部署。每一步都有坑,但踩过去之后,你会发现效果比传统方法好太多了。
下一章咱们聊车道保持控制算法,到时候会用到今天检测出来的车道线。嗯,先把检测做好,控制才有意义。