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 的精髓在于:它把「分割」和「聚类」放在一个网络里端到端训练。分割分支负责找出哪些像素是车道线,嵌入分支负责把这些像素分到不同的车道线实例中。

关键点: LaneNet 的损失函数是分割损失 + 聚类损失的加权和。聚类损失用的是「判别性损失」,让同一车道线的像素嵌入向量靠近,不同车道线的远离。

4.2 实例分割与聚类:把车道线分开

语义分割只能告诉你「哪些像素是车道线」,但分不清「这是第几条车道线」。实例分割要解决的就是这个问题。

常用的方法有两种:

  • 基于聚类的后处理:LaneNet 的嵌入分支输出每个像素的嵌入向量,然后用 DBSCAN 或 MeanShift 聚类。我建议用 DBSCAN,因为它不需要预先指定聚类数量。
  • 基于注意力机制的端到端方法:比如 CLRNet、LaneATT,直接用 Transformer 或注意力机制预测每条车道线的曲线参数。

我曾经在项目中试过聚类方法,发现一个问题:聚类速度太慢。每帧图像都要跑一次 DBSCAN,在嵌入式设备上根本扛不住。后来换成了基于锚点的回归方法,速度直接提升了 5 倍。

避坑指南: 如果你用聚类方法,建议把嵌入向量的维度设小一点(比如 4 维或 8 维)。维度太高,聚类效果反而变差,而且计算量暴增。我曾经设成 64 维,结果聚类时间比推理时间还长。

4.3 轻量化模型部署:TensorRT 实战

模型训练好了,得部署到车上。车上的算力有限,不能指望 3090 跟着跑。TensorRT 是 NVIDIA 的推理优化工具,能把模型压缩、量化、加速。

4.3.1 模型转换流程

从 PyTorch 到 TensorRT,我习惯走这条路:

  1. 导出 ONNX:PyTorch 模型转成 ONNX 格式
  2. ONNX 转 TensorRT:用 trtexec 或 Python API 转换
  3. 精度验证:对比 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。

警告: TensorRT 的 INT8 量化需要校准数据集。校准数据集必须覆盖各种场景(白天、夜晚、雨天、隧道)。如果只用白天数据校准,晚上部署时精度会崩。我吃过这个亏,后来专门收集了 2000 张多场景图片做校准。

4.3.3 部署优化技巧

除了 TensorRT 本身的优化,还有一些工程技巧:

  • 输入尺寸固定:不要用动态尺寸,固定为 288x800 或 360x640,能省不少时间
  • 异步推理:用 CUDA Stream 做异步推理,CPU 和 GPU 可以并行工作
  • 预处理融合:把归一化、减均值等操作融合到 TensorRT 引擎里,减少 CPU-GPU 数据传输

我记得有一次,光是把预处理从 CPU 挪到 GPU,推理帧率就从 30fps 提到了 45fps。你想想看,这个优化多划算。

4.4 小结

深度学习做车道线检测,说白了就是:用分割网络找出像素,用聚类或回归区分实例,用 TensorRT 加速部署。每一步都有坑,但踩过去之后,你会发现效果比传统方法好太多了。

下一章咱们聊车道保持控制算法,到时候会用到今天检测出来的车道线。嗯,先把检测做好,控制才有意义。