VOC格式交通目标数据集解析与YOLOv8训练实战
2026/9/15 3:30:26 网站建设 项目流程

简介:交通道路上的车辆、行人、自行车与摩托车自动识别数据集,面向计算机视觉目标检测任务,适用于车辆计数、交通流量分析、辅助驾驶感知等真实场景。压缩包内共2000个文件,全部为VOC格式的XML标注文件,对应一组道路实景图片的物体框标注,类别涵盖机动车、非机动车和行人,边界框坐标与类别信息完整,可直接用于YOLO、SSD、Faster R-CNN等主流检测框架的训练与验证。资源包整体大小约129.73MB,标注文件命名与原图一一对应,便于自行匹配图像数据后快速开展模型实验。对需要扩充交通场景训练样本、希望省去大量人工标注工作的开发者或学生而言,这套数据能显著提升项目前期准备效率。已有590人浏览学习,适合具备一定深度学习基础、正在从事目标检测课题或竞赛的读者参考使用。

1. 2998张VOC格式标注的交通目标数据集,直接用于车辆与行人检测

在真实道路监控项目里,最耗时的是数据整理而不是模型选型。这套2998张原始图片的VOC格式标注数据集,覆盖了车辆、行人、自行车和摩托车四类常见交通目标,图片尺寸和视角接近实际监控画面,目标大小跨度很真实。它解决了两个痛点:一是原始标注文件是标准VOC XML,Faster R-CNN、YOLO、MMDetection都能直接解析;二是类别覆盖了道路交通流统计和安防场景最常用到的几类目标。我会把数据集内部格式解析、转成YOLO训练格式、基于YOLOv8跑通训练、再到小目标推理优化整个流程过一遍,新手按步骤能跑通,熟手可以跳到自己关注的坑点。

2. VOC标注文件字段解析与数据集类别分布统计

压缩包解压后是一堆jpg和xml同名文件,文件名类似3270_jpg.rf.1a5e7e548782201cdef87bfe13753825.xml。rf是Roboflow标注平台导出时留下的标记,说明数据来源经过半自动预标注后再人工修正。虽然目录结构不是标准VOC三件套,但xml内容符合VOC格式,单独拉出来用完全没问题。

2.1 VOC标准结构与实际目录的差异

标准VOC2007/2012数据集要求JPEGImages、Annotations、ImageSets/Main三个目录。这个数据集只有平铺的图片和xml文件,我会先建好标准目录结构再把文件归类。注意xml里的<filename>字段有时不带完整路径,甚至和实际文件名大小写不一致,归类时直接用文件系统里的名字配对,不要依赖xml内的名称。

2.2 XML文件里的object信息与标注框含义

每个xml对应一张原始图片,<annotation>根标签下保存图片尺寸和所有目标。下面这个片段是VOC标注的标准结构:

<annotation> <folder>annotations</folder> <filename>3270_jpg.rf.1a5e7e548782201cdef87bfe13753825.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>person</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>240</xmax> <ymax>260</ymax> </bndbox> </object> </annotation>

<bndbox>里是绝对像素坐标,不是归一化值。交通场景中一辆车常被多个目标框部分遮挡,所以一个xml里会有多个<object>块。如果同一张图片里出现两个完全重合的框,多半是预标注阶段重复画了,训练时会产生高置信度的错误回归。我拿到数据集第一件事就是解析全部xml做统计,确认类别样本量是否均衡,同时排查重复或越界框。

2.3 用Python解析全部XML统计类别分布

下面脚本遍历Annotations目录,统计每个类别出现次数、每张图的平均目标数量,以及图片尺寸范围,为后续训练选择合适imgsz提供依据。

import os import xml.etree.ElementTree as ET ann_dir = "Annotations" class_counter = {} img_box_counter = [] widths, heights = [], [] for f in os.listdir(ann_dir): if not f.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, f)) root = tree.getroot() size = root.find("size") if size is not None: w = int(size.find("width").text) h = int(size.find("height").text) widths.append(w) heights.append(h) count_per_img = 0 for obj in root.iter("object"): name = obj.find("name").text.strip().lower() class_counter[name] = class_counter.get(name, 0) + 1 count_per_img += 1 img_box_counter.append(count_per_img) print("类别出现总次数:", class_counter) print("平均每张图目标数:", sum(img_box_counter) / len(img_box_counter)) print("图片宽度范围:", min(widths), "-", max(widths)) print("图片高度范围:", min(heights), "-", max(heights))

逻辑并不复杂:ElementTree解析每个xml,iter("object")避免层级嵌套问题,strip().lower()用来防止"Person"和"person"被当成两个类别。width和height范围决定了后续训练图像缩放策略,如果图片大多是1920x1080,训练时imgsz设640和设960,小目标的特征响应差异会很大。

