ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

道路缺陷检测YOLO数据集:1.4万真实图+原生格式+可视化调试

道路缺陷检测YOLO数据集:1.4万真实图+原生格式+可视化调试

简介:本资源是面向计算机视觉方向研究者与深度学习工程师的道路缺陷目标检测专用数据集,聚焦于路面裂缝、坑洼等典型病害的识别建模需求,可直接用于YOLO系列模型(v5/v8/v10等)的训练、验证与测试全流程。数据集共约1.4万张训练图像、260张验证图像及1000张测试图像,全部标注为标准YOLO格式,含4类缺陷类别(详见class.txt),并附带可视化脚本show.py,支持快速绘制边界框验证标注质量。压缩包内含2000个文件,以1999个txt标签文件(每图一标)和1个Python可视化脚本为核心,结构简洁、开箱即用;整体体积920.68MB,适配中等算力环境部署。目前已有373人学习下载,资源提供完整数据划分、规范标注、可运行工具链与清晰目录组织,显著降低道路缺陷检测任务的数据准备门槛,助力算法验证与工程落地。

1. 这不是「又一个道路数据集」:1.4 万张真实道路缺陷图 + YOLO 原生格式 + 可视化脚本,专治训练时 bbox 飘、mAP 上不去、label 跑偏这三类玄学翻车

你有没有试过:YOLOv8 训练跑通了,val mAP 却卡在 0.35 不动;或者 inference 时 bbox 明明框住了裂缝,但 confidence 总是低于阈值被 filter 掉;又或者用 labelImg 手动核对几张图,发现标注文件里 x,y,w,h 数值全是对的,可画出来就是偏移半像素——最后查到是归一化坐标用了错误的图像宽高?这个数据集不是那种「网上爬几十张图+人工扩增」的玩具集。它来自日本关东、近畿地区 2021–2023 年间市政道路巡检车实拍,包含沥青路面龟裂、块状裂缝、纵向裂缝、修补痕迹四类典型缺陷(class.names 明确列出),14,000+ 张训练图全部经过双人交叉校验+坐标重投射校准,验证集 260 张与测试集 1000 张独立采集、未参与任何增强或清洗流程。最关键的是:所有标签已按 YOLOv5/v6/v7/v8/v10 兼容格式预处理完毕(txt 文件中每行cls_id x_center y_center w h,归一化至 [0,1]),且附带show.py——这不是个 demo 脚本,而是能直接加载你的训练权重、叠加预测框与真值框对比的调试工具。如果你正卡在「数据质量」这一环,而不是模型结构或超参,这份资源就是你该立刻拉下来的后悔药。


2. 数据结构解剖:从原始 JPG 到 YOLO 标签,看清每个文件夹、每个 txt 的生成逻辑与物理意义

2.1 目录树即训练流水线:为什么 train/val/test 不能混放,以及 class.names 的隐藏约束

解压后你会看到标准 YOLO 结构:

road_defect_yolo/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── class.names ├── show.py └── README.md

注意:images/和labels/下子目录名必须严格对应(train/val/test),YOLO 系列默认通过路径匹配图像与标签——比如images/train/Japan_007600.jpg对应labels/train/Japan_007600.txt。若你手动改名或移动文件,train.txt(YOLO 训练用的图像路径列表)会失效。class.names是纯文本,每行一个类别,顺序即 cls_id(0-based):

crack patch longitudinal_fissure alligator_crack

这个顺序必须与你训练时data.yaml中的names:字段完全一致,否则cls_id=2会被模型当成patch而非longitudinal_fissure,loss 计算全错。

2.2 标签文件(.txt)的坐标真相:归一化不是“除以图像尺寸”那么简单

打开任意一个labels/train/Japan_007600_jpg.rf.e3ea45e961a5f445fd838593c1abaa18.txt,内容类似:

0 0.4213 0.6789 0.1824 0.0912 2 0.7651 0.3420 0.2105 0.1247

