简介: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. 道路损伤检测用 YOLO:945 张带标签数据集的真实分量
做道路巡检的朋友多半遇到过这种尴尬:手上攒了一批现场照片,想拿 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, encoding="utf-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(parents=True, exist_ok=True) lbl_dst.parent.mkdir(parents=True, exist_ok=True) if not img_dst.exists(): os.symlink(img_path, img_dst) if not lbl_dst.exists(): os.symlink(label_path, lbl_dst) print(f"train 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 \ data=data.yaml \ model=yolov8n.pt \ imgsz=640 \ batch=16 \ epochs=100 \ patience=20 \ lr0=0.01 \ augment=True \ project=./runs \ name=road_damagemodel=yolov8n.pt表示加载 YOLOv8n 的预训练权重,首次运行时会尝试下载权重文件到当前目录缓存,之后这段命令会直接复用缓存离线启动,不必每次联网拉取。imgsz=640是默认输入尺寸,215 万像素的输入对道路损伤检测足够,改到 1280 会成倍增加显存占用和训练时间,而小目标增益有限。batch=16在 V100 这类 16G 显存的卡上可以开到 32,显存不够时优先降 batch 而不是降分辨率。lro=0.01是初始学习率,yolo 训练时采用余弦退火调度,这个值配 100 个 epoch 是官方验证过的组合。
道路损伤检测里我们需要盯住三个损失分量:box_loss负责回归框的位置和宽高,cls_loss是分类置信度的交叉熵,dfl_loss是分布焦点损失,专门优化边界框的边缘质量。小数据集上最常见的现象是box_loss下降很快而cls_loss波动,说明模型在学“东西在哪”,但还没把“东西是什么”分开。如果你打开训练日志发现cls_loss在前 20 个 epoch 几乎不动,先检查数据增强是否开得太大,再看类别分布是否严重失衡。
数据增强参数是另一个决定成败的旋钮。Ultralytics 默认开mosaic=1.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(data="data.yaml", split="val", plots=False) 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", encoding="utf-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), encoding="utf-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(f"id {cls_id} -> {names[cls_id]}, 示例图片: {images[:3]}")跑完后人工抽查几个典型图片,重点看坐标框压在什么位置上。比如wheel_mark的框应该横跨车辙痕迹而不是框住一小段裂纹,如果框的位置和类名语义对不上,就说明这个类别在标注时就混进了其他损伤,这是原始数据集的标注质量问题,只能通过人工重新清理局部标签。
4.4 现象:纵向裂纹识别成横向,左右翻转后性能骤降
道路损伤检测和常规物体检测有个核心差异:损伤的几何方向是物理属性。纵向裂纹是沿行车方向延伸,横向裂纹、反射裂纹则是垂直的,翻转增强会把纵向裂纹变成横向裂纹,模型被迫学两个方向的特征,而训练样本里纵向裂纹可能占绝大多数,横向只是零星几条,结果模型在真实横向裂纹上全部漏检。
解决办法是关掉flipud(垂直翻转),把fliplr(水平翻转)概率调低甚至关掉。水平翻转对裂纹方向同样有影响,只是影响比垂直翻转小。在训练命令里显式指定:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ fliplr=0.0 \ flipud=0.0 \ scale=0.5 \ translate=0.1scale=0.5表示缩放范围是 0.5 到 1.5 倍,对小目标数据还可以收窄到 0.3。translate=0.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 \ data=data.yaml \ split=val \ conf=0.25 \ iou=0.5 \ plots=True \ save_json=Trueconf=0.25和iou=0.5是验证用最常见的阈值组合,plots=True会生成 PR 曲线、混淆矩阵、F1 曲线以及每个类别的预测样本图。打开 PR 曲线看横轴召回率、纵轴精确率,曲线越贴近右上角越好。如果某个类别的曲线像直角弯折,说明模型对该类的置信度分布比较极端——类内差异大,一部分框高置信度,一部分完全漏掉。这时候盲目堆 epoch 效果不如去挖数据:把预测样本图里漏得最多的场景挑出来,回源标注。
最后一招是针对小样本损伤类别做复制粘贴增强。把 pothole 这类稀缺目标的真实框裁剪出来,随机贴到其他训练图片的道路区域上,同时把对应标签写进粘贴后的 txt。实现时控制在每个 epoch 粘贴 3 到 5 个实例,并做小幅缩放和 HSV 抖动,避免模型过拟合到裁剪边缘。这个技巧只需要脚本操作,不用改动模型结构,对小类别的 AP 提升通常比调三个月超参都明显。
训练跑完,我的习惯是先冻结主干和 neck,只训练 head 10 个 epoch 让分类头先收敛,再解冻全模型微调。这样在 945 张小数据上能显著缓解过拟合。如果未来你想扩数据集,优先补碰撞和坑洼这两类的多样场景,比盲目加图更有效。希望帮到你,也欢迎在这个思路上继续往下做。
本文还有配套的精品资源,点击获取