简介:面向目标检测应用的大型道路缺陷数据集,包含超过一万四千张训练图像、二百六十张验证图像和一千张测试图像,已统一处理为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_path=None): img = cv2.imread(img_path) if img is None: print(f"无法读取图像: {img_path}") return h, w = img.shape[:2] with open(label_path, "r", encoding="utf-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", encoding="utf-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(parents=True, exist_ok=True) (dst_root / "labels" / split).mkdir(parents=True, exist_ok=True) 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(f"train={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 训练时调cls=0.7或cls=1.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 训练时imgsz=640会把图像等比缩放到 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 \ model=yolov8s.pt \ epochs=120 \ imgsz=1280 \ batch=8 \ device=0参数说明:model=yolov8s.pt表示加载 COCO 预训练权重做迁移学习,s 版本在速度和精度之间平衡,适合第一轮摸情况;imgsz=1280是道路缺陷小目标的正确选择,代价是显存,8 的 batch 在 24G 显存上跑 1280 分辨率差不多是上限,如果只有 12G 显存就降到 6 或把分辨率降到 960;epochs=120是参考值,实际看patience参数,默认 100 个 epoch 没提升就早停。
跑起来之后重点看两个输出:第一个是每个 epoch 结束后的mAP50-95,第二个是 loss 曲线的下降形态。第一轮不要调参,目标是拿到一个可用的 baseline,为后续对比提供参照。这个数据集是 YOLO 格式,YOLOv5、YOLOv8、YOLO11 直接换model=参数就能跑,不需要改数据。
5.2 道路缺陷场景的超参倾向:imgsz、mosaic 与 cls 权重
道路缺陷这类目标有两个极端的尺度分布:裂缝细长,坑槽大而近椭,所以超参设置不能照搬通用目标检测的方案。下面这组参数是我在类似巡检数据集上验证过的倾向值,不是绝对标准,但作为起点比默认参数靠谱:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| imgsz | 1280 | 小裂缝保留像素,别用 640 |
| close_mosaic | 10 | 最后 10 轮关闭马赛克增强 |
| cls | 1.2 | 类别不平衡时提高分类损失权重 |
| fliplr | 0.5 | 道路缺陷左右镜像不影响语义 |
| scale | 0.5 | 限制缩放幅度,防止大坑槽被缩得太小 |
| mixup | 0.2 | 轻微混合增强,加多了会干扰细长边缘 |
close_mosaic=10这个参数值得单独解释。Mosaic 增强在训练前期能把四张图拼在一起,让模型见过更多上下文和尺度变化,但在训练后期,它会让目标边缘被拼接缝截断,对细长裂缝是灾难。YOLOv8 里有close_mosaic参数,意思是最后 N 个 epoch 自动关闭 mosaic,保证模型在真实完整图像上收敛。
cls=1.2的作用是直接放大分类损失的权重。在类别不平衡时,模型容易把少数类率降低,把cls从默认 0.5 调到 1.2 以上,本质是让梯度更新更偏向分类正确。如果你统计脚本发现最小类别占比不到 5%,这个参数就值得调。迁移学习时,COCO 预训练权重里有 80 个类别,和这 4 类道路缺陷完全不同,但前几层的特征提取器仍然有效,所以第一轮用预训练权重而不是随机初始化,收敛速度快非常多。
5.3 断点续训与模型选择
训练中断是常态,可能是断电,可能是显存溢出,也可能是你手动 Ctrl+C。YOLOv8 的训练会实时保存last.pt和best.pt,恢复不需要从零开始:
# 方式一:从上次 checkpoint 继续 yolo detect train resume=True # 方式二:从指定权重文件继续,适合手动改参数后再续 yolo detect train \ data=/home/user/road_defect_dataset/data.yaml \ model=/home/user/runs/detect/train3/weights/last.pt \ epochs=140 \ imgsz=1280参数说明:resume=True会自动找到最近一次训练的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 \ split=test运行完看四个关键输出,按顺序排查:
第一张是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 \ conf=0.25 \ save_txt=True \ save_conf=True \ save_dir=/home/user/badcase_output参数说明:save_txt=True会把预测框以 YOLO 格式写到 txt,save_conf=True在每行末尾附加置信度,输出目录里每张图会有同名 txt 和画框图。我拿到这些结果后,会写一段脚本把置信度低于 0.4 的预测框单独导出成小图拼版,快速翻看低置信度区域是什么目标,判断是标注漏标、特征本身弱,还是背景干扰。
真正让我改变工作习惯的一次经历是:某次模型 mAP50 到了 0.85,以为可以部署,结果 grep 一下坏例输出才发现,置信度 0.3 到 0.5 之间全是新裂缝——裂缝初期痕迹极淡,人眼都费劲,标注员把它漏标了。后来我在数据集迭代时强制加一道“低置信度挖掘”流程:用当前模型预测训练集,找出预测框与真实框 IoU 大于 0.3 但未被标注的候选框,人工确认后排进待标注队列,再补一轮训练。从那以后我每次拿到新数据集都强制走一遍这个流程:先校验标签完整性,再统计类别分布,再跑分层划分,最后训完必做坏例回灌,不做完这些不碰部署配置。这套流程不只对道路缺陷数据集有效,任何 YOLO 格式的数据集都可以照搬,希望帮到你。
本文还有配套的精品资源,点击获取