这里0和2是 cls_id,后四列是(x_center, y_center, width, height),全部归一化。但关键细节在于:归一化分母不是原始图像尺寸,而是标注时使用的参考尺寸。这批数据在标注阶段统一将图像 resize 到 1280×720 后标注,再反算回原始分辨率坐标,最后归一化。这意味着:

  • 若你用 OpenCV 读取Japan_007600.jpg得到(w,h) = (3840,2160),直接x_center * w会得到错误像素坐标;
  • 正确做法是:先读取图像,获取(orig_w, orig_h),再确认该图是否被 resize 过(本数据集所有图均为原始分辨率,无 resize)→ 因此x_center * orig_w才是真值;
  • show.py内部已硬编码此逻辑,所以它能正确绘制;但你自己写 DataLoader 时,必须显式传入orig_w, orig_h,不能依赖img.shape[1], img.shape[0]—— 因为 PIL 和 OpenCV 读图宽高顺序不同,且部分图有 EXIF 旋转标记。

2.3 show.py 可视化脚本:不只是画框,它是你的标注质检员和训练监控探针

show.py不是简单cv2.rectangle()。它支持三种模式:

# 模式1:仅显示真值框(验证标签是否错位) python show.py --img_dir images/train/ --label_dir labels/train/ --class_file class.names --mode gt # 模式2:加载训练权重,显示预测框+真值框对比(查漏补缺) python show.py --img_dir images/val/ --label_dir labels/val/ --class_file class.names --weights yolov8n_road.pt --mode pred # 模式3:批量生成可视化图存入 output/(用于论文/汇报) python show.py --img_dir images/test/ --label_dir labels/test/ --class_file class.names --output_dir output/ --mode batch

核心逻辑在draw_boxes()函数内:它会检查每个 bbox 的x_center ± w/2是否超出[0,1],若越界则打印警告并自动 clip——这能立刻暴露标注错误(比如某张图的x_center=1.002)。我第一次跑--mode gt就发现 37 张图存在w>0.95的异常宽框(实际是修补痕迹标注成整块区域),这些图在训练时会严重干扰 anchor 匹配。show.py把这类问题变成一眼可见的红色虚线框,比看 loss 曲线早三天发现问题。


3. YOLO 训练全流程:从 data.yaml 配置到超参微调,避开「训不动」「训不稳」「训不准」三大坑

3.1 data.yaml 必填字段详解:为什么 path 必须是绝对路径,以及 nc 的陷阱

YOLO 训练必须指定data.yaml,本数据集已提供模板,但需你手动修正:

# road_defect.yaml train: /full/path/to/road_defect_yolo/images/train # ← 必须绝对路径!相对路径在多卡DDP下会报错 val: /full/path/to/road_defect_yolo/images/val test: /full/path/to/road_defect_yolo/images/test nc: 4 # ← 必须等于 class.names 行数,且与 labels/ 中 cls_id 最大值一致(0,1,2,3 → nc=4) names: ['crack', 'patch', 'longitudinal_fissure', 'alligator_crack'] # ← 必须与 class.names 完全一致,包括空格和大小写

提示:YOLOv8 默认使用train: ../images/train这种相对路径,但在分布式训练(如torchrun --nproc_per_node=4 train.py)时,各进程工作目录不同,会导致路径解析失败。务必改成绝对路径,并用os.path.abspath()验证。

3.2 训练命令与关键超参:batch_size、imgsz、epochs 的取舍逻辑

针对 1.4 万张图,推荐配置(RTX 4090 × 1):

yolo detect train \ data=road_defect.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=32 \ workers=8 \ lr0=0.01 \ patience=10 \ name=road_defect_v8n

参数说明:

  • imgsz=640:原始图平均尺寸约 3840×2160,缩放至 640×360 保持宽高比(YOLO 自动 pad),既保证小缺陷(如细裂缝)不丢失,又控制显存;
  • batch=32:4090 显存 24GB,batch=32时 GPU 利用率 92%,若设batch=64会 OOM;若用 3090(24GB),建议batch=16;
  • lr0=0.01:YOLOv8 默认学习率 0.01,但道路缺陷背景复杂(阴影、反光、污渍),需稍激进以突破局部最优;若 loss 前10轮不降,可试lr0=0.02;
  • patience=10:val mAP 连续10轮不升则早停,避免过拟合——本数据集验证集仅 260 张,过拟合风险高。

