简介:这份道路病害数据集面向从事道路检测、智能交通与计算机视觉方向的开发者与研究者,提供可直接用于模型训练与验证的标注资源,免去自行采集、筛选与标注图像的时间成本。压缩包共2000个文件,以1998个XML标注文件为主,另附1个Python脚本与1个说明文本,整体约815.67MB,标注文件记录病害位置、类型等属性,脚本可用于数据读取或格式转换。图像覆盖城市街道、乡村道路、高速公路与住宅区等多种场景,清晰度较高,适合目标检测与图像分类任务。目前已有241人学习下载,可作为工程化应用的起点,帮助读者快速搭建训练流程、验证算法效果并对照标注格式排查数据问题。
1. 道路病害数据集到底解决什么问题:从一份标注数据说起
做过路面巡检的人都知道,模型训不出来,十有八九不是网络结构的问题,而是数据的问题。你拿一个在 ImageNet 上预训练过的检测器,直接丢几百张手机拍的裂缝图进去,mAP 能到 0.3 就算烧高香。道路病害检测这个方向,公开可用的高质量标注数据一直稀缺,尤其是同时覆盖横向裂缝、纵向裂缝、龟裂、坑槽这几类的数据集,要么类别不全,要么标注粗糙,要么图像分辨率低到标注框本身比病害还宽。一份整理好的道路病害数据集,核心价值就在于把“图像采集—类别定义—标注规范—格式转换”这条链路提前跑通了,你拿到手就能直接进训练流程,不用再从零做清洗和标注对齐。这类数据集适合三类人:做智慧交通巡检的算法工程师、需要快速验证检测方案的学生、以及想把路面病害识别集成到已有巡检系统里的开发者。下面从数据组织、格式转换、训练调参到踩坑排查,把这条路走一遍。
2. 道路病害数据集的文件组织与标注格式拆解
2.1 拿到压缩包后先看什么:目录结构与类别定义
解压之后别急着写 DataLoader,先花十分钟把目录结构摸清楚。常见的道路病害数据集一般按以下方式组织:根目录下分images/和labels/(或annotations/),images/里按训练集、验证集、测试集分文件夹,labels/里对应存放标注文件。标注格式可能是 VOC 的 XML、COCO 的 JSON,也可能是 YOLO 的 TXT。你得先确认三件事:类别有哪几种、标注是边界框还是分割掩码、图像分辨率和命名规则是否统一。
| 检查项 | 常见情况 | 影响 |
|---|---|---|
| 类别数量 | 3~6 类(横向裂缝、纵向裂缝、龟裂、坑槽等) | 决定检测头输出维度 |
| 标注格式 | VOC XML / COCO JSON / YOLO TXT | 决定是否需要格式转换 |
| 图像分辨率 | 640×480 到 1920×1080 不等 | 影响输入尺寸和增强策略 |
| 标注粒度 | 边界框为主,少数带像素级掩码 | 决定用检测还是分割模型 |
| 类别平衡 | 裂缝类样本多,坑槽类样本少 | 影响损失函数和采样策略 |
我一般会先写一个统计脚本,把每个类别的标注框数量、平均面积、宽高比分布跑出来。这一步看起来多余,但它能帮你提前发现“某个类别只有十几张图”这种致命问题。如果坑槽类只有 20 个标注框,你后面训练时不做重采样或强增强,模型基本学不到这一类。
import os import xml.etree.ElementTree as ET from collections import Counter def count_voc_classes(anno_dir): """统计VOC格式标注中各类别出现的次数""" counter = Counter() for fname in os.listdir(anno_dir): if not fname.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, fname)) root = tree.getroot() for obj in root.findall('object'): cls_name = obj.find('name').text counter[cls_name] += 1 return counter # 用法:替换成你的标注目录路径 # result = count_voc_classes('./annotations') # print(result.most_common())这段代码遍历所有 XML 文件,提取每个<object>下的<name>字段并计数。anno_dir参数指向标注文件夹,输出是一个 Counter 对象,按类别名统计出现次数。如果你拿到的是 COCO JSON,把解析逻辑换成json.load后遍历annotations列表里的category_id即可。统计结果直接决定你后面要不要做类别重采样。
2.2 标注质量怎么快速判断:三个可视化检查手段
标注质量不过关是道路病害数据集最常见的暗坑。你以为标注框都画好了,实际上可能存在框偏移、漏标、类别标错、框重叠严重等问题。我通常用三种方式快速过一遍:第一种是随机抽 50 张图,把标注框画到原图上,肉眼扫一遍;第二种是统计标注框的宽高比,正常裂缝的宽高比应该偏极端(细长),如果出现大量接近 1:1 的框,很可能是把龟裂区域误标成了单条裂缝;第三种是检查是否有零面积框或超出图像边界的框。
import cv2 import xml.etree.ElementTree as ET import random def visualize_voc_sample(img_dir, anno_dir, num_samples=50): """随机抽取样本,将VOC标注框绘制到图像上保存""" xml_files = [f for f in os.listdir(anno_dir) if f.endswith('.xml')] samples = random.sample(xml_files, min(num_samples, len(xml_files))) for xml_file in samples: tree = ET.parse(os.path.join(anno_dir, xml_file)) root = tree.getroot() img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) img = cv2.imread(img_path) if img is None: continue for obj in root.findall('object'): bbox = obj.find('bndbox') x1 = int(bbox.find('xmin').text) y1 = int(bbox.find('ymin').text) x2 = int(bbox.find('xmax').text) y2 = int(bbox.find('ymax').text) cls_name = obj.find('name').text cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, cls_name, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) out_path = os.path.join('./vis_check', xml_file.replace('.xml', '.jpg')) os.makedirs('./vis_check', exist_ok=True) cv2.imwrite(out_path, img) # 用法:visualize_voc_sample('./images', './annotations')img_dir和anno_dir分别指向图像和标注目录,num_samples控制抽样数量。代码会把标注框和类别名画到图上,输出到./vis_check文件夹。重点看三种异常:框明显偏离病害区域、同一处病害被多个框重复标注、类别名和实际病害对不上。发现这些问题后,要么手动修,要么在训练时用数据增强和损失函数设计来部分抵消。
3. 把道路病害数据集转成 YOLO 格式:脚本与四个边界坑
3.1 VOC 转 YOLO 的完整脚本与坐标归一化逻辑
YOLO 系列模型要求标注格式为每行class_id x_center y_center width height,且坐标全部归一化到 [0,1]。VOC 给的是绝对像素坐标xmin, ymin, xmax, ymax,转换时要注意:中心点坐标是框的中心,宽高是框的宽高,不是右下角坐标。另外类别名要映射成从 0 开始的整数索引,映射关系必须固定,否则训练和推理时类别会对不上。
import os import xml.etree.ElementTree as ET # 类别映射:根据你的数据集实际类别修改 CLASS_MAP = { 'longitudinal_crack': 0, 'transverse_crack': 1, 'alligator_crack': 2, 'pothole': 3, } def voc_to_yolo(anno_dir, output_dir, img_width, img_height): """将VOC XML标注转换为YOLO TXT格式""" os.makedirs(output_dir, exist_ok=True) for fname in os.listdir(anno_dir): if not fname.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, fname)) root = tree.getroot() # 优先从XML读取图像尺寸,读不到则用传入的默认值 size = root.find('size') if size is not None: w = int(size.find('width').text) h = int(size.find('height').text) else: w, h = img_width, img_height lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text.strip() if cls_name not in CLASS_MAP: continue # 跳过未定义类别 cls_id = CLASS_MAP[cls_name] bbox = obj.find('bndbox') x1 = float(bbox.find('xmin').text) y1 = float(bbox.find('ymin').text) x2 = float(bbox.find('xmax').text) y2 = float(bbox.find('ymax').text) # 裁剪到图像边界内 x1 = max(0, min(x1, w)) y1 = max(0, min(y1, h)) x2 = max(0, min(x2, w)) y2 = max(0, min(y2, h)) # 归一化中心点和宽高 x_center = (x1 + x2) / 2.0 / w y_center = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h # 过滤零面积框 if bw <= 0 or bh <= 0: continue lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") out_name = fname.replace('.xml', '.txt') with open(os.path.join(output_dir, out_name), 'w') as f: f.write('\n'.join(lines)) # 用法:voc_to_yolo('./annotations', './labels_yolo', 1920, 1080)CLASS_MAP是类别名到整数 ID 的映射,必须和你的训练配置文件里的names列表顺序一致。img_width和img_height是默认图像尺寸,当 XML 里没有<size>字段时作为兜底。代码里做了边界裁剪和零面积过滤,这两个操作能避免训练时出现 NaN 损失。转换完成后,建议随机抽几个 TXT 文件,用脚本把归一化坐标反算回像素坐标,画到图上再确认一遍。
3.2 转换过程中最容易翻车的四个边界问题
第一个坑是图像尺寸不一致。有些数据集的图像来自不同设备,分辨率从 640×480 到 1920×1080 都有,但 XML 里的<size>字段可能写的是统一值,导致归一化坐标全部偏移。解决办法是转换时以实际读取的图像尺寸为准,而不是信任 XML 里的声明。第二个坑是类别名大小写和空格。Longitudinal_Crack和longitudinal_crack在映射表里对不上,转换后这类标注会被静默跳过,你训练时发现某个类别 AP 为 0,回头查才发现标注全丢了。第三个坑是标注框超出图像边界。VOC 标注里偶尔会出现xmax大于图像宽度的情况,不裁剪的话归一化坐标会大于 1,YOLO 训练时直接报错或产生异常梯度。第四个坑是文件名不匹配。图像叫img_001.jpg,标注叫img_001.xml,但转换后的 TXT 叫img_001.txt,如果图像和标注不在同一级目录,DataLoader 按文件名索引时就会找不到对应标注。
注意:转换脚本跑完后,务必写一个校验脚本,逐行检查 TXT 里的坐标是否都在 [0,1] 范围内,类别 ID 是否在合法区间内。这个校验花不了五分钟,但能帮你省掉几小时的排查时间。
4. 用道路病害数据集训练检测模型:参数怎么设、指标怎么看
4.1 训练配置里的五个关键参数
拿到转换好的 YOLO 格式数据后,下一步是配训练文件。以 YOLOv8 为例,你需要准备一个 YAML 文件描述数据路径和类别,然后通过命令行或 Python API 启动训练。关键参数有五个:输入尺寸imgsz、批量大小batch、初始学习率lr0、数据增强强度、以及预训练权重。道路病害图像里裂缝通常占像素比例很小,输入尺寸建议不低于 640,条件允许就上 1024 或 1280。批量大小根据显存来,显存不够就用梯度累积。学习率方面,从预训练权重微调时lr0=0.001到0.01之间比较稳,太大容易震荡,太小收敛慢。
# road_damage.yaml path: ./dataset train: images/train val: images/val test: images/test names: 0: longitudinal_crack 1: transverse_crack 2: alligator_crack 3: pothole# 启动训练:YOLOv8 示例 yolo detect train \ data=road_damage.yaml \ model=yolov8m.pt \ imgsz=1024 \ batch=8 \ epochs=150 \ lr0=0.005 \ lrf=0.01 \ patience=30 \ augment=True \ mosaic=1.0 \ mixup=0.1 \ degrees=10 \ translate=0.1 \ scale=0.3 \ fliplr=0.5 \ flipud=0.0 \ device=0imgsz=1024是为了保留小目标裂缝的细节,batch=8是 8GB 显存下的保守值。patience=30表示 30 个 epoch 验证指标不提升就早停,避免过拟合。mosaic=1.0开启马赛克增强,对密集小目标检测帮助很大。flipud=0.0是因为道路病害图像上下翻转不符合真实场景,开了反而引入噪声。degrees=10控制旋转角度,道路图像一般不会有大角度旋转,10 度以内足够。
4.2 训练过程中看什么指标:mAP50 之外的三个信号
训练日志里最显眼的是mAP50和mAP50-95,但这两个指标有时候会骗你。我一般还会盯三个信号:一是分类损失cls_loss和框回归损失box_loss的下降曲线,如果cls_loss早早降到很低但box_loss还在高位,说明类别分对了但框不准,可能是标注框质量有问题;二是验证集和训练集的损失差距,差距持续扩大就是过拟合,需要加增强或减模型容量;三是每个类别的 AP,如果坑槽类的 AP 远低于裂缝类,说明样本不平衡问题没解决,需要做类别重采样或 focal loss。
# 训练完成后,用验证集跑一遍,输出每个类别的详细指标 from ultralytics import YOLO model = YOLO('./runs/detect/train/weights/best.pt') metrics = model.val(data='road_damage.yaml', imgsz=1024, batch=8) # 打印每个类别的AP for i, name in enumerate(metrics.names.values()): print(f"{name}: AP50={metrics.box.ap50[i]:.4f}, AP={metrics.box.ap[i]:.4f}") # 打印混淆矩阵 print(metrics.confusion_matrix)metrics.box.ap50是每个类别在 IoU=0.5 时的 AP,metrics.box.ap是 IoU 从 0.5 到 0.95 取平均的 AP。混淆矩阵能告诉你哪两个类别最容易混,比如横向裂缝和纵向裂缝在局部纹理上确实相似,如果混淆严重,可以考虑在数据增强里加弹性形变,或者换用更强的骨干网络。
5. 道路病害检测的避坑与排查:五条血泪经验
5.1 现象:训练 loss 正常下降但验证 mAP 始终低于 0.2
原因通常有三个:标注格式转换时类别映射错了、图像和标注文件名没对上、或者验证集里混入了训练集图像。先检查 TXT 文件里的类别 ID 是否和 YAML 里的names顺序一致,再写脚本比对图像目录和标注目录的文件名集合是否完全相同。如果这两步都没问题,检查数据划分时有没有用随机种子,确保训练集和验证集没有重叠。
5.2 现象:模型把裂缝检测成背景,召回率极低
道路病害图像里裂缝像素占比往往不到 1%,模型很容易被背景主导。解决办法是在训练配置里调低box_loss的权重或者用 focal loss 替代交叉熵。另一个常见原因是锚框尺寸不匹配,YOLO 默认锚框是基于 COCO 数据集聚类得到的,对细长裂缝不友好。可以用yolo detect train时加anchor_t=4.0放宽锚框匹配阈值,或者自己用 K-means 在道路病害数据集上重新聚类锚框。
5.3 现象:推理时框大量重叠,同一个病害被检出多次
这是 NMS 阈值设得太高导致的。默认iou=0.7对裂缝这种细长目标偏松,两个框只要重叠不超过 70% 就都保留。把推理时的iou降到 0.3 到 0.5 之间,能显著减少重复框。如果降了之后漏检变多,说明模型本身对同一目标的定位不够稳定,需要回头检查标注框是否一致——同一类病害在不同图像里的标注框松紧程度差异太大,模型就学不稳。
5.4 现象:换一台机器或换一个环境,推理结果完全不一样
检查三件事:图像预处理是否一致(归一化参数、通道顺序)、输入尺寸是否一致、模型权重是否加载正确。YOLO 默认用 RGB 通道,如果你用 OpenCV 读图后没做cv2.cvtColor转换,通道顺序就是 BGR,推理结果会完全错乱。另外,训练时用了imgsz=1024,推理时也必须用 1024,用 640 推理会导致小目标漏检。
5.5 现象:某些类别的 AP 突然从 0.6 掉到 0.1
先看这一类别的标注数量在训练集和验证集里是否差异过大。如果训练集有 500 个坑槽标注但验证集只有 10 个,AP 波动会非常大。另一个可能是这一类别的标注在某个子目录里被遗漏了,转换脚本只处理了部分文件夹。写一个统计脚本,把训练集和验证集里每个类别的标注数量分别打印出来,对比一下就能定位。
6. 把道路病害数据集用出更高价值:半自动标注与模型迭代
数据集不是用完就扔的。第一轮训练出来的模型,哪怕 mAP 只有 0.5,也可以拿来做半自动标注:用模型对未标注图像跑推理,把置信度高于 0.7 的检测框导出成预标注文件,人工只需要修正漏检和误检,标注效率能提升三到五倍。具体做法是用训练好的权重跑model.predict,把结果按 YOLO 格式写回 TXT,然后人工在标注工具里加载图像和预标注进行微调。
from ultralytics import YOLO import os model = YOLO('./runs/detect/train/weights/best.pt') unlabeled_dir = './unlabeled_images' output_dir = './pre_labels' os.makedirs(output_dir, exist_ok=True) results = model.predict(source=unlabeled_dir, imgsz=1024, conf=0.7, iou=0.4, save_txt=True, save_conf=True) # save_txt=True 会自动在 runs/detect/predict/labels/ 下生成YOLO格式TXT # 人工修正后,把这些TXT和对应图像加入训练集,重新训练conf=0.7是预标注的置信度阈值,设太低会引入大量误检,人工修正成本反而更高。iou=0.4控制 NMS 强度,减少重复框。save_conf=True会在 TXT 里额外保存置信度,方便后续按置信度排序筛选。第二轮训练时,把预标注修正后的数据按 1:3 的比例混入原始训练集,模型对难样本的识别能力会有明显提升。
还有一个技巧是难例挖掘。第一轮训练后,把验证集里漏检和误检的图像单独挑出来,人工重新标注或修正,然后对这些难例做过采样。我一般会跑三轮:第一轮用原始数据训基线,第二轮加入预标注数据,第三轮加入难例。三轮下来,mAP50 通常能从 0.5 左右提到 0.75 以上。这个过程没有捷径,但每一步的投入产出比都很清楚。希望帮到你。
本文还有配套的精品资源,点击获取