
简介交通道路上的车辆、行人、自行车与摩托车自动识别数据集面向计算机视觉目标检测任务适用于车辆计数、交通流量分析、辅助驾驶感知等真实场景。压缩包内共2000个文件全部为VOC格式的XML标注文件对应一组道路实景图片的物体框标注类别涵盖机动车、非机动车和行人边界框坐标与类别信息完整可直接用于YOLO、SSD、Faster R-CNN等主流检测框架的训练与验证。资源包整体大小约129.73MB标注文件命名与原图一一对应便于自行匹配图像数据后快速开展模型实验。对需要扩充交通场景训练样本、希望省去大量人工标注工作的开发者或学生而言这套数据能显著提升项目前期准备效率。已有590人浏览学习适合具备一定深度学习基础、正在从事目标检测课题或竞赛的读者参考使用。1. 2998张VOC格式标注的交通目标数据集直接用于车辆与行人检测在真实道路监控项目里最耗时的是数据整理而不是模型选型。这套2998张原始图片的VOC格式标注数据集覆盖了车辆、行人、自行车和摩托车四类常见交通目标图片尺寸和视角接近实际监控画面目标大小跨度很真实。它解决了两个痛点一是原始标注文件是标准VOC XMLFaster 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 folderannotations/folder filename3270_jpg.rf.1a5e7e548782201cdef87bfe13753825.jpg/filename size width1920/width height1080/height depth3/depth /size object nameperson/name bndbox xmin120/xmin ymin80/ymin xmax240/xmax ymax260/ymax /bndbox /object /annotationbndbox里是绝对像素坐标不是归一化值。交通场景中一辆车常被多个目标框部分遮挡所以一个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解析每个xmliter(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_okTrue) os.makedirs(os.path.join(out_labels, split), exist_okTrue) 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(fcp {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训练图片jpglabels/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.yamlpath: /home/user/traffic_dataset train: images/train val: images/val test: images/test names: 0: person 1: car 2: bicycle 3: motorcyclepath是数据集根目录的绝对路径train/val/test是相对于path的路径。names的顺序必须和转换脚本中class_dict保持一致从0开始连续编号。如果你把person改成1而car改成0训练时不报错预测时框和类别就完全错位。4.2 使用预训练权重开始迁移学习执行训练命令yolo detect train datadata.yaml modelyolov8s.pt epochs100 imgsz640 batch16 patience20 projecttraffic_runs nameexp1我习惯用yolov8s.pt作为起点推理速度和精度比较均衡。如果显存小于8GB把batch降到8或者把imgsz降到512。交通场景里自行车和摩托车在远处可能只占几十个像素imgsz太小会让小目标在特征图上几乎没有有效响应所以imgsz我一般至少用640显卡够用就上960。参数说明参数含义建议值epochs完整训练轮数100-200patience连续多少轮验证集mAP不提升就早停20batch每批图片数显存允许下越大越好imgsz输入分辨率640或960lr0初始学习率默认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 datadata.yaml modeltraffic_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张预测输出把漏检目标补标进训练集再微调一轮。这种从数据标注、格式转换、模型训练到误差反馈的闭环才是交通目标自动识别项目能持续迭代的基础。本文还有配套的精品资源点击获取