3.3 避坑:训练时最常踩的五个坑及血泪修复方案

现象1:RuntimeError: invalid argument 0: Sizes of tensors must match

原因:labels/train/下某个.txt文件为空,或只含空行,YOLO 加载时 tensor size 为 0。
解决:运行find labels/train/ -name "*.txt" -size 0c | xargs rm -f删除空文件;再用grep -r "^[[:space:]]*$" labels/train/查空行并删除。

现象2:训练 loss 下降但 val mAP 停滞在 0.2~0.3

原因:验证集images/val/与labels/val/文件名不完全匹配(如Japan_001.jpgvsJapan_001.txt),YOLO 无法加载真值,mAP 计算失效。
解决:执行diff <(ls images/val/ | sort) <(ls labels/val/ | sed 's/.txt$/.jpg/' | sort),修复不匹配文件名。

现象3:inference 时大量 bbox 被过滤,conf输出全为 0.0

原因:show.py或自定义推理脚本中conf_thres=0.25过高,而道路缺陷小目标多,模型输出 confidence 普遍在 0.15~0.22。
解决:训练时加--conf=0.15,或在val.py中修改model.predict(..., conf=0.15)。

现象4:训练中途 CUDA out of memory

原因:workers=8导致 dataloader 预加载过多图,显存被 worker 进程占用。
解决:先设workers=2,观察 GPU memory usage;若仍 OOM,加--cache参数启用内存缓存(需 32GB RAM)。

现象5:box_loss极低(<0.05)但cls_loss>0.5,mAP 却很低

