简介这份资源面向计算机视觉学习者、自动驾驶感知方向的研究人员以及目标检测模型训练者提供交通道路场景下的多类别目标识别标注数据可解决车辆、行人、自行车与摩托车检测任务中样本不足、标注格式不统一的问题。压缩包内共2000个文件全部为VOC格式的xml标注文件整体约129.73MB每份xml对应一张原始图片记录目标类别与边界框坐标可直接接入YOLO、Faster R-CNN等主流检测框架进行训练与验证。原始图片规模为2998张覆盖道路、街口等真实交通场景类别分布兼顾机动车与非机动车、行人适合做多目标检测、类别平衡实验与数据增强对比。目前已有590人学习下载说明该数据集在同类资源中具备一定参考价值。读者可获得开箱即用的标注文件与清晰的目录组织省去自行标注与格式转换的时间快速搭建训练流水线并复现检测效果。1. 交通道路车辆行人两轮车识别数据集2998 张 VOC 标注到底能训出什么手上拿到一份标注好的交通场景数据集第一反应通常不是「太好了」而是「这 2998 张到底够不够、标得规不规范、能不能直接喂给模型」。这份数据集把道路上的车辆、行人、自行车、摩托车四类目标用 VOC 格式框了出来原始图片 2998 张配套同名 XML 标注文件。它解决的是最朴素也最刚需的问题你想做一个路口或车载视角的目标检测但不想从零标几千张图也不想在类别定义上反复纠结。适合的人很明确——做智慧交通、园区安防、自动驾驶感知预研的算法工程师以及需要快速跑通 YOLO 或 Faster R-CNN 全流程的学生和独立开发者。VOC 格式是这套数据的入口也是它最大的兼容性优势几乎所有主流检测框架都提供 VOC 转 YOLO、VOC 转 COCO 的现成脚本你不需要为格式再写一遍解析器。但 2998 张这个量级决定了它是「能跑通、能验证、能出 demo」的起点而不是「直接上线」的终点后面几章会把边界讲清楚。2. VOC 标注结构拆解从 XML 字段到四类目标的真实分布2.1 一份 VOC XML 里到底存了什么VOC 格式的核心是一个图片对应一个 XML文件名与图片同名只是后缀不同。真正决定训练成败的不是图片而是 XML 里那几个字段。下面是一份典型的交通场景标注文件结构我按字段逐个说明。annotation folderJPEGImages/folder filenameroad_0001.jpg/filename size width1280/width height720/height depth3/depth /size object namecar/name !-- 类别名四类之一 -- poseUnspecified/pose truncated0/truncated !-- 是否被截断0 完整 1 截断 -- difficult0/difficult !-- 是否难样本训练时可选忽略 -- bndbox xmin312/xmin ymin204/ymin xmax498/xmax ymax361/ymax /bndbox /object /annotationsize里的宽高必须和真实图片一致这是后面坐标缩放不出错的前提。name是类别字符串这份数据里就是 car、person、bicycle、motorcycle 四类。bndbox是左上角和右下角绝对像素坐标注意 VOC 用的是 1-based 还是 0-based 在不同标注工具里会有差异转换时要统一。truncated和difficult这两个字段很多人直接忽略但在交通场景里恰恰重要被前车挡住一半的车辆、只露出半个身子的行人如果difficult1还硬当正样本训模型会学到一堆残缺特征。2.2 四类目标的分布决定了你的训练策略拿到数据第一件事不是写训练脚本是统计类别分布和框的尺寸分布。交通场景有个天然特点车辆框大且多行人和两轮车框小且容易被遮挡。用下面这段脚本快速摸清家底。import os import xml.etree.ElementTree as ET from collections import Counter ann_dir Annotations cls_counter Counter() size_buckets {small: 0, medium: 0, large: 0} for xml_file in os.listdir(ann_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text cls_counter[name] 1 bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) area w * h # 按 COCO 惯例分桶面积阈值可按自己图片分辨率调整 if area 32 * 32: size_buckets[small] 1 elif area 96 * 96: size_buckets[medium] 1 else: size_buckets[large] 1 print(类别分布:, cls_counter) print(尺寸分布:, size_buckets)这段脚本做两件事统计每个类别出现次数以及按面积把框分成小中大三类。逻辑很直白但结果直接指导决策。如果 person 和 bicycle 的 small 桶占比很高你就不能把输入分辨率压到 416否则小目标在特征图上只剩几个像素召回率会很难看。如果 car 占了七成以上说明数据是车辆主导的训练时可以考虑对行人、两轮车做类别加权或过采样否则模型会偏向预测 car。参数上面积阈值 32×32 和 96×96 是 COCO 的通用划分如果你的图片普遍是 720p 以上可以适当上调让「小目标」的定义更贴合实际。2.3 为什么选 VOC 而不是直接上 COCO 或 YOLO 格式很多人会问既然要训 YOLO为什么不直接用 YOLO 的 txt 格式。原因是 VOC 是标注工具的原生输出LabelImg、Labelme 这类工具默认吐 XML改格式是下游一步脚本的事而反过来从 txt 还原成 XML 就麻烦。VOC 的可读性也更好出问题时你能直接打开 XML 看某个框是不是标歪了txt 里一串归一化数字很难肉眼核对。所以常见做法是标注和存档用 VOC训练前用脚本转成框架要的格式原始 XML 永远保留。这份 2998 张的数据以 VOC 交付等于把最灵活的中间态给了你转 YOLO、转 COCO、转 CSV 都是一行命令的事。3. 从 VOC 到 YOLO 训练转换脚本、目录结构和必调参数3.1 目录组织与转换脚本YOLO 系列要求图片和标签分目录存放标签是每行class_id cx cy w h的归一化 txt。转换的核心是把 VOC 的绝对坐标转成归一化中心点坐标同时把类别名映射成从 0 开始的整数。下面是我常用的转换脚本。import os import xml.etree.ElementTree as ET import shutil # 类别顺序一旦定下就不能改它对应模型输出的索引 CLASSES [car, person, bicycle, motorcycle] cls_to_id {c: i for i, c in enumerate(CLASSES)} img_dir JPEGImages ann_dir Annotations out_img images out_lbl labels os.makedirs(out_img, exist_okTrue) os.makedirs(out_lbl, exist_okTrue) for xml_file in os.listdir(ann_dir): if not xml_file.endswith(.xml): continue stem os.path.splitext(xml_file)[0] tree ET.parse(os.path.join(ann_dir, xml_file)) root tree.getroot() size root.find(size) W float(size.find(width).text) H float(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in cls_to_id: continue # 跳过不在四类里的脏标注 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) # 归一化中心点坐标YOLO 要求 0~1 cx (xmin xmax) / 2.0 / W cy (ymin ymax) / 2.0 / H bw (xmax - xmin) / W bh (ymax - ymin) / H lines.append(f{cls_to_id[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: with open(os.path.join(out_lbl, stem .txt), w) as f: f.write(\n.join(lines)) shutil.copy(os.path.join(img_dir, stem .jpg), os.path.join(out_img, stem .jpg))逻辑说明先建立类别名到 id 的固定映射这个顺序必须和训练时的names配置完全一致否则模型学出来的类别会整体错位。遍历每个 XML读宽高做归一化中心点坐标是(xminxmax)/2/W宽高是(xmax-xmin)/W。if lines:这个判断很关键——没有有效目标的图片不生成空标签文件避免训练时读到空 txt 报错。参数上:.6f保留六位小数足够精度类别映射表建议单独存成classes.txt训练配置直接引用。3.2 训练配置里三个必调参数转换完只是开始训练配置里这三个参数直接决定这份数据能不能收敛。第一个是输入尺寸imgsz。2998 张里如果小目标多别用 416起步 640显存够就上 768。交通场景的行人和两轮车在远景里很小分辨率砍一半等于直接放弃这部分召回。第二个是类别权重或采样策略。如果统计出来 car 占绝对多数训练时对 person、bicycle、motorcycle 做适度过采样或者用带类别权重的损失。常见做法是在数据配置里复制小类样本的标签行简单粗暴但有效。第三个是mosaic和mixup这类增强的开关。交通场景本身遮挡就多mosaic 把四张图拼一起能显著提升小目标和遮挡场景的鲁棒性建议前期开着。但如果你的数据里已经有很多密集车流mosaic 可能让框更乱训到后期可以关掉做微调。# data.yaml 示例 path: ./dataset train: images/train val: images/val nc: 4 names: [car, person, bicycle, motorcycle]nc必须等于类别数 4names顺序必须和转换脚本里的CLASSES一模一样。这个文件写错一个字母训练不报错但结果全废是血泪经验里最常见的一种翻车。3.3 划分训练验证集时别踩的坑2998 张不能随机切完就完事。交通数据往往来自连续视频抽帧相邻帧几乎一样。如果纯随机划分验证集里会出现和训练集高度相似的帧指标虚高你以为模型泛化好实际换个路口就崩。正确做法是按场景或时间段划分同一段路、同一时段的帧要么全进训练要么全进验证。如果数据里没有场景标识至少按文件名前缀或时间戳排序后分段切别用random.shuffle一把梭。验证集比例 15% 到 20% 比较稳2998 张大概留 450 到 600 张做验证。4. 标注质量排查2998 张里最容易出现的五类脏数据4.1 坐标越界与零面积框现象转换后某些框的归一化坐标大于 1 或小于 0或者宽高为 0。原因通常是标注时框拖到了图片外或者 xmin 大于 xmax 手滑标反。解决在转换脚本里加一层校验if xmax xmin or ymax ymin: continue同时把越界坐标裁剪到[0, W]和[0, H]。别指望标注员零失误脚本兜底是必须的。4.2 类别名不统一现象统计类别时冒出Car、car、cars这种变体。原因是大写、空格、复数混用。解决转换前先跑一遍类别名清洗统一转小写去空格再映射到四类。这一步不做模型会把同一类当成多个类输出维度对不上。4.3 漏标与误标现象训练 loss 降不下去或者模型在明明有车的图上不框。原因可能是漏标——图里有目标但 XML 里没有模型被惩罚去学「这里没有目标」。解决抽查验证集的可视化结果把预测框和原图叠一起看凡是模型稳定检出但标注里没有的大概率是漏标。2998 张全查不现实按 5% 抽样人工过一遍能发现系统性问题。4.4 遮挡目标全标成 difficult现象统计发现difficult1占比异常高。原因是标注员把稍微遮挡的都标成难样本训练时全被忽略等于白标。解决difficult只留给真正无法辨认的目标轻微遮挡应该正常参与训练这恰恰是交通场景最需要的鲁棒性来源。4.5 图片与 XML 不对应现象训练时报找不到图片或标签。原因是文件名大小写不一致、扩展名不统一jpg 和 jpeg 混用。解决转换脚本里用stem做匹配不硬编码扩展名同时输出一份缺失清单人工补齐。5. 小数据集训出可用模型的进阶技巧与验证方法2998 张要训出能用的模型靠的不是堆 epoch而是把迁移学习和验证做扎实。第一招是预训练权重必须用。从 COCO 预训练的检测模型出发冻结主干先训几个 epoch 让检测头适应四类再解冻全网络微调。这一步能让小数据集少走一大半弯路学习率用 0.001 起步解冻后降到 0.0001。第二招是交叉验证代替单次划分。把数据按场景分成 5 折轮流做验证看指标方差。如果某一折 mAP 明显低说明那折的场景和其余差异大模型泛化有短板这比单次验证集上的漂亮数字可信得多。验证方法上别只看 mAP 一个数。交通场景要分开看四类的 AP尤其关注 person 和 bicycle 的召回率这两类才是实际部署里最容易漏的。用混淆矩阵看 car 和 motorcycle 有没有互相误判这两个类在远景里轮廓接近误判很常见。最后一定要做视频级验证找一段没参与训练的连续视频逐帧推理看框的抖动和漏检。单帧指标好不代表时序稳定抖动严重的模型没法用。我自己踩过最深的坑是早期图省事把 2998 张随机切了验证集指标 0.9 以上结果换一段路测直接掉到 0.5。后来老老实实按场景划分、做交叉验证指标虽然没那么好看但上线后稳定得多。数据量小的时候验证的诚实比指标的高低重要得多。希望帮到你。本文还有配套的精品资源点击获取