做桥梁检测这行的人应该都有同感:巡检照片攒了一堆,靠肉眼一张张看裂缝和剥落,既费眼又很难给甲方一个"量化"结论。我这次用无人机和手持设备拍回来的桥梁表观影像整理成一个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,后期能把掩码细节打磨细 |
| 训练轮数 | 300(patience 50) | 长训练让裂缝这类难样本有足够的拟合机会 |
| 数据增强 | mosaic 1.0, mixup 0.2, 随机平移90°翻转, HSV微调 | 提升泛化,缓解桥底昏暗和光照差异 |
| 旋转角度 | 仅±15° | 桥梁构件方向性强,大角度旋转会生成失真样本 |
这里特别说一下旋转增强。很多人做目标检测习惯性开90°随机旋转,但在桥梁场景里这是负优化,因为墩柱和梁体的几何方向是固定的,病害在竖直或水平方向上的分布有结构意义,强行把横裂缝旋转成竖裂缝,模型会学到错误的先验。我这边只保留±15°的小角度扰动,模拟拍摄姿态变化。
原始的YOLO训练脚本里还有一个值得调的点:mask loss的权重。默认配置里box loss权重较大,但我们的业务主要消费掩码而不是框。我把box loss权重下调到0.5,seg 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.76,mask 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曲线的数值里看出来的。整条链路做下来最深的感觉是,桥梁损伤分割很难靠一味的加数据和堆参数刷分,把类别定义、标注均匀性和推理后处理这三层理顺,精度才能真正落在工程报告里。