原因:class.names顺序与labels/中 cls_id 不一致,导致分类 loss 计算错位。
解决:用python -c "print([l.strip() for l in open('class.names').readlines()])"确认顺序,再head -n 5 labels/train/*.txt | grep -E '^[0-9]' | sort | uniq -c统计 cls_id 分布,确保 0/1/2/3 全出现。


4. 数据增强实战:针对道路缺陷的定制化策略,拒绝「把裂缝变模糊」的无效增强

4.1 为什么默认 Mosaic 不适合道路缺陷?裂缝的几何连续性会被破坏

YOLO 默认开启 Mosaic,但道路裂缝具有强方向性(纵向/横向)和像素级连续性。Mosaic 将 4 张图拼接,裂缝在拼接缝处必然断裂,模型学到的是「断裂伪影」而非真实裂缝特征。实测关闭 Mosaic 后,val mAP 提升 2.3%(0.521 → 0.534),且 false positive 减少 17%。

# 在 data.yaml 中禁用 Mosaic(YOLOv8.1+) augment: mosaic: 0.0 # 关键!设为 0.0 彻底关闭 mixup: 0.1 # 保留轻量 mixup,避免过拟合

4.2 必开的三项增强:CLAHE、RandomPerspective、HSV 调整

道路图像受光照影响极大(隧道口明暗交界、正午反光),需针对性增强:

增强类型参数设置作用原理为什么必要
CLAHEclip_limit=2.0, tile_grid_size=(8,8)局部直方图均衡,增强裂缝纹理对比度原始图中 63% 的裂缝在阴影区,CLAHE 能让其 pixel value 差异从 15 提升至 42
RandomPerspectivedegrees=0, translate=0.1, scale=0.1, shear=0, perspective=0.0001微小透视变形,模拟巡检车角度变化防止模型过拟合「正上视角」,提升泛化到斜拍图的能力
HSVhgain=0.015, sgain=0.7, vgain=0.4轻微调整饱和度与明度补偿不同天气(阴天/雨天)下的色彩偏差,sgain=0.7 避免过度饱和导致裂缝边缘失真

注意:vgain=0.4是经验值——过高(>0.6)会使雨天图过曝,裂缝消失;过低(<0.2)则阴天图仍发灰。我用show.py --mode gt对比增强前后图,确认裂缝像素 contrast ratio > 3.0 才算合格。

4.3 自定义增强:添加「裂缝遮挡模拟」提升鲁棒性

真实巡检中,裂缝常被水渍、落叶、车轮遮挡。我们添加RandomOcclude增强:

# augment.py import random import numpy as np from PIL import Image, ImageDraw def random_occlude(img, p=0.3): if random.random() > p: return img h, w = img.size[1], img.size[0] draw = ImageDraw.Draw(img) # 随机画 1~3 个矩形遮挡(模拟水渍) for _ in range(random.randint(1, 3)): x1 = random.randint(0, w//2) y1 = random.randint(h//3, 2*h//3) x2 = x1 + random.randint(20, 80) y2 = y1 + random.randint(10, 30) draw.rectangle([x1,y1,x2,y2], fill=(128,128,128)) # 灰色水渍 return img

在train.py的Albumentationspipeline 中插入此函数。实测加入后,在测试集「雨天子集」(327 张)上的 mAP 提升 4.1%,证明遮挡模拟有效。


5. 模型部署与工业落地:从 .pt 到 TensorRT,解决「推理慢」「显存炸」「结果抖」三大现场问题

5.1 TensorRT 加速:为什么 FP16 比 INT8 更稳,以及 dynamic shape 的致命陷阱

YOLOv8.pt模型转 TensorRT,关键在输入 shape:

# 错误:固定 shape 会导致 resize 失真 trtexec --onnx=yolov8n_road.onnx --saveEngine=yolov8n_road_fp16.trt --fp16 # 正确:启用 dynamic shape,但必须指定 min/opt/max trtexec --onnx=yolov8n_road.onnx \ --saveEngine=yolov8n_road_fp16.trt \ --fp16 \ --minShapes=input:1x3x320x320 \ --optShapes=input:1x3x640x640 \ --maxShapes=input:1x3x1280x1280 \ --workspace=4096
  • optShapes是推理最常用尺寸,设为640x640(与训练imgsz一致);
  • minShapes设320x320是为了兼容小图(如手机端截图),但本数据集最小缺陷尺寸约 40px,320x320已足够;
  • maxShapes设1280x1280是上限,防止 OOM——若设1920x1080,TensorRT 会分配 1.2GB 显存,远超必要。

注意:FP16 比 INT8 稳定。实测 INT8 量化后 mAP 下降 6.2%,因裂缝边缘梯度小,INT8 会抹平细节;FP16 仅下降 0.3%,且推理速度提升 2.1 倍(RTX 4090)。

5.2 C++ 部署避坑:cv::dnn::Net 的 blobFromImage 输入必须 BGR

Python 端cv2.dnn.blobFromImage(img, 1/255.0, (640,640), [0,0,0], True, False)输出是 RGB;但 C++ 端cv::dnn::blobFromImage()默认 BGR,若不转换:

// 错误:直接传入 cv::Mat img(RGB) cv::Mat blob = cv::dnn::blobFromImage(img, 1.0/255.0, cv::Size(640,640), cv::Scalar(0,0,0), true, false); // 正确:先转 BGR cv::cvtColor(img, img, cv::COLOR_RGB2BGR); cv::Mat blob = cv::dnn::blobFromImage(img, 1.0/255.0, cv::Size(640,640), cv::Scalar(0,0,0), true, false);

否则模型输入通道错位,输出 bbox 全乱。

5.3 工业级后处理:NMS 之外,必须加「裂缝连通域合并」逻辑

YOLO 输出的 bbox 常将一条长裂缝切分为多个短框(如crack类别 bbox 长度 < 50px)。单纯 NMS 无法合并,需后处理:

def merge_crack_boxes(boxes, scores, labels, iou_thresh=0.3, merge_thresh=0.7): # boxes: (n,4) xyxy, scores: (n,), labels: (n,) crack_idx = np.where(labels == 0)[0] # cls_id=0 is crack if len(crack_idx) < 2: return boxes, scores, labels # 计算裂缝框间的中心距离与重叠度 centers = (boxes[crack_idx, :2] + boxes[crack_idx, 2:]) / 2 widths = boxes[crack_idx, 2] - boxes[crack_idx, 0] # 合并条件:中心距 < max(width)*merge_thresh 且方向角差 < 15° merged = [] used = set() for i in crack_idx: if i in used: continue group = [i] for j in crack_idx: if j <= i or j in used: continue dist = np.linalg.norm(centers[i] - centers[j]) if dist < max(widths[i], widths[j]) * merge_thresh: # 方向角计算(简化版) angle_i = np.arctan2(boxes[i,3]-boxes[i,1], boxes[i,2]-boxes[i,0]) angle_j = np.arctan2(boxes[j,3]-boxes[j,1], boxes[j,2]-boxes[j,0]) if abs(angle_i - angle_j) < np.pi/12: # 15° group.append(j) used.add(j) # 取 group 中 score 最高者,扩展 bbox 覆盖全部 best = group[np.argmax(scores[group])] x1 = min(boxes[group,0]) y1 = min(boxes[group,1]) x2 = max(boxes[group,2]) y2 = max(boxes[group,3]) merged.append([x1,y1,x2,y2, scores[best], 0]) used.add(best) if not merged: return boxes, scores, labels merged = np.array(merged) return merged[:,:4], merged[:,4], merged[:,5].astype(int)

此逻辑将单条裂缝的多个 bbox 合并为一个,mAP@0.5 提升 1.8%,且符合工程验收标准(「一条裂缝一个框」)。


6. 验证与调优:用三组对照实验锁定瓶颈,以及我每次训练必做的「五步质检」

6.1 实验设计:用 A/B/C 三组验证,精准定位是数据、模型还是部署问题

不要一上来就调 learning rate。先做三组隔离实验:

实验组修改项目标判定标准
A组(基线)yolov8n.pt + road_defect.yaml + default hyperparameters建立 baseline mAPval mAP ≥ 0.515(官方报告值)
B组(数据验证)A组 +--data=road_defect_debug.yaml(仅 100 张 train + 20 张 val)验证数据 pipeline 是否正常train loss 快速收敛,val mAP ≥ 0.45
C组(模型验证)A组 +model=yolov8s.pt(更大模型)验证是否模型容量不足val mAP 提升 < 0.015 → 说明瓶颈在数据

我跑完 B 组发现 val mAP=0.32,立刻意识到labels/val/有 12 张图缺失——show.py --mode gt批量检查时发现这些图的 bbox 全是(0,0,0,0)。这就是为什么 B 组必须先跑:它能在 3 分钟内告诉你问题出在数据层,而不是花 2 小时调超参。

6.2 「五步质检」清单:每次训练前强制执行,省下 80% 的 debug 时间

这是我从第 7 个道路项目开始养成的习惯,现在已固化为 pre-train hook:

  1. 路径校验:python -c "import os; print(os.path.exists('/your/path/images/train'))"—— 确保data.yaml中所有路径真实存在;
  2. 文件名对齐:diff <(ls images/train \| sed 's/.jpg$//' \| sort) <(ls labels/train \| sed 's/.txt$//' \| sort)—— 零输出才安全;
  3. 标签完整性:grep -r "^[0-9]" labels/train/ \| wc -l应 ≈ls images/train \| wc -l× 平均 bbox 数(本数据集约 2.3);
  4. cls_id 范围:awk '{print $1}' labels/train/*.txt \| sort -n \| uniq -c—— 输出应只有0,1,2,3四行;
  5. show.py 快检:python show.py --img_dir images/train/ --label_dir labels/train/ --class_file class.names --mode gt --num_show 5—— 5 张图全显示正确 bbox 才继续。

从那以后我每次启动训练前,都强制走一遍这五步。哪怕只是改了一个lr0,也重新跑。去年有个项目因为跳过了第 2 步(文件名大小写不一致),debug 了 17 小时才发现JAPAN_001.jpg和japan_001.txt不匹配。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进