ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

道路缺陷检测YOLO数据集:1.4万张标注图与训练落地指南

道路缺陷检测YOLO数据集:1.4万张标注图与训练落地指南 简介面向目标检测应用的大型道路缺陷数据集包含超过一万四千张训练图像、二百六十张验证图像和一千张测试图像已统一处理为YOLO格式可直接用于YOLO全系列网络的训练与验证。压缩包共两千个文件其中一千九百九十九个txt为逐图标注文件另有一个show.py可视化脚本整体包体约九百二十兆字节。类别涵盖四种缺陷并附有class类别文本便于快速索引。目前已有三百七十二人学习下载。这份数据面向计算机视觉开发者和算法初学者能省去标注格式转换环节show.py可将目标框绘制在图像上方便核查标注质量与预测效果是道路缺陷检测实验、课程设计或模型调参的可靠数据基础尤其适合缺少现成道路缺陷标注样本的场景。1. 道路缺陷检测数据集1.4 万张 YOLO 标注图能省多少人工做过道路巡检的人都知道最磨人的不是算法是数据。外业跑一天拍回来两三千张照片内业对着屏幕一张张找裂缝、坑槽、网裂、修补眼快瞎了。这个道路缺陷检测数据集的定位很直接约 1.4 万张训练图、260 张验证图、1000 张测试图全部处理成 YOLO 格式带 4 类缺陷的矩形框标注配套 show.py 可以把标注画回原图检查。适合正在做道路巡检视觉方案、想把 YOLO 系列快速落地的工程师也适合拿真实项目数据练手的同学。它不是那种几张图配一个 txt 的demo 级数据量级和标注格式都能直接喂给训练脚本。2. 数据集内部结构拆解从文件命名到标签校验先把它看透拿到资源第一步不是急着训练而是先搞清楚里面每层目录、每个文件在干什么。这一章我把文件命名规则、标签内容格式和 show.py 可视化逻辑拆开讲顺便给出我自己日常校验数据集的脚本思路。2.1 一长串文件名里藏着什么Roboflow 导出格式的读法数据集里大量文件名长这样Japan_007600_jpg.rf.e3ea45e961a5f445fd838593c1abaa18.txt Japan_005119_jpg.rf.fa8bdf4193020cbdbb42821539bdec78.txt Japan_003245_jpg.rf.a0ce0b5daba90c9e176ce66a24217b51.txt这种命名是 Roboflow 导出标注时留下的痕迹拆开读Japan_007600是原始图像名_jpg表示原图后缀是.jpg中间的rf是 Roboflow 标记最后 32 位十六进制是那张图的唯一哈希值避免不同批次图片同名冲突。对应地同目录或 images 子目录下应该有一张同名的.jpg比如Japan_007600_jpg.rf.e3ea45e961a5f445fd838593c1abaa18.jpg。每个.txt是 YOLO 格式的标签文件内容长这样2 0.45087226866824346 0.3549196853478619 0.15693430656934307 0.09133858267716535 0 0.7832116788321168 0.39548809118240053 0.08211678832116788 0.07952119017027999每行五个数字第一个是类别 id后四个是归一化后的中心点 x、中心点 y、框宽 w、框高 h全部除以了原图宽高取值范围在 0 到 1 之间。这里要强调一个容易看走眼的地方x_center和y_center是中心点坐标不是左上角坐标转成像素框时经常有人忘记减半宽高导致画出来的框往右下角偏。这一整个数据集是处理成 YOLO 格式的所以 YOLOv5、YOLOv8、YOLO11 这一系列直接用就行。如果你之前用过 VOC 的 XML 或 COCO 的 JSON就会理解 YOLO 格式对训练脚本有多友好不需要解析 XML 树也不需要查 COCO 的 annotation 表一行一个框读取速度极快。2.2 show.py 可视化脚本把黑盒标签搬回图像上确认摘要里专门提到 show.py这个脚本在数据集里承担“标注质检”角色。它的核心逻辑并不复杂读一张图读同名 txt把归一化的框转成像素坐标再用 OpenCV 画出来。我按常见做法给你还原一个可用的版本#!/usr/bin/env python3 import cv2 import os import sys def show_single(img_path, label_path, class_names, output_pathNone): img cv2.imread(img_path) if img is None: print(f无法读取图像: {img_path}) return h, w img.shape[:2] with open(label_path, r, encodingutf-8) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) 5: print(f跳过非法标签行: {line}) continue cls_id 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) # 防止坐标越界 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) color (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) if 0 cls_id len(class_names): label_text class_names[cls_id] cv2.putText(img, label_text, (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) if output_path: cv2.imwrite(output_path, img) print(f已保存: {output_path}) else: cv2.imshow(show, img) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ __main__: CLASS_NAMES [Alligator_Crack, Longitudinal_Crack, Pothole, Transverse_Crack] img_file sys.argv[1] label_file sys.argv[2] show_single(img_file, label_file, CLASS_NAMES)参数说明CLASS_NAMES顺序必须和包里的class.txt完全一致否则画出来的标签名会错位cv2.rectangle的坐标要求是左上角和右下角所以代码里从中心点换算后要做cx - bw/2和cx bw/2这步错了框就偏移waitKey(0)表示按任意键切换下一张。实际使用中我更推荐批量抽检而不是一张张弹窗口把output_path指定成一个目录随机抽 50 张图统一画框保存再快速翻看效率高得多。这个脚本的真正价值在于它让你在训练之前就发现标签问题。比如某个框的中心点在图像边缘、某个框宽高为 0、某些类别的框明显画大了这些都逃不过肉眼。2.3 类别文件与标签统计先摸清 4 类缺陷的底细数据集是 4 类缺陷。类别定义以class.txt为准前提是你得打开它看看类别 id 的排列顺序。在做任何训练之前我一般会先跑一个标签统计脚本把每类的目标数量、单张图的平均目标数、框的宽高分布算出来。这一步看起来多余但能避免训练到一半才发现某个类别只有几百个框。import os from collections import Counter from pathlib import Path label_dir Path(labels) class_counter Counter() box_sizes [] images_with_no_label 0 total_images 0 for label_file in label_dir.glob(*.txt): total_images 1 with open(label_file, r, encodingutf-8) as f: lines f.readlines() if not lines: images_with_no_label 1 continue for line in lines: parts line.strip().split() if len(parts) 5: continue cls_id parts[0] bw, bh float(parts[3]), float(parts[4]) class_counter[cls_id] 1 box_sizes.append((bw, bh)) print(类别统计:, dict(class_counter)) print(空标签图片数:, images_with_no_label) print(总图片数:, total_images) w_avg sum(s[0] for s in box_sizes) / len(box_sizes) h_avg sum(s[1] for s in box_sizes) / len(box_sizes) print(f平均框尺寸: w{w_avg:.4f} h{h_avg:.4f})逻辑说明glob(*\.txt)只扫标签目录每读一个 txt 就对应一张图片所以total_images统计的是图片数空标签文件单独计数这类图片在训练时会被 YOLO 跳过或报 warning如果占比超过 2% 就要人工复查。框宽高的均值能反映目标尺度如果平均框宽小于 0.1说明大量缺陷是小目标后面选 imgsz 时要考虑要不要用 1280 甚至更高分辨率训练。这个统计脚本还有一个用途检查类别不平衡。4 类缺陷里裂缝往往远多于坑槽。后面第 5 章会讲怎么针对不平衡做调整这里先把数据底数摸清楚后面不至于盲目调参。3. 训练前数据准备目录重组、data.yaml 与类别平衡实操YOLO 系训练脚本对数据目录有自己的约定不是把 jpg 和 txt 混在一个文件夹就能直接跑的。这一章给出目录重组的具体方法、data.yaml 的写法以及类别不平衡时的处理思路。3.1 目录结构强制规范images 和 labels 镜像YOLOv5 和 YOLOv8 默认的目录结构是 images 和 labels 两个平行目录下面再按 train、val、test 分。脚本内部通过把路径里的images替换成labels来定位标签所以你必须保证images/train/xxx.jpg对应labels/train/xxx.txt文件名完全一致。# 假设解压后原始结构是 images/ 和 labels/ 下各自有 train val test 三个子目录 project/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── class.txt └── show.py如果原始数据集把所有图片放在一个目录、所有 txt 放在另一个目录没有按 train/val/test 分开那就需要做划分。我用 Python 脚本处理按 0.85/0.1/0.05 比例随机切分并且保证每一类目标在三个集合里都有出现import os import random from pathlib import Path from collections import defaultdict random.seed(42) src_images Path(images_all) src_labels Path(labels_all) dst_root Path(dataset) # 找出所有有标签的图片 pairs [] for img_path in src_images.glob(*.jpg): label_path src_labels / (img_path.stem .txt) if label_path.exists(): pairs.append((img_path, label_path)) # 按标签类别做分层采样保证 val/test 覆盖所有类 class_to_pairs defaultdict(list) for img_path, label_path in pairs: with open(label_path) as f: first_line f.readline().strip() if first_line: cls_id first_line.split()[0] class_to_pairs[cls_id].append((img_path, label_path)) train_pairs, val_pairs, test_pairs [], [], [] for cls_id, cls_pairs in class_to_pairs.items(): random.shuffle(cls_pairs) n len(cls_pairs) n_train int(n * 0.85) n_val int(n * 0.10) train_pairs cls_pairs[:n_train] val_pairs cls_pairs[n_train:n_train n_val] test_pairs cls_pairs[n_train n_val:] # 写文件 for split, pairs in [(train, train_pairs), (val, val_pairs), (test, test_pairs)]: (dst_root / images / split).mkdir(parentsTrue, exist_okTrue) (dst_root / labels / split).mkdir(parentsTrue, exist_okTrue) for img_path, label_path in pairs: img_dst dst_root / images / split / img_path.name label_dst dst_root / labels / split / label_path.name os.link(img_path, img_dst) os.link(label_path, label_dst) print(ftrain{len(train_pairs)} val{len(val_pairs)} test{len(test_pairs)})逻辑说明用os.link创建硬链接而不是复制避免浪费一倍磁盘空间在同一个盘符下硬链接几乎不占额外空间但要注意如果后续要修改某个文件硬链接会同步改动所以只读训练没问题。按类别做分层采样是为了防止 val 集里缺了某个类别导致验证时那个类的 mAP 直接没分数这个坑在 260 张的验证集里特别容易出现。类别统计那里用的是每张图的第一个标注类别做分层依据对大部分场景够用如果你追求更精确可以改成统计该图包含的所有类别再做多标签分层但工程上通常没那个必要。3.2 data.yaml 的写法与三个细节目录结构整理好后写data.yaml这是 YOLOv8 训练时的数据描述文件path: /home/user/road_defect_dataset train: images/train val: images/train test: images/test nc: 4 names: 0: Alligator_Crack 1: Longitudinal_Crack 2: Pothole 3: Transverse_Crack三个细节值得单独说。第一个是path建议写绝对路径。train和val字段是相对path的如果你的终端当前目录变了相对路径解析就会失败训练报AssertionError: train set not found排查半天往往就是 cwd 问题。我习惯把path直接写成本机绝对路径换机器训练时全局替换一下即可。第二个是names的索引必须和标签文件里的 class_id 严格对应。YOLO 对类别名是什么不敏感它只认 id如果你顺序搞反了训练出的模型输出名字和实际类别对不上部署时推理结果张冠李戴。收到数据集第一步就先打开 class.txt 对着统计脚本的类别分布看一遍确认 id 0 到 3 分别是什么。第三个是val可以指到train。当验证集图片太少或者分布不均时有人会把 val 指向训练集的一部分来做阶段评估这是权宜之计不代表模型泛化能力。真正的泛化指标靠test集验证这也是这个数据集里保留 1000 张测试图的意义所在。3.3 类别不平衡不是无脑过采样道路缺陷数据集的天然倾向是裂缝类远多于坑槽类因为路面裂缝本来就更常见。类别不平衡在 YOLO 里不是死局见过不少项目一上来就复制小类别图片做重复采样把数据集撑大反而出现过拟合。我的做法分两步看。第一步看比例拿第 2 章的统计脚本跑完如果最小的类占比不到 5%才考虑干预如果占到 10% 以上直接原样训练把希望寄托在损失函数的cls权重上YOLOv8 训练时调cls0.7或cls1.5就能改变分类损失占比。第二步看目标尺度坑槽类通常是大目标裂缝类是小目标这两类的不平衡不只在数量上还在尺度上需要用 mosaic 增强和大 imgsz 共同解决。这一步数据准备的产出是一份目录干净、路径绝对、类别顺序确认过的训练集接下来进入真正的调参环节。4. 标注数据落地避坑五条来自一线的翻车记录这份数据集整体质量不错但“整体不错”在训练时毫无意义只要有一个文件出问题训练要么报错中断要么指标被拉低。这一章写五条我实际踩过的坑按“现象 → 原因 → 解决”的顺序来。4.1 训练刚开始就报 “Label file not found”现象启动训练命令后控制台刷出大量警告类似WARNING ⚠️ labels not found in ...然后图片被跳过实际参与训练的图片数量远小于预期。原因YOLO 通过将图像路径中的images替换为labels来查找标签文件但在上一轮目录重组时我把图片和标签分别放在两个根目录且子目录名不叫images和labels脚本替换后找不到路径。还有一种情况是 jpg 和 txt 主文件名不一致比如原图叫Japan_007600.jpg标签却叫Japan_007600_jpg.rf.xxx.txt两者 stem 对不上。解决统一以txt文件名为基准反向找 jpg再做一次文件硬链接配对而不是以 jpg 为准找 txt。跑完后随机抽 30 对用脚本检查每张图是否能定位到同名标签确认无缺失再进入下一步。4.2 标签类别 id 越界导致 loss 变成 NaN现象训练到几个 epoch 后 loss 突然变成 NaN或者直接报index out of range。原因标注文件里出现了大于nc-1的类别 id。比如 class.txt 定义 4 类但某个 txt 里第一列写了 5 或 6可能是因为原始标注导出时类别文本被改动过训练脚本在计算分类损失时索引越界。这类脏数据常常只藏在几百个文件里统计脚本只做了计数没做最大值校验就容易漏过去。解决在数据准备脚本里加一行硬校验max_cls 3 # nc - 1 bad_files [] for label_file in label_dir.glob(*.txt): with open(label_file) as f: for line in f: if line.strip(): cls_id int(line.split()[0]) if cls_id 0 or cls_id max_cls: bad_files.append((label_file.name, cls_id)) print(非法类别文件:, bad_files[:20])逻辑说明这行代码遍历所有标签行检查类别 id 是否落在合法区间。发现非法文件后不要直接删除而是打开原图看框的位置是否合理再用标注工具修正或剔除。这个检查我每次换数据集都会跑一遍成本极低收益极高。4.3 大图直接用 640 训练小裂缝全丢了现象训练出来的模型 val 指标看着还行但在测试图上小裂缝几乎检不出或者检出的框偏移严重。原因这个数据集的原始图片大概率是道路巡检相机拍的大图可能 3000x4000 甚至更大。YOLO 训练时imgsz640会把图像等比缩放到 640 边长一条原本只有 20 像素宽的裂缝缩放后只剩 4 像素细节全没了。特征图上一旦小于 1 个像素检测器基本不可能召回。解决把imgsz提到 1280如果显存不够就用 960 或 1104。代价是训练时间几乎翻倍显存占用也涨但道路缺陷这种小目标场景必须要喂高分辨率。如果实在受限于显存一个折中是先用 640 跑 50 个 epoch 做预训练再用 1280 微调最后 20 个 epoch。4.4 验证集 260 张图里某个类别完全没有现象训练正常结束验证结果里某个类的 mAP 是 0.00或者类对应的 PR 曲线面积为零。原因260 张验证图本来就少如果划分时没有做类别分层很容易凑巧把某一类全部留在训练集里。YOLO 验证时遇到这个情况不会报错就是那个类 mAP 记 0整体 mAP 被拉低不少。解决按照 3.1 的脚本用类别分层重新划分 val 集如果还是凑不满把 val 集图片数扩到 600 到 800 张反正验证集占训练集比例本来就该在 5% 到 10% 之间260 张实在偏少。4.5 细长目标框宽高比极端mAP50 高但定位不准现象裂缝是细长条目标归一化后框的宽高比可能到 10:1 甚至 20:1模型预测的框在 mAP50 指标上能过但边框和真实缺陷贴合度很差到 mAP50-95 直接崩掉。原因mAP50 允许 IoU 达到 0.5 就判定命中对细长目标来说 0.5 的 IoU 其实很宽松框整体偏移 30% 都能过但 mAP50-95 要求更高 IoU细长框的 IoU 对偏移极其敏感稍微偏一点 IoU 就从 0.7 掉到 0.3。解决不要只盯 mAP50重点看 mAP50-95。另外在推理阶段可以适当调低conf阈值到 0.2 左右配合 NMS 的 IoU 阈值 0.45 做后处理能救回部分被阈值误杀的低置信度裂缝框。如果项目对框精度要求高建议后续用语义分割模型做裂缝细分分割对细长目标天生更友好。5. 用 YOLOv8 实测基线训练、超参倾向与断点续训数据确认无误避坑清单排了一遍进入训练环节。以 YOLOv8 为例跑第一轮基线这章给出命令、关键超参倾向以及中断训练后的恢复方式。5.1 第一轮基线命令与显存预期环境默认是装了ultralytics的 Linux 机器单张 GPU。先跑一条最简单的命令yolo detect train \ data/home/user/road_defect_dataset/data.yaml \ modelyolov8s.pt \ epochs120 \ imgsz1280 \ batch8 \ device0参数说明modelyolov8s.pt表示加载 COCO 预训练权重做迁移学习s 版本在速度和精度之间平衡适合第一轮摸情况imgsz1280是道路缺陷小目标的正确选择代价是显存8 的 batch 在 24G 显存上跑 1280 分辨率差不多是上限如果只有 12G 显存就降到 6 或把分辨率降到 960epochs120是参考值实际看patience参数默认 100 个 epoch 没提升就早停。跑起来之后重点看两个输出第一个是每个 epoch 结束后的mAP50-95第二个是 loss 曲线的下降形态。第一轮不要调参目标是拿到一个可用的 baseline为后续对比提供参照。这个数据集是 YOLO 格式YOLOv5、YOLOv8、YOLO11 直接换model参数就能跑不需要改数据。5.2 道路缺陷场景的超参倾向imgsz、mosaic 与 cls 权重道路缺陷这类目标有两个极端的尺度分布裂缝细长坑槽大而近椭所以超参设置不能照搬通用目标检测的方案。下面这组参数是我在类似巡检数据集上验证过的倾向值不是绝对标准但作为起点比默认参数靠谱参数推荐值说明imgsz1280小裂缝保留像素别用 640close_mosaic10最后 10 轮关闭马赛克增强cls1.2类别不平衡时提高分类损失权重fliplr0.5道路缺陷左右镜像不影响语义scale0.5限制缩放幅度防止大坑槽被缩得太小mixup0.2轻微混合增强加多了会干扰细长边缘close_mosaic10这个参数值得单独解释。Mosaic 增强在训练前期能把四张图拼在一起让模型见过更多上下文和尺度变化但在训练后期它会让目标边缘被拼接缝截断对细长裂缝是灾难。YOLOv8 里有close_mosaic参数意思是最后 N 个 epoch 自动关闭 mosaic保证模型在真实完整图像上收敛。cls1.2的作用是直接放大分类损失的权重。在类别不平衡时模型容易把少数类率降低把cls从默认 0.5 调到 1.2 以上本质是让梯度更新更偏向分类正确。如果你统计脚本发现最小类别占比不到 5%这个参数就值得调。迁移学习时COCO 预训练权重里有 80 个类别和这 4 类道路缺陷完全不同但前几层的特征提取器仍然有效所以第一轮用预训练权重而不是随机初始化收敛速度快非常多。5.3 断点续训与模型选择训练中断是常态可能是断电可能是显存溢出也可能是你手动 CtrlC。YOLOv8 的训练会实时保存last.pt和best.pt恢复不需要从零开始# 方式一从上次 checkpoint 继续 yolo detect train resumeTrue # 方式二从指定权重文件继续适合手动改参数后再续 yolo detect train \ data/home/user/road_defect_dataset/data.yaml \ model/home/user/runs/detect/train3/weights/last.pt \ epochs140 \ imgsz1280参数说明resumeTrue会自动找到最近一次训练的last.pt连优化器状态和 epoch 都会恢复适合被意外打断的情况方式二加载last.pt但重置优化器状态适合你想中途修改某个超参的场景改了epochs为总轮数后继续。best.pt是在验证集上 mAP 最高的权重训练完部署优先用它last.pt只作为续跑的中转不要直接拿去推理。第一轮基线跑完之后不要急着部署。下一章讲的验证分析方法才是决定这个模型能不能用的关键环节。6. 拿到训练结果后不急着部署验证、坏例分析与我现在的固定流程训练完成只是开始。拿best.pt在测试集上做验证分析失败样例决定是部署还是回到数据侧补标注这一章讲具体做法。yolo detect val \ data/home/user/road_defect_dataset/data.yaml \ model/home/user/runs/detect/train3/weights/best.pt \ splittest运行完看四个关键输出按顺序排查第一张是PR_curve.png看每个类别的 PR 曲线包络面积面积小的类别就是当前模型最弱的地方。第二张是confusion_matrix_normalized.png看类别之间的混淆方向道路缺陷里常见的是横向裂缝和纵向裂缝互相混淆这类混淆靠调置信度阈值是救不回来的只能补充难例数据。第三张是F1_curve.png峰值对应的是 F1 最优的置信度阈值推理时把conf设在这个值附近。第四张是val_batch2_pred.jpg直接看预测框与真实框的贴合留意有没有大量漏检。坏例分析我习惯把预测结果导出来细看yolo predict \ model/home/user/runs/detect/train3/weights/best.pt \ source/home/user/road_defect_dataset/images/test \ conf0.25 \ save_txtTrue \ save_confTrue \ save_dir/home/user/badcase_output参数说明save_txtTrue会把预测框以 YOLO 格式写到 txtsave_confTrue在每行末尾附加置信度输出目录里每张图会有同名 txt 和画框图。我拿到这些结果后会写一段脚本把置信度低于 0.4 的预测框单独导出成小图拼版快速翻看低置信度区域是什么目标判断是标注漏标、特征本身弱还是背景干扰。真正让我改变工作习惯的一次经历是某次模型 mAP50 到了 0.85以为可以部署结果 grep 一下坏例输出才发现置信度 0.3 到 0.5 之间全是新裂缝——裂缝初期痕迹极淡人眼都费劲标注员把它漏标了。后来我在数据集迭代时强制加一道“低置信度挖掘”流程用当前模型预测训练集找出预测框与真实框 IoU 大于 0.3 但未被标注的候选框人工确认后排进待标注队列再补一轮训练。从那以后我每次拿到新数据集都强制走一遍这个流程先校验标签完整性再统计类别分布再跑分层划分最后训完必做坏例回灌不做完这些不碰部署配置。这套流程不只对道路缺陷数据集有效任何 YOLO 格式的数据集都可以照搬希望帮到你。本文还有配套的精品资源点击获取
返回列表