去年做道路巡检相关的项目手头有一批路面视频和图片核心需求是把车道线的位置、完整程度、磨损情况给自动评估出来替代人工一帧帧看录像。试过传统视觉方案边缘检测加阈值分割加霍夫变换晴天的路况还能凑合一碰到阴影、逆光、雨后的反光就崩得一塌糊涂。后来转向基于YOLOv8的车道线实例分割检测系统把问题的粒度从“哪里有线”推进到“每条线的掩码区域”训练和应用链路都能落地。这篇文章就从选型、数据集制作、训练细节、后处理到接入巡检系统把我实际摸索的过程完整梳理一遍。1. 车道线识别到底难在哪先说清楚问题边界1.1 巡检场景和自动驾驶场景的诉求差异同样是车道线识别自动驾驶和道路巡检的诉求其实不太一样。自动驾驶关心的是“当前帧我能不能看到线”要的是实时性对单帧误检的容忍度有一定空间毕竟还有融合定位兜底。巡检场景则关心“这段路线漆还剩多少、哪里断线了、哪里磨损得换”要的是准确性、可量化和稳定性。这个差异直接决定了技术选型的方向。巡检系统里车道线不是“辅助信息”而是被检测的主体目标。你需要知道一条完整的车道线在图像上是连续还是断裂的实线区域的掩码比例是多少虚线段的长度和间距是否满足规范甚至黄线、白线的反光率变化。这些信息靠目标检测的目标框给不出来靠语义分割又分不开同一类别下的不同实例。所以我最终定下来用实例分割而不是检测或者语义分割。1.2 实例分割在车道线任务上的具体优势目标检测的边界框特别不适合细长结构。一条车道线在图像上可能占据从画面底部一直延伸到远处消失点的细长区域目标框会包含大量背景给后续判断“线是否完整”带来很大干扰。语义分割能把所有线像素标出来但多条线连在一起时无法区分彼此等于把实例信息丢掉了。实例分割的输出是“每个独立对象的掩码”。对车道线这个场景来说这个“实例”怎么定义很关键一条连续的实线是一个实例一段虚线其中的每一段是独立实例还是整条虚线算一个实例需要根据业务需求去设计。我在实际项目中按“单位长度内的线对象”来定义实例这样后面做连续性分析时会很方便掩码自带了每个对象的边界信息可以直接计算面积、长度、方向。1.3 为什么基于深度学习的方式更适合这种任务传统方案失败的核心原因在于车道线特征极其不稳定。阴影、积水反光、栏杆遮光、沥青修补痕迹、前车遮挡这些情况都会让边缘和颜色特征失效。深度学习模型实际上是在学习“车道线整体形态的上下文模式”对局部干扰天然更鲁棒。尤其是引入实例分割之后模型输出的是带空间语义的掩码比单纯“找出线”或者“框住线”高一整个维度后面做车道磨损评估、缺失检测所有算法逻辑都有了可靠的输入。2. 技术选型对照为什么我会选YOLOv8-seg2.1 跟Mask R-CNN对比精度和速度的取舍我最早搭过一版Mask R-CNN做车道线分割实验精度确实不错但推理速度在巡检场景里不够理想。巡检系统虽然不像自动驾驶要求毫秒级响应但面对长时间视频回放单帧推理时间直接影响整体处理效率。Mask R-CNN的两阶段设计让它天然偏重而YOLOv8-seg是端到端的单阶段方案不做候选区域提议和二次分类分割头直接从多尺度特征图上预测掩码系数速度和显存占用都友好得多。同时YOLOv8-seg的检测头和解码结构又保证了足够的精度。我在验证集上做过对比YOLOv8s-seg的mask mAP50大概能到0.91Mask R-CNN大概0.93差距没有想象中大但推理速度差了3-4倍。对这种工程落地项目这个精度差距完全可以通过数据增强和后续训练技巧弥补速度优势却是实打实的。2.2 YOLOv8相比更早版本YOLO系列的变化YOLOv8在结构上换了C2f模块替代了之前的C3模块梯度流通更顺畅特征融合更充分。同时它全面转向anchor-free设计输出的目标框直接由中心点和宽高确定省去了anchor聚类的调试步骤对车道线这种长宽比极端变化的目标反而更友好因为不再需要预设匹配的先验框形状。解耦头也是v8一个重要变化。分类和回归各走各的分支车道线这类目标类别简单实线、虚线、黄线、白线等分类分支压力不大回归分支和分割分支可以更专注地学习几何结构。我用相同数据训练过YOLOv5s-seg和YOLOv8s-seg,在细长线的完整度上v8明显更稳尤其是远处接近消失点的那部分v8能保住的掩码范围更大。2.3 工程化生态是最后决定性的因素技术选型不只看模型精度还要看整个工程链路是否顺畅。Ultralytics这个框架把训练、验证、导出、推理的流程做得比较完善配置文件是yaml格式数据集组织规则固定预训练权重直接读命令行工具和Python接口都能用。模型导出也非常方便不管是导出ONNX还是TensorRT模型结构基本不需要改动只需处理好输入输出的张量映射就行。对于巡检项目的后续部署来说这一点能省下大量时间你不需要去重新搭数据Loader不需要自己写训练循环把精力都放在真实业务逻辑上。3. 数据集制作决定模型上限的环节3.1 样本采集场景覆盖面比数量更关键训练车道线分割模型数据质量的重要性远高于数量。我一开始从公开数据集里搬了一批图像加上自己采样的几百张训练完在测试视频上效果一般。后来复盘发现公开数据集中在白天晴天的高速场景而实际巡检会遇到多车道城市道路、磨损标线、夜间反光、雨天湿路面类型分布完全错位。我的建议是先把应用场景拆成维度光线白天、黄昏、夜间、天气晴、阴、雨、雨后、道路类型高速、国道、城市道路、线型实线、虚线、双线、黄白、路面状况新线、磨损线、覆盖线、阴影。每个组合至少保证一定比例的样本宁可总数少一点也要让每个维度有代表数据。采集方式上也有些讲究。用行车记录仪还是专业相机、安装高度角度固定不固定、分辨率大小都会影响模型泛化。我实际操作时发现训练集和验证集的采集设备最好保持一致特性不然模型会学到由画幅比例引起的偏差。3.2 标注规范类别设计直接决定业务能力上限车道线的类别怎么分不是随意的决定后续能做什么业务分析。我最初把类别定为简单的“line”和“dashed_line”后来发现黄线和白线在巡检里需要分别评估于是调整为类别ID名称说明0white_line白实线含停止线等连续白色标线1white_dashed白色虚线按单段作为一个实例2yellow_line黄色实线含双黄线中的单侧3yellow_dashed黄色虚线类别调整之后训练成本变化不大但标注工作量上去了因为同一条道路上的黄白线都得分别框出。这里有个细节双黄线在图像上可能是两条挨得很近的线标注时也必须分开标成两个实例否则模型会倾向于把两条线合并成一个整体掩码下游做分车道分析时数据就废了。标注工具我选的是LabelMe因为它输出JSON格式并且支持多边形标注非常适配细长车道线的形状。对于细长物体多边形标注比矩形或者刷子更精确尤其在线段弯道和靠近画面边缘的地方多边形的点密度决定了分割边界的平滑程度。标注的时候有几个容易踩的坑。一是线的端点位置磨损的线可能中间缺一块到底按完整线标还是按磨损后的现状标我的做法是标注实际可见的线区域磨损缺失的地方不标这样模型学到的就是“当前路面的真实线状态”而不会靠“脑补”把缺失区域填上。二是阴影和遮挡区域原则是只要线本身可见就尽量标注完整如果影子刚好投在线上导致线像素完全看不清那这个区域就按实际可见部分标。三是聚合点远处的线会越来越窄直到消失很多标注员习惯直接不标远处这会导致模型对远处线的召回率偏低建议宁可增加标注工作量也要保到足够远的距离。3.3 数据格式转换从LabelMe JSON到YOLO分割格式LabelMe标注完成后要把JSON转成YOLO实例分割需要的txt格式。YOLO分割格式每行对应一个实例class_id x1 y1 x2 y2 ... xn yn所有坐标都归一化到0-1。我写过一个转换脚本核心处理分几步读取JSON中的shapes提取每个多边形的类别标签和原始坐标点将坐标归一化为图像宽高比例保存为同名txt文件。这里需要注意坐标顺序YOLO格式要求点按顺时针排列有些工具输出的多边形的点序不对训练时会出问题。数据组织上Ultralytics框架要求建立images和labels两个目录各自分train和val对应的文件同名但后缀不同。验证集的比例我一般卡在10%-15%之间但要保证每个类别在验证集里都有足够的样本量尤其是黄虚线这种相对少见的类别不能因为它少就把大量验证信息丢掉。3.4 数据增强策略针对细长结构的特殊处理YOLOv8自带一系列数据增强包括Mosaic、随机透视、翻转、颜色变换等。默认配置在检测任务上表现不错但在车道线这种细长结构上不能直接全盘照收。Mosaic增强把四张图拼在一起它的好处是增加目标多样性但对细长物体来说缩放到小尺寸后线会变细甚至断裂反而干扰学习。我实际训练中把Mosaic概率调低到0.5并且只在训练前期开启后期关闭让模型专注于真实分辨率的特征。透视增强对车道线来说是必要的。车辆行驶中相机视角会有微小变化摄像头安装高度不同也会带来消失点位置的偏移。适度做随机透视可以让模型学会“线在不同视角下的形态变化”但幅度不能过大否则会出现车道线被扭曲成波浪形的假数据。亮度对比度增强在这个场景里非常有效。因为白天强光逆光和黄昏低光照是两种极端条件色阶抖动能够模拟这种变化。色调增强不建议开很大车道线的颜色信息是重要特征色调偏移过多会让黄线变绿线。4. 训练与调参从能跑到跑好的几条关键路径4.1 环境配置和模型选择训练环境我用的单卡显存24GYOLOv8s-seg在这种配置下完全跑得动。如果显存紧张就降低batch size或者换更小的模型。模型尺寸方面我对比过n、s、m三个版本模型参数量显存占用mask mAP50推理延迟(单卡)YOLOv8n-seg约3.4M较低0.86约8msYOLOv8s-seg约11M中等0.91约14msYOLOv8m-seg约27M较高0.93约25ms实测下来s版本是性价比最合适的精度比n提升明显速度又没有m那么大的压力。如果部署设备是嵌入式或者边缘盒子可以优先考虑n后续用蒸馏或者半监督的方式把精度提上来。4.2 训练参数配置训练时我用预训练的YOLOv8s-seg权重作为起点。预训练权重虽然在COCO数据集上没有车道线类别但骨干网络已经学好了通用的边缘、纹理、形状特征迁移到车道线任务可以极大加速收敛。关键超参数配置参考如下model: yolov8s-seg.pt data: dataset/road_line.yaml epochs: 200 batch: 16 imgsz: 1280 optimizer: SGD lr0: 0.01 lrf: 0.01 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 mosaic: 0.5图像尺寸选择需要注意。默认的训练尺寸是640但对车道线这种细长目标640分辨率下远处的线可能只有几个像素宽分割掩码很难保住细节。我把imgsz提到1280模型对远处细线的召回率明显改善代价是训练速度下降。如果是高分辨率巡检图像先裁剪而不是直接缩放能保留更多细节。优化器我最终选了SGD而不是AdamW。虽然AdamW收敛更快但在细长分割任务上SGD最终精度更稳不容易在后期出现抖动。学习率策略用cosine衰减前3个epoch做warmup让模型先稳定起步。4.3 训练过程的loss曲线解读和常见异常观察loss曲线是训练中最重要的事情。多看看box_loss、seg_loss、cls_loss和dfl_loss这几条曲线seg_loss持续下降但缓慢说明分割头在正常学习继续训练即可。如果seg_loss在几十个epoch后出现反弹可能是学习率设置太高或者batch size太小。cls_loss降得很快是正常的因为车道线类别少、分类难度低。box_loss和dfl_loss如果波动大看一下是不是数据标注中有框尺寸异常的情况。我还遇到过一个奇怪的状况seg_loss一直不降维持在某个水平震荡。后来排查发现是数据集里有一批图像的标签文件坐标混乱某些多边形点的顺序不对、甚至出现坐标超出图像范围的情况。这种脏数据不会让训练报错但会让损失无法收敛。所以训练之前做数据的合法性检查非常有必要包括坐标范围校验、多边形点数校验、空标签处理。4.4 验证指标和混淆问题验证阶段重点看的是mask mAP50和mask mAP50-95前者是主要参考后者更严格。我习惯再看每个类别的PR曲线和混淆矩阵。车道线任务中常见的混淆模式有两个一是白色实线和白色虚线混淆二是黄色实线和白色实线在低光照下混淆。虚线实线混淆多数原因是标注端没有严格执行“虚线按段落独立实例”的规范或者数据增强把短实线切成了类似虚线的片段。黄白线混淆主要是因为夜间或黄昏时黄线受光照影响在图像上呈现偏白的色调。这时候数据侧的做法是增加夜间和黄昏样本模型侧可以考虑在颜色通道上做针对性增强但不能过度依赖颜色分类。4.5 细线断裂问题的原因训练完成后常见的bad case是远处细线偶尔断裂表现为一条连续的实线在远端被分割成几段掩码不连续。这个问题的根源是多尺度特征融合时浅层高分辨率特征和深层语义特征的平衡没有调到最优。我的处理办法是第一把推理尺寸提高到和训练尺寸一致甚至略高。第二训练时增大Mosaic关闭后的epoch占比让模型在最后阶段看到更多真实分辨率的完整长距离线。第三后处理阶段加一个小尺寸闭运算操作把断裂的掩码重新连接起来。5. 推理后处理把掩码变成巡检系统能用的结果5.1 从掩码到结构化数据的转换模型输出的masks是二进制掩码直接可视化没有问题但巡检系统要的不是图片是可量化的结构化数据。我的后处理流程大概分这几步第一步是提取掩码连通域。YOLOv8输出的每个实例掩码本身就可以看作一个连通域如果你的定义是“每条线是一个实例”则不需要再做连通域拆分。但如果模型把两条靠近的并行线合并成一个掩码就需要用形态学处理或者连通域分析拆开。第二步是骨架化。用形态学细化算法提取掩码的中心线得到一组像素点序列然后通过简单的polyline拟合把这些像素点压缩成折线坐标。这样一条车道线就变成了“这条线的几何路径”。第三步是补点和平滑。车道线远端部分可能检测不到或者抖动要用轨迹拟合的方法补上消失点方向的延伸点。我用的是基于透视关系的外推而不是简单线性延伸因为真实道路上两条平行线在图像中会汇聚到消失点外推方向要根据这个约束来调整。5.2 状态判断逻辑完整性、连续性、磨损程度有了坐标和掩码之后就可以做状态判断了完整性检测计算掩码面积与这条线理论存在区域的覆盖率。我用一个固定ROI窗口对着标准车道线采样比较当前检测掩码与正常线掩码的重叠比例。比例低于阈值判定为磨损或缺失。连续性检测沿骨架坐标每隔一定像素取点计算相邻点的掩码存在性。如果连续多个点都没有掩码覆盖说明线在这个区间断掉了记录断裂位置和断裂长度。虚线段之间的间隔检测对虚线类别提取各个实例的起止坐标计算相邻两段之间的空隙长度。空隙显著大于常规值时说明这一段的虚线变成残线了。5.3 接入巡检系统的架构思路接入巡检系统时我的整体思路是分帧处理加结果汇总。视频进来后按固定间隔抽帧每帧经模型推理和后处理输出JSON结果包含每条车道线的坐标序列、类别、完整性状态。之后这些逐帧结果按时间和空间维度聚合得到整段路的车道线状况报告。这里有一个工程上的建议不要把后处理逻辑直接堆在模型推理的代码里。推理、后处理、汇总分析这三层尽量解耦前面是纯模型服务中间是几何算法模块后面做业务数据库。这样在模型迭代时只要输入输出格式不变业务逻辑完全不用动。可视化输出也很重要。我习惯在图上画出每个实例的掩码边界用不同颜色区分不同类别同时把断裂位置用特殊标记标出来。这样既方便开发阶段人工核对也可以作为巡检报告里的截图证据。6. 实战避坑与后续优化方向6.1 标注一致性带来的教训数据标注的一致性直接影响模型输出的质量。我项目一开始几个人分头标注结果同样一条磨损的实线有的人标出了完整线形有的人只标了线漆残留区域还有人把空白缺口也标进去了。模型在矛盾数据里学到的“平均线”形态实际效果就是边界模糊、掩码膨胀或者缩小。后来我整理了一份标注规范文档把线的端点规则、磨损处理规则、阴影遮挡处理规则都写清楚并对标注结果做了二次抽检把不一致的样本重新修正。每次标注完再做一次自动检查样式包括多边形面积过小、点数异常、坐标超出边界。这一步虽然费时间但对模型上限的提升是最直接的。6.2 类别不均衡的处理车道线数据中白色实线通常占据大头黄色虚线样本量少如果训练时不加处理模型对少类别的召回率会明显偏低。我试过几种方式最有效的是“类别加权采样”。YOLOv8支持在训练时通过每个文件的采样权重来控制类别出现频率做法是统计每个类别占比对数量少的类别样本给更高的采样概率。也可以用copy-paste增强把黄色虚线的目标区域从别的图里复制拼接到其他背景图上从而增加该类别的样本量。不过做copy-paste时要注意透视关系拼接的位置和尺度不能太违和否则模型学到的是不自然的形态。6.3 从单帧分割到轨迹级分析单帧的实例分割结果再准也解决不了“这条线到底该不该修”的最终问题。帧与帧之间信息可以综合利用同一处断裂在连续多帧里都出现说明是真的缺失只在个别帧出现大概率是光照或者遮挡引起的误差。这个逻辑非常朴素但效果显著能把单帧模型的临时误检滤掉。我在聚合模块里维护了一个滑动窗口对每条车道线的历史掩码状态做投票判断只有连续多帧一致时才会更新状态。这样最终的巡检报告稳定性高很多也方便给非技术背景的人看。6.4 模型压缩与部署的考虑训练好的模型如果要部署到边缘设备上精度和速度的平衡还得再做一轮。实测中INT8量化对分割任务的精度损耗明显大于检测任务尤其是掩码边缘会出现锯齿状退化。可以先用TensorRT的FP16模式速度比FP32快一倍精度损失很小。如果设备性能实在吃紧再考虑INT8同时需要在验证集上重新审视mask指标看看是否在可接受范围内。还有一个可选方向是知识蒸馏用m版本模型做teacher给s甚至n版本模型做蒸馏训练。实践下来分割的mAP能提升1-2个点成本只是多跑一次训练。7. 一些零碎但实用的经验关于训练迭代过程我的习惯是先快速跑一版短训练比如50个epoch把数据集质量问题暴露出来再决定是否加长训练时间。不要一上来就200个epoch最后发现数据有硬伤浪费时间。标注时候如果条件允许尽量用带有平滑曲线感的多边形工具而不是密密麻麻的点。点太少形状失真点太多训练样本里会产生大量冗余坐标反而增加了归一化过程中的误差。推理时如果画面中有强烈反光或者水下倒影模型有可能把倒影区域也预测成车道线。这种情况在后处理时加一个位置约束真实车道线不会出现在水分界线以下过远的位置可以按图像中路面区域设定一个感兴趣区域掩码丢弃区域外的预测。最后提一下YOLOv8支持SAHI切片推理对高分辨率巡检图像特别有用。把大图切成多个重叠切片分别推理再合并结果远处的细线召回率会进一步提高。切片大小设置和重叠率需要做几组实验但整体思路值得尝试。