资讯动态

Ultra-Fast-Lane-Detection车道线检测复现实战:从行分类原理到模型部署

发布时间:2026/9/15 19:28:52 来源:尧图企业网站定制
前阵子要把车道线检测模块接进一个实时项目对比了传统分割路线和anchor-based路线之后最终选了Ultra-Fast-Lane-Detection来复现。这个项目最吸引人的地方在于它把车道线检测从逐像素分割换成了“基于行方向的选择”问题在不引入复杂后处理的前提下能跑到很高的速度同时也保持了不错的精度。我断断续续踩了两周坑把训练、评估、推理到导出整个链路跑通了一遍这里把我的复现过程和遇到的实际问题完整记录下来。这篇内容适合正在做车道线检测、要快速跑通一个可落地模型或者想研究行分类方案的同学参考。我尽量把每一步的“为什么这样做”讲清楚不只是贴命令。1. 先聊聊为什么值得复现这个模型1.1 车道线检测的几个现实痛点车道线检测在自动驾驶和辅助驾驶里属于“看似不难、做起来痛点却很多”的任务。早期大家习惯用分割模型输出一个 H×W 的 mask然后通过后处理把车道线区域提取出来。这个思路的问题是分割的空间分辨率要求越高计算量越大而车端的推理资源往往有限。车道线本身在整张图里占比并不大逐像素去做分割其实把大量算力浪费在了背景上。另一个痛点是车道线的拓扑结构。车道线常常呈现细长条形存在遮挡、磨损、光照变化分割模型容易把一条连续的车道线断成好几段。后续如果要拟合曲线断线会带来不少麻烦。此外分割模型的输出还需要经过聚类或者连通域分析才能得到“哪几个像素属于哪一根线”这个后处理过程有时候比模型本身还慢。在复现 Ultra-Fast-Lane-Detection 之前我需要的是一个能兼顾实时性和结构化输出的方案。它的设计思路正好避开了这么多麻烦把问题换了一个角度来解。1.2 Ultra-Fast-Lane-Detection 的核心思路从分割到行分类这个模型的关键创新点我个人理解可以浓缩成一句话把车道线检测从“像素分类”改成了“行方向上的位置分类”。具体来说论文把图像在竖直方向划分为固定的行锚点row anchor车道线在某些行位置上总会存在。模型要做的事情是对于每一根车道线、在每一个行锚点上预测它落在水平方向上的哪一个单元格。这样每个点的输出就是一个分类概率分布对应一辆车前方横向的若干个位置格子。这样设计的收益非常直接。假设输入分辨率是 288×800分割方案要对 288×800230400 个像素做逐像素分类而行分类方案只需要在每行锚点上做少量格子分类输出的 logits 数量大幅减少推理速度快得不是一点半点。而且结果天然就是结构化的同一根车道线在不同行上的分类结果连起来再做一个简单的后处理就能画成一条线。这也解释了为什么这个模型在英伟达 Jetson 这类边缘设备上也能跑到实时的原因。除了速度模型还有一个比较巧妙的设计是加入了结构感知的损失。它把属于同一根车道线的行位置之间的预测做了一致性约束避免预测出来的点在相邻行之间跳来跳去。换言之它不光要分类正确还要求点在空间上连续平滑这对最终的曲线拟合帮助很大。1.3 复现之前需要具备的基础条件我的建议是至少在动手前准备好这些一台有 NVIDIA GPU 的机器显存 8GB 以上会比较舒服我用 11GB 显存跑 288×800 输入没有压力Linux 环境或者 WSL2已经装好的 Anaconda以及一个最基本的 PyTorch 环境。Ultra-Fast-Lane-Detection 官方仓库是基于 PyTorch 实现的版本不用追求最新我用的是 PyTorch 1.10 左右的版本跑得很稳。如果没有高配 GPU也不用绝望。这个模型本身比较轻用 4GB 显存跑小 batch 也可以起步只是训练时间会拉长或者可以退而求其次用 MobileNetV2 作为 backbone 来训练精度有所下降但速度更快。2. 环境准备与数据集处理2.1 环境配置清单官方仓库的结构比较清晰第一步自然是把代码拉下来。我这里直接给出一份我实验过可用的环境配置清单git clone https://github.com/cfzd/Ultra-Fast-Lane-Detection.git cd Ultra-Fast-Lane-Detection conda create -n ufld python3.8 -y conda activate ufld pip install torch1.10.0 torchvision0.11.0 --extra-index-url https://download.pytorch.org/whl/cu113 pip install numba opencv-python tqdm scikit-learn imgaug从实际经验看numba 版本和 numpy 版本比较容易打架。如果 import numba 报错检查一下是不是 numpy 版本过高直接pip install numpy1.24能解决大多数兼容纠纷。除此之外imgaug 这个库用来做数据增强在较新的 Python 环境下可能需要手动安装一些依赖不过正常pip install一般都能装上。整体来说环境搭建的成本不高真正的重点在数据上。2.2 数据集下载与目录整理官方默认以 TuSimple 数据集作为例子我也用的是 TuSimple。这个数据集包含高速场景下的车道线标注每张图尺寸是 1280×720大约有 3626 张训练图像和 2782 张测试图像每张图最多标注五条车道线。下载页面提供了多个压缩包包括 train_set、test_set 和 test_label。下载完成后把文件解压后整理成官方 README 中建议的目录结构。这里有一个容易忽略的点很多新手把测试集的标注放错位置导致评估的时候找不到 ground truth。测试集的格式和训练集不完全一样两者后的 JSON 文件结构也有差异需要留意。整理后的目录可以这样组织TUSIMPLE/ ├── train_set/ │ ├── clips/ │ └── label_data_0313.json │ └── label_data_0531.json │ └── label_data_0601.json ├── test_set/ │ ├── clips/ └── test_label.json官方代码里的数据集路径可以在配置文件中设置我一般把数据集放在仓库外的一个固定目录然后在配置里改成绝对路径避免每次重装环境都把数据集搬来搬去。2.3 标注文件到底长什么样TuSimple 的标注格式值得花点时间看明白。JSON 文件里每个条目包含lanes、h_samples、raw_file字段。其中h_samples是一系列固定的纵坐标lanes里每一组数字对应一条车道线在这些纵坐标上的横坐标位置如果某条线在该位置不存在对应的坐标值是 -2。这个设计也决定了后面模型输出要对齐成类似的结构模型预测的也是“在预定义行位置上的横坐标位置”所以后续测试脚本计算 ACC 时可以直接和 ground truth 做比较。理解了这个格式再看代码里的Dataset类和损失函数思路会顺很多。2.4 配置文件按需调整官方仓库里configs/tusimple.py是 TuSimple 的默认配置。我需要调整的地方通常有这几个dataset路径改成自己的绝对路径。batch_size根据显存调整官方默认是 8我没改太多。epochs默认比较大刚开始可以缩短到 30~50 个 epoch 验证流程是否通。backbone可以选择resnet18、resnet34、resnet50等如果要快速验证就选resnet18。这里有一个小技巧第一次跑通流程时可以先把epochs设为 1batch_size设为 2同时把num_workers设为 0确认数据加载、训练循环、日志输出都能正常走完。跑通了再把参数调回去做正式训练。很多复现失败不是因为代码不对而是第一次直接全量训练中途报错连排查的耐心都没有。3. 训练全流程实操3.1 修改模型配置与超参数训练前我习惯再检查几个关键配置项。首先是输入尺寸官方默认是 288×800这个比例和 TuSimple 的原图比例接近。追求精度可以调成 320×800追求速度可以降到 256×640但第一次复现建议保持官方默认别一上来就自己发明参数。其次是行锚点相关的设置配置文件里的num_cells是一个重要的量。它表示水平方向把图片分成多少个格子默认是 100。这个值设置得越小输出规模越小速度越快但格子变粗预测的位置精度会下降。另一组相关的划分是num_rows也就是垂直方向锚点的数量。这些参数直接决定了模型最后输出的张量 shape如果改了之后和预训练权重尺寸不匹配加载权重时会报错需要注意。学习率策略上官方使用 SGD 优化器初始学习率设置得比较大配合 poly 学习率衰减。如果使用预训练 backbone学习率可以适当调低一点否则很容易在初期就震荡。3.2 启动训练与训练日志解读配置没问题之后启动训练的命令其实很简单python train.py configs/tusimple.py训练日志会打印每个 epoch 的 loss 和评估指标。第一次跑通会感觉到这个模型的训练速度确实快哪怕用 ResNet18 作为 backbone单卡也能比较快地过一个 epoch。这个“快”不只是模型结构带来的也跟输入分辨率比较低有关。训练日志里需要重点关注两个指标的变化一个是总 loss一个是验证集 Acc。总 loss 会包含分类损失和结构相似性损失。如果 loss 下降得特别慢可以先检查学习率或者看看是不是 backbone 没有加载预训练权重。验证集 Acc 是衡量当前模型检测准度的主要依据Acc 在 0.9 以上基本说明模型已经学到了合理的车道线位置特征。还有一个细节训练脚本在每一个 epoch 或者固定间隔会保存模型权重。这些权重文件名通常会包含 epoch 数。建议不要只留最后一个把表现最好的那个也保留一份后续评估时可以做对比。3.3 损失函数与训练加速的取舍损失函数部分模型输出包含多个分支总损失是多项损失的加权和。相信很多人在读代码时最困惑的就是这里为什么要加这么多项。第一个是分类损失用于约束每个行锚点上的格子预测正确。第二个是相似性损失它的作用是让同一根车道线在不同行上的预测点彼此靠近、保持连续性。第三个是辅助分割损失这个是在训练时才有的它让模型学习一个粗粒度的语义分割辅助任务帮助 backbone 提取到更好的特征。推理时这个分支会被去掉因此不会增加实际部署的计算量。理解这三项之后训练时就不太会犯“看到 loss 不降就乱调权重”的错。比如相似性损失权重调大了模型会把车道线附近的点往一起拉但如果分类分支本身没收敛这样反而会误导模型。一般资料上推荐的默认权重是经过实验校验的复现阶段不建议大改。如果想要加速训练除了换更轻量的 backbone还可以减小num_cells来减少输出维度或者把输入分辨率降一点。但要注意这些改动都会直接影响精度指标。我实际测试过把分辨率从 288×800 降到 256×640推理速度提升了大约 20%但 TuSimple 的 ACC 下降了约 1 到 2 个百分点属于可以接受的权衡。3.4 评估指标ACC 和 IOU 的正确打开方式训练完之后需要验证效果官方测试脚本会计算多个指标最重要的是 ACC 和 IOU。ACC 计算的是预测车道线点与真值点之间的“横向距离是否在阈值范围内”的准确率。每个行锚点上都会比较一次预测位置和 GT 位置。这个指标直观理解就是“预测的车道线点有没有落到正确横向位置”阈值一般是像素距离具体是 20 像素还是 30 像素要看最终评测需要。官方测试脚本里还支持对不同 row span 的预测做对齐避免误判。IOU 则是对检测出的车道线段和真实车道线区域做重叠度计算。这个指标更接近传统检测任务里的“检出质量”概念它综合考量了检出的完整性以及误检率。这两个指标结合起来看基本可以判断模型是偏保守还是偏激进ACC 高但 IOU 低说明预测点大多靠近真值但可能漏掉了一部分车道线IOU 高但 ACC 低说明检出的线段区域完整但横向位置有偏差。评估命令也简单python test.py configs/tusimple.py --test_model ./logs/tusimple.pth跑完会输出一张结果表。我第一次跑的时候 ACC 大概在 0.92 左右跟论文报告的水平接近证明复现流程本身没有问题。4. 推理、可视化和模型导出4.1 编写推理脚本与可视化输出训练之后我写了一个简单的 Python 脚本把模型在真实图片上的预测结果画出来这样可以很直观地感受模型效果。流程大概是读图resize 到模型输入尺寸归一化送入模型取输出中的预测坐标然后再换算回原图尺寸。关键代码如下我对官方 demo 稍作了简化import cv2 import torch import numpy as np from model.model import parsingNet from data.constant import tusimple_row_anchor # 配置模型结构 net parsingNet(pretrainedFalse, backboneresnet18, cls_dim(100 1, 56, 4)) state_dict torch.load(logs/tusimple.pth, map_locationcpu)[model] net.load_state_dict(state_dict) net.cuda().eval() img cv2.imread(demo.jpg) img cv2.resize(img, (800, 288)) img img / 255.0 img torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0).float().cuda() with torch.no_grad(): out net(img) # out 里包含多个输出根据官方实现取预测结果这里有个非常容易犯错的点cls_dim中的 100 必须是配置文件里的num_cells56 对应的是行锚点数量4 对应最大车道线数量。如果这三个数字和训练时不一致权重加载就会失败或者输出结果错得离谱。预测完成后要把每个类的 argmax 结果转换成横向像素坐标然后结合固定的纵向坐标画到原始图像上。整个过程不复杂但值得写个封装函数后面做视频推理会反复用到。4.2 模型转换与部署注意事项如果要把模型部署到实际项目里PyTorch 原生的推理效率通常不够。我的做法是先转成 ONNX再根据目标平台决定是否转成 TensorRT 引擎。转 ONNX 时第一步需要把模型的 TorchScript 导出做好因为模型在推理时有一些辅助分支是不需要输出的。我的做法是直接在导出脚本里构造模型后只保留主输出 tensor其它分支都去掉。同时把输入输出的动态维度固定下来这样可以避免在 TensorRT 转换时遇到动态 shape 带来的麻烦。另一个坑是模型的输入归一化方式。训练时作者选择了简单的除以 255而有些部署环境里的图像预处理流水线会先做减均值再除以标准差这会让模型效果大打折扣。切换部署平台之后一定要先拿几张同样的图做对比确认预处理逻辑完全一致。4.3 把车道线画到视频流里画视频流本质上就是在推理循环里不断执行“读帧、resize、推理、画线、显示”。不过我的经验是不要直接在每帧里做太多 numpy 操作否则即使模型很快后处理也会成为瓶颈。可以把 resize、归一化、坐标转换这些步骤都用简单数组操作完成并预先分配好缓存。对于车道线的平滑可以在多帧之间做一次简单的均值滤波。比如记录前几帧预测出的车道线点坐标当前帧的点坐标和前几帧的平均值按 7:3 的比例混合出来的效果会稳定不少。这个技巧在真实驾驶视频里尤其有用因为单帧预测可能会有轻微抖动。5. 复现过程中踩过的坑5.1 训练不收敛或过拟合的常见原因第一个遇到的坑是模型不收敛。我当时的现象是 loss 一直在几十附近徘徊降不下去。排查下来发现是初始学习率设置过大模型在前期震荡太严重。后来调整学习率并加入 warmup问题就缓解了。另一个更容易被忽略的问题是 backbone 预训练权重没加载。如果随机初始化 backbone训练周期又不够长模型很难在短时间内学到有效的特征。检查方式很简单在训练日志里打印第一层卷积的参数是否和预训练权重一致或者看训练初期 loss 下降的斜率。过拟合在 TuSimple 这类数据量不算很大的任务里也容易发生。表现是训练集 loss 降到很低但验证集 Acc 反而变差。这时候可以适当增加数据增强强度比如随机旋转、仿射变换、水平翻转官方配置里已经支持这些操作。我建议第一次跑的时候不要关闭数据增强它能明显帮模型提升泛化能力。5.2 显存不足的几个有效解法显存不足基本出现在训练阶段。第一个解法是直接减小 batch size这个最直观。你可能会担心 batch size 减小影响 BN 统计不过在实际使用中只要不是特别极小比如不要小到 1影响一般可以接受。第二个解法是降低输入分辨率。训练时把 288×800 改成 256×640显存占用会小很多。如果需要更高的精度可以锁定输入分辨率同时把 batch size 调低凑合跑。第三个解法是使用混合精度训练PyTorch 的 AMP 模块可以直接用对显存的节省效果非常明显而且不会损失太多精度。如果显存还是不够可以考虑换更轻的 backbone。backbonemobilenet_v2这类轻量网络能显著降低显存占用缺点是精度会有一定程度下降。5.3 验证结果与预期不一致时的排查思路我遇到过模型在训练集上表现很好但验证集上一塌糊涂的情况。后来发现是验证时使用了和训练时不同的图像预处理流程比如尺寸缩放插值方式不同。不要小看这个差异车道线是细长结构对插值方式敏感用最近邻插值重采样之后线条位置可能偏移好几个像素导致评估结果不理想。另一个常见问题是评估脚本加载权重时发现分类维度对不上。这个一般是因为行锚点数量、num_cells、最大车道线数量任何一个改了而权重是基于另一组参数训练的。遇到这种情况先把配置恢复成权重训练时的参数。最后要留意设备一致性。我曾在 GPU 上训练然后在 CPU 上测试发现结果和和 GPU 测试的差别不小尤其是 BatchNorm 层的统计行为在不同设备上不完全一致。部署时也要确保推理阶段的数据流和训练时基本一致否则模型的输出会偏移。5.4 一个问题速查表现象可能原因解决办法训练 loss 不下降学习率过大或过小调整学习率、加入 warmup验证 ACC 低但训练 ACC 很高数据增强不足或过拟合增强数据增强、增加训练数据、早停加载权重时报 shape 不匹配配置参数与训练时不一致检查num_cells、num_rows、num_lanes推理时画面闪烁跳动单帧预测不稳定多帧均值滤波或平滑处理显存不足输入分辨率过高或 batch 过大降低输入分辨率、减小 batch、用 AMPtest.py 结果与论文差距大预处理不一致或权重不对对齐预处理流程、重新训练6. 从复现到改进还能往哪个方向走6.1 轻量化变体模型结构本身对轻量化很友好。除了官方支持的 ResNet 系列也可以把 backbone 换成 MobileNetV2 或者更极端的 ShuffleNetV2。换 backbone 的代价是精度会有一定损失但如果结合知识蒸馏用大模型当 teacher、小模型当 student能在精度和速度之间找到更好的平衡点。我简单试过用 ResNet50 训练一个 teacher 模型再去蒸馏 MobileNetV2 版本的 student 模型两个模型在 TuSimple 上的 ACC 差距大约能收缩一半以上。这个方向适合做嵌入式部署场景能把速度提升的非常明显。6.2 与其他任务结合模型的行分类输出天然是结构化的这意味它很容易跟下游任务融合。比如在规划模块里与其拿一个分割 mask 再拟合曲线不如直接拿行分类输出点做三次样条插值转弯时的曲率估计会更稳定。另外也可以把模型和多目标跟踪结合起来。同一根车道线在连续帧里会有稳定的横向分类结果通过时间序列平滑能进一步降低误检。我做的项目里就把预测的点序列和车辆自身的横向速度做了融合车道线位置预测的抖动明显减少了。6.3 性能与精度的最终盘点复现完成之后我对这个模型的综合评价是在实时性、结构性和部署便利性这三个维度上它的平衡做得很好。TuSimple 这类标准数据集上官方配置就能达到相当不错的效果在 2080Ti 上单帧推理时间非常短输出天然是结构化点集不需要做复杂后处理这对后续工程化很友好。如果让我重新再复现一次我会先花更多时间在数据增强和数据检查上而不是一上来就全量跑训练。很多复现翻车都出在对输入输出格式、标注坐标体系理解不透彻上。把这些基础问题摸清后面无论换数据集还是改 backbone流程都会顺很多。希望你也能一次跑通不用像我一样被各种小坑打磨到半夜。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价