ARTICLE DETAIL

资讯详情

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

道路坑洞目标检测数据集:VOC与YOLO格式转换及训练避坑指南

道路坑洞目标检测数据集:VOC与YOLO格式转换及训练避坑指南 简介这份道路坑洞目标检测数据集聚焦路面病害识别场景共包含665幅真实道路jpg图像类别统一为pothole并提供Pascal VOC与YOLO双格式标注文件适合刚接触目标检测的学习者快速体验从数据组织到模型训练的全流程也能用于道路养护、自动驾驶辅助感知等方向的前期算法验证。压缩包内含2000个文件以txt标签、xml标注和jpg图像为主体图像与标注一一对应所有框均由labelImg按矩形框规则完成pothole类别合计1740个标注框可为训练提供可靠的监督信号。整包仅23.87MB下载与迁移都很轻便尤其适合在本地或云环境快速跑通检测基线。目前已有408人学习下载数据格式规范、目录结构简洁既便于直接输入YOLO系列训练也可随时转换成其他检测框架所需格式省去大量标注与格式转换时间是路面坑洞检测项目起步阶段的实用数据集。1. 665张单类别道路坑洞数据集为什么它够用又为什么有人翻车道路坑洞检测这几年被反复拿出来做场景倒是很明确市政养护、路面巡检、自动驾驶的路面预瞄甚至外卖配送三轮车的避震系统都想前置一个“前面有坑”的提醒。做这类任务最卡人的不是模型选型而是数据本身。市面上公开的路面破损数据集要么是实验室内的小样张要么是无人机俯拍跟装在车上平视路面的真实分布差得太远。而标题里这个“道路坑洞目标检测数据集VOCYOLO格式665张1类别.zip”本质上是把采集好的路面图片整理成了两种主流标注格式单类、数量不大但胜在干净、好上手、能用标准工具链直接吃进去。665张单类别到底够不够用这要看你对“够用”的定义。如果目标是拿去做施工验收那种拍脑袋判断随便一个分类器都能过。但如果是要在真实路面视频里把坑的位置框出来665张作为冷启动基线完全够作为最终模型则多半要配合数据增强和自采数据迭代。很多人在这个数据上翻车不是因为图片少而是因为根本没搞清楚VOC和YOLO两种格式的对应关系——训练时要么读到一堆空白标签要么类别对不上。这篇就把格式拆开、把训练跑通、把常见坑填平。2. 拆开 .zip 看两种标注格式VOC 的 XML 和 YOLO 的 TXT 如何一一对应拿到zip的第一反应不要是直接解压扔给训练脚本。先看一眼目录结构能省掉后面至少两小时的排错时间。这类数据集的常规组织方式是 images / labels / annotations 三件套或者简单粗暴地一个images一个labelsVOC的XML和YOLO的TXT同时给你等于把“换工具链”的成本提前付掉了。2.1 文件结构与命名规则常见文件结构长这样pothole_dataset/ ├── images/ # 全部原始图片 │ ├── 001_pothole_01.jpg │ ├── 001_pothole_02.jpg │ └── ... ├── annotations/ # VOC格式的XML标注 │ ├── 001_pothole_01.xml │ ├── 001_pothole_02.xml │ └── ... └── labels/ # YOLO格式的TXT标注 ├── 001_pothole_01.txt ├── 001_pothole_02.txt └── ...命名规则是整个数据集最容易出猫腻的地方。理想情况下每张图对应一个同名的 .xml 和一个同名的 .txt图片是 .jpg 那标注后缀也要跟着同名。有些打包方会用不一样的前缀比如图片叫 road_001.jpgXML却叫 001.xml这种错位在训练时不会立刻报错而是表现为“图像找不到标签”或者“这个batch的loss异常低”。拿到手先做一步数一下三种文件的数量是否一致检查有没有孤儿文件。# 在数据集根目录下执行 ls images/*.jpg | wc -l ls annotations/*.xml | wc -l ls labels/*.txt | wc -l如果三个数字不一致别急着训练先找出多出来的那几张图多半是标注时漏标了。漏标的处理方式不是删图而是给一个空标签文件——稍后会在避坑部分展开。2.2 同一张坑洞图的两种标注长什么样打开同一张图的XML和TXT你会看到两种完全不同的信息组织方式。VOC格式把一张图里所有的目标对象嵌套在annotation根节点下每个object里带类别名和矩形框的四个像素坐标。YOLO格式则是一行一个框五个数字分别是类别ID、归一化的中心点x、中心点y、归一化宽度、归一化高度。拿一张 1920x1080 的图坑洞左上角在 (510, 380)右下角在 (690, 540) 举例VOC的XML片段annotation folderimages/folder filename001_pothole_01.jpg/filename size width1920/width height1080/height depth3/depth /size object namepothole/name bndbox xmin510/xmin ymin380/ymin xmax690/xmax ymax540/ymax /bndbox /object /annotation同一目标在YOLO格式的TXT里就是一行0 0.3125 0.4259 0.09375 0.1481类别ID这里写成0对应的是类别列表里的第一个类也就是 pothole。四个小数分别是归一化后的中心坐标和宽高。之所以要归一化是因为YOLO训练时会把图缩放到固定尺寸比如640x640如果标注存的是绝对像素缩放后坐标就全错了。2.3 从VOC XML到YOLO TXT的换算公式与边界坑如果数据集只给了VOC格式你需要自己做转换。公式不复杂核心是四行# 假设 xmin, ymin, xmax, ymax 是整数像素坐标W, H 是图像宽高 x_center (xmin xmax) / 2.0 / W y_center (ymin ymax) / 2.0 / H box_w (xmax - xmin) / W box_h (ymax - ymin) / H换算本身不坑坑在两个地方。第一个是 xmax 用不用加1。VOC标注里矩形框是像素的包裹范围像素坐标从左上角开始计所以严格的框宽度应该是xmax - xmin 1。这个 1 在1920宽的大图上影响微乎其微但对那种只有40像素宽的小坑洞差值能达到2.5%。不少人转完格式后发现mAP总差一点查半天就是这里。第二个坑是归一化后的数值精度。TXT里的浮点数建议保留6位小数直接拿python的字符串格式化%.6f就行。别用print(x_center)默认输出默认精度在某些情况下会截断到小数点后4位对小目标来说等于给标签加了噪声。还要确认类别ID从0开始还是从1开始。YOLO官方的要求是0-based也就是唯一的类别写0。一些野路子转换脚本习惯从1开始对应到训练配置里names: {0: pothole}就完全对不上表现为训练loss正常但预测结果全是空框。3. 把665张数据喂给YOLO训练数据划分与YAML配置的实操套路格式理顺了下一步就是把数据切成训练集和验证集写好YAML启动训练。这一步的细节决定你整个训练过程是丝滑还是反复重启。3.1 数据划分脚本8:1:1还是9:0.5:0.5665张图不多划分比例上建议训练集:验证集取9:1测试集可以不要。理由很实际单类检测模型的验证集主要用于看loss曲线和精度的动态变化不需要像多类别那样留额外的测试集做类别均衡评估。如果你打算做超参对比验证集还能临时充当测试集。真到了要出报告的时候再单独留一份现场采集的图做盲测更靠谱。import os import random import shutil random.seed(42) image_dir pothole_dataset/images label_dir pothole_dataset/labels # 建输出目录 for sub in [train/images, train/labels, val/images, val/labels]: os.makedirs(os.path.join(pothole_split, sub), exist_okTrue) images sorted(os.listdir(image_dir)) random.shuffle(images) val_count int(len(images) * 0.1) val_images images[:val_count] for img in images: stem os.path.splitext(img)[0] src_img os.path.join(image_dir, img) src_txt os.path.join(label_dir, stem .txt) if img in val_images: dst_img fpothole_split/val/images/{img} dst_txt fpothole_split/val/labels/{stem}.txt else: dst_img fpothole_split/train/images/{img} dst_txt fpothole_split/train/labels/{stem}.txt shutil.copy(src_img, dst_img) shutil.copy(src_txt, dst_txt) print(ftrain: {len(images) - val_count}, val: {val_count})这段脚本做了三件事固定随机种子、按9:1切分、把图片和同名标签一起拷贝到新目录。固定种子这步很多人嫌麻烦但它在复现实验时极其重要——同样的参数跑两次如果切分集合不同结果差0.05个mAP都是正常的到时候你根本分不清是调参效果还是数据分布变化。3.2 YOLOv8的YAML配置和路径坑切分完成后写一个 YAML 配置文件指向这些目录。用 YOLOv8 作演示YOLOv5 的字段也兼容只是路径写法略有差别。path: /home/yourname/pothole_split # 改成你的绝对路径 train: train/images val: val/images names: 0: pothole三个字段别写错。path是数据集根目录train和val是相对path的图片目录YOLO会自动去找同级的 labels 目录——也就是你把 images 和 labels 并存于同一父目录下它不需要你显式声明labels路径。这里最常翻车的点是path写成相对路径。YOLOv8训练时如果工作目录不在数据集旁边相对路径会解析失败报AssertionError: train: ... does not exist看着像数据丢了其实是路径解析问题。直接用绝对路径最稳。关于类别ID如果你的TXT里写的是0那names的第一项必须是pothole。如果TXT里是1names里就得放一个占位的background或空字符串在0的位置上。这点在避坑章会再展开。3.3 训练启动命令与640分辨率下的锚框问题启动训练就一行yolo detect train datapothole.yaml modelyolov8s.pt epochs150 imgsz640 batch16 device0几个参数说明一下。modelyolov8s.pt是加载COCO预训练权重复训不是从零开始。对665张的小数据来说从零初始化的收敛速度和精度都会差很多。imgsz640是YOLO系列一贯输入尺寸不要轻易改到320或1280。320对小坑洞太不友好1280则直接把显存需求翻四倍训练时间也跟着翻倍。batch16是综合考虑显存后的默认值如果你的卡是8G显存降到8。锚框在这里不需要手动设置。YOLOv5时代还要跑kmeans_anchor算自定义锚框YOLOv8已经是anchor-free的检测头设计锚框玄学基本退场了。不过坑洞这种目标有个特点——它宽高比的分布不像行人和车辆那么固定路面视角下有的坑是细长裂缝有的是近圆形所以如果你的模型对长条坑漏检严重先别怀疑锚框先查输入尺寸和目标像素大小。4. 从0.6到0.9调参、迁移学习和数据增强模型跑起来不代表模型好用。单类、665张前期的主要矛盾是过拟合训练集loss掉得很快验证集mAP在0.6附近打转。这章讲怎么用现有手段把精度往上推。4.1 预训练权重怎么选、要不要冻结主干谈YOLO训练必谈预训练。905万张带标签的COCO预训练权重对坑洞这种专业场景真正的价值在于它已经把“边缘、纹理、深浅色块”这些底层特征学好了。你在这665张坑洞图上要做的是把输出头重新校准到“路面上的黑色/深色凹坑”这个语义上。实操中我倾向于这样前20个epoch冻结主干只训练检测头然后再解冻全模型微调。原因很直接——道路坑洞图像的背景柏油路、水泥路、裂缝纹理和COCO里的日常场景差异极大如果一开始就全量训练yolov8s这种小模型很容易被背景里的纹理细节带偏出现训练集loss很低、验证集不动的症状。冻结主干的做法相当于让检测头先学会在COCO特征上找坑洞再松开主干去适应路面纹理。# 以ultralytics为例在训练脚本里做两段式 from ultralytics import YOLO model YOLO(yolov8s.pt) # 第一阶段冻结backbone只训head model.train(datapothole.yaml, epochs20, imgsz640, batch16, freeze10, # yolov8s主干10层 namepothole_stage1) # 第二阶段解冻全模型小学习率微调 model.train(datapothole.yaml, epochs80, imgsz640, batch16, lr00.0005, namepothole_stage2)注意第二阶段里我把lr0调到了0.0005比默认的0.01低了一个量级。这是血泪经验前期已经收敛得差不多突然解冻主干并恢复默认学习率很容易把主干里学好的底层特征冲掉验证集mAP不升反降。4.2 数据增强策略对坑洞场景的作用YOLOv8默认开启mosaic增强对665张的小数据集来说mosaic几乎是必需品。它把四张图拼成一张等于变相扩大了训练样本的多样性。但坑洞场景有个特殊点坑洞一般分布在地面中下部mosaic随机拼接后坑洞可能出现的位置被强行搬到画面上方或边缘和真实路况分布不一致。这个偏差在训练早期帮助很大在后期反而会拖后腿。所以一个常见的做法是训练后半段关闭mosaic。yolo detect train datapothole.yaml modelruns/detect/pothole_stage2/weights/last.pt \ epochs30 imgsz640 batch16 lr00.0003 mosaic0.0 namepothole_finetunemosaic0.0就是关掉。另外两个值得调的参数是hsv_h和degrees。路面图像颜色单调HSV色相抖动给不了太多增益反而可能把柏油路的颜色弄成奇怪的紫色干扰模型对“正常/坑洞”的颜色判断。旋转角度也别开太大路面上的坑洞在鸟瞰或平视视角下基本都是水平或近水平的你把它旋转±30度训练等于在教模型认识一些现实中不存在的姿态。4.3 训练日志怎么读哪些loss崩溃是翻车信号训练日志里一堆指标新手很容易只看最后的mAP。实际上中间那些loss变化才暴露问题。看三组关键数据box_loss、cls_loss、dfl_loss以及验证集的对应项。正常情况是三者单调下降到中后期进入平台期。如果出现下面两种异常基本可以断定有问题第一种cls_loss训练中后期突然反弹然后振荡。原因多半是数据里混入了错误的类别标注——比如某几张图的TXT里类别ID写了1而不是0。解决方法是重新跑一遍标签检查脚本。第二种box_loss降不下去一直卡在1.4以上。这时候去翻训练前几个epoch的图片样本看你标注的框是不是有大面积错位。坑洞的边缘在画面上往往不明显光照不良时人眼都会标偏几个像素这个偏差会导致box_loss下不去。还有个常见误区训练时盯着loss曲线看看到它降到接近0就认为模型好。其实针对小数据集训练loss接近0往往是过拟合的第一信号判断标准是val的box_loss是否同步下降。val loss不掉反升就是过拟合马上停掉回到4.1的冻结策略或加数据增强。5. 避坑道路坑洞数据集最常见的5个坑单类数据集看着简单实际跑起来能翻车的点一个不少。下面这五条是按出现频率排的每条都是现象和原因讲清楚给出直接能落地的解决办法。5.1 空标签文件导致的loss异常现象训练loss在某几个epoch突然跳变验证集mAP在0.3左右飘忽不定且日志里没有显式报错。原因数据集中有若干张图对应的TXT文件是空的。这类文件产生的原因是标注时偶然打开又没有保存或格式转换脚本遗漏。空标签在YOLO训练里不会报错它把这个batch的loss贡献变成0让模型的梯度方向被其他样本左右训练噪声被放大。解决训练前扫一遍所有TXT的行数。find pothole_split/train/labels -name *.txt -empty | wc -l统计出空文件数量。空文件不能直接删因为对应的图片还在训练集里模型会拿一张无目标图硬学。正确处理是保留图片但把空TXT补上“不存在目标”的语义——对单类数据集最简单的办法是把这张图分到验证集而不是删除因为YOLO的验证集允许图片没有标签。5.2 类别编号不一致模型永远输出空框现象训练过程正常loss下降也顺利但用训练好的模型做预测置信度不高、框的位置全偏甚至一个框都出不来。原因数据集里的TXT是别人用脚本转的类别ID从1开始而你的YAML里names只有下标0。比如TXT里写1 0.35 0.42 0.1 0.1模型会认为这是类别1但配置里没有类别1于是推理时对这个目标直接按背景处理。还有一种情况是多个类别ID混用这通常是原始标注文件拼接造成的。解决写一段检查脚本统计所有TXT里第一列数字的集合。cat pothole_split/train/labels/*.txt | awk {print $1} | sort | uniq -c如果输出的ID包含除了0之外的值要么改TXT要么在names里补上相应的类名。注意改TXT的时候要全量替换别只替换训练集漏了验证集。5.3 图片和标签文件名错位现象训练日志每个epoch都提示WARNING: found x missing images或者某个epoch的loss明显偏低验证集mAP波动剧烈。原因之前的脚本按文件名排序后硬切但数据集内部图片命名是1.jpg、2.jpg……10.jpg字典序排序会变成1、10、100、2、20。标注文件序列和图片序列顺序不同拷贝时错位导致一部分图配了别人的标签。解决别依赖人眼核对写脚本用文件名做key逐个对比图片和标签是否存在。for img in $(ls pothole_split/train/images/); do name${img%.*} if [ ! -f pothole_split/train/labels/$name.txt ]; then echo missing label: $name fi done跑完如果输出为空才表示该目录下图片和标签是严格对应的。这个坑在批量整理多个来源的数据时特别常见切分脚本里加一段同样的校验逻辑能省很多事。5.4 EXIF旋转导致标注框错位现象训练loss曲线正常但预测时模型给出的框位置比实际坑洞位置偏了大约90度或180度。原因手机或部分行车记录仪拍摄的照片带有EXIF方向信息图片本身存的是倒着的显示时由软件自动转正。标注工具在显示时看到的是正向图片保存的坐标也是正向的但训练框架读图时如果直接用原始像素不理会EXIF方向模型看到的图是倒的标签坐标也跟着错。YOLO的数据加载默认不做EXIF矫正。解决训练前统一转正。# 用ImageMagick批量转正 for img in pothole_dataset/images/*.jpg; do convert $img -auto-orient $img done注意转正之后要重新生成一遍标签因为像素坐标已经变了。如果不想重标更省事的办法是训练前先确认所有图片的EXIF方向一致。大部分车载相机的原始输出不带旋转标记但混杂来源数据集里很容易混入手机照片。检查方法identify -verbose pothole_dataset/images/001_pothole_01.jpg | grep Orientation输出为Orientation: TopLeft表示无旋转其他值都要处理。5.5 小目标漏检标注框太小和imgsz的关系现象大坑洞检测得不错但远处的小坑洞全漏验证集mAP0.5和mAP0.5:0.95的差距越来越大。原因坑洞类别的目标尺寸跨度极大。近距离的大坑可能占画面三分之一远距离的可能只有30x30像素。当输入缩放到640x640后30x30的坑缩成了约10x10特征图上一两层就把它抹平了。解决优先在保持宽高比前提下裁切而不是直接resize全图。或者把imgsz从640提到960显存允许的话这是最直接的解法。另外要看标注统计分布python -c import os sizes[] for f in os.listdir(pothole_dataset/labels): with open(os.path.join(pothole_dataset/labels,f)) as fp: for line in fp: _, cx, cy, w, h map(float, line.split()) sizes.append(w*h) print(min:, min(sizes), max:, max(sizes), mean:, sum(sizes)/len(sizes)) 如果最小框面积占比低于0.02说明确实有大量小目标这种数据去做检测不能只看整体mAP要单独统计小目标这个子集的AP。YOLOv8的验证输出里本身就按尺寸拆分了metrics训练完记得看small那一列。6. 让这665张值回票价验证集的三种用法和一个经验技巧数据集本身是死的怎么把它的价值榨干才是重点。这里说三种验证集的高级用法以及一个我在单类检测里特别依赖的经验技巧。6.1 验证集的三种用法第一拿验证集当“临时测试集”做置信度阈值的调优。mAP是模型在所有置信度下的匀速表现但工程上你需要的是一个确定的阈值。用训练好的模型跑验证集预测输出每张图的置信度和框然后扫一遍0.2到0.6之间的置信度阈值找到精确率和召回率交叉点那个值就是部署时的默认阈值。这个操作会让误报率直观下降。第二验证集做早停的裁判。不看训练loss只看验证集mAP0.5:0.95连续20个epoch没有提升就截断训练。665张的数据训练到150轮左右基本平台期再硬train只会过拟合。第三用验证集做“回归测试”。每次调参改代码后固定验证集重训或重推理对比新旧mAP。长期迭代的项目里这个固定验证集就是你的后悔药——防止越改越差而不自知。6.2 网格化裁切这个经验技巧单类检测最烦的是小目标和密集目标叠在一起YOLO的全局预测对这类问题天生吃亏。我的做法是推理阶段把原图按50%重叠切成四块每块分别预测再把框坐标映射回原图合并。坑洞数据集因为目标集中在路面中下方这种方法能显著提升小坑召回。代码不复杂核心是记住裁切后坐标要加回偏移量import cv2 from ultralytics import YOLO model YOLO(runs/detect/pothole_stage2/weights/best.pt) img cv2.imread(road_001.jpg) H, W img.shape[:2] crop_size 640 # 和训练尺寸一致 # 50%重叠x方向步进320 for y in range(0, H, crop_size // 2): for x in range(0, W, crop_size // 2): crop img[y:y crop_size, x:x crop_size] results model.predict(crop, conf0.25) for box in results[0].boxes.xyxy.cpu().numpy(): # 坐标还原到原图 x1, y1, x2, y2 box print(fpothole at ({x1 x:.1f}, {y1 y:.1f}))注意最右和最下的边缘裁切会超出图像边界需要做min(x crop_size, W)的截断。这个技巧不是YOLO官方的标准姿势但对单类小而多的检测任务非常有效。说到最后想起之前做路面巡检项目时的一个教训当时拿到类似数据直接开训调了一周参数精度都卡在0.7上不去后来发现是验证集里混了十几张空标签图把mAP硬生生拉低了。从那以后我拿到任何数据集第一件事永远是跑数据校验而不是急着启动训练。数据集的665张只是个起点把格式吃透、把校验跑好、把增强调对它就能顶得上平时两三千张的效果。希望这篇能帮你少走这些弯路把时间花在真正有用的调参和模型迭代上。本文还有配套的精品资源点击获取
返回列表