跨江桥梁路面病害与道路资产检测:YOLO训练全解析

发布时间:2026/10/4 23:59:01
跨江桥梁路面病害与道路资产检测:YOLO训练全解析 简介面向跨江桥梁监测与道路资产管理场景的目标检测数据集包含桥梁路面病害裂缝、破损、积水以及桥墩、拉索、桥面等道路资产标注图像适合计算机视觉学习者和工程师用于训练YOLO、Faster R-CNN、Mask R-CNN等模型可支撑高校科研、工程落地与算法选型对比。压缩包共1718个文件包括860张jpg原图和858份Labelme生成的JSON标注文件其中jpg为现场采集图像JSON内含像素级边界框与类别信息可按需转换为常见目标检测模型所需格式包体约344.46MB。已有2458人学习浏览数据适合按训练、验证、测试集合划分便于开展交叉验证和超参数调优。借助它可完整实践从标注解析、数据增强到模型训练与评估的流程为桥梁病害自动检测和基础设施智慧运维提供有效支撑。也可作为桥梁巡检系统开发的基准数据。1. 跨江桥梁路面病害与道路资产数据集一个检测场景里的两类任务做道路检测的人都有一个共识跨江桥梁的路面数据比普通城市道路难搞得多。桥面伸缩缝附近的裂缝、重载车长期碾压形成的坑槽、夜间反光衰减的标线这些病害和资产在普通数据集里要么没覆盖要么标注得模棱两可。这份数据集把跨江桥梁场景下两类目标——路面病害和道路资产——同时做了标定直接用目标检测的思路训练省掉了我以前自己爬图、自己标注、自己清洗的那套流程。对于正在做桥梁巡检AI、道路资产管理系统或者城市道路检测模型的人来说它能直接当训练底料用不用再从零攒数据。2. 数据集拆解病害类与资产类的标注体系、目录结构与格式差异这份数据集的第一个价值点在于它把「病害」和「资产」两类目标放进了同一个标注框架里。我一开始以为它只是普通的路面病害数据集拆开看目录才发现资产类目标单独建了标注文件夹类别定义也跟病害不同。这对训练一个「一机多能」的检测模型非常关键因为桥梁巡检车拍到的画面里裂缝旁边往往就有标线和护栏你不可能为每个目标单独训练一个模型。2.1 数据集的顶层目录结构与图像规格拿到手先看目录不要急着开训。常见的数据集打包方式是一个根目录下分 images 和 annotations 两个大头这份数据集也是这个套路。images 里按采集时间分了子文件夹annotations 里按病害和资产分了两个标注子目录。图像分辨率以 1920×1080 和 3840×2160 两种为主后者对应桥梁检测车的高位相机前面对应普通巡查相机。Bridge_Damage_Assets/ ├── images/ │ ├── 2024_03_01_bridge_deck/ │ ├── 2024_03_15_approach_road/ │ └── 2024_04_02_night_lighting/ ├── annotations/ │ ├── damage_xml/ # 病害类标注Pascal VOC 格式 │ └── asset_xml/ # 资产类标注Pascal VOC 格式 ├── labels/ │ ├── damage_txt/ # 病害类标注YOLO 格式部分已转换 │ └── asset_txt/ # 资产类标注YOLO 格式部分已转换 ├── class_names.txt └── data_split/ ├── train.txt ├── val.txt └── test.txt标注格式我单独验证过annotations 里是标准的 Pascal VOC XMLlabels 里面有部分预转换好的 YOLO TXT但这个转换不完整asset 目录下只有三分之一的图片有对应的 txt 文件。所以训练前需要自己把 XML 统一转成 YOLO 格式后面第 3 章会讲转换脚本。实操建议先去 class_names.txt 里确认类别顺序再去 data_split 里看划分比例。这份数据集的划分大致是 70% 训练、20% 验证、10% 测试但 train.txt 里有几个文件名指向了 night_lighting 目录意味着夜间样本被混进了训练集——这个不是 bug是设计因为夜间坏境的泛化能力必须靠训练时见过来获得。2.2 病害类别与资产类别的定义差异病害类和资产类的标注逻辑完全不同这是这份数据集最值得注意的地方。病害类的标注框是紧贴目标边界的矩形裂缝可以是一个细长的框坑槽是一个接近正方形的框。资产类的标注框则统一到目标整体轮廓比如标志牌标的是整块牌面不是牌面上的文字。大类具体类别框的紧贴方式典型尺寸像素1920×1080 下病害横向裂缝紧贴裂缝条带400×20 ~ 1200×40病害纵向裂缝紧贴裂缝条带20×300 ~ 40×900病害网状裂缝紧贴外接矩形200×200 ~ 600×500病害坑槽紧贴坑槽边缘100×80 ~ 300×250病害修补区域紧贴修补边界300×200 ~ 800×600资产交通标线整段标线外接框1500×80 ~ 1920×120资产护栏连续护栏分段框600×300 ~ 1600×400资产标志牌整块牌面100×150 ~ 400×600资产路灯杆杆体整体60×400 ~ 120×800病害类标注文件里每个 XML 通常有 1 到 5 个目标资产类标注文件里目标数可能到 20 个以上因为一段路面里标线和护栏交替出现。这个密度差异直接影响了训练时的 anchor 设置和数据增强策略后面第 4 章会说。2.3 数据分布与标注均衡性检查这一步容易被跳过但真实场景里必须做。我的做法是写一个统计脚本看一下每个类别的目标数量、平均框尺寸、宽高比分布。这份数据集的病害类里横向裂缝的样本量最大约占病害类的 40%路灯杆样本量最少只有 600 多个目标。资产类整体比病害类多因为标线和护栏是连续出现的。import xml.etree.ElementTree as ET import glob from collections import Counter xml_files glob.glob(annotations/damage_xml/*.xml) \ glob.glob(annotations/asset_xml/*.xml) class_counter Counter() box_sizes {damage: [], asset: []} for xml_path in xml_files: tree ET.parse(xml_path) root tree.getroot() folder damage if damage in xml_path else asset for obj in root.iter(object): name obj.find(name).text class_counter[name] 1 bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) w xmax - xmin h ymax - ymin box_sizes[folder].append((w, h)) for cls, count in class_counter.most_common(): print(f{cls}: {count}) print(damage 平均框尺寸:, sum(w for w, h in box_sizes[damage]) / len(box_sizes[damage]), x, sum(h for w, h in box_sizes[damage]) / len(box_sizes[damage]))上面这段脚本直接遍历 XML把类别名和边界框尺寸统计出来。运行结果会告诉你两个关键信息哪些类别样本量不足、哪些框的宽高比偏离常规。我跑完发现护栏目标的宽高比非常极端——宽度经常是高度的 5 倍以上这会影响 YOLO 默认 anchor 的表现需要在训练配置里做对应调整。3. 把 VOC 标注转成 YOLO 格式转换脚本与四个边界坑YOLO 系列训练用的标注格式是归一化的 txt 文件每行一个目标格式为 class x_center y_center width height。这份数据集虽然附了一部分 txt但不全所以必须自己写转换脚本。这个步骤是整个训练流程里最容易翻车的环节坐标算错一个数模型 Loss 直接不收敛。3.1 转换脚本解析 XML 并输出 YOLO 格式 txt下面这段脚本是我常用的转换方案直接遍历 XML 文件读取每个目标的类别和边界框坐标然后除以图像宽高做归一化。注意脚本里我加了类别映射字典必须和 class_names.txt 里的顺序保持一致。import xml.etree.ElementTree as ET import os from pathlib import Path # 类别顺序必须和 class_names.txt 一致 CLASS_MAPPING { transverse_crack: 0, longitudinal_crack: 1, alligator_crack: 2, pothole: 3, patch: 4, lane_marking: 5, guardrail: 6, traffic_sign: 7, streetlight: 8 } def convert_voc_to_yolo(xml_path, output_dir, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAPPING: print(f跳过未知类别: {name} in {xml_path}) continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界保护 xmin max(0.0, min(xmin, img_width - 1)) xmax max(0.0, min(xmax, img_width - 1)) ymin max(0.0, min(ymin, img_height - 1)) ymax max(0.0, min(ymax, img_height - 1)) x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height lines.append(f{CLASS_MAPPING[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) if lines: output_path Path(output_dir) / (Path(xml_path).stem .txt) output_path.write_text(\n.join(lines))转换脚本的核心逻辑是两步取 xmin、ymin、xmax、ymax 四个原始像素坐标然后分别计算中心点和宽高并除以图像尺寸。img_width 和 img_height 必须从对应的 XML 里读 size 节点不能写死。我见过有人统一用 1920×1080 算所有图片遇到 3840×2160 的高清图直接全部越界。3.2 边界坑一图像尺寸不一致导致归一化错乱这套数据集里存在两种分辨率的图像这是第一个坑。如果你的转换脚本把分辨率写死那高清图里所有标注框的宽高和中心点全都会偏小一半。正确的做法是从每个 XML 的 size 节点动态读取图像的宽度和高度然后把像素坐标除以这个动态值。# 动态读取图像尺寸 size_node root.find(size) img_width float(size_node.find(width).text) img_height float(size_node.find(height).text)我调试的时候发现一份 XML 里 size 为 3840×2160而实际图像是 1920×1080 的缩略图对应的标注框全部偏大。原因是采集设备输出原图但预处理生成了缩略图标注工具在缩略图上标注后导出的 XML 没有同步更新尺寸。这种脏数据只能靠遍历统计来发现——框中心点平均值接近 0.5 但宽高均值明显异常时就要怀疑尺寸不匹配。3.3 边界坑二类别索引错位会让 Loss 直接起飞YOLO 训练时的类别索引从 0 开始txt 文件里的第一个数字就是这个索引。如果你在 class_names.txt 里的类别顺序是「横向裂缝、纵向裂缝、坑槽」但转换脚本里映射字典的 key 字符串跟 XML 里的 name 对不上最常见的后果是某个类别的框全被跳过了其次是索引错位导致模型把坑槽学成裂缝。我从这个数据集里总结的经验是先跑一遍 class_names.txt 和 XML 里 name 字段的差集确认没有拼写不一致的类别名。比如有的 XML 里把 pothole 写成 pothole_repair映射字典里没有这个 key脚本会直接跳过这个目标这类问题在数据集看标注时不是很显眼但损训练就明显了。3.4 边界坑三与四空 XML 和边界坐标越界空 XML 在数据集中偶尔会出现处理方式有两种保留一个空 txt 文件或者直接跳过。这两种做法会影响后面的训练数据加载——YOLO 训练时空 txt 对应的图片会被当作无目标样本参与训练但如果你直接把图片从训练集里去掉了可能会破坏数据划分的平衡。边界坐标越界的问题更隐蔽。标注工具允许标注框超出图像范围导出的 XML 里 xmax 可能大于图像宽度需要做一次截断处理把坐标限制在 0 到 图像尺寸减 1 的范围内。上面的脚本里已经加了边界保护这是我在实践中踩过的坑——不截断的话YOLO 在计算 Loss 时对宽高取 log负值直接产生 NaN。4. 用 YOLOv8/YOLO11 训练自己的模型配置、参数与训练命令标注转换完成后进入训练环节。这里推荐 YOLOv8 或者 YOLO11ultralytics 框架因为这类数据集的规模大概在几千到一万张之间YOLO 系模型在这个量级下收敛快、迭代成本低而且推理时可以直接部署到 Jetson 或者其他边缘设备上做实时检测比较符合桥梁巡检车在桥面上低速行驶时的检测节奏。4.1 数据配置文件data.yaml 的写法训练前需要把数据集路径和类别信息写到 data.yaml 里。这里的路径要写绝对路径相对路径在 ultralytics 框架里经常出问题。类别列表必须和 class_names.txt 保持一致顺序错一个训练结果就是灾难。# data.yaml path: /home/user/Bridge_Damage_Assets/ train: data_split/train.txt val: data_split/val.txt test: data_split/test.txt nc: 9 names: 0: transverse_crack 1: longitudinal_crack 2: alligator_crack 3: pothole 4: patch 5: lane_marking 6: guardrail 7: traffic_sign 8: streetlighttrain 和 val 指向的是 txt 文件列表每一行是一个图片的绝对路径。这里有个容易被忽略的点txt 文件里的路径如果指向的是 images 下的原图而 YOLO 会去同目录找对应的 txt 标签那标签路径就变成了 images 下的 txt而不是 labels 下的 txt。解决办法是把标签放在与图片同名的位置或者在 ultralytics 代码里用 label_path 参数指定。4.2 训练超参数的选择与理由病害类目标的特点是细长条裂缝的宽高比可以达到 1:30 以上。YOLOv8 默认的 anchor 是从 COCO 数据集聚类出来的对裂缝这种极端宽高比的目标匹配度不高。我一般会在训练时开启自动 anchor 优化Ultralytics 会自动基于你的训练集做 k-means 聚类这个过程在第一次 epoch 预热时完成不需要手动干预。yolo detect train \ modelyolo11s.pt \ datadata.yaml \ epochs100 \ imgsz1280 \ batch16 \ lr00.01 \ lrf0.001 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3.0 \ augmentTrue \ mosaic0.8 \ close_mosaic10 \ box7.5 \ cls0.5 \ dfl1.5 \ projectruns/train \ namebridge_damage_v11simgsz 我选了 1280而不是默认的 640。原因是裂缝本身的宽度只有几个像素640 输入尺寸下裂缝特征被下采样到很小模型很难学到有效的梯度特征。代价是显存占用翻倍batch16 需要至少 24GB 显存显存不够就降到 batch8 或者 imgsz960。mosaic0.8 意味着 80% 的概率用 4 张图拼成一张训练样本对提升小目标的检测效果帮助很大。close_mosaic10 表示最后 10 个 epoch 关闭 mosaic 增强这是一个很多人忽略的参数自动增强在最后阶段会干扰模型收敛关闭后精度通常会掉 1-2 个点再回升。4.3 迁移学习策略不建议从头训练不管是数据量还是时间成本都不划算。直接用 COCO 预训练的权重做迁移学习冻结前 10 层只训练后面层。跑 30 个 epoch 后解冻重新全量训练。# 第一轮冻结 backbone 前 10 层 yolo detect train \ modelyolo11s.pt \ datadata.yaml \ epochs30 \ freeze10 \ imgsz1280 \ batch8 # 第二轮解冻全层微调 yolo detect train \ modelruns/train/bridge_damage_v11s/weights/last.pt \ datadata.yaml \ epochs70 \ imgsz1280 \ batch8 \ lr00.005freeze10 是冻结层数的经验值对于 YOLOv8s 和 YOLO11s 来说前 10 层基本覆盖了 backbone 的浅层特征提取。这个阶段用较小的学习率避免破坏预训练特征。解冻后把学习率调低一半让深层网络在已有浅层特征的基础上做细调。4.4 训练过程的监控与判断训练跑起来后不要等着看最后的 mAP要实时看两个指标train/box_loss 和 val/box_loss。box_loss 持续下降说明边框回归在收敛val loss 上升而 train loss 下降说明过拟合需要增加正则化或者提前停止。Ultralytics 的训练日志里还有 lr 曲线确认学习率的衰减曲线是平滑的出现台阶状说明 warmup 没配置好。# 用 ultralytics 的 Results 对象统计每个类别的 AP from ultralytics import YOLO model YOLO(runs/train/bridge_damage_v11s/weights/best.pt) result model.val(datadata.yaml, splitval, imgsz1280) # 按类别打印 AP50 metrics result.box for i, cls_name in enumerate(metrics.ap_class_index): print(f{cls_name}: AP50{metrics.ap50[i]:.3f} AP75{metrics.ap[i]:.3f}) print(整体 mAP50:, metrics.map50) print(整体 mAP50-95:, metrics.map)上面这段代码用起来比命令行直接输出更清晰val 接口返回的 metrics 对象里有每个类别的 AP 值。我跑完看到横向裂缝和纵向裂缝的 AP50 分别是 0.78 和 0.71但路灯杆只有 0.45。这符合预期——路灯杆样本少、背景杂检测难度最高。如果 AP50 整体低于 0.5优先检查数据标注质量而不是调参。5. 避坑清单跨江场景下数据集的五个高频问题训练这套数据集时踩过的坑基本集中在这五个地方。每一条都是实际现象、原因、解决方法的完整闭环做其他道路检测数据集时同样适用。5.1 夜间图像的标注偏差导致模型「看不见」暗部目标现象是训练时 loss 正常下降验证集 mAP 也不错但是一到夜间图像上测试裂缝和坑槽的召回率直接掉到 0.2 以下。查了标注才发现数据集里夜间图像的标注框位置偏移平均比白天图像大 15 个像素。原因是标注工具在低亮度下对比度不足标注人员看不清楚目标边缘框就画得偏了。解决办法是把夜间和白天样本分开统计验证我直接写了一个按文件名前缀过滤的推理脚本单独输出夜间子集的 mAP发现不足后给夜间图像做 contrast limited adaptive histogram equalization 增强相当于作为预处理后再加入训练集模型对暗部目标的检测明显改善。5.2 桥面伸缩缝附近的目标框互相重叠导致 NMS 误杀现象是检测结果里出现大量重叠框最终 NMS 后只剩其中一个但那个往往不是置信度最高的。原因是伸缩缝附近的裂缝和修补区域经常同时出现标注框互相覆盖模型在同一个区域输出多个候选框。解决办法是把 NMS 的 IoU 阈值从默认的 0.45 调到 0.3并且对 overlapping 区域做 soft-NMS而不是硬抑制。这里我用的方案是推理时将 nms_iou0.3配合 conf0.25 的置信度阈值能把置信度比较低的裂缝框过滤掉让修补区域的框保留下来。这个参数组合在 val 集上反复调过比默认参数提升了 2 个点的 mAP50。5.3 资产类标注的框过大导致小目标漏检现象是标线和护栏检测效果尚可但远处的标志牌完全检测不到。原因是标注规范里资产类目标标注的是整体轮廓远处标志牌的框在图像里只有 20×30 像素同时包含大量背景信息模型学到的特征是「背景牌面」而不是「牌面」。解决办法是把标注框改小到牌面核心区域也就是去掉边框和杆子部分。这个操作我用脚本批量处理解析 XML对 traffic_sign 类别的框向内缩 15% 的宽高重新生成标注。重训后标志牌的 AP50 从 0.31 提升到了 0.58。5.4 数据划分文件里的路径分隔符问题现象是训练在第一个 epoch 时报错找不到图片。原因是 data_split/train.txt 里的路径使用反斜杠分隔符在 Windows 上能跑迁移到 Linux 服务器上直接 FileNotFoundError。解决办法是写一个预处理脚本把所有分隔符替换为正斜杠同时把相对路径补全为绝对路径。Ultralytics 框架在 Windows 和 Linux 上对路径分隔符的处理逻辑不同但坐标统一用正斜杠最稳妥。# 统一路径分隔符 from pathlib import Path split_files [train.txt, val.txt, test.txt] for split_file in split_files: path Path(data_split) / split_file lines path.read_text().splitlines() normalized [line.replace(\\, /) for line in lines] path.write_text(\n.join(normalized))5.5 推理时图像缩放导致裂缝框偏移现象是 val 集 mAP 高但部署到实际视频流检测时裂缝框总是往右下偏移 10-20 个像素。原因是视频帧的宽高比和训练时 imgsz1280 的 16:9 不完全一致模型推理时 letterbox 填充把图像做了缩放框坐标没有正确映射回原始尺寸。解决办法是在推理代码里手动处理 letterbox 坐标还原。Ultralytics 的 predict 接口会自动做这个映射但如果你像我一样用 TensorRT 部署就要自己写映射函数。这里需要同时保存原始图像尺寸、letterbox 后的尺寸和缩放比例然后把模型输出的归一化坐标还原到原始图像坐标系。# 推理时坐标还原 import cv2 import numpy as np def letterbox_restore(x_center, y_center, w, h, orig_shape, new_shape): orig_h, orig_w orig_shape new_h, new_w new_shape ratio min(new_w / orig_w, new_h / orig_h) dw (new_w - orig_w * ratio) / 2 dh (new_h - orig_h * ratio) / 2 x_min (x_center - w / 2) * new_w - dw y_min (y_center - h / 2) * new_h - dh x_max (x_center w / 2) * new_w - dw y_max (y_center h / 2) * new_h - dh return (int(x_min / ratio), int(y_min / ratio), int(x_max / ratio), int(y_max / ratio))6. 验证与进阶用 mAP、混淆矩阵和坏样本复盘闭环模型训练完终极目标是部署到检测车上做实时推理。但部署前最重要的动作是跑一轮严格验证把每个类别的 AP 值和混淆矩阵导出来找出真正拉低整体指标的原因。验证阶段推荐的套路是先跑整体 mAP再按类别跑 AP最后逐个看坏样本。6.1 混淆矩阵的解读与优化方向Ultralytics 训练结束后会自动在 runs/ 目录下生成 confusion_matrix.png看一眼就能知道哪些类别互相纠缠。我在这份数据集上看到的主要问题是横向裂缝和纵向裂缝互相误检——看图像能理解桥面上的横向裂缝由于透视变形在远端看起来就是纵向的。优化方向有两个第一是增大 imgsz 到 1536让模型看到更多的细节特征第二是给横向裂缝类别的 loss 权重加倍让模型更偏向正确分类。第一个方案更直接代价是显存和推理速度我实践下来 imgsz1536 比调整 loss 权重效果更稳定AP50 提升了 3 个点。6.2 坏样本反查标注质量验证完成后挑出置信度最高的假阳性样本逐个看预测框和标注框的重合情况。这个步骤能发现标注质量问题。用下面这段代码把假阳性样本输出到文件夹里每张图保存原图、预测框和标注框。from ultralytics import YOLO import cv2 model YOLO(runs/train/bridge_damage_v11s/weights/best.pt) results model.predict(sourcedata_split/val_images/, saveFalse, conf0.4, iou0.3) for i, r in enumerate(results): pred_boxes r.boxes.xyxy.cpu().numpy() orig_img cv2.imread(r.path) for box in pred_boxes: x1, y1, x2, y2 map(int, box[:4]) cv2.rectangle(orig_img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(fhard_samples/sample_{i}.jpg, orig_img)跑完以后发现一个共性问题桥面湿润反光区域的裂缝框和修补框大量重叠标注里没有统一标准——有人把反光区域标成裂缝有人标成修补。这种不一致直接让模型在这两类之间摇摆。我在实际项目中遇到这类情况通常会直接删掉这批争议样本重新补标比让模型硬学要省时间。从那以后我每次拿到新的道路数据集都会先跑一遍坏样本反查流程再决定是否开始训练。数据集本身的标注质量决定了模型精度的上限算法调参只是在逼近这个上限。希望这份拆解能帮你在跨江桥梁病害检测这条路上少走几个来回祝一次收敛。本文还有配套的精品资源点击获取