
简介面向目标检测初学者与算法调试人员这份车辆检测数据集按 VOC 规范整理覆盖 bus、car、SUV、taxi、truck 五类常见车型可用于训练 YOLO、SSD、Faster R-CNN 等检测模型也能帮助理解不同标注格式的差异。资源共 2000 个文件包含 699 张 JPG 图像、699 份 XML 标注和 700 份 TXT 标注两类标签分目录存放TXT 便于 YOLO 系列直接加载XML 可走标准 VOC 流程切换框架时无需重新标注。压缩包大小约 466.3MB当前已有 961 人学习下载。拿到后可先划分训练集与验证集检查五类样本及边界框的分布情况按需转换为 COCO 或自定义格式也可直接用于主流检测框架训练统计目标数量、框尺寸分布为数据增强和超参数调优提供依据。适用于智慧交通、车辆识别等实验项目也能作为毕业设计或课程实践的数据支撑省去自行采集、清洗与标注图像的大量时间。1. VOC各种类型车辆检测数据集多类型车的难点不在“检测”而在“类型”我第一次意识到“各种类型”这四个字的分量是在一个园区道闸项目里。单类别模型对轿车效果很好换到卡车和三轮车就疯狂漏检后来把标注统一整理成VOC格式、按类型重新清洗数据mAP才从0.43拉到0.71。所谓VOC各种类型车辆检测数据集核心是把车辆细分成多种目标类别并按照VOC标注规范把图片和XML组织成标准目录让YOLO、Faster R-CNN这类检测器可以直接吃进去训练。这篇文章写给两类人想找一个可用的车辆检测数据集训练自己模型的人以及手里有杂散图片、想把它们整理成标准VOC数据集的工程师。2. 读懂VOC标注格式XML关键字段与目录结构的组织方式VOC不是一种模型而是一套“图片标注索引”的数据组织协议。它把图像、标注和数据集划分拆成三个目录又被大量检测框架原生支持因此成了交换车辆检测数据集最稳妥的中转格式。这一章先讲清楚目录结构再讲XML里五个字段的含义以及它们对“各种类型”车辆模型训练的影响。2.1 目录结构是第一个坑JPEGImages、Annotations、ImageSets/Main缺一不可一个标准的VOC数据集目录是三层结构JPEGImages放原图Annotations放同名XMLImageSets/Main下放train.txt、val.txt、trainval.txt。train.txt里每行是一个不带扩展名的图片ID训练脚本就靠它找到图片和XML三个目录必须严格对应。我看到不少新手把标签直接丢在根目录训练程序找不到ImageSets报错后还说是模型的问题。目录结构确定后要保证图片文件名和XML是一一对应的。常见做法是用脚本核对例如遍历Annotations里每个XML的filename字段再在JPEGImages里确认同名文件存在不一致时先修数据不要心存侥幸。我自己见过一张jpg被压缩两次后文件名带了后缀导致一张图对不上XML训练时少了几十张图当时还以为是自己样本不够。VOC这套结构之所以能成为事实标准是因为它把“图片”和“标注”分离只用索引文件来组合不同训练集。同一个Annotations可以被train.txt和val.txt引用想看某个集有哪些图直接看索引想换训练集划分只需重新生成一份txt不必复制图片。这对“各种类型车辆”这类多标签数据尤其重要因为同一张街景里可能有轿车、卡车、公交重新划分时不用动XML只改索引就能平衡各类别在训练和验证中的占比。2.2 XML标注的五个关键字段name、bndbox、size、truncated、difficultXML每个object块里name是类别名bndbox是四个坐标外面的size是图片宽高。训练器读取时只用这三样但truncated和difficult会影响评估和增强策略。truncated为1说明目标被截断difficult为1说明目标极难辨认很多模型训练时会忽略difficult的框车辆检测中被栏杆挡住的半边车、被前车遮挡的尾部都建议标difficult1而不是删掉。删掉会让模型误以为这种车不存在保留并标记又会让评估更真实。坐标类型也值得强调。VOC规范要求bndbox里的值用整数像素但有些转换工具会写成浮点数解析时int()一把过训练器内部再做插值就可能把框扩大或缩小。稳妥的做法是统一用int(xmin)、int(ymin)、int(xmax)、int(ymax)收口四舍五入影响不大。多个类型共用一个XML是这个格式最方便的地方。一辆车在画面里可能同时有轿车、SUV和皮卡只要object块按顺序罗列训练器会自动作为多个实例处理。早期我犯过一个错用一张图一个标签的旧思路去管理同一个目标重复画框类别统计翻倍模型精度反而下降。现在拿到数据集第一件事就是跑一遍读取脚本把每个XML里的name完整列出来确认没有重复和大小写混用。import xml.etree.ElementTree as ET tree ET.parse(Annotations/000001.xml) root tree.getroot() img root.find(filename).text w int(root.find(size/width).text) h int(root.find(size/height).text) for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) print(img, name, xmin, ymin, xmax, ymax, w, h)逻辑说明先把XML解析成ElementTree对象用find(filename)拿到当前图名再用find(size/width)取宽高。object块可能不止一个所以要findall(object)遍历对每个目标打印类别和坐标。这段代码是所有后续清洗脚本的底子转换、质检都以它为骨架。参数说明xml_path换成自己的标注文件绝对路径即可。注意find(size/width)这类带斜杠的写法是ElementTree支持的子节点路径比连续find再find省事但前提是XML结构里size必须位于annotation一级如果拿到的是VOC衍生格式要先确认层级。3. 从COCO/KITTI/BDD100K转成VOC转换脚本与四个边界坑大部分公开车辆数据集不是原生VOC格式。COCO是单个JSONKITTI是txtBDD100K是带attributes的JSON直接拿去训练会遇到格式兼容问题。把它们统一转成VOC是构建“各种类型车辆检测数据集”最常见的第一步。这一章给出COCO转换脚本讲清KITTI和BDD100K的差异再列一个转换后验证清单。3.1 从COCO的JSON转成VOC的XML脚本与调用参数COCO格式的车辆数据很多但它的标注文件是单个JSON把图片、类别、标注放在一起。转到VOC时最常用的方案是写脚本把JSON拍平为逐图XML。常见做法是先读categories建立id到name映射再按image_id把annotations聚合最后每张图生成一个XML。下面这个脚本我一直在用足够处理绝大多数场景import json import os import xml.etree.ElementTree as ET def coco_to_voc(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: coco json.load(f) cat_id2name {cat[id]: cat[name] for cat in coco[categories]} anns_by_img {} for ann in coco[annotations]: if ann.get(iscrowd, 0) 1: continue anns_by_img.setdefault(ann[image_id], []).append(ann) for img in coco[images]: if img[id] not in anns_by_img: continue root ET.Element(annotation) ET.SubElement(root, folder).text os.path.basename(out_dir) ET.SubElement(root, filename).text img[file_name] size ET.SubElement(root, size) ET.SubElement(size, width).text str(img[width]) ET.SubElement(size, height).text str(img[height]) ET.SubElement(size, depth).text 3 for ann in anns_by_img[img[id]]: obj ET.SubElement(root, object) ET.SubElement(obj, name).text cat_id2name[ann[category_id]] x, y, w, h ann[bbox] xmin max(0, int(round(x))) ymin max(0, int(round(y))) xmax min(img[width], int(round(x w))) ymax min(img[height], int(round(y h))) box ET.SubElement(obj, bndbox) ET.SubElement(box, xmin).text str(xmin) ET.SubElement(box, ymin).text str(ymin) ET.SubElement(box, xmax).text str(xmax) ET.SubElement(box, ymax).text str(ymax) tree ET.ElementTree(root) name os.path.splitext(img[file_name])[0] .xml tree.write(os.path.join(out_dir, name), encodingutf-8, xml_declarationTrue)逻辑说明先加载JSON建立category_id到类别名的映射这一步决定了车辆类型最终叫什么。接着把annotations按image_id聚合保证一个XML里能包含同一图片上的多个不同车辆类型。输出XML时bbox从COCO的x,y,w,h转换为xmin、ymin、xmax、ymax并用max/min把越界部分裁回图像范围内这是多类型车辆数据集里常见的脏数据来源。参数说明json_path填COCO标注文件比如instances_train2017.jsonout_dir填输出XML的目录建议直接命名为Annotations。注意脚本不会生成ImageSets索引索引要另外用图片列表生成train.txt和val.txt这个区隔是VOC结构的关键不要漏掉。3.2 KITTI和BDD100K转换浮点坐标与类别命名差异KITTI的标注是txt每行代表一个目标常见类别包含Car、Truck、Van、Pedestrian、Cyclist、Tram。如果你只需要“各种车辆类型”通常会把Van并入Truck或单独保留。一个容易翻车的地方是坐标KITTI的bndbox是浮点数转换时要决定用round还是int另一个坑是Tram这类别在车辆检测里和公交重合度很高合并与否要提前定。BDD100K的JSON里直接给出了box2d的x1、y1、x2、y2还附带了attributes。把attributes里的天气、时段信息丢进XML比较麻烦但不代表没用训练时按“白天/夜晚”分组做验证能看到夜间场景对哪些车辆类型特别不友好。这里我的经验是转换时顺手把attributes另存一份CSV等模型训完再结合它分析失败case效果比事后翻图好得多。数据源标注格式车辆类别示例坐标类型主要转换动作COCOJSONcar/truck/bus浮点xywhJSON转XMLx,y,w,h转四角KITTItxtCar/Truck/Van/Cyclist/Tram浮点xyxytxt解析并统一类别名BDD100KJSONcar/truck/bus/trailer整数xyxy按image_id聚合保留属性3.3 转换后必做验证清单三张表一眼看出哪里有错转换脚本跑完别急着训练。我一般会先跑三张表的检查第一张统计每个类别的目标数量最好打印成“类别名数量”的表格第二张统计每张XML的object数找到object数为0的文件这类文件会让部分训练器报错第三张随机抽5到10张图把XML的框画上去肉眼核对。前两张用脚本几秒出结果第三张是人工活但最能发现坐标偏移比如COCO转VOC后xmax反超图像宽度画图时会一眼看出来。提示把这三张检查写进同一个脚本以后每次拿新数据都能跑一遍不依赖肉眼盯文件。这是我在多类型车辆数据上最划算的一次投入。4. 自建多类型车辆VOC数据集类别设计、标注流程与清洗质检如果公开数据集满足不了特定场景比如厂区里全是工程车、港口全是集卡就得自己建。自建的收益是样本贴合你的落地场景代价是类别设计、标注和质检都要自己管每一步都有可以预见的坑。4.1 类别设计先于标注从场景反推车辆类型清单很多人拿到图片就开标结果类别乱到没法用。常规做法是先用场景约束类型清单。停车场场景关心轿车、SUV、面包车高速场景关心轿车、卡车、挂车园区道闸则还要三轮车、摩托车、自行车。把场景列出来再对照图片素材初步定出5到8个类型同时给每个类型写一句判定标准比如“车长超过6米的货车算卡车”“带货箱的三轮载货车算三轮车”。没有判定标准两个标注员会把同一类车标成不同名称最后模型混淆严重。车辆检测数据集里常见的问题是类别过细、样本过少。宁可先合并再细分先把“皮卡”和“货车”合并为“卡车”训练一版验证mAP稳定后再拆开看两者是否可分。这样做的好处是前一轮数据集的构建和清洗流程可以复用拆类只影响XML里的name字段不影响图片匹配。4.2 用labelImg产出VOC XML标注流程与三个常用快捷键标注工具最常见的是labelImg选PascalVOC模式保存后直接生成XML。开始之前要确认一件事标注框的姿态和遮挡。VOC没有显式的“遮挡程度”字段只有difficult因此标注时要约定“目标超过50%被挡就勾difficult”。这不是无所谓的小事因为YOLO等模型训练时会按difficult过滤部分框过滤策略不同结果差别很大。标注流程大体是打开图像目录W键开始框选D键切到下一张CtrlS保存XMLA键回退上一张。每标完一批就及时保存软件崩了也不用重标。另一个容易被忽略的点是labelImg的默认“自动保存”选项如果不打开切图时不会写XML最后输出一堆空标注这个坑我至少踩过两次。4.3 清洗质检脚本统计类别、检查越界框、查空文件用脚本代替人眼做标注质量检查非常必要。下面是一个我常用的质检脚本遍历Annotations目录里的所有XML输出类别统计、空文件列表和非法框列表import os import glob import xml.etree.ElementTree as ET from collections import Counter def inspect_voc(ann_dir): classes Counter() empty_files [] bad_boxes [] for xml_path in glob.glob(os.path.join(ann_dir, *.xml)): root ET.parse(xml_path).getroot() img root.find(filename).text W int(root.find(size/width).text) H int(root.find(size/height).text) objs root.findall(object) if not objs: empty_files.append(img) for obj in objs: name obj.find(name).text classes[name] 1 box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) if x1 0 or y1 0 or x2 W or y2 H or x2 x1 or y2 y1: bad_boxes.append((img, name, (x1, y1, x2, y2))) print(类别统计:, dict(classes)) print(空文件数:, len(empty_files), empty_files[:10]) print(异常框数:, len(bad_boxes), bad_boxes[:10])逻辑说明这个脚本用Counter统计每个类别出现次数同时检查空XML和越界框。empty_files能找出漏标的样本bad_boxes能揪出坐标异常这两类问题在训练时往往表现为loss不降或验证mAP异常。参数说明ann_dir是Annotations目录路径运行后直接看三个输出。如果bad_boxes数量超过总数的1%就需要回到标注工具逐张修正不要用脚本去硬改坐标因为有些框是标注员主观框偏了机器改不回来。这个脚本同样适合新下载的数据集运行一次就可以评估是否直接可用。在YOLOv8训练自己的车辆检测数据集时VOC格式不是最终格式还要转换成YOLO的txt但这一步放在清洗之后做先质检后转换会少很多麻烦。后面我会讲这个转换和验证的写法。5. VOC车辆数据集排查记录5条踩坑经验从现象到解决这一章是踩坑记录。每条都按现象、原因、解决的顺序写直接对照自查能省下大半天调试时间。5.1 训练报错“width mismatch”XML里的size和真实图像对不上现象用VOC数据集训练自己的检测网络时某些批次直接抛错提示标注框宽度和图片尺寸不匹配。原因转换脚本或手工标注时XML里size/width、height写成了裁剪前的尺寸或者图片被无感旋转后保存的XML还是旋转前的宽高。这很隐蔽因为只有个别图片出错训练进程要迭代到该样本才崩。解决用质检脚本统一核对XML的size和JPEGImages里的实际宽高不一致就直接改XML或用程序重写size字段。这块改动尽量做成数据预处理的一部分而不是每个训练前手动检查。5.2 验证集mAP很高但现场漏检严重train和val存在同一场景的图现象模型在VOC验证集上mAP很漂亮跑到现场却漏检频繁尤其是卡车和三轮车。原因数据划分时把同一段视频抽帧的结果同时放进了train.txt和val.txt两边的背景几乎一致模型等于默写答案。解决按视频片段或拍摄地点分组划分数据集同组画面要么全在训练集、要么全在验证集。对车辆数据集来说还要考虑同一辆车的多个连续帧不能因为画面看起来不一样就分到两侧。5.3 类别样本极端不均衡某类车型AP几乎为零现象训练完成后mAP整体可用但某类车型的AP只有0.05左右肉眼可见地漏检。原因数据集中轿车标了8000个框该类车型只有300个框。模型倾向于预测出现频率高的类别低频率类别很容易被背景抑制。解决第一优先收集该类别的补充样本尤其是不同角度、不同光照的图第二在清洗阶段对该类别做重复采样或复制粘贴增强。不要指望着靠调loss权重弥补权重调高了反而会让原本好的类别变差。5.4 转换后XML数量对不上同名覆盖导致图片和标注错位现象某数据集目录里图片有10000张JSON里标注了10000张图转换后Annotations只剩9500个XML剩下500张找不到标签。原因COCO的images里有同名文件比如来自不同子目录的img_0001.jpg直接按file_name写XML后写的覆盖先写的肉眼很难发现。解决转换脚本里用image的id作为文件名前缀比如0000000123_000001.xml从命名上规避冲突或者先检查file_name是否有重复有重复则改用id。这个坑在“各种类型”车辆数据集上尤其常见因为公开数据的子目录组织方式差异很大。5.5 框整体偏移半辆车身EXIF方向信息没归一化现象训练好的模型在大部分图上框得准但在部分手机拍摄的图上整个框向右侧偏移偏移量接近一张竖图的宽度。原因手机拍摄的JPG带EXIF旋转标志查看器自动转正但检测器的预处理直接按原始像素读取没有转正VOC标注的坐标是基于转正后图像的标准。解决数据入库前先用工具批量转正图片并清除EXIF方向信息再用转正后的图片重新生成XML。这个坑发生概率不高但一旦发生排查起来很绕而且只出现在特定来源的图片上容易被误判为训练参数问题。排查顺序建议先跑一次类别统计再查空文件和越界框最后按场景划分验证集。三个动作五分钟内完成能过滤掉上面至少三条问题。养成这个习惯之后新数据集入库的试错成本直线下降。6. 让VOC车辆数据集物尽其用训练参数与验证指标的进阶调法VOC数据集构建完成后的落地动作通常是把XML转成YOLO格式用YOLOv8训练自己的数据集。转换时每个XML对应一个txt每行写类别id和归一化的中心点xywh代码上不难实现难在参数和验证口径。data.yaml可以这样写train: voc_car/train.txt val: voc_car/val.txt nc: 5 names: 0: car 1: suv 2: truck 3: bus 4: tricycle要注意的是VOC的评估习惯是mAP0.5YOLO默认输出mAP0.5:0.95。多类型车辆检测里车型差异大mAP0.95会被小目标严重拖低只看0.5更容易看出类型短板。训练时mosaic增强建议保持默认但对车型比例悬殊的数据集可以把mosaic概率降到0.8左右避免过多小图拼接后让中小目标变形。验证指标的读法上不要只盯整体mAP多看一眼每类AP和混淆矩阵。如果bus和truck互相混优先检查标注时是否把中型客车标成了卡车而不是立刻调模型。这是数据驱动型排查习惯能把方向搞正确的唯一途径。我现在的习惯是任何新数据集入库前先跑一遍质检脚本把类别统计、空文件、越界框三张表看明白再谈训练。这个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取