YOLOv8桥梁病害分割实战:从数据集构建到量化评估 做桥梁检测这行的人应该都有同感巡检照片攒了一堆靠肉眼一张张看裂缝和剥落既费眼又很难给甲方一个量化结论。我这次用无人机和手持设备拍回来的桥梁表观影像整理成一个5类分割数据集代号YHT3261然后全程用YOLOv8分割模型做训练、调优和部署验证覆盖钢筋外露、混凝土剥落、裂缝、钢筋锈蚀、结构变形。这篇不是理论课是我从头到尾把数据集、标注规范、训参、踩坑都过一遍的完整复盘给准备在结构健康监测方向动手做分割落地的朋友做个参考。先说结论分割比检测框更适合桥梁损伤场景因为桥梁病害几乎都是不规则几何体矩形框给不了面积和形态信息。但分割模型的坑也明显最典型的就是裂缝这种细长目标的掩码一致性问题以及病害类别之间天然重合导致的标注口径冲突。这些都在后面细说。1. 为什么桥梁病害必须做到像素级检测框给不了面积先把业务逻辑捋清楚。桥梁表观巡检最终要回答的问题不是这儿有没有裂缝而是裂缝有多长、剥落面积占多大、钢筋锈蚀范围是否在扩展。结构工程师拿到检测报告看到的是病害位置、尺寸、范围等级这些数据而不是一张画满框的照片。传统目标检测输出的是矩形包围盒对圆形剥落坑勉强能套住对一条延伸半米以上的弯曲裂缝框出来的面积和真实裂缝形态差得太远就没法用于评级计算。所以我在项目启动时直接跳过了纯检测方案选择实例分割。实例分割能给每个病害区域输出像素级掩码这意味着两件事第一裂缝的长度可以沿掩码骨架来计算而不是拿框的对角线瞎估第二剥落和锈蚀的面积可以通过像素统计得出再配合现场标定比例尺换算成平方厘米或平方米。这个从像素到物理面积的链路是检测方案无法提供的。YHT3261这个数据集就是在这样的业务需求下组起来的。初始影像来源包括无人机绕桥拍摄的梁底、墩柱、支座区域也包括桥检车和人工持机在近距离拍的局部特写分辨率跨度从几百万像素的手机摄影到大疆一类的航拍原图。原始素材我筛了一轮剔除严重虚焦、过曝和重复率高的画面最终保留有效样本3261张。这也是代号里3261的直接来头。样本场景里最麻烦的是光线和角度。桥底常年背光同一个病害在不同光源下呈现出来的对比度差异非常大有的裂缝在上午十点和下午四点的照片里几乎是两种形态。所以数据集里的病害样本刻意混合了顺光、逆光、带阴影遮挡、湿水反光等不同状况训练的时候模型需要见过这些变化才不会被光照牵着走。2. YHT3261数据构成与标注口径五类病害的边界其实经常打架2.1 五类损伤怎么定义才不产生歧义数据集的五类目标分别是裂缝、混凝土剥落、钢筋外露、钢筋锈蚀、结构变形。听起来很清晰真正落到标注的时候才发现几对类别之间的边界非常容易扯皮我最后订了一套严格的定义口径才把标注员的重复返工压下来。裂缝的定义是混凝土表面因拉应力或疲劳开裂形成的线状裂隙标注时统一沿裂缝可见缝隙的走向画出带状区域带宽按画面中裂缝实际可见宽度来但要求宽度的最小值不低于6个像素。为什么要卡这个下限因为实拍里毛孔级细纹和裂缝在低分辨率下几乎没有区别硬标细纹只会让模型学到噪声。这个阈值过滤了大量无效标注整体标注一致性提上来了。混凝土剥落是表层砂浆或混凝土块体脱落形成的坑洼区域特征是剥落坑内能看到粗骨料或砂浆层。它和钢筋外露经常同时出现保护层剥落以后里面的钢筋自然就露出来了。我的处理原则是钢筋可见的剥落坑整体标为钢筋外露钢筋未露出的剥落坑标为混凝土剥落两类掩码不重叠。这个归属优先级在标注规范里写死避免同一个人在不同批次里标出两种结果。钢筋锈蚀单独成一类针对的是钢筋表面已经生成锈层、出现变色起皮的区域不需要伴随混凝土脱落。结构变形最特殊它不是局部的表面纹理变化而是构件整体形态异常比如梁体下挠导致的局部压溃区。实际标注中我把这类样本限定为已经能从外观目视判别的明显形变区域比如支座偏移后附近的挤压破损带、梁端裂缝密集区。标注量相对最少这也在后面训练时造成了类别不平衡问题。2.2 标注工具与流程里的隐性成本标注工具我用了Labelme输出的是JSON多边形后面转成COCO格式再喂给YOLOv8。选Labelme不因为它多强主要是多边形标注交互顺手团队里现成会的人多。但Labelme有一个容易踩的坑同一个对象如果被断断续续标成多个多边形YOLOv8的掩码合并阶段可能出现粘连和空洞。所以我要求单条裂缝在画面内连续可见时就标成一个多边形遇到明显被遮挡的地方切段也要保证相邻段边界尽量贴近不需要过渡衔接。标注流程上我排了两轮第一轮按初版规范全量粗标第二轮让另一组标过桥隧病害的人抽检20%并回标错误样本。两轮下来最主要的修改集中在两类地方一是细裂缝被漏标二是水渍和浅层露砂被误标成混凝土剥落。露砂和剥落确实长得像判断依据是剥落坑必须有明显的凹面阴影或小块缺失露砂只是表面砂浆磨掉的平面。这条是否有凹陷的口径最后写进了标注SOP返工率才明显降下来。2.3 数据划分与增广补充3261张样本按约8:1:1切成训练集、验证集和测试集分层划分保证每一折里五个类别都有一定实例量。实例统计大致是裂缝最多约1800个实例混凝土剥落约900个钢筋外露约600个钢筋锈蚀约400个结构变形约150个。这个分布是真实的病害频率反映裂缝本来就最常见但落到模型上就带来了类别不平衡的问题训练时后面会专门处理。3. 选YOLOv8-seg不是拍脑袋三版分割方案对比与参数落地3.1 为什么不是Mask R-CNN也不是DeepLab在确定YOLOv8-seg之前我实际对比了三类主流方案结论可以用一张表说清楚。方案类型优点在我这个场景的短板Mask R-CNN两阶段实例分割精度上限高掩码质量细推理慢部署重裂缝这类多目标场景整体吞吐不足DeepLabv3语义分割对裂缝这种线状目标边界保持好不区分实例无法单独输出每条裂缝面积YOLOv8-seg单阶段实例分割速度与精度平衡好锚框换中心点检测掩码分支轻量小目标细长掩码能力需要靠分辨率和增强补偿我的业务里不需要区分同一条裂缝的每一段叫什么名字只需要每段掩码能统计出面积和长度所以语义分割其实也可以但结构工程师的检测报告通常要按处来记录病害数量实例级输出更方便提交成果。再叠加部署环境的性能预算最终我选了YOLOv8-seg并且基于中杯版本加高分辨率输入来跑。有个经验说一下如果场景里全是单一的剥落坑和锈斑用语义分割完全够但凡是报告里要写到共发现XX条裂缝就老老实实用实例分割YOLOv8-seg的掩码分支在速度和内存上都比Mask R-CNN友好得多。3.2 关键训练配置和这么配的理由训练参数是基于裂缝是细长小目标这个核心约束来设的具体配置我放在下面参数取值设置理由输入分辨率1280×1280裂缝宽度常只有几像素640缩放后基本丢失1280才能保住细部优化器SGD momentum 0.937比AdamW在分割任务上收敛更稳不容易在后期震荡初始学习率0.01 3轮warmup大分辨率下直接用高学习率容易炸warmup过渡平滑学习率调度cosine decay配合300轮总epoch后期能把掩码细节打磨细训练轮数300patience 50长训练让裂缝这类难样本有足够的拟合机会数据增强mosaic 1.0, mixup 0.2, 随机平移90°翻转, HSV微调提升泛化缓解桥底昏暗和光照差异旋转角度仅±15°桥梁构件方向性强大角度旋转会生成失真样本这里特别说一下旋转增强。很多人做目标检测习惯性开90°随机旋转但在桥梁场景里这是负优化因为墩柱和梁体的几何方向是固定的病害在竖直或水平方向上的分布有结构意义强行把横裂缝旋转成竖裂缝模型会学到错误的先验。我这边只保留±15°的小角度扰动模拟拍摄姿态变化。原始的YOLO训练脚本里还有一个值得调的点mask loss的权重。默认配置里box loss权重较大但我们的业务主要消费掩码而不是框。我把box loss权重下调到0.5seg loss权重上调到1.2实测验证集mask mAP50有小幅提升。3.3 预训练权重的选择用的官方预训练权重做的迁移学习而不是从零训练。原因是结构损伤图像的纹理模式和人造场景有共性底层特征可以直接复用。我在ImageNet和COCO预训练权重之间也做过对比结论是直接用YOLOv8官方分割预训练权重起步最稳收敛速度比从零开训快了一倍以上最终mask mAP也略高。预训练权重里的各种纹理特征对裂缝边缘的响应很有帮助这本身体现了迁移学习的价值。4. 真实训练过程复盘那些loss正常但AP不涨的坑4.1 裂缝的掩码AP低到让人怀疑人生训练前半段整体mask mAP50升到0.7以后就开始横盘细看每个类别的AP裂缝只有0.5上下远低于剥落和锈蚀。第一反应是欠拟合继续加了100轮裂缝AP纹丝不动。然后我做了错误样本的可视化发现模型输出的裂缝掩码普遍断成一节一节而且沿裂缝走向有大量虚线式的空洞。原因其实很典型裂缝的宽度只有几个像素标注的掩码区域太窄在YOLOv8的mask分支里下采样之后窄条区域的特征被周围背景稀释掉了。直白说模型把细长物体当成了散落的碎点没法串成线。解决问题我用的是三层组合。第一层是前面提到的高分辨率输入1280下采样后裂缝宽度还有明显像素留存。第二层是切块推理训练时虽然用1280但测试时把原图切成一堆1280重叠块分别预测再合并避免大幅降采样。第三层是标签层面给裂缝掩码做轻微膨胀把标注宽度向外扩2像素让掩码宽度至少占到8像素以上。膨胀后裂缝严格说来和真实轮廓有偏差但对细长目标来说反而帮助模型学到了连续性裂缝AP提升到0.6以上。4.2 类别不平衡的代价和对策五个类别的实例数量从1800到150差距超过十倍模型天然偏向高频类别。最直观的反映是结构变形这个类别的recall很低验证集里偶发漏检但错检倒是很少因为形变区域的视觉特征确实比较特殊。钢筋锈蚀的问题是precision偏低模型经常把混凝土表面泛黄的水渍当成锈蚀这两者颜色纹理相近很难硬靠卷积特征区分。针对结构变形漏检我在每轮的batch采样里做了类别重加权最低频类别从150份上采到每轮至少占batch的15%。这个做法牺牲了一点整体mAP交换来的是结构变形recall从不足0.5提高到0.72。对于锈蚀误检我加了反向难例挖掘把验证集里所有水渍被标成锈蚀的图片收集成负样本在训练后期以纯背景图的形式混进数据加载器。效果不错precision从0.6附近拉到0.73。4.3 一个容易被忽略的loss细节训练过程中我发现mask loss在后期有一波诡异的回升检查后确认是mosaic增强导致的。mosaic把四张随机图拼在一起但桥梁病害图之间的尺度差异极大无人机远景里的剥落坑可能只有20像素而近景特写里同样的剥落坑占了画面三分之一。拼接后跨尺度的掩码对齐产生大量噪声标签seg loss自然上涨。我把mosaic概率从1.0降到0.8同时把最后一轮epoch的关闭阈值设到0.2让模型在最后阶段能看到更多干净的完整图像。这个改动对mAP50-95的贡献比预想更大。5. 评估、量化与部署从mask到病害面积报告5.1 指标怎么评才算有效最终这一批模型在测试集上的结果是mask mAP50约0.76mask mAP50-95约0.49。分类到每一类差异明显裂缝是最弱项结构变形因为区域特征完整反而表现最好。这个分布说明一个问题单看整体mAP会掩盖裂缝这条业务主线能力不足的事实做评估报告时必须按类别拆开看。类别mask mAP50典型错误模式裂缝0.612细缝断裂、短缝漏检混凝土剥落0.873水渍/露砂误检钢筋外露0.748强光下边界膨胀钢筋锈蚀0.694泛黄水渍误检结构变形0.890弱形变漏检我给检测单位交付的不只是模型权重还有一套量化流程。先在拍摄时布置好已知尺寸的标定物推理拿到掩码后按像素面积乘比例尺系数换算成实际面积。裂缝额外用骨架化方法求长度规则是先对掩码做距离变换提取中轴线再沿中轴线累加像素距离并乘比例尺。这套流程实测能给出裂缝长度和剥落面积的估测值达到现场记录精度要求后面要升级的话可以引入畸变校正但目前的量级够用。5.2 大图切块推理的工程细节实际部署里一批原图是8192×4096级别的无人机拼片直接喂给模型不现实。我做了重叠滑窗推理切块尺寸1280重叠率50%推理后把所有块的掩码拼回大图坐标系再按类别的置信度阈值做非极大值抑制。这里有个坑必须提醒YOLOv8在重叠区域会产生大量重复掩码如果NMS的IoU阈值设太高跨块的重复掩码消不掉报告里病害数量会虚高。我自己最后用的是IoU阈值0.45配置信度0.35并结合掩码面积过滤小于500像素的碎片。切块方案还能顺带缓解细长目标的问题。滑窗推理得到的裂缝掩码比整图预测的连续性明显好因为单块里细裂缝占特征图的比例更大细节不容易丢。推理速度上一块1280图在边缘算力设备上大概20到30毫秒整张全景拼片配合多线程跑下来在可接受范围。5.3 部署形态与交付格式模型导出我用FP16精度的TensorRT引擎在边缘推理设备上跑的延时比原生PyTorch明显下降。甲方实际要的不是模型是结论表单所以我在后处理里直接生成JSON报告内容包括病害类别、实例ID、掩码面积、裂缝长度估算、置信度再批量转成Excel表格。整套流程跑通以后原来几个人一周才能出完的桥梁巡检报告现在当天就能拿到初版结果人工只需要复核模型标记的疑难点。6. 这轮实操之后的几点剩饭和经验先分享一个关于标注成本的切身体会。YHT3261从原始影像到可训练总共花了三周左右大头不是画框而是来回对口径。如果再来一次我会在第一轮标注前就把裂缝宽度下限、剥落与露砂判定、钢筋外露的类别优先级这些条目做成示例图集直接发给标注员而不是用文字规范一遍遍解释。图文对照的SOP能把返工率压到原来的三分之一。再一个经验是分割模型的应用边界。YOLOv8-seg对表面纹理类病害裂缝、剥落、锈蚀表现稳定但结构变形这个类别本质上是三维几何位移在二维图像里的投影分割模型能做的非常有限。下一轮迭代我已经在考虑引入关键点检测和回归模型来估计梁体下挠量或支座位移把这些连续量作为独立分支和分割结果配合输出。最后给大家一个立刻能用的建议训练分割任务时每20轮就导出验证集错误样本做一次可视化的习惯非常好。靠眼睛看掩码和GT的重叠情况比盯训练日志更能发现模型能力上限在哪。我这一版模型很多有效的调参都是看着错误切片改出来的而不是从loss曲线的数值里看出来的。整条链路做下来最深的感觉是桥梁损伤分割很难靠一味的加数据和堆参数刷分把类别定义、标注均匀性和推理后处理这三层理顺精度才能真正落在工程报告里。