简介YOLO 系列算法道路损伤检测数据集面向计算机视觉与智能交通养护场景包含纵向裂纹、碰撞、车轮痕迹、坑洼、裂缝等目标的标注图像可直接用于目标检测模型的训练、验证与测试。数据集已划分完成配套 data.yaml 配置文件兼容 YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11 等主流版本。包内共 2000 个文件945 个 XML 标签对应 VOC 格式945 个 TXT 标签对应 YOLO 格式109 张 JPG 原始图像另有 1 个 YAML 配置文件压缩包整体约 19.61MB标注与图像一一对应便于直接读取。YOLO 格式标签每行记录类别索引、归一化中心点坐标与宽高XML 按 VOC 标准保存目标框信息两类文件分别存放使用清晰目前已有 124 人学习下载。对需要快速验证 YOLO 训练流程、对比不同版本效果或开展道路损伤检测实验的研究者、开发者与学生这套数据能省去标注格式转换的繁琐环节开箱即用。1. 道路损伤检测用 YOLO945 张带标签数据集的真实分量做道路巡检的朋友多半遇到过这种尴尬手上攒了一批现场照片想拿 YOLO 训练一个能识别裂纹和坑洼的模型结果光是理清标签就花了两天。这个标题里的数据集解压后是 945 张图像加同名 txt 标签覆盖纵向裂纹、碰撞、车轮痕迹、坑洼、裂缝五类损伤格式是 YOLO 最常用的归一化坐标框。对刚接触 yolo 训练的人它是个能直接跑通全流程的练手包对已经上线过模型的人它更像一份小样本基准——告诉你五类损伤在真实道路场景里长什么样、标注边界怎么切。这篇笔记按我自己落地的顺序写先验标签再配环境最后过训练和验证把参数和坑一次说清。2. 解开 zip 先看标签五类损伤的标注格式与数据分布核对2.1 目录结构与 YOLO 标签 txt 的五个字段拿到 zip 后别急着解压到桌面就开训。先用命令行确认压缩包是否完整再按固定目录结构解压。常见做法是解压到纯英文路径下避免中文字符在数据加载阶段引发编码问题mkdir -p ~/datasets/road_damage unzip yolo算法-道路损伤检测数据集-945张图像带标签-纵向裂纹-碰撞-车轮痕迹-坑洼-裂缝.zip -d ~/datasets/road_damage find ~/datasets/road_damage -maxdepth 2 -type d | head -20第一条命令创建数据集的根目录第二条解压到指定位置第三条用 find 查看解压后的目录层级。很多标注工具导出的 zip 会自带一个外层目录比如images/和labels/两个子文件夹或者每张图片配一个同名 txt 混在同一目录。看到混放结构时先拆开再训YOLO 的默认约定是图片与标签分目录存放硬塞进同一目录会在数据加载时报“label not found”。每张图的标签是纯文本文件文件名与图片名一致只是扩展名不同。打开任意一个 txt里面每一行代表一个目标框固定五个字段0 0.5123 0.4456 0.2134 0.1876五个字段的含义分别是类别编号、框中心点 x 坐标归一化、框中心点 y 坐标归一化、框宽度归一化、框高度归一化。所有坐标值都在 0 到 1 之间因为已经除以了图像宽高。这个格式是 YOLO 系列的通用输入格式YOLOv5、YOLOv8 以及后来的 YOLO11 都能直接读。注意这里的类别编号不是类名而是 classes 列表的下标所以后面配置 yaml 时列表顺序必须和 txt 里的数字一一对应。2.2 用 Python 统计类别分布与坐标合法性先把账算清楚不先统计标签分布就开训是我见过最多人踩的坑。945 张图五类损伤你以为每类差不多两百个目标实际可能某一类占了七百个框某一类只剩十几个。先跑一段统计脚本把每个类的目标数量、空标签图片数、非法坐标数一次查清import os from collections import Counter labels_dir /home/user/datasets/road_damage/labels class_names [longitudinal_crack, collision, wheel_mark, pothole, crack] cls_counter Counter() zero_label_images [] invalid_lines [] for fname in os.listdir(labels_dir): if not fname.endswith(.txt): continue path os.path.join(labels_dir, fname) lines [line.strip() for line in open(path, encodingutf-8) if line.strip()] if not lines: zero_label_images.append(fname) continue for line in lines: parts line.split() if len(parts) ! 5: invalid_lines.append((fname, line)) continue cls_id int(parts[0]) if cls_id len(class_names): invalid_lines.append((fname, line)) continue coords list(map(float, parts[1:])) if not all(0 v 1 for v in coords): invalid_lines.append((fname, line)) continue if coords[2] 0 or coords[3] 0: invalid_lines.append((fname, line)) continue cls_counter[class_names[cls_id]] 1 print(类别分布, dict(cls_counter)) print(空标签图片数, len(zero_label_images)) print(非法行数, len(invalid_lines)) print(非法行示例, invalid_lines[:3])这段脚本只做三件事按类别编号累加目标数、找出完全没有标签的图片、过滤坐标越界或宽高为负的坏行。逻辑上要注意两个参数class_names这个列表的排列顺序必须和训练时用的 data.yaml 完全一致否则统计出来类别分布是错位的zero_label_images这类图片通常要手工检查如果确实没有目标就放到验证集当负样本如果是有目标但漏标了建议补标而不是直接删图。非法坐标行大多来自标注工具的导出 bug要么手工修要么用脚本剔除千万不能带着坏标签训否则损失函数在训练早期会被这些异常框带偏。3. 用 YOLOv8 跑通道路损伤训练最小命令与关键参数3.1 数据划分与 yaml 配置把 images 和 labels 挂到 train/val我自己做 yolov8 训练自己的数据集第一步永远是划分数据集并写 data.yaml。945 张图不算多常见的划分比例是 8:2也就是 756 张训练、189 张验证。原则上测试集可省因为最后看验证集指标就够了但如果你想发论文或做横向对比最好严格按 7:2:1 切三份。这里给出一个可复现的划分脚本按图片文件名分配并生成软链接不移动原文件import os import random from pathlib import Path random.seed(42) image_dir Path(/home/user/datasets/road_damage/images) label_dir Path(/home/user/datasets/road_damage/labels) split_dir Path(/home/user/datasets/road_damage/split) image_files sorted(image_dir.glob(*.jpg)) sorted(image_dir.glob(*.png)) random.shuffle(image_files) val_size int(len(image_files) * 0.2) train_files image_files[val_size:] val_files image_files[:val_size] for split_name, files in [(train, train_files), (val, val_files)]: for img_path in files: label_path label_dir / (img_path.stem .txt) if not label_path.exists(): continue img_dst split_dir / split_name / images / img_path.name lbl_dst split_dir / split_name / labels / img_path.name.replace(img_path.suffix, .txt) img_dst.parent.mkdir(parentsTrue, exist_okTrue) lbl_dst.parent.mkdir(parentsTrue, exist_okTrue) if not img_dst.exists(): os.symlink(img_path, img_dst) if not lbl_dst.exists(): os.symlink(label_path, lbl_dst) print(ftrain images: {len(train_files)}, val images: {len(val_files)})划分逻辑用了一个随机种子 42保证每次运行结果一致。注意val_size取的是图片数量的 20%但实际写进验证集标签时脚本会跳过没有对应标签文件的图片所以最终验证集可能略少于 189 张。用软链接而不是复制图片有两个实际好处不浪费磁盘空间、后续想调整划分时不用重新复制数据。数据集结构整理好后data.yaml 应该长这样path: /home/user/datasets/road_damage/split train: train/images val: val/images names: 0: longitudinal_crack 1: collision 2: wheel_mark 3: pothole 4: crack这里的path是数据集的绝对路径YOLO 训练时会自动拼上相对子路径去找图片。names列表的顺序非常关键它唯一对应标签 txt 里的类别编号。在我经手的项目里至少有三次模型训练完成后所有 AP 都是 0最后定位到的问题都是 names 顺序和标注文件不一致。如果原始数据集的类别编号不是从 0 开始或者缺了某个类别都要在 yaml 里显式补齐不要留空档。3.2 训练命令与损失函数小数据集下先锁哪些超参配置就绪后直接跑训练。这里我推荐用官方预训练模型做迁移学习特别是 945 张这种规模的数据集从零训练会非常吃力。最稳妥的命令是cd ~/datasets/road_damage yolo detect train \ datadata.yaml \ modelyolov8n.pt \ imgsz640 \ batch16 \ epochs100 \ patience20 \ lr00.01 \ augmentTrue \ project./runs \ nameroad_damagemodelyolov8n.pt表示加载 YOLOv8n 的预训练权重首次运行时会尝试下载权重文件到当前目录缓存之后这段命令会直接复用缓存离线启动不必每次联网拉取。imgsz640是默认输入尺寸215 万像素的输入对道路损伤检测足够改到 1280 会成倍增加显存占用和训练时间而小目标增益有限。batch16在 V100 这类 16G 显存的卡上可以开到 32显存不够时优先降 batch 而不是降分辨率。lro0.01是初始学习率yolo 训练时采用余弦退火调度这个值配 100 个 epoch 是官方验证过的组合。道路损伤检测里我们需要盯住三个损失分量box_loss负责回归框的位置和宽高cls_loss是分类置信度的交叉熵dfl_loss是分布焦点损失专门优化边界框的边缘质量。小数据集上最常见的现象是box_loss下降很快而cls_loss波动说明模型在学“东西在哪”但还没把“东西是什么”分开。如果你打开训练日志发现cls_loss在前 20 个 epoch 几乎不动先检查数据增强是否开得太大再看类别分布是否严重失衡。数据增强参数是另一个决定成败的旋钮。Ultralytics 默认开mosaic1.0它会把四张图拼成一张一起训练显著增加样本多样性但 945 张图里如果损伤目标小mosaic 拼图后会进一步缩小目标像素占比。我一般会把原始训练命令拆开先按默认跑 20 个 epoch 看损失曲线形态再决定是否调增强。裁剪、翻转、HSV 抖动对小数据集是免费的午餐但旋转和透视变换要克制——道路损伤跟路面纹理绑定过度旋转会生成现实中不存在的几何形态让模型学到伪特征。4. 避坑945 张小数据集的 5 个常见翻车点4.1 现象训练 loss 降了但验证 mAP 乱跳训练日志里 box_loss 一路向下验证集 mAP 却跟心电图一样上下乱蹦甚至某个 epoch 0.65、下个 epoch 0.38。这是小数据集最经典的戏码。原因拆开看有三层训练集只有 756 张图一个 batch 16每个 epoch 只有 47 步随机采样波动自然大验证集只有 180 多张图少目标更少单张图的检测结果对 mAP 影响显著再加上 mosaic 增强是随机拼接的每个 epoch 生成的不同组合会改变分布。解决办法分两步走。先在训练命令里固定随机种子yolo detect train支持通过环境变量或者直接把seed作为参数传入固定种子后两次训练结果可以复现方便判断改动是真实收益还是噪声。再缩小验证集波动做法是统计验证集目标数量如果某类目标不足 10 个这类 AP 的跳变属于统计噪声不用花精力调参重点关注样本充足的损伤类别。4.2 现象混淆矩阵总和不是样本总数矩阵“不唯一”很多人跑完训练看到混淆矩阵把矩阵所有格子加起来发现不等于验证集目标总数怀疑 yolov8 算错了。其实这是对混淆矩阵归一化逻辑的误解。Ultralytics 绘制的混淆矩阵默认按行归一化每一行代表一个真实类别行的总和是 100%。某个类别 30 个目标全部检测对了它那行显示 1.00但其他行因为有漏检和误检被压缩所有格子相加自然不是样本数。你在网上搜 yolov8 混淆矩阵总合不唯一能搜到一堆同样困惑的人。想验证真实数字把归一化关掉看原始计数。用训练完成后生成的 confusion_matrix.png 不够需要调接口拿到原始统计from ultralytics import YOLO from ultralytics.utils.metrics import ConfusionMatrix import torch model YOLO(./runs/road_damage/weights/best.pt) metrics model.val(datadata.yaml, splitval, plotsFalse) matrix metrics.confusion_matrix.matrix print(matrix) print(验证集目标总数, matrix.sum())这里matrix是一个二维长尾数组行是真值、列是预测。matrix.sum()应该等于验证集所有真实目标加背景误检的总数跟你数据划分脚本统计出来的目标数对得上。如果对不上大概率是划分脚本与训练 yaml 的 val 路径不一致或者验证集里包含了空标签图片但配置里没有设置对应的负样本处理。4.3 现象某个类别 AP 一直为 0标签看起来没问题五类损伤里有一类 AP 永远是 0其他类很正常标签 txt 里也有这类目标坐标也合法。这种情况十有八九是类别编号映射错位。标注工具导出的类别编号是按照某个内部顺序排的比如 0 可能是纵向裂纹而你写 data.yaml 时按照中文名首字母排序把 0 给了碰撞。模型学到的类别语义和标签编号对不上训练过程中 cls_loss 永远降不下去最终输出出来的混淆矩阵里这一类会全部排到背景。排查手段是写一个映射检查脚本直接读 txt 里的类别编号再按 data.yaml 的 names 顺序反查名字并统计图片内容import yaml from collections import defaultdict with open(data.yaml, encodingutf-8) as f: cfg yaml.safe_load(f) names cfg[names] label_dir split/val/labels id_to_images defaultdict(list) for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname), encodingutf-8) as f: for line in f: cls_id int(line.split()[0]) id_to_images[cls_id].append(fname) for cls_id, images in sorted(id_to_images.items()): print(fid {cls_id} - {names[cls_id]}, 示例图片: {images[:3]})跑完后人工抽查几个典型图片重点看坐标框压在什么位置上。比如wheel_mark的框应该横跨车辙痕迹而不是框住一小段裂纹如果框的位置和类名语义对不上就说明这个类别在标注时就混进了其他损伤这是原始数据集的标注质量问题只能通过人工重新清理局部标签。4.4 现象纵向裂纹识别成横向左右翻转后性能骤降道路损伤检测和常规物体检测有个核心差异损伤的几何方向是物理属性。纵向裂纹是沿行车方向延伸横向裂纹、反射裂纹则是垂直的翻转增强会把纵向裂纹变成横向裂纹模型被迫学两个方向的特征而训练样本里纵向裂纹可能占绝大多数横向只是零星几条结果模型在真实横向裂纹上全部漏检。解决办法是关掉flipud垂直翻转把fliplr水平翻转概率调低甚至关掉。水平翻转对裂纹方向同样有影响只是影响比垂直翻转小。在训练命令里显式指定yolo detect train \ datadata.yaml \ modelyolov8n.pt \ fliplr0.0 \ flipud0.0 \ scale0.5 \ translate0.1scale0.5表示缩放范围是 0.5 到 1.5 倍对小目标数据还可以收窄到 0.3。translate0.1控制平移幅度路面上目标分布比较均匀平移增强帮助不大。损伤检测场景下我更推荐依赖hsv_h、hsv_s、hsv_v色彩抖动来模拟不同光照和路面颜色的变化这对沥青路面的混凝土裂纹最有效。4.5 现象zip 明明标注普通压缩包解压却提示需要密码偶尔会碰到压缩包用工具打开正常但unzip命令报错说需要密码或者头部损坏先别急着按加密处理。这可能是 zip 伪加密标志位在作怪——打包工具把文件的加密标志写进了解压时需要校验的头部但实际文件内容没加密。7z和 Windows 自带解压都能正常解开而某些 Linux 发行版自带的 unzip 会在看到伪加密标志后直接拒绝解压。解决思路是绕过标志位强制解压或者用 Python 的 zipfile 模块手动读取。Python 库不校验伪加密位可以直接拿到文件流import zipfile with zipfile.ZipFile(yolo算法-道路损伤检测数据集-945张图像带标签-纵向裂纹-碰撞-车轮痕迹-坑洼-裂缝.zip) as zf: zf.extractall(/home/user/datasets/road_damage)如果 Python 也报RuntimeError: File is encrypted再看压缩包源头的加密说明确认是否是制作数据集时误加了保护。真实的小规模数据集分享包大多是伪加密或干脆没有加密处理过一次之后我现在的习惯是先跑一遍 2.2 节的统计脚本再决定是否重传压缩包。5. 验证与补强用 PR 曲线和复制粘贴增强把 AP 顶上去训练完成best.pt 已经落盘但只看一个 mAP50 就收工远远不够。我习惯先看测试集预测结果的自检图再看 PR 曲线和每个类别的 AP50 与 AP50-95。用自带的 val 接口一次导出yolo detect val \ model./runs/road_damage/weights/best.pt \ datadata.yaml \ splitval \ conf0.25 \ iou0.5 \ plotsTrue \ save_jsonTrueconf0.25和iou0.5是验证用最常见的阈值组合plotsTrue会生成 PR 曲线、混淆矩阵、F1 曲线以及每个类别的预测样本图。打开 PR 曲线看横轴召回率、纵轴精确率曲线越贴近右上角越好。如果某个类别的曲线像直角弯折说明模型对该类的置信度分布比较极端——类内差异大一部分框高置信度一部分完全漏掉。这时候盲目堆 epoch 效果不如去挖数据把预测样本图里漏得最多的场景挑出来回源标注。最后一招是针对小样本损伤类别做复制粘贴增强。把 pothole 这类稀缺目标的真实框裁剪出来随机贴到其他训练图片的道路区域上同时把对应标签写进粘贴后的 txt。实现时控制在每个 epoch 粘贴 3 到 5 个实例并做小幅缩放和 HSV 抖动避免模型过拟合到裁剪边缘。这个技巧只需要脚本操作不用改动模型结构对小类别的 AP 提升通常比调三个月超参都明显。训练跑完我的习惯是先冻结主干和 neck只训练 head 10 个 epoch 让分类头先收敛再解冻全模型微调。这样在 945 张小数据上能显著缓解过拟合。如果未来你想扩数据集优先补碰撞和坑洼这两类的多样场景比盲目加图更有效。希望帮到你也欢迎在这个思路上继续往下做。本文还有配套的精品资源点击获取