注意,标注人员偶尔会写成"bicycle "带空格,或者在名称里混入全角字符。运行完统计后,建议打印所有不重复的原始name列表,人工核对一遍,避免同义词类别导致转换后目标丢失。

从常见道路交通数据集看,类别一般集中在四类。下表列出了类别与典型形态、标注难点的对应关系:

类别典型形态常见标注难点
person行走、站立、骑行者遮挡多、边缘不清晰
car轿车、SUV相邻车辆互相遮挡
bicycle自行车车轮辐条导致框不贴合
motorcycle摩托车、电动车骑行姿态变化大

做完统计后重点看bicycle和motorcycle的样本量。交通场景里这两类目标数量天然少于car,如果差距超过5倍,后续训练要对少样本类别做增强或加权。

3. VOC转YOLO标注并划分训练验证集

YOLO训练需要的是与图片同名、放在labels目录下的txt文件,每行内容是class_id x_center y_center width height,坐标统一归一化到0~1。VOC的XML坐标不能直接用,下面这个转换脚本我一直在用,兼顾了格式转换、越界过滤和数据集划分。

3.1 为什么需要归一化坐标

Faster R-CNN这类模型直接接受绝对坐标,但YOLO系列的anchor计算和损失函数都建立在相对尺寸上。归一化后,无论图片是1920x1080还是800x600,训练时都能统一映射到特征图,不会因分辨率差异导致回归目标尺度跳变。另外归一化坐标可以直接配合Mosaic、MixUp等数据增强,不用做像素坐标的额外换算。

3.2 转换脚本:解析XML、生成txt、校验边界

这里给出完整的转换与划分脚本:

