道路病害数据集23k张:VOC转YOLO格式与YOLOv8训练实战
简介面向道路病害检测、计算机视觉与深度学习模型训练场景这份资源提供了可直接用于工程化落地的道路病害标注数据包。完整数据集合计超过23000张道路病害图像覆盖城市街道、乡村道路、高速公路与住宅区等多样化场景本压缩包收录其中的XML标注文件及配套工具。数据采用VOC风格组织以1998个XML标注文件为主体配合说明文本与Python辅助脚本方便研究者快速解析标注框、类别与病害位置信息。资源包共2000个文件压缩后约411.72MB适合已有道路图像数据或希望学习VOC标注格式的开发者作为补充工具集使用。目前已有374人学习下载作者在配套博客中给出了更详细的数据集说明帮助使用者理解目录结构与标注规则。对于需要节省数据收集和标注时间的团队这份资源能显著降低前期准备成本直接支撑目标检测、图像分类等模型的训练与验证。1. 道路病害数据集23k张图像的目标检测基础道路病害检测是公路养护和智慧交通里的一个现实需求裂缝、坑槽、龟裂等病害如果不及时发现会快速恶化维修成本成倍上升。很多项目团队优先卡在数据上——要么自己拍还要请人标注要么网上找的数据规模小、场景单一。这个压缩包的价值在于提供了超过23,000张真实道路图像并且每张都有LabelImg标注的XML文件覆盖城市街道、乡村道路、高速公路和住宅区等多种场景。我拿到这个zip之后的第一反应不是急着跑训练脚本而是先把XML和JPEGImages的对应关系、标注字段的分布摸清楚。因为标注质量决定了模型收敛速度如果坐标归一化出错训练出来就是一堆没有意义的loss。这篇内容从目录结构开始逐步讲到VOC转YOLO格式的脚本、YOLOv8训练配置以及最后验证标注质量的细节。适合正在拿数据集训练YOLOv5/YOLOv8目标检测模型的工程人员。2. 目录结构与XML标注从JPEGImages到Annotations的对应关系2.1 解压后的文件布局把优质道路病害数据集5.zip解压后核心是两个目录JPEGImages存放原始jpg图像Annotations存放同名xml文件。比如China_MotorBike_001965.xml对应的就是China_MotorBike_001965.jpg。这种命名一致性是后续批量处理的关键如果文件名对不上脚本转换时会直接产出空txt或者报错。资源包里还带了leishu1.py和说明.txt。说明.txt通常记录了标注类别、标注工具和版本信息leishu1.py我一般会先打开扫一眼看它是生成xml还是做转换的脚本。很多类似数据集会附带一个辅助脚本但这个脚本只能作为参考不要直接执行因为它的路径、类别列表很可能和你的工作目录不匹配。2.2 XML字段解析需要读哪些节点一个标准的Pascal VOC标注XML核心信息分布在filename、size和object节点中。下面用Python自带的xml.etree.ElementTree解析一个样本把关键字段打印出来import xml.etree.ElementTree as ET xml_file Annotations/China_MotorBike_001965.xml tree ET.parse(xml_file) root tree.getroot() filename root.find(filename).text size root.find(size) w int(size.find(width).text) h int(size.find(height).text) print(f图像文件: {filename}, 尺寸: {w}x{h}) for obj in root.iter(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) print(f类别: {name}, bbox: ({xmin},{ymin})-({xmax},{ymax}))这段代码先解析XML然后读取filename和图像宽高再遍历所有object节点取类别名和边界框坐标。因为XML里bndbox的值是字符串转成int方便后续计算。注意有些标注文件里可能存在不带bndbox的object或者name字段为空后续脚本里要加过滤逻辑。XML中的主要字段可以整理成下表实际用的时候关注带*号的行字段路径是否必须说明/annotation/filename是图像文件名需与真实文件一致/annotation/size/width是图像宽度像素值/annotation/size/height是图像高度像素值/annotation/object/name是病害类别名称/annotation/object/bndbox/xmin是左上角x坐标/annotation/object/bndbox/ymin是左上角y坐标/annotation/object/bndbox/xmax是右下角x坐标/annotation/object/bndbox/ymax是右下角y坐标/annotation/object/truncated否目标是否被截断影响验证集划分/annotation/object/difficult否是否难识别通常训练时忽略从工程角度看width和height这两个值特别关键。因为后面转YOLO格式时要拿bbox坐标除以图像宽和高如果XML里的尺寸和jpg实际尺寸不一致归一化坐标就会偏训练时会莫名出现边框偏移。所以我在解析XML后一般会额外用PIL打开同名图片比对尺寸是否一致这比直接信任XML更稳妥。2.3 统计类别分布和样本数量不先做统计就训练后面类别不平衡问题会被埋住。写一个简单的遍历脚本把每个类别的标注框数量统计出来for xml in Annotations/*.xml; do grep -o name.*/name $xml | sort | uniq -c; done | awk {count[$2]$1} END {for (name in count) print name, count[name]}这条命令对每个xml文件提取name标签按类别累加计数。如果某个类别只有几百个实例而另一个类有数万个就需要在训练时使用类别权重或者先扩充数据。另外也可以顺便统计图像数量ls JPEGImages | wc -l确认和xml数量匹配。数据集说明里写了超过23,000张但不同压缩包版本可能略有差异以你解压后的实际数量为准。统计完之后我一般会把结果保存成一个classes.txt里面每行一个类别名顺序就是后面YOLO训练时的类别ID。这个顺序要和训练用的yaml保持一致否则标签对不上模型预测出来的类别就全乱了。3. 把XML转成YOLO格式坐标转换脚本与边界处理3.1 为什么必须转换格式YOLOv5、YOLOv8训练时默认读txt标注每一行格式是class_id x_center y_center width height其中坐标是相对图像宽高的比例值范围在0到1之间。而这份数据集给的是VOC XML的绝对像素坐标不能直接丢给yolo train。常见的做法是写一个转换脚本批量把每个xml生成同名txt。需要注意的是YOLO格式的x_center、y_center是矩形中心点相对于图像宽度和高度归一化后的值不是左上角坐标。具体转换公式为x_center (xmin xmax) / 2 / image_widthy_center (ymin ymax) / 2 / image_heightbox_width (xmax - xmin) / image_widthbox_height (ymax - ymin) / image_height3.2 一个可直接改用的转换脚本这里给出一个完整的Python脚本读取Annotations目录里的所有XML依次生成YOLO格式txt到labels目录同时做一个80/20的训练验证划分。import os import random import glob import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, img_dir, label_dir, classes): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text img_path os.path.join(img_dir, filename) if not os.path.exists(img_path): print(f图像不存在: {img_path}) return None, None size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) txt_lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue class_id classes.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坐标边界保护防止越界 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: print(f跳过无效框: {filename}, {name}) continue x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h txt_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) label_path os.path.join(label_dir, os.path.splitext(filename)[0] .txt) with open(label_path, w) as f: f.write(\n.join(txt_lines)) return filename, label_path def main(): xml_dir Annotations img_dir JPEGImages label_dir labels os.makedirs(label_dir, exist_okTrue) # 这个列表需要根据说明.txt里的实际类别填写 classes [crack, pothole, patch, rut] # 示例按实际数据修改 xml_list glob.glob(os.path.join(xml_dir, *.xml)) random.shuffle(xml_list) train_file train.txt val_file val.txt train_f open(train_file, w) val_f open(val_file, w) for i, xml_path in enumerate(xml_list): filename, _ xml_to_yolo(xml_path, img_dir, label_dir, classes) if filename is None: continue img_abs_path os.path.join(os.path.abspath(img_dir), filename) if i int(len(xml_list) * 0.8): train_f.write(img_abs_path \n) else: val_f.write(img_abs_path \n) train_f.close() val_f.close() print(f转换完成共处理 {len(xml_list)} 个标注文件) if __name__ __main__: main()脚本的逻辑是遍历Annotations下的xml解析filename、size和每个object的box然后把绝对坐标转成归一化坐标最后写到labels目录。这里我加了两层保护一是坐标边界裁剪避免由于标注时的边缘误差导致xmax超过图像宽度二是过滤掉宽或高为0的无效框。在划分train.txt和val.txt时用了80%训练、20%验证注意要先把xml_list随机打乱避免分类分布不均。参数说明classes列表的顺序必须与后续yaml里的names一致脚本里只是占位示例你按说明.txt里的实际类别修改。xml_path是单个xml文件路径img_dir是JPEGImages目录label_dir用于保存生成的txt。如果是多标签情况同一个object里name重复出现这段代码会保留重复可以用set去重但道路病害场景里一个框一般只标一个主类别。3.3 转换后的格式对照转换前XML里的坐标信息和转换后txt里的行可以用下面的表直观对照属性XML字段YOLO txt字段类别object/name第1列class_id中心x坐标(xminxmax)/2第2列x_center除以宽度中心y坐标(yminymax)/2第3列y_center除以高度框宽xmax-xmin第4列box_width除以宽度框高ymax-ymin第5列box_height除以高度转换完成后随便打开一个txt文件看起来像这样2 0.574201 0.366203 0.043636 0.061818。同时要检查labels目录中的txt数量等于Annotations中的xml数量因为存在异常xml被跳过的情况。可以用find labels -name *.txt | wc -l对比。另外建议抽样对比几个文件手动用图像查看一幅图的标注框是否大致贴合病害区域。这一步虽然枯燥但能提前发现bbox取值异常。提示如果转换出来的txt某一行出现了大于1或者负数说明原始XML坐标或图像尺寸有问题不要直接去训练先检查那几个xml文件。4. 用YOLOv8跑通道路病害检测数据集配置与训练参数4.1 准备data.yamlYOLOv8要求提供一个yaml文件描述数据集路径和类别名。把上一节生成的train.txt、val.txt以及labels目录放在同一级目录下然后写一个road_pothole.yamlpath: /your_absolute_path/road_damage_dataset train: train.txt val: val.txt names: 0: crack 1: pothole 2: patch 3: rut这里path写数据集根目录的绝对路径train和val指向我们生成的txt文件。我习惯把train.txt、val.txt与data.yaml放在一起避免路径嵌套太深导致URLError或找不到图片。names的索引必须和转换脚本里的classes顺序一致一个字母拼错就会在训练时报错。4.2 启动训练命令YOLOv8安装好后直接使用命令行接口yolo train dataroad_pothole.yaml modelyolov8s.pt epochs100 imgsz640 batch16 lr00.01 optimizerSGD训练过程中重点观察val/box_loss和metrics/precision、metrics/recall两个指标。如果precision和recall一直上不去通常是数据标注噪声太大或者类别不平衡而不是单纯调参能解决的。下表中列出了几个最常调整的参数参数建议值范围说明imgsz640 / 1280道路病害中细节多显存够就用1280batch8 / 16 / 32由显存决定batch太小导致batch norm不稳定epochs100 / 20023k张图100轮基本收敛lr00.01 / 0.001预训练模型可以用0.01注意warmupmosaic1.0 / 0.5适合小目标但数据类别多时会引入噪声optimizerSGD / AdamWSGD泛化好AdamW收敛快如果你更习惯YOLOv5命令几乎一样只是把yolo train换成python train.py然后增加--data road_pothole.yaml --weights yolov5s.pt。另外道路病害往往有方向性比如裂缝呈长条状水平框会包含大量背景这种场景下如果后续想提升精度可以考虑用MMRotate做旋转目标检测但前提是要把标注从水平框改成旋转框工作量取决于标注工具是否支持。这里不展开但值得了解。4.3 训练时的数据增强策略对道路病害这种小目标占多数的数据集数据增强不能开太猛。我一般会把mosaic开启但设为0.8左右scale设置为0.3避免把裂缝缩放成一条线。还有flipud如果道路图片中上下翻转的物理意义不大建议关掉或只做随机左右翻转。hsv增强可以默认但对颜色敏感的路面裂缝来说饱和度偏移不宜超过0.2。另外类别不平衡最直接的手段是在loss里加权。YOLOv8的class_weights用户不用手动设置它会在验证时自动计算每个类别的频率权重但实际效果有限。更实际的做法是每个epoch后看混淆矩阵找出被混淆的类别比如裂缝和伸缩缝然后检查这些类别的标注边界是不是标的太宽。训练结束后模型权重会保存在runs/detect/train/weights/best.pt。下一步不要只看mAP还要在真实道路上做可视化推理确认模型在实际场景中的误检率。这正好是最后一章要处理的问题。5. 验证模型与标注质量的几个小技巧5.1 用predict做快速可视化验证训练完best.pt后先拿验证集里的一小批图跑预测yolo predict modelruns/detect/train/weights/best.pt sourceval_images/ conf0.25 save_txtTrue save_cropTruesave_cropTrue会把每个检测到的病害单独裁剪保存我一般优先看这些小图。如果裁剪图中混入了大量的路面纹理、轮胎印说明模型学到的特征偏了。此时可以回看loss曲线若val loss在后期反弹多半是过拟合epochs适当减小或用更大的imgsz配合早停。5.2 定位XML与图像尺寸不一致这个数据集在人工标注过程中容易出现的典型问题是xml里的width/height和jpg实际尺寸不一致。写一个校验脚本批量比对from PIL import Image import os import glob import xml.etree.ElementTree as ET for xml_path in glob.glob(Annotations/*.xml): root ET.parse(xml_path).getroot() img_path os.path.join(JPEGImages, root.find(filename).text) if not os.path.exists(img_path): print(找不到图像:, img_path) continue with Image.open(img_path) as img: w, h img.size size root.find(size) xw int(size.find(width).text) xh int(size.find(height).text) if w ! xw or h ! xh: print(f尺寸不一致: {img_path}, xml{xw}x{xh}, actual{w}x{h})这段脚本用PIL读取图像实际尺寸和xml里的size比较。如果输出很多不一致说明标注时图片被resize过而xml没有同步更新。这种情况下直接用原始xml转YOLO格式会得到偏移的边框必须先把xml里的尺寸改成图像的真实尺寸或者把坐标按比例缩放到真实尺寸。这比在训练脚本里写自适应缩放更稳因为YOLO训练时读取的txt只认relative坐标一旦图片被imgsz重新缩放错误会被放大。提示训练前把脚本跑一遍如果超过总文件数的1%存在尺寸不一致建议重新导出标注避免模型在低质量标签上浪费算力。另外一个技巧是统计全部标注框的宽高比分布。道路病害里的裂缝通常宽高比很大如果数据集中大部分框都是接近正方形的标准框说明标注的box把周围路面也包进去了。这类标注会让模型学出一堆背景特征。可以用matplotlib画一个宽高比直方图然后设定阈值筛选出异常框人工复查几个xml。整个过程不复杂但对于提升最终检测精度很有帮助值得花半小时过一遍。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →