尧图精选

YOLO道路损伤检测数据集实战:从拆包验数到训练避坑全流程

🕒 发布时间:2026/10/1 6:13:59 📁 来源:尧图网络
简介面向道路损伤检测的目标检测数据集覆盖纵向裂纹、碰撞、车轮痕迹、坑洼、裂缝五类典型路面损坏适用于自动驾驶路况感知、公路养护自动化巡检等场景。图片与标注均已完成预处理数据集已按比例划分训练集与验证集并附带数据配置文件可直接用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流YOLO版本训练与验证适合算法学习者、项目开发者快速验证模型或搭建道路巡检方案。压缩包共2000个文件主要包含945个XML标注与945个TXT标注对应VOC和YOLO两种常用格式另含109张JPG原图和1个YAML配置文件坐标已按图像宽高归一化便于切换不同训练框架。资源整体约19.61MB体积小巧离线下载使用非常便捷。目前已有124人学习具有一定的参考价值。使用该数据集可直接开展模型训练与效果对比省去手工标注和格式转换的大量时间是道路缺陷检测入门、算法选型与模型迭代的实用数据支撑。1. 道路损伤检测的数据集困局945 张 YOLO 标注图能省多少事做道路损伤检测的人第一道坎往往不是模型选型而是目标检测数据集从哪来。自己拍照并标注945 张图像按每张平均三到五个目标框来算一个熟练标注员连续做也要两三周网上找公开数据集又常卡在 VOC 或 COCO 格式转换上光写转换脚本就够折腾一晚上。这个 zip 包里装的是一份直接可用的 YOLO 格式道路损伤目标检测数据集945 张图像全部带标签类别覆盖纵向裂纹、碰撞、车轮痕迹、坑洼、裂缝解压后放进 YOLOv5 或 YOLOv8 工程里就能直接训练。适合做路面巡检、道路养护检测的工程师也适合拿来做目标检测入门实战的学生。下文按拆包验数据、训练前准备、避坑、验证的顺序把这份资源从 zip 包到出模型完整走一遍。2. 数据集结构与 YOLO 标签格式拆开 zip 先搞懂这四件事数据集和代码不一样代码跑不起来会立刻报错数据集有问题往往是训到一半才暴露浪费的是整个训练周期。所以拿到 zip 包第一步不是解压后直接扔进训练脚本而是把目录结构、标签格式、类别分布、坐标边界这四件事逐项查清楚。这一章就按这个顺序来。2.1 目录布局与同名对应先查这两件事这类 YOLO 格式数据集的打包方式通常很固定根目录下分 images/ 和 labels/ 两个文件夹图像文件是 jpg 或 png标签文件是与图像同名的 txt。有的发布者还会附带 classes.txt 或 data.yaml 用来声明类别顺序如果没附就得自己从标签文件里的类别 id 反推这一步最容易忽略避坑章我会专门展开。先把结构列出来同时做一次同名对应检查。这个动作的价值在于确认 zip 包完整性以及在训练开始前发现文件缺失而不是等训练中断再回头查。from pathlib import Path root Path(road_damage_dataset) images_dir root / images labels_dir root / labels imgs {p.stem: p for p in images_dir.glob(*) if p.suffix.lower() in {.jpg, .jpeg, .png}} labels {p.stem: p for p in labels_dir.glob(*.txt)} print(f图像文件: {len(imgs)} 个) print(f标签文件: {len(labels)} 个) print(有图无标签:, sorted(set(imgs) - set(labels))[:10]) print(有标签无图:, sorted(set(labels) - set(imgs))[:10])这段脚本的核心逻辑是拿文件名主干做集合差集。这里特意把图像扩展名统一转成小写再比较因为 Windows 和 Linux 混用的数据集里.jpg和.JPG并存非常常见直接拿字符串比对会误报缺失。如果「有图无标签」列表非空说明部分图像没有标注这段数据在训练时会被跳过但会拉低对应类别的 recall如果「有标签无图」非空说明标签文件残留通常不影响训练但会让你统计的数据量虚高。两份集合完全对上之后再往下走。2.2 标签坐标还原读一行 txt算出它在图上的哪个位置YOLO 的标签是文本文件每一行代表一个目标框五个数值依次是类别 id、中心点 x、中心点 y、框宽 w、框高 h。前四个数值做了归一化除以图像宽高后落在 0 到 1 之间所以同一份标签在任意分辨率图像上都有效。这也是 YOLO 格式比 VOC 的xmin, ymin, xmax, ymax绝对坐标省事的地方代价是解析时容易忘掉还原步骤。随便抽一个标签文件验证from pathlib import Path import cv2 img_path Path(road_damage_dataset/images/000123.jpg) img cv2.imread(str(img_path)) img_h, img_w img.shape[:2] label_path Path(road_damage_dataset/labels/000123.txt) for line in label_path.read_text(encodingutf-8).strip().splitlines(): if not line.strip(): continue parts line.split() cls int(parts[0]) cx, cy, w, h map(float, parts[1:5]) x_min int((cx - w / 2) * img_w) y_min int((cy - h / 2) * img_h) x_max int((cx w / 2) * img_w) y_max int((cy h / 2) * img_h) print(f类别 {cls}: 像素框 ({x_min}, {y_min}) - ({x_max}, {y_max}))这里img_h, img_w从 OpenCV 读取的图像数组形状里拿shape[0]是高度、shape[1]是宽度顺序别写反。还原公式就是把归一化坐标乘回像素尺寸框左上角 x 是中心点 x 减半宽右下角 x 是中心点 x 加半宽y 方向同理。这段脚本主要用途不是看内容而是验证还原后的坐标有没有越界——如果 x_max 大于图像宽度或 y_min 小于 0说明标签归一化数据本身就有问题这类样本在训练时会被某些 YOLO 版本自动裁剪也可能引起 loss 异常波动发现了要单独剔出来。2.3 类别分布与目标尺寸统计决定训练参数的第一手数据知道标签格式后下一个动作是对全量数据集做统计五个类别的目标框各有多少、框的尺寸分布如何。这一步直接决定后续训练参数怎么设。比如坑洼和碰撞痕迹这类目标通常只占整张图很小一块如果框面积普遍小于图像面积的 1%训练时就该考虑提高输入分辨率或调整锚框而不是无脑用默认配置。from pathlib import Path from collections import Counter import cv2 cls_counter Counter() tiny_boxes 0 total_boxes 0 for img_path in sorted(images_dir.glob(*)): if img_path.suffix.lower() not in {.jpg, .jpeg, .png}: continue img cv2.imread(str(img_path)) h, w img.shape[:2] label_file labels_dir / (img_path.stem .txt) if not label_file.exists(): continue for line in label_file.read_text(encodingutf-8).strip().splitlines(): parts line.split() cls int(parts[0]) bx, by, bw, bh map(float, parts[1:5]) box_area bw * w * bh * h image_area w * h cls_counter[cls] 1 total_boxes 1 if box_area image_area * 0.01: tiny_boxes 1 print(类别分布:, dict(cls_counter)) print(小目标框占比: {:.1f}%.format(tiny_boxes / total_boxes * 100))脚本按类别统计目标框数量同时用「框面积小于全图面积 1%」作为小目标阈值做评估阈值 0.01 是经验值不是固定标准。跑完建议把阈值改成 0.005 和 0.02 各算一次看小目标占比对阈值敏不敏感。如果某一类只有几十个框那一类在训练里大概率学不好得提前想好是补数据还是做重采样。如果小目标占比超过三分之一训练参数里 imgsz 就别用 640 默认值了这个判断越早做越省时间。提示统计脚本建议只读不要改运行前先备份原始 zip。后面做任何清洗都基于副本原始包保留作为后悔药。2.4 可视化抽检把标注框画回图像上人工过一遍数据统计只能告诉你数量分布不能告诉你框画得准不准。道路损伤数据里最容易出的标注问题是「纵向裂纹」和「裂缝」这两个类别边界模糊——同一条裂纹有的标注员画成纵向裂纹有的画成裂缝模型会把它们当成两个类去学但实际上特征高度重叠。所以抽几十张图把框画出来人工过一遍视觉确认是必要步骤。import cv2 import random from pathlib import Path random.seed(42) sample_imgs random.sample(sorted(images_dir.glob(*.jpg)), 30) output_dir Path(check_vis) output_dir.mkdir(exist_okTrue) colors [(0, 0, 255), (0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0)] class_names {0: longitudinal_crack, 1: collision, 2: wheel_track, 3: pothole, 4: crack} for img_path in sample_imgs: img cv2.imread(str(img_path)) h, w img.shape[:2] label_file labels_dir / (img_path.stem .txt) if not label_file.exists(): continue for line in label_file.read_text(encodingutf-8).strip().splitlines(): parts line.split() cls int(parts[0]) cx, cy, bw, bh map(float, parts[1:5]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls % len(colors)], 2) cv2.putText(img, class_names.get(cls, str(cls)), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls % len(colors)], 1) cv2.imwrite(str(output_dir / img_path.name), img) print(f可视化结果已保存到 {output_dir})这段脚本里 class_names 是我按常见命名写的示例映射不代表这个数据集实际使用的英文标签具体以包内 classes.txt 为准。人工抽查时重点看两点框有没有偏离目标本体、相近类别是否出现明显画法不一致。如果发现纵向裂纹和裂缝的框大量叠加在同一条纹路上说明这两个类本身区分度存疑要么合并成一个类要么靠加大每个类的样本量来补救这个判断要在训练前做不能等模型训完再后悔。3. 训练前数据准备划分、配置与首轮训练数据集验证通过之后进入训练前的准备环节。这一章只做三件事划分训练集和验证集、写 data.yaml 配置文件、跑通第一条训练命令。很多初学者在这三步上翻车常见的是划分时没固定随机种子导致实验结果不可复现或者 data.yaml 里路径写成相对路径导致训练时报数据集找不到。3.1 划分训练集与验证集固定随机种子避免结果不可复现945 张图不算多按 8:2 划分是训练集 756 张、验证集 189 张这个比例对这个规模是合理默认值。划分的逻辑看起来简单但有个细节容易翻车如果直接用random.shuffle而不设种子每次运行结果都不一样后续想复现实验就无从谈起。正确做法是固定随机种子或者基于文件名做散列取模。import random from pathlib import Path random.seed(42) root Path(road_damage_dataset) images sorted(p for p in (root / images).glob(*) if p.suffix.lower() in {.jpg, .jpeg, .png}) random.shuffle(images) val_ratio 0.2 val_count int(len(images) * val_ratio) val_images set(images[:val_count]) train_images set(images[val_count:]) train_images_dir root / train / images val_images_dir root / val / images train_labels_dir root / train / labels val_labels_dir root / val / labels for d in [train_images_dir, val_images_dir, train_labels_dir, val_labels_dir]: d.mkdir(parentsTrue, exist_okTrue) for img in train_images: img.rename(train_images_dir / img.name) lab root / labels / (img.stem .txt) if lab.exists(): lab.rename(train_labels_dir / lab.name) for img in val_images: img.rename(val_images_dir / img.name) lab root / labels / (img.stem .txt) if lab.exists(): lab.rename(val_labels_dir / lab.name) print(f训练集 {len(train_images)} 张验证集 {len(val_images)} 张)这里直接用了文件移动而不是复制因为原始 zip 已经备份过了没必要在磁盘上存两份。random.seed(42)保证重跑时得到相同的划分结果42 是我个人习惯换成任意整数都行关键是固定下来。val_ratio取 0.2 是目标检测常规默认值如果某个类别框数特别少可以考虑分层划分按类别先从每类里抽再合并成验证集保证验证集里每类都有代表样本。945 张规模下简单随机划分通常够用但如果 2.3 节统计发现某一类只有二十几个框简单随机很可能让验证集里这一类只有三四个框这种就要手动干预了。划分完成后目录结构变成 train/images、train/labels、val/images、val/labels 四个文件夹这是 YOLOv5 和 YOLOv8 都认识的经典布局。3.2 data.yaml 编写类别顺序错一位模型全盘错认data.yaml 是 YOLO 训练的数据入口里面声明训练集路径、验证集路径和类别名列表。最容易犯的错误是 names 的顺序和标签里类别 id 对不上比如标签里 0 代表纵向裂纹、1 代表碰撞yaml 里却把碰撞写在第一位。模型不会报错但训练出来的模型会把每一类都错认成名字对应错位的那个类而且很难从 loss 曲线上看出来——loss 照样收敛mAP 照样涨一头扎进推理才发现全乱了。path: /absolute/path/to/road_damage_dataset # 数据集根目录建议写绝对路径 train: train/images val: val/images names: 0: longitudinal_crack # 纵向裂纹 1: collision # 碰撞 2: wheel_track # 车轮痕迹 3: pothole # 坑洼 4: crack # 裂缝上面这个 names 是常见映射示例不代表 zip 包内实际的类别 id 顺序以包内 classes.txt 或 data.yaml 为准。如果发布者没提供类别文件可以跑一段脚本从标签文件里反推from pathlib import Path label_files list(labels_dir.glob(*.txt)) class_ids set() for f in label_files: for line in f.read_text(encodingutf-8).strip().splitlines(): if line.strip(): class_ids.add(int(line.split()[0])) print(标签中出现过的类别 id:, sorted(class_ids))这段代码把全量标签里的类别 id 收集起来去重输出结果应该是[0, 1, 2, 3, 4]这样的连续序列。如果输出跳号比如[0, 1, 3]说明数据里少了某一类这个信息要和 2.3 节的类别统计交叉验证。反推出 id 范围后再结合图像内容人工确认每个 id 对应的类别名这一步要认真做因为 YOLO 训练框架不会替你校验名字和视觉内容是否匹配。path 字段建议写绝对路径相对路径在 ultralytics 里会拼接在工程目录下经常报 No such file or directory排查起来很费时间。3.3 首轮训练参数选择与日志阅读loss 不降先查数据数据集规模不到一千张模型没必要一上来就选最大的。以 YOLOv8 为例第一轮先用 nano 版本跑通流程验证数据链路没问题再换 s 或 m 提精度。这既能快速暴露数据问题也能给后续实验留出对比基线。yolo detect train \ dataroad_damage.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ project./runs \ nameroad_damage_v1参数说明data指向刚写好的 yamlmodel用 yolov8n.pt 预训练权重作为初始权重这是迁移学习的标准做法比从零训练收敛快得多epochs在 945 张图上跑 100 轮足够 loss 进入平台期再往后容易过拟合imgsz用 640 是速度与精度的折中如果 2.3 节统计出小目标占比高这一步改成 960batch根据显卡显存调整16G 显存跑 batch16 的 640 输入没有压力显存小就降到 8另外 batch 太小会影响 BN 层统计尽量别低于 8。训练开始后不要只盯着终端滚动条重点观察两个指标。第一box_loss和cls_loss是否稳定下降这两个值对应的是 YOLO 损失函数里的定位分支和分类分支如果 loss 不降反升大概率是标签有问题而不是模型有问题回到第 2 章重新检查标签越界和类别错位。第二验证集mAP50有没有随训练轮数正常抬升如果卡在某个值上不去先别急着调模型回 2.3 节看类别分布——多数类把整体指标拉高、少数类一直学不好是最常见的原因。注意YOLOv5 和 YOLOv8 的数据集目录结构要求略有差异但 train/images 和 val/images 这种布局两者通用。如果用的是 mmrotate 这类旋转目标检测工具目录约定和标签格式又是另一套需要单独适配。训练中断想续跑ultralytics 会自动从runs/road_damage_v1/weights/last.pt继续前提是 project 和 name 参数保持一致。4. YOLO 道路损伤训练避坑945 张图上最容易翻车的五个问题这一章记录的是实际跑这份数据时最容易遇到的问题每一条按「现象、原因、解决」展开。这些问题不是同时出现但每个都真实发生过建议当成训练前的检查清单用。4.1 类别 id 与 names 文件错位模型全类错认但 loss 正常现象训练过程 loss 正常下降mAP50 看起来不错用训练好的模型推理时坑洼的检测框标成了纵向裂纹碰撞痕迹显示成裂缝所有类别整体平移了一位而且很难从指标上察觉。原因data.yaml 里 names 的顺序和标签文件里的类别 id 不一致。标签里 0 号是纵向裂纹yaml 第一个名字写的却是碰撞模型学到的是「id0 的视觉特征对应碰撞」这个错误映射。YOLO 训练过程没有任何机制检查这个对应关系loss 该收敛还是收敛这是这类问题最阴险的地方。解决训练启动前跑一遍 3.2 节的类别 id 反推脚本把标签里出现过的 id 集合与 data.yaml 的 names 键值逐一对照。发布者给了 classes.txt 就以它为准没给就人工抽 20 张图像把框画出来确认。把这步写进训练脚本的启动前置检查里比等训练完再发现省两小时。从那以后我每次换新数据集第一件事就是跑这个对照不再信文件名。4.2 小目标占比高坑洼碰撞检不出来现象验证集上纵向裂纹和裂缝的 mAP50 能到 0.7 以上坑洼和碰撞只有 0.2 甚至接近 0单独拿包含小目标的图像推理模型几乎不输出这些类别的框。原因道路损伤数据集的天然属性——坑洼和碰撞痕迹在巡检图像里往往只占很小一块像素面积。如果 2.3 节统计的小目标占比超过三分之一默认 640 分辨率下小目标被缩放到只有十几个像素宽特征几乎丢失。YOLO 默认锚框是按 COCO 数据集统计的对这类极小目标本身就不友好。解决两条路。第一imgsz 从 640 提到 960 或 1280输入分辨率越大小目标保留的像素越多代价是显存上涨、训练变慢第二数据增强里叠加 mosaic 和 copy-paste让包含小目标的样本在每张训练图里出现得更频繁。我一般先跑 imgsz960 的对比实验坑洼类 mAP50 通常能拉上来 10 到 15 个点还不够再考虑针对性补数据。降低 conf 阈值不是正路那是事后补救不是根治。4.3 类别样本量失衡少数类被多数类带偏现象训练初期 loss 下降很快但验证集上少数类的 precision 和 recall 明显低于多数类混淆矩阵里少数类样本大量被预测成相邻的多数类比如车轮痕迹被预测成碰撞。原因945 张图不等于每个类别都有 945 个框。实际分布往往是纵向裂纹和裂缝占了大半碰撞和车轮痕迹只有几十个框。模型每轮训练见到多数类的机会远多于少数类决策边界整体偏向数据量大的那边这和数据增强无关是类别不平衡的典型表现。解决先看 2.3 节的统计结果。如果少数类框数不到多数类的五分之一做两类处理一是对少数类图像做离线增强翻转、亮度抖动、随机裁剪都行但别做太狠导致标注和图像内容不匹配二是训练时给少数类更高的采样权重ultralytics 里可以按类别频率调整 sampler 权重。不要只调 loss 权重少数类本身特征多样性不足权重调再高也学不到足够的泛化特征。补数据是最稳的但周期长离线增强是当天能见效的折中方案。4.4 有图无标签或有标签无图训练中断或漏检现象训练脚本启动后没多久报错提示某个图像找不到对应标签或者标签数组维度不匹配另一种情况完全不报错训练正常完成但验证集漏检严重尤其集中在某几类。原因数据集在传输或解压过程中丢失部分文件或者原始打包时某些图像本身就没有标注。2.1 节的同名对应检查在训练前没跑的话要等到训练加载数据时才发现缺口。这种缺口在 945 张规模里哪怕只有二三十张对应类的 recall 就会明显变差因为模型根本没机会看到这部分样本。解决严格养成先跑 2.1 节脚本再训练的习惯。有图无标签的样本要么补标注、要么直接从数据集里移除有标签无图的文件直接删除避免统计混乱。补标注人力不够时先用训练好的模型做预标注再人工修正这是一线项目里最常见的操作节奏。另外每个 epoch 结束后扫一眼验证集的 loss如果验证 loss 突然跳变排除学习率因素后优先怀疑数据加载链路。4.5 解压乱码与扩展名大小写混用路径匹配全乱现象在 Windows 上解压 zip 后部分图像文件名显示成乱码或者图像是 .JPG 后缀而脚本里 glob 匹配的是小写 .jpg导致匹配数量对不上代码在数据加载阶段报找不到文件。原因zip 包如果在 Linux 或 macOS 上打包中文文件名用 UTF-8 编码Windows 自带解压工具默认按 GBK 解压就会乱码扩展名大小写混用则常见于相机直接导出的素材未做统一处理。这两类问题不影响标签内容本身但会让自动化脚本匹配失效肉眼很难发现。解决解压优先用 7-Zip解压时选择保留原始编码已经乱码的用 Python zipfile 模块重新解压并指定编码。扩展名混用问题解压后统一跑一次 rename 把 .JPG、.jpeg、.Jpeg 全部转小写 .jpg。处理完再走一遍 2.1 节对应检查数量对上再进训练流程。另外注意标签文件内容如果是 UTF-8 带 BOM第一行的类别 id 前会多一个不可见字符用utf-8-sig编码读取能避开这个坑。提示这五个问题不是每次全遇到但每一条都真实发生。建议把五条整理成 checklist 写进数据准备脚本的注释里换新数据集时强制跑一遍别跳过。5. 训练完成后的验证闭环混淆矩阵、实图抽查与小目标专项评估模型训练完第一件事不是急着导出部署而是做验证闭环。只看终端最后一行 mAP50 是最容易自欺欺人的做法这个数字在类别不平衡数据上会被多数类拉高掩盖少数类的真实表现。我现在的固定流程是三步看混淆矩阵和每类 PR 曲线做一批实图推理抽查最后针对表现最差的类别单独评估。验证命令在 YOLOv8 里很直接yolo detect val \ dataroad_damage.yaml \ modelruns/road_damage_v1/weights/best.pt \ imgsz640跑完后重点看 results 目录下的 confusion_matrix.png 和 PR_curve.png。混淆矩阵横纵轴是真实类别和预测类别对角线越亮越好亮点出现在非对角线位置说明哪两类在互相混淆就去翻那一类的样本找视觉共性纵向裂纹和裂缝混淆就要考虑是不是类别定义本身有问题。PR 曲线看的是曲线下面积面积小说明 recall 提升时 precision 掉得快这类目标要么特征弱、要么样本少。实图抽查不能省。选验证集里包含坑洼和碰撞小目标的图像跑推理把检测框画回原图from ultralytics import YOLO model YOLO(runs/road_damage_v1/weights/best.pt) results model.predict(val/images/000223.jpg, imgsz640, conf0.25) for r in results: r.plot()这里 conf0.25 是默认阈值小目标在 0.25 下检不出来时把阈值降到 0.1 再看一次。降阈值后能检出但框位置偏移明显说明是回归精度问题回训练阶段提 imgsz降阈值后依然检不出来说明模型根本没学到这个特征问题在数据端。这一步能快速区分模型问题和数据问题避免盲目调参消耗时间。最后是小目标专项评估。把验证集里坑洼和碰撞类的框单独拉出来算各自 mAP50如果专项分数比整体低 20 个点以上回第 4 章对症下药处理。从那以后我每次跑完训练都强制走一遍这个验证闭环不再沉迷于终端里那个好看的总 mAP毕竟道路损伤检测最终要看的是坑洼和碰撞这类真正影响行车安全的目标能不能被稳定识别出来希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →