
简介这是一份面向道路病害检测方向的优质数据集适合从事计算机视觉、目标检测与智能交通研究的开发者、学生及工程人员使用可解决自建数据集耗时长、标注质量参差的问题。压缩包共2000个文件以1998个XML标注文件为主另含1个说明文本与1个Python脚本整体约411.72MBXML文件对应每张图像的病害位置、类型等属性脚本与说明则便于快速了解目录组织与使用方式。数据集涵盖城市街道、乡村道路、高速公路及住宅区等多种场景图像清晰度高标注经人工精细处理可直接用于模型训练与验证。目前已有382人学习下载读者可省去收集、挑选与标注图像的重复劳动将精力集中于算法设计与工程化落地快速搭建道路病害识别流程。1. 优质道路病害数据集5.zip从一堆标注图到能跑的训练集你拿到一个叫「优质道路病害数据集5.zip」的压缩包解压后大概率是几千张路面照片加一堆同名 txt 或 xml 标注类别写着纵向裂缝、横向裂缝、龟裂、坑槽。真正的问题不是「有没有数据」而是「这批数据能不能直接喂给 YOLO 或 DeepLab训出来的模型敢不敢上车巡检」。我在市政巡检项目里踩过的坑是标注格式不统一、类别名带空格、负样本混进正样本训练 loss 降得漂亮一上路全是误报。这篇笔记就按「先看清数据长什么样再决定怎么清洗、怎么切分、怎么训、怎么验」的顺序把道路病害数据集从压缩包变成可复现训练流程的每一步讲透。适合做道路巡检、市政养护 AI 的算法同学也适合刚接手数据集、还没搞清标注体系的新手。2. 先摸清道路病害数据集的标注体系与类别定义2.1 道路病害常见类别与标注粒度道路病害检测不是通用目标检测它的类别体系有行业惯例。常见做法是分四级裂缝类纵向裂缝、横向裂缝、网状裂缝/龟裂、变形类车辙、沉陷、拥包、松散类坑槽、剥落、其他修补、泛油。数据集里如果只给 4 类通常是纵向裂缝、横向裂缝、龟裂、坑槽这是最通用的最小集合。标注粒度决定模型能干什么。bounding box 只能告诉你「这块区域有坑槽」语义分割才能给出坑槽边界和面积。如果你要做养护决策——算修补面积、排优先级——box 不够用必须上分割掩码。我一般先看标注文件后缀.xml是 VOC 格式的 box.txt可能是 YOLO 格式的 box 或分割多边形.json可能是 COCO 格式。别急着转格式先抽 20 张图把标注画出来看这一步能省掉后面 80% 的返工。2.2 用脚本统计类别分布与图像尺寸拿到数据集第一件事不是训练是统计。下面这段脚本遍历标注目录统计每个类别的实例数、图像尺寸分布、以及有没有空标注文件。import os import xml.etree.ElementTree as ET from collections import Counter from PIL import Image ann_dir annotations # VOC xml 目录 img_dir images cls_counter Counter() size_counter Counter() empty_files [] for xml_file in os.listdir(ann_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, xml_file)) root tree.getroot() objs root.findall(object) if len(objs) 0: empty_files.append(xml_file) # 空标注可能是纯背景图 for obj in objs: name obj.find(name).text.strip() # 去空格坑之一 cls_counter[name] 1 size root.find(size) w int(size.find(width).text) h int(size.find(height).text) size_counter[(w, h)] 1 print(类别分布:, cls_counter) print(尺寸分布 top5:, size_counter.most_common(5)) print(空标注文件数:, len(empty_files))逻辑说明ET.parse逐文件解析 VOC xmlfindall(object)拿到所有标注框name.text.strip()必须去空格否则「纵向裂缝」和「纵向裂缝 」会被当成两类。size_counter统计分辨率如果尺寸五花八门训练前要统一 resize 或做多尺度增强。empty_files是负样本候选别直接删——纯背景图对降低误报有用但要控制比例。参数说明ann_dir和img_dir按实际目录改如果标注是 YOLO txt把解析换成读每行cls x y w h类别名从classes.txt读。统计结果里如果某一类实例数不到总数 5%就是长尾类别后面要单独做重采样或 focal loss。2.3 标注质量抽检三类高频脏数据统计完数量必须抽检质量。我见过最多的三类脏数据一是框贴边或超出图像边界训练时坐标归一化后变成负数二是同一病害被拆成多个小框模型学不到完整目标三是类别混淆把龟裂标成纵向裂缝。抽检方法很简单写个可视化脚本随机抽 30 张画框人眼过一遍。import random import matplotlib.pyplot as plt import matplotlib.patches as patches from PIL import Image import xml.etree.ElementTree as ET samples random.sample(os.listdir(ann_dir), 30) fig, axes plt.subplots(5, 6, figsize(18, 15)) for ax, xml_file in zip(axes.ravel(), samples): img_file xml_file.replace(.xml, .jpg) img Image.open(os.path.join(img_dir, img_file)) ax.imshow(img) root ET.parse(os.path.join(ann_dir, xml_file)).getroot() for obj in root.findall(object): b obj.find(bndbox) x1, y1 int(b.find(xmin).text), int(b.find(ymin).text) x2, y2 int(b.find(xmax).text), int(b.find(ymax).text) rect patches.Rectangle((x1, y1), x2-x1, y2-y1, linewidth2, edgecolorr, facecolornone) ax.add_patch(rect) ax.text(x1, y1-5, obj.find(name).text, coloryellow, fontsize8) ax.axis(off) plt.tight_layout() plt.savefig(check_vis.jpg, dpi100)逻辑说明随机抽 30 张把 box 和类别名画在图上存成一张大图人工过。重点看框是否贴合、类别是否正确、有没有漏标。这一步花 10 分钟能发现后面训练几天都调不回来的问题。参数说明random.sample的 30 可改数据量小就全看dpi调高方便放大看细节。如果发现某类系统性标错别自己改先确认是不是数据集本身的定义和你理解不一致——有些数据集把「修补」也归到裂缝类这种要先统一口径。3. 把 VOC 标注转成 YOLO 格式转换脚本与四个边界坑3.1 转换原理与坐标归一化VOC 用绝对像素坐标(xmin, ymin, xmax, ymax)YOLO 用归一化中心点加宽高(x_center, y_center, w, h)值域 0~1。转换公式x_center (xmin xmax) / 2 / img_ww (xmax - xmin) / img_wy 同理。看着简单坑全在边界和类别映射上。import os import xml.etree.ElementTree as ET from PIL import Image classes [纵向裂缝, 横向裂缝, 龟裂, 坑槽] # 顺序必须固定 cls2id {c: i for i, c in enumerate(classes)} def voc_to_yolo(xml_path, img_path, out_txt): img_w, img_h Image.open(img_path).size root ET.parse(xml_path).getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in cls2id: continue # 未定义类别直接跳过别硬塞 cls_id cls2id[name] b obj.find(bndbox) x1 float(b.find(xmin).text) y1 float(b.find(ymin).text) x2 float(b.find(xmax).text) y2 float(b.find(ymax).text) # 边界裁剪防止归一化后越界 x1, x2 max(0, min(x1, img_w)), max(0, min(x2, img_w)) y1, y2 max(0, min(y1, img_h)), max(0, min(y2, img_h)) if x2 - x1 2 or y2 - y1 2: continue # 过滤无效小框 xc (x1 x2) / 2 / img_w yc (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))逻辑说明cls2id固定类别顺序训练和推理必须一致改顺序等于重训。边界裁剪max(0, min(...))处理贴边框否则归一化后出现负数YOLO 直接报错或静默丢弃。x2-x1 2过滤掉标注噪声产生的一两个像素小框。:.6f保留六位小数够用且不丢精度。参数说明classes列表按你的数据集实际类别改顺序写进data.yaml的names。如果标注是分割多边形转换逻辑不同要按多边形点算外接矩形或直接转 YOLO-seg 格式。3.2 四个边界坑贴边、越界、零面积、类别缺失第一个坑是贴边框。病害在图像边缘被截断标注框xmax等于图像宽度归一化后x_center w/2 1.0有些框架要求严格小于 1会报 warning。解决就是上面的裁剪加min(x2, img_w - 1)。第二个坑是越界框。标注员手抖把xmax写成 2000图像宽才 1920。不裁剪的话w 1训练直接崩。裁剪后如果框变得极小按零面积处理丢弃。第三个坑是零面积框。xmin xmax或ymin ymax转换后w0YOLO 会报ZeroDivisionError或产生 NaN loss。必须在转换阶段过滤。第四个坑是类别缺失。标注里出现classes列表没有的类别名比如「裂缝」而不是「纵向裂缝」。直接continue跳过会丢样本更好的做法是打印出来人工确认是合并到某一类还是新增类别。我一般先跑一遍统计所有出现的name和classes做差集确认无误再转。3.3 生成 data.yaml 与目录结构YOLO 训练要求固定目录结构images/train、images/val、labels/train、labels/val图片和同名 txt 放对应目录。data.yaml写路径和类别。path: /data/road_damage train: images/train val: images/val nc: 4 names: [纵向裂缝, 横向裂缝, 龟裂, 坑槽]逻辑说明path是数据集根目录train/val是相对路径。nc是类别数必须等于names长度。names顺序和转换脚本的classes完全一致错一个位置所有标签都错。参数说明如果做分割任务names不变但标签格式换成多边形data.yaml加task: segment。路径别用中文和空格Windows 下容易出编码问题。4. 训练集/验证集切分与道路病害长尾分布处理4.1 按图像切分而不是按标注框切分切分最大的坑是数据泄漏同一张图的不同病害框被分到训练和验证集模型在验证集上表现虚高。必须按图像切分一张图的所有框要么全在训练集要么全在验证集。常见比例 8:1:1 或 7:2:1数据量少于 2000 张就用 8:2别设测试集验证集当测试用。import os import random import shutil random.seed(42) # 固定种子保证可复现 img_files [f for f in os.listdir(images) if f.endswith(.jpg)] random.shuffle(img_files) n len(img_files) n_train int(n * 0.8) train_files img_files[:n_train] val_files img_files[n_train:] for split, files in [(train, train_files), (val, val_files)]: os.makedirs(fimages/{split}, exist_okTrue) os.makedirs(flabels/{split}, exist_okTrue) for f in files: shutil.copy(fimages/{f}, fimages/{split}/{f}) txt f.replace(.jpg, .txt) shutil.copy(flabels/{txt}, flabels/{split}/{txt})逻辑说明random.seed(42)固定随机种子别人复现你的切分结果一致。先 shuffle 再按比例切避免原始文件按类别排序导致切分偏斜。shutil.copy保留原文件方便回溯。参数说明0.8是训练比例数据量小可调 0.85。如果数据集本身按场景分文件夹白天/夜间、城市/乡村要做分层切分保证每个场景在训练和验证集都有。4.2 长尾类别重采样与增强策略道路病害数据天然长尾龟裂和坑槽少纵向裂缝多。直接训模型对少样本类别召回率极低。三种处理方式按成本排序一是过采样把少样本图像在训练时重复采样二是增强对少样本类别做更强的几何和色彩变换三是损失加权用 focal loss 或类别权重。from torch.utils.data import WeightedRandomSampler import numpy as np # 假设 dataset 能返回每张图的类别标签列表 labels_per_img [get_labels(f) for f in train_files] cls_count np.zeros(len(classes)) for labels in labels_per_img: for l in labels: cls_count[l] 1 cls_weight 1.0 / (cls_count 1e-6) sample_weight [max(cls_weight[l] for l in labels) if labels else 1.0 for labels in labels_per_img] sampler WeightedRandomSampler(sample_weight, num_sampleslen(sample_weight), replacementTrue)逻辑说明cls_count统计每类实例数cls_weight取倒数少样本类别权重大。sample_weight取一张图里最大权重保证含少样本类别的图被多采。WeightedRandomSampler配合DataLoader的sampler参数用注意设shuffleFalse。参数说明replacementTrue允许重复采样。如果过采样后训练不稳定把权重开方1/sqrt(count)缓和一下。增强方面少样本类别加随机旋转、亮度扰动、Cutout但别用水平翻转——道路病害的纵向和横向裂缝翻转后语义变了这是血泪经验。4.3 负样本与困难样本挖掘纯背景图无病害是负样本能显著降低误报。但比例要控制负样本太多模型偏向预测背景。我一般让负样本占训练集 10%~20%。困难样本挖掘是训练一轮后用模型在验证集上跑把误报和漏报的图挑出来人工复核后加入训练集。这个循环做两三轮mAP 通常能涨 3~5 个点。5. 道路病害检测训练避坑从 loss 不降到误报满天飞5.1 现象loss 正常下降但验证集 mAP 不动原因通常是标注格式转换后类别 id 错位或者data.yaml的names顺序和标签不一致。模型在学但学的是错的映射。解决抽一张训练图用转换后的 txt 画框和原图对比确认类别和位置都对。另一个原因是验证集和训练集分布差异大比如训练集全是白天验证集混了夜间。5.2 现象训练几个 epoch 后 loss 变 NaN原因一是学习率太大YOLO 系列初始 lr 一般 0.01数据量小要降到 0.001。原因二是标注框归一化后越界w或h大于 1产生 NaN。原因三是某张图所有框都被过滤标签文件为空某些版本会报错。解决转换脚本加边界裁剪和空文件检查训练前用assert扫一遍所有标签值在 0~1 之间。5.3 现象模型在验证集好上路巡检误报满天飞原因是数据集和实际场景的域差异。数据集可能是手机拍的清晰图实际巡检是车载摄像头、有运动模糊和光照变化。解决训练时加运动模糊、亮度对比度扰动、随机裁剪模拟不同视角。另外实际场景的负样本正常路面、井盖、标线要补进训练集这些是误报重灾区。5.4 现象小目标病害细裂缝检测不到原因是下采样后细裂缝只剩几个像素特征丢失。解决提高输入分辨率640 提到 1024 或 1280用 P2 小目标检测层或者改用分割任务。另外标注时细裂缝的框要贴紧框太松会让模型学到背景。5.5 现象同一病害被重复检测原因是 NMS 阈值不合适或标注时一个病害拆成多个框。解决调低 NMS 的 IoU 阈值到 0.5 以下训练数据里合并重叠度高的同类框。如果病害本身是长条形用旋转框或分割更合适。6. 验证道路病害模型是否可用的三个硬指标训练完别只看 mAP那只是中间指标。真正决定能不能上路的是下面三个。第一个指标是分类别召回率尤其是少样本类别。整体 mAP 0.6 但龟裂召回只有 0.2这个模型上路会漏掉最需要修的病害。用model.val()输出每类 P/R或者自己写脚本按类别统计。第二个指标是误报率按每公里或每百张图算。市政巡检能接受的误报大概是每公里 1~2 次超过这个数人工复核成本比人眼巡检还高。测法拿一段实际道路视频抽帧跑推理人工数误报。第三个指标是推理速度。车载设备算力有限YOLOv8n 在 Jetson 上能到 30 FPSYOLOv8x 可能只有 5 FPS。速度不够就换小模型或量化别硬上大模型。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml, splitval) for i, name in enumerate(metrics.names.values()): p, r metrics.box.p[i], metrics.box.r[i] print(f{name}: P{p:.3f} R{r:.3f})逻辑说明model.val()返回每类的 precision 和 recallmetrics.names是类别名映射。重点看 recall漏报比误报在养护场景更致命。参数说明splitval指定验证集如果要用测试集改splittest。metrics.box是检测指标分割任务用metrics.seg。我自己的习惯是每次拿到新数据集先花半天做统计和可视化再花半天写转换和切分脚本训练反而只占一小部分时间。数据对了模型调参才有意义。希望帮到你。本文还有配套的精品资源点击获取