道路坑洞检测实战:YOLO数据集校验与训练全流程解析
发布时间:2026/9/12 22:22:52 作者:尧图编辑部 阅读量:1,286

简介这是一份面向计算机视觉开发者的道路坑洞检测目标检测数据集基于YOLO系列算法整理适用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10及YOLO11等主流版本。道路坑洞检测是基础设施维护与自动驾驶场景中的常见任务该数据集已按训练与验证需求划分好并附带data.yaml配置文件解压后即可直接开展模型训练与测试。压缩包共两千个文件包含五百八十个TXT格式的YOLO标注文件与一千四百二十个XML格式的VOC标注文件两种格式分别存放。其中TXT文件每行记录类别、归一化中心点坐标与宽高比例XML文件则保存目标框绝对坐标与类别信息方便在不同框架间切换使用。标签数据可帮助用户省去手动标注与格式转换的时间整体大小约一百六十六点五二MB已有148人学习适合需要快速搭建道路病害识别、路面质量检测实验的算法工程师与科研人员。1. 1990张图像带标签这套坑洞检测数据集能直接开跑吗拿到“yolo算法-道路坑洞检测数据集-1990张图像带标签-坑洼.zip”第一反应通常是解压、扔进训练脚本、看 mAP。但做道路场景检测的人会多留一个心眼1990 张图不算多却也不至于不够用真正的问题在于这 1990 张图里包含多少种路面、多少种光照、多少种坑洞形态以及标签是“能用”还是“刚好能训”。很多 YOLO 项目卡住的不是模型而是数据集的校验、格式转换和划分方式。这篇文章不讨论“去下载更多数据”而是把这一份 zip 从交付到上线的完整路径拆开先验证压缩包和数据本身再统一标注格式接着按坑洞场景的特点做数据划分然后用 YOLOv8 训练并盯住损失和评估指标最后一轮用模型预测反向审计标注质量。适合正在做道路巡检、市政养护、施工验收或者车路协同感知的工程师也适合刚拿到别人打包的数据集、准备训练第一个检测模型的同学。2. 打开 zip 之前的检查先验证数据集完整性再谈训练2.1 不要急着解压先跑一遍完整性校验这种数据集大概率经过网盘、邮件或者移动硬盘转手压缩包在传输过程中可能出现尾部截断或文件损坏。直接解压后训练到一半发现缺图、标签读不出来返工成本远高于先花一分钟做校验。Linux 下最直接的做法是让 unzip 自己检查 CRCunzip -t yolo算法-道路坑洞检测数据集-1990张图像带标签-坑洼.zip | tail -20-t参数让 unzip 进入测试模式逐个文件计算校验值并与压缩包内记录比对。tail -20只看最后的结果汇总正常输出会有No errors detected in compressed data of ...。如果中间出现bad CRC或mismatch相关字样说明该文件在打包或者传输时已经损坏。Windows 环境下没有 unzip 命令时用 Python 的 zipfile 模块也能达到同样目的import zipfile zpath yolo算法-道路坑洞检测数据集-1990张图像带标签-坑洼.zip bad_files [] with zipfile.ZipFile(zpath, r) as zf: for info in zf.infolist(): try: with zf.open(info.filename) as f: f.read() except zipfile.BadZipFile: bad_files.append(info.filename) print(f文件总数: {len(zf.infolist())}) print(f损坏文件: {bad_files if bad_files else 无})这段脚本先遍历压缩包内所有文件逐个完整读入并校验 CRC。BadZipFile异常会捕获具体是哪个文件损坏。输出里同时给出文件总数方便和标题声称的 1990 张图对照。如果文件总数和预期差异很大说明数据可能被重新筛选过训练前需要重新评估类别分布。提示zip 内文件名如果含中文Python 在某些老版本上会遇到编码问题。遇到乱码时先尝试设置encodinggbk再重新打开。2.2 目录结构和标注格式识别先看清楚再写转换脚本解压之后的下一步是确认标注格式。市面上的 YOLO 数据包常见的交付形态有三种识别特征非常明显标注格式文件后缀关键特征对应目录常见形态YOLO txt.txt每行 5 个数class x_center y_center width height全部归一化到 0-1images/ 与 labels/ 平级VOC XML.xml含objectname,bndbox节点JPEGImages/ 与 Annotations/COCO JSON.json顶层含images、annotations、categories三个数组单 JSON 图目录先用最轻量的命令看一眼unzip -l yolo算法-道路坑洞检测数据集-1990张图像带标签-坑洼.zip | head -30unzip -l只列出压缩包内容清单不解压。从头部路径能看到 images 和 labels 的目录层级。再抽查一张标签文件unzip -p yolo算法-道路坑洞检测数据集-1990张图像带标签-坑洼.zip */labels/*.txt | head -3-p直接把文件内容输出到标准输出不落盘。一行里如果是 5 个数且都在 0 和 1 之间基本可以确定是 YOLO 格式。如果看到annotation开头则是 VOC 系。这里有个坑洞场景特有的问题标注方可能把同一类破损路面拆成多个名字比如“坑洞”“坑槽”“沉陷”“网状裂缝”甚至同一张图里两种叫法混用。YOLO 训练时类别名和类别 id 的映射一旦错位损失函数会混乱。先统计压缩包内所有 txt 文件的首列取值分布排查类别 id 是否连续且与数据说明一致unzip -p yolo算法-道路坑洞检测数据集-1990张图像带标签-坑洼.zip *.txt | awk {print $1} | sort | uniq -cawk 取出首列sort 和 uniq -c 输出每个类别 id 出现的次数。如果出现 0 和 1 之外的值按 YOLOv8 的默认类别约定就需要做重映射。也顺便看一眼最大类别 id超过 2 说明这个数据集里可能混了其他路面病害类型后续训练计划要相应调整。2.3 标注质量快检坑洞的框最容易标在阴影里确认格式之后不能直接进入转换。标签质量和图像内容不匹配的问题在道路坑洞数据里尤其突出坑洞边界模糊、周围常有阴影和积水反光标注员有时候会把矩形框的一部分切进阴影区域或者把同一个坑洞拆成两个半框。只看 txt 的数字检查不出这种问题需要把标注画回到图像上。写一个快速可视化脚本逐张检查前几十张图import cv2 import os label_dir labels image_dir images sample_files os.listdir(label_dir)[:50] for label_name in sample_files: img_path os.path.join(image_dir, label_name.replace(.txt, .jpg)) img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, label_name), r) as f: for line in f: cls, xc, yc, bw, bh map(float, line.strip().split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, str(int(cls)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imshow(check, cv2.resize(img, (1024, 576))) cv2.waitKey(0)这个脚本把归一化坐标乘以图像宽高还原成像素坐标再画红色框。注意waitKey(0)会逐张暂停按任意键换下一张。检查重点有两个框是否完全贴着坑洞边缘、有没有出现框内大部分是路面的情况。数据标注的偏差会直接影响后续训练的收敛方向YOLO 的损失函数会把边界框回归到标注框的位置如果标注本身就偏移 10 个像素模型学出来的框也会跟着偏 10 个像素。提示如果标注文件里出现w或h大于 1 的值说明坐标没有归一化或者单位不是“相对于图宽高的比例”。这类标签要回退给标注方确认不能靠转换脚本硬改因为单纯把大于 1 的值除以宽高并不总是能还原标注意图。3. 标注转 YOLO 格式与数据划分这一步决定后面能不能省心3.1 VOC 或 COCO 转 YOLO txt坐标换算是第一道坎如果压缩包里是 YOLO txt这一节可以跳过。但很多“道路坑洞检测数据集”实际交付的是 VOC 或 COCO 标注标题写“带标签”并没承诺格式。转换的核心公式是把 VOC 的xmin/ymin/xmax/ymax像素坐标换成 YOLO 需要的归一化中心点加宽高def voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h return x_center, y_center, box_w, box_h分子上的中心点计算之所以用(xmin xmax) / 2而不是(xmax - xmin) / 2是因为前者的坐标中心考虑了起始偏移后者只算了框宽。这一步算错会导致所有框整体偏移半个身位。转换时如果原标注是四点多边形而不是矩形框坑洞场景常见的做法是取最小外接轴对齐矩形因为 YOLO 系列的检测头输出就是轴对齐框多边形训练需要走 mask 分支或者用旋转框检测框架超出了普通检测任务范围。类别映射也要一并处理category_map {pothole: 0, corrugation: 1, rut: 2}把原数据的类别名转换成目标数字 id。类别字典要单独保存为 yaml 文件训练和验证共用一份避免训练脚本和验证脚本各写一套导致 id 错位。3.2 划分数据集不能随机抽按采集来源分组随机划分在坑洞检测场景是典型的错误做法。道路数据往往来自同一路段连续拍摄的视频帧同一个坑洞可能出现在连续十几张图里只是视角和距离略有变化。随机划分会把同一坑洞的相邻帧同时分进 train 和 val验证集的 mAP 虚高真实场景里的检测能力被高估。常用做法是按文件名的采集批次前缀分组。如果文件名形如jpgrp104_05460.jpg前缀jpgrp104代表一个采集批次按前缀粒度划分from sklearn.model_selection import GroupShuffleSplit from pathlib import Path import yaml labels sorted(Path(labels).glob(*.txt)) groups [p.name.split(_)[0] for p in labels] gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(labels, groupslabels, groupsgroups))GroupShuffleSplit的核心思想是整个批次进一边不拆散。没有 sklearn 环境时手动按前缀去重再抽样划分的效果也一样。划分完要落到文件清单或者直接移动文件mkdir -p dataset/images/train dataset/images/val dataset/labels/train dataset/labels/val划分时注意后缀统一有些数据集里 jpg 和 JPG 混用、txt 和 txt 混用先在脚本里全部转成小写后缀再处理。3.3 统计类别分布和目标尺寸1990 张图里小目标可能占一半坑洞数据有一个显著特点小目标占比高。车载相机视角下远处刚出现的坑洞可能只有整张图的 1% 面积靠近后才会变大。YOLO 训练这类数据之前统计目标尺寸分布能提前预判要不要调整imgsz或者切图策略。from PIL import Image import os img_dir images label_dir labels area_ratios [] cls_counts {} for label_file in os.listdir(label_dir): img Image.open(os.path.join(img_dir, label_file.replace(.txt, .jpg))) img_w, img_h img.size total_pixels img_w * img_h with open(os.path.join(label_dir, label_file), r) as f: for line in f: cls, _, _, bw, bh map(float, line.strip().split()) cls_counts[int(cls)] cls_counts.get(int(cls), 0) 1 area_ratios.append(bw * bh) small_obj sum(1 for r in area_ratios if r 0.01) print(f类别分布: {cls_counts}) print(f面积占比 1% 的目标数: {small_obj}/{len(area_ratios)})bw * bh是归一化面积乘上total_pixels就能得到像素面积。面积占比小于 1% 的目标在 640x640 输入下大约只占 40x40 像素属于小目标范畴需要关注后续训练时的分辨率选择。4. 用 YOLOv8 跑通训练损失和评估指标要盯这么几个4.1 最小训练命令和参数设定数据集目录准备成 YOLOv8 期望的结构后先写 data.yamlpath: /path/to/dataset train: images/train val: images/val names: 0: pothole 1: corrugation 2: rutpath是数据集根目录的绝对路径train 和 val 指向 images 子目录。跑训练前先把检查点模型下载好用最小的 nano 模型验证整体流程yolo detect train \ datadata.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16 \ device0 \ projectruns/pothole \ nameexp1参数建议值说明modelyolov8n.pt先小模型跑通确认数据和流程没问题再换 s 或 mimgsz640取决于小目标占比小目标多时可上调到 1280但显存占用会翻数倍epochs1001990 张图建议至少 100观察损失是否还在下降再决定是否续训batch按显存调单卡 16 是起步值显存不够就降到 8 或 4patience30早停参数连续 30 轮 val loss 不降就自动停止为什么建议先 nano因为数据集只有 1990 张很多漏检和误检问题在 nano 模型上就会暴露排查清楚再换大模型效率远超直接用 m 训练然后面对一堆不确定是不是数据问题的结果。4.2 坑洞场景下损失函数趋势怎么判断YOLOv8 的损失由三部分组成box_loss回归框位置、cls_loss分类置信度、dfl_loss分布焦点损失。训练日志长这样Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 30/100 4.12G 1.102 1.245 1.003 46 640坑洞数据的规律cls_loss会很快降到 1.2 附近并进入平台期因为坑洞和背景的纹理差异容易被卷积网络学出来但box_loss会持续缓慢下降框的位置精度对数据集标注质量和阳光阴影非常敏感。如果box_loss在 60 轮之后还在缓慢走低不必急着早停通常说明模型还在精修边界。如果cls_loss始终降不到 1.0 以下大概率是类别混淆——坑洞和排水井盖、阴影的形状太像需要从标注层面再检查是否有误标。训练过程中把mosaic1.0默认开着的增强用在坑洞数据上好处是让小目标在拼接图中获得更多采样机会代价是坑洞本身边界模糊拼接切边可能把坑洞切成两半。训练后期把 mosaic 关掉做最后若干轮微调常见做法是yolo detect train \ datadata.yaml \ modelruns/pothole/exp1/weights/last.pt \ imgsz640 \ epochs20 \ batch16 \ device0 \ mosaic0.0mosaic0.0相当于关闭马赛克增强让模型在完整的、未拼接的图像上做最后的边界回归调整对坑洞这类边界不锐利的目标有明显帮助。4.3 评估指标拆开看mAP50 会骗人训练完成后用验证集评估yolo detect val \ datadata.yaml \ modelruns/pothole/exp1/weights/best.pt输出里同时给出 mAP50 和 mAP50-95。坑洞检测只看 mAP50 会高估模型实际能力mAP50 只要求在 IoU 0.5 以上判断为正确框偏移十几个像素仍然算命中而道路巡检里框能不能贴住坑洞边缘是后续测量和修补的基础。mAP50-95 从 0.5 到 0.95 做了多档 IoU 平均数值通常比 mAP50 低 20 到 30 个百分点差距过大说明框的定位精度不够优先检查标注质量而不是调阈值。误检方面当前场景最常见的两类是深色水渍被框成坑洞、以及接缝阴影误报。排查方法是在验证集上把conf_thres调低到 0.05输出全部候选框再按类别统计得分分布yolo detect val \ datadata.yaml \ modelruns/pothole/exp1/weights/best.pt \ conf_thres0.05低置信度检出的目标如果在真实道路上根本不存在说明模型学到的是颜色特征而非结构特征需要补充带阴影和积水反光的负样本强化数据集。5. 用模型预测反向审计标注把漏标挖出来再补一轮1990 张图训练出第一版模型之后不要急着部署。最值得做的一件事是拿这个模型去预测训练集本身找出“模型认为有坑洞、但标注文件里没有框”的区域。这类区域大概率是漏标的坑洞——人工标注时视线疲劳漏掉的小目标在模型眼里反而很明显。from ultralytics import YOLO import os import numpy as np model YOLO(runs/pothole/exp1/weights/best.pt) label_dir labels image_dir images for img_name in sorted(os.listdir(image_dir))[:200]: img_path os.path.join(image_dir, img_name) label_path os.path.join(label_dir, img_name.replace(.jpg, .txt)) gt_boxes [] if os.path.exists(label_path): with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: _, xc, yc, bw, bh map(float, parts[:5]) gt_boxes.append([xc, yc, bw, bh]) results model.predict(img_path, conf0.25, imgsz640, verboseFalse) for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() xc (x1 x2) / 2 / results[0].orig_shape[1] yc (y1 y2) / 2 / results[0].orig_shape[0] in_gt False for gx, gy, gw, gh in gt_boxes: if abs(xc - gx) max(gw, (x2 - x1) / results[0].orig_shape[1]): in_gt True break if not in_gt: print(f疑似漏标: {img_name} 置信度 {box.conf[0]:.2f})脚本思路是用训练好的模型对图像做推理然后把预测框中心点与所有真值框做比对中心点没有落在任何真值框范围内的预测视为疑似漏标。conf0.25是经验值漏标候选太多就调高几乎没结果就调低。筛出的文件名清单可以回交给标注人员复核这部分工作量远小于重新标注全量数据。第二轮训练时把之前高置信度疑似漏标的图像拿回来修正标注再把 mosaic 关掉做最后 20 轮微调。这个流程跑完模型对小坑槽的框通常会更贴边验证集上的 mAP50-95 会有新一轮提升。本文还有配套的精品资源点击获取