import os import random import xml.etree.ElementTree as ET class_dict = {"person": 0, "car": 1, "bicycle": 2, "motorcycle": 3} ann_dir = "Annotations" img_dir = "JPEGImages" out_images = "images" out_labels = "labels" for split in ["train", "val", "test"]: os.makedirs(os.path.join(out_images, split), exist_ok=True) os.makedirs(os.path.join(out_labels, split), exist_ok=True) def convert_box(w, h, xmin, ymin, xmax, ymax): dw, dh = 1.0 / w, 1.0 / h cx = (xmin + xmax) / 2.0 * dw cy = (ymin + ymax) / 2.0 * dh bw = (xmax - xmin) * dw bh = (ymax - ymin) * dh return cx, cy, bw, bh xml_list = [f for f in os.listdir(ann_dir) if f.endswith(".xml")] random.seed(42) random.shuffle(xml_list) n = len(xml_list) train_cut, val_cut = int(n * 0.8), int(n * 0.9) splits = {"train": xml_list[:train_cut], "val": xml_list[train_cut:val_cut], "test": xml_list[val_cut:]} for split, files in splits.items(): for xml_file in files: base = os.path.splitext(xml_file)[0] tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] valid = True for obj in root.iter("object"): name = obj.find("name").text.strip().lower() if name not in class_dict: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) if not (0 <= xmin < xmax <= w and 0 <= ymin < ymax <= h): valid = False print(f"跳过越界标注:{xml_file} {name}") continue cx, cy, bw, bh = convert_box(w, h, xmin, ymin, xmax, ymax) lines.append(f"{class_dict[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if not valid: print(f"{xml_file} 中部分目标越界,已忽略") if lines: src_img = os.path.join(img_dir, base + ".jpg") if os.path.exists(src_img): os.system(f"cp {src_img} {out_images}/{split}/") with open(os.path.join(out_labels, split, base + ".txt"), "w") as f: f.write("\n".join(lines))

脚本分四步:类别名称转id、绝对坐标转归一化、越界过滤、按8:1:1划分并复制到对应目录。random.seed(42)保证每次运行划分一致。注意class_dict的顺序必须和后续data.yaml里的names顺序完全一致,顺序不一致导致class_id错位时,训练不会报错,只会让mAP长期不涨。

越界框直接忽略是一个保守做法。如果越界比例超过5%,建议回到原图检查,通常是标注工具导出时坐标被整体平移了,这种系统性偏差会污染模型回归。

3.3 划分比例与验证集监控策略

这里我用80%训练、10%验证、10%测试。验证集主要用于观察过拟合,每5个epoch做一次评估。注意不要将验证集和训练集图片取同一段连续监控视频的相邻帧,否则验证loss参考价值很低。如果图片来自不同监控点位,那就按点位拆分,保证验证集视角独立。

3.4 转换后目录结构

标准化后的目录如下:

目录内容
images/train训练图片jpg
labels/train对应的txt标签
images/val验证图片
labels/val对应txt标签
images/test测试图片
labels/test对应txt标签

转换完成后我会随机抽几张图,用OpenCV把标注框画回去确认坐标位置。一次坐标系换算错误往往要到训练三四轮后loss不下降才暴露,手动检查可以把这个周期缩到几分钟。补充一个小脚本,验证labels和images是否一一对应:

from pathlib import Path for label in Path("labels/train").glob("*.txt"): if not label.with_suffix(".jpg").exists(): print(label, "缺少对应图片")

如果缺图,说明xml和jpg数量本来就不一致,这类样本要直接剔除,否则训练时会报空标签错误或图片加载失败。

4. 基于YOLOv8训练自己的道路交通多类别检测模型

YOLOv8是目前工程落地比较多的版本,训练自己的数据集不需要改模型结构,只要准备好data.yaml和images/labels目录就能开始。迁移学习能让模型在几百张图上快速收敛。

4.1 编写data.yaml配置

在项目根目录创建data.yaml:

path: /home/user/traffic_dataset train: images/train val: images/val test: images/test names: 0: person 1: car 2: bicycle 3: motorcycle

path是数据集根目录的绝对路径,train/val/test是相对于path的路径。names的顺序必须和转换脚本中class_dict保持一致,从0开始连续编号。如果你把person改成1而car改成0,训练时不报错,预测时框和类别就完全错位。

4.2 使用预训练权重开始迁移学习

执行训练命令:

yolo detect train data=data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 patience=20 project=traffic_runs name=exp1

我习惯用yolov8s.pt作为起点,推理速度和精度比较均衡。如果显存小于8GB,把batch降到8,或者把imgsz降到512。交通场景里自行车和摩托车在远处可能只占几十个像素,imgsz太小会让小目标在特征图上几乎没有有效响应,所以imgsz我一般至少用640,显卡够用就上960。

参数说明:

参数含义建议值
epochs完整训练轮数100-200
patience连续多少轮验证集mAP不提升就早停20
batch每批图片数显存允许下越大越好
imgsz输入分辨率640或960
lr0初始学习率默认0.01,数据量小时可调低

训练log目录下会默认生成results.png,包含box_loss、cls_loss、mAP50等曲线。正常情况box_loss和cls_loss应该在20轮内明显下降,mAP50曲线稳步上升。如果mAP50一直低于0.3,先回去检查标签和图片是否匹配。最常见的错误就是标签里已经是归一化值,又被当成像素坐标做了二次归一化。

4.3 数据集不均衡时的自动识别优化技巧

前面统计发现bicycle和motorcycle样本偏少时,可以通过YOLOv8内置的类别权重缓解。在data.yaml中添加可选字段:

weights: 0: 1.0 1: 1.0 2: 1.5 3: 1.5

类别权重只能缓解问题,不能解决标注缺失。我一般会用CVAT标注工具补充几百张夜间或雨天图片,这些场景是模型泛化的主要短板。CVAT支持直接导入VOC格式,手动修正后再导出保持VOC,和本数据集兼容性很好。数据标注的闭环迭代,是交通目标自动识别项目长期维护的关键。

4.4 训练过程中的常见坑与排查

训练时最隐蔽的问题是标签txt里出现负坐标或坐标大小反了,导致loss为NaN。写一个简单的过滤脚本:

import os for root, _, files in os.walk("labels"): for f in files: with open(os.path.join(root, f)) as fh: for i, line in enumerate(fh): parts = list(map(float, line.split())) if (len(parts) != 5 or not (0 <= parts[1] <= 1) or not (0 <= parts[2] <= 1) or parts[3] <= 0 or parts[4] <= 0): print(f"{f}:{i} 非法标注")

这段检查每行是否5个数字,中心点坐标是否在0~1之间,宽高是否大于0。很多标注工具导出txt时带列名或者引号,转换脚本没做清洗,会在这里暴露。

5. 模型评估与针对小目标的推理提速优化

训练完成后先用验证集测一下模型底子,再用测试集评估真实表现。

yolo detect val data=data.yaml model=traffic_runs/exp1/weights/best.pt

输出会给出mAP50、mAP50-95、每个类别的精确率和召回率。重点关注mAP50-95,它对框定位精度更敏感。如果car类的mAP50-95在0.5以上而bicycle只有0.2,说明小目标框位置和类别都还有提升空间。

针对自行车和摩托车这类小目标,可以用TTA增强推理:验证命令加--augment,推理端自动做多尺度翻转增强。TTA计算代价约是原来3倍,但小目标召回率通常能提升2-3个百分点。如果对帧率有要求,我不会全图直接predict,而是先把大图切成1280x1280的overlap分块,每块跑一次模型,再对重叠区域做NMS合并。道路监控帧率要求不高时,这个方案比单纯放大imgsz更划算。切块时要留10%左右重叠率,避免目标被切在边界上。

训练后可以进一步用CVAT抽检100张预测输出,把漏检目标补标进训练集再微调一轮。这种从数据标注、格式转换、模型训练到误差反馈的闭环,才是交通目标自动识别项目能持续迭代的基础。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询