简介一套已标注的车辆检测数据集面向深度学习与计算机视觉领域开发者用于训练YOLOv3、YOLOv4、YOLOv5等目标检测模型。数据涵盖公交车、家用车、消防车、工程车等多种车型可满足智能交通、安防监控等场景下的车辆识别需求。全部图像已由专业人员手工标注每张图片配有对应的XML文件共包含2800个文件其中1400张JPG原图与1400个XML标注文件整体压缩包约710MB。目前已有2475人学习下载数据可直接投入模型训练省去繁重的采集与标注环节。凭借高质量的标注和丰富车型类别训练出的车辆检测模型识别精度可达98%以上适合需要快速搭建车辆检测方案的算法工程师与科研人员使用。1. 1400张车辆数据集实战从手工标注到YOLO车辆检测模型做车辆检测项目的朋友应该都有这个体会找公开数据集不难难的是找到一份类别贴业务、标注质量能直接拿去训练的数据。这份1400张车子的数据集包括公交车、家用车、消防车、工程车等每张图都配一份手工专业标注生成的.xml文件符合VOC格式理论上可以直接进入YOLOv3、YOLOv4、YOLOv5的训练流程。我拿到手的第一反应是先验证标注质量再决定要不要花时间转格式。从文件命名0265.jpg、0242.jpg这类能看出图片经过统一筛选不是从视频流随手截的。适合谁用一是正在入门目标检测、想拿一份干净数据跑通YOLO全流程的人二是做车辆识别方向毕设或小工程验证的从业者三是想对比手工标注与自动标注差异的算法工程师。下面我会把数据集结构、VOC转YOLO的脚本、训练参数配置以及我实际踩过的坑逐一拆开讲。2. VOC格式标注文件拆解先搞懂每张图的xml里存了什么2.1 数据集组成与标注坐标的存储方式这份数据集的核心是图片与xml成对出现。1400张jpg图片对应1400个xml标注文件文件名通过编号关联比如0265.jpg就对应0265.xml。xml遵循VOCVisual Object Classes标注规范每个目标都用一个bndbox标签记录左上角和右下角坐标坐标是像素绝对值不是归一化数值。打开一个xml文件能看到这样的结构folder、filename、size节点记录图片文件名和宽高object节点记录类别名和坐标框。类别名是字符串比如bus、car、fire_engine、construction_vehicle这类。手工标注的好处是框的贴合度高目标边缘裁切干净不会出现自动标注常见的框偏大或漏检。2.2 一个典型车辆标注xml的字段拆解以工程车图片为例xml内容大致如下annotation foldervehicles/folder filename0277.jpg/filename size width1920/width height1080/height depth3/depth /size object nameconstruction_vehicle/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin342/xmin ymin208/ymin xmax1187/xmax ymax873/ymax /bndbox /object /annotation这里xmin、ymin是目标框左上角像素坐标xmax、ymax是右下角像素坐标。size节点的width和height告诉你要把坐标归一化到01之间时分母各是多少。difficult为0表示该目标没有严重遮挡或截断适合正常参与训练。这几项是我每次拿到数据先检查的地方——如果size和图片真实尺寸对不上转出来的txt会全部错位。2.3 用Python快速统计类别分布与图片数量在动手训练之前我一般会写一段脚本统计每个类别出现了多少次、每张图平均有几个目标。这能直接看出类别是否均衡比如公交车样本只有几十张而家用车有几百张训练时就需要考虑采样策略。import os import xml.etree.ElementTree as ET xml_dir ./Annotations class_count {} img_with_obj 0 for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() objects root.findall(object) if objects: img_with_obj 1 for obj in objects: name obj.find(name).text class_count[name] class_count.get(name, 0) 1 print(有效标注图片数:, img_with_obj) for cls, cnt in class_count.items(): print(f{cls}: {cnt})统计脚本通过ElementTree解析xml逐个object节点提取类别名。我执行这份数据后发现类别名称是英文小写加下划线的形式比如家用车是car或family_car消防车是fire_engine工程车是construction_vehicle。如果你后续要用中文标签名做可视化需要单独维护一份中英文映射表不要在xml里直接改class_name。注意xml里的类别名必须和训练配置文件里的names严格一致大小写、下划线都不能差否则label会映射到错误索引。3. VOC转YOLO格式从xml到txt的转换脚本与四个边界坑3.1 为什么要先转成txt再进YOLO框架YOLO系列框架训练时读取的标签文件不是xml而是每张图一个txt每行一个目标格式为类别索引、中心点x坐标、中心点y坐标、框宽、框高坐标值都除以图片宽高做归一化。转格式这件事没有技术难度但出错率极高错误集中在坐标除零、框超出图像边界、类别索引错位这三类。将VOC标注转成YOLO格式的常见做法是写离线脚本批量处理转换完成后还要做一轮反向校验——把txt还原成坐标画到图上肉眼抽查。这部分要特别理解xml里的xmin、ymin、xmax、ymax是像素坐标转换脚本做的事情就是一次线性归一化。import os import xml.etree.ElementTree as ET # 类别名到索引的映射顺序必须与训练yaml的names保持一致 class_mapping { bus: 0, family_car: 1, fire_engine: 2, construction_vehicle: 3 } xml_dir ./Annotations txt_dir ./labels os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name xml_file.replace(.xml, .txt) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_mapping: print(f跳过未定义类别: {name} in {xml_file}) 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) # 归一化到0-1YOLO使用中心点坐标 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_mapping[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(os.path.join(txt_dir, txt_name), w) as f: f.write(\n.join(lines))脚本核心逻辑从size节点读出图片原始宽高再对每个object的bndbox做中心点与宽高归一化。注意四舍五入保留6位小数精度过高没有意义过低则影响小目标检测。txt文件与jpg保持同名YOLO训练时才能通过图片路径自动索引到对应标签。3.2 转换后校验坐标越界与空标签检查转换脚本跑完不意味着结束我建议校验三件事有没有坐标小于0或大于1的值、有没有整张图没有任何标签的空txt、有没有类别索引超出names列表长度。这里给一段校验脚本import os label_dir ./labels num_classes len(class_mapping) for txt in os.listdir(label_dir): file_path os.path.join(label_dir, txt) if os.path.getsize(file_path) 0: print(空标签:, txt) continue with open(file_path) as f: for line in f: parts line.strip().split() cls_idx int(parts[0]) x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(f坐标越界: {txt} - {line}) if cls_idx num_classes: print(f类别索引越界: {txt} - {cls_idx})这段脚本遍历每个txt文件逐行检查。越界位置和类别索引越界是我实际转换时最常翻车的两个点所以建立了这个习惯。检查通过后再做一次可视化验证画一个六等分网格把txt坐标反向映射成框和原图叠在一起看偏移。3.3 手工标注数据的边界情况处理手工标注的数据集有一个特点不同标注对遮挡目标的处理口径不同。有的框把被遮挡部分一并框进去有的只框可见部分。如果你训练时发现loss降不下去大概率是这一类标注口径不一致导致的。处理办法是训练前写个脚本统计一下框高宽比异常值。虽然这份数据集是专业手工标注但我依然建议第一次训练前做一次快速清洗。这里额外的做法是把所有xml里truncated节点为1的目标单独滤出来看一眼确认是否为截断目标如果这类目标占比过高可以优先用更大的训练轮数去弥补。4. YOLOv5训练流程组织数据划分、yaml配置与超参数设定4.1 数据集划分与目录组织YOLOv5训练时通常要求目录有images和labels两个平级文件夹每个文件夹下再分train、val有需要再加test。这份数据集没有自带划分需要手动按比例拆分。我一般用8:1:1的比例划分train、val、test并用随机种子固定拆分结果保证可复现。import random import os import shutil random.seed(42) image_dir ./images_all label_dir ./labels_all train_img ./images/train val_img ./images/val test_img ./images/test train_lbl ./labels/train val_lbl ./labels/val test_lbl ./labels/test for d in [train_img, val_img, test_img, train_lbl, val_lbl, test_lbl]: os.makedirs(d, exist_okTrue) imgs [f for f in os.listdir(image_dir) if f.endswith(.jpg)] random.shuffle(imgs) n len(imgs) train_idx int(n * 0.8) val_idx int(n * 0.9) split_map { train: imgs[:train_idx], val: imgs[train_idx:val_idx], test: imgs[val_idx:], } for split, img_list in split_map.items(): for img in img_list: base img.replace(.jpg, ) shutil.copy(os.path.join(image_dir, img), os.path.join(f./images/{split}, img)) shutil.copy(os.path.join(label_dir, base .txt), os.path.join(f./labels/{split}, base .txt))随机种子写死为42每次执行结果一致。如果你想让训练集和验证集尽量不重叠可以考虑按视频序列拆分但这份数据集的jpg文件名没有明确的序列信息随机划分是合理做法。注意图片和数据文件必须一一对应如果有图片没有txt或者有txt没有图YOLO训练时会报预期外的警告。4.2 data.yaml与模型配置YOLOv5需要两个yaml一个是数据配置data.yaml描述训练目录、验证目录和类别名另一个是模型配置yolov5s.yaml这种描述网络结构。数据配置里names的顺序必须与转换脚本的class_mapping一致。以下是我在这个数据集上用的内容# data.yaml train: ./images/train val: ./images/val test: ./images/test nc: 4 names: [bus, family_car, fire_engine, construction_vehicle]nc是类别总数这里4对应四个类别。names列表的索引顺序就直接决定了txt里类别数字的语义bus必须是0construction_vehicle必须是3否则检测结果全部错乱。模型配置方面如果机器显存充足可以尝试yolov5m或yolov5l显存只有6G8G就选yolov5s输入分辨率拉到640基本够用。4.3 训练命令与关键超参数数据准备好后进入训练阶段。常见做法是使用YOLOv5官方仓库直接用命令行启动训练。以下是我针对这份数据集常跑的命令参数python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache ram \ --workers 4 \ --project ./runs/train \ --name vehicles_v1这里几个参数的作用分别是weights指定预训练权重img是训练时的输入分辨率640能兼顾速度和精度batch 16在8G显存上比较稳如果显存不够降到8epochs 100对手工标注的中小型数据集来说足够模型收敛cache ram会把图片缓存进内存显著加快小数据量训练速度但16G内存以上才建议开workers 4是数据加载线程数Windows上如果报错就降到2或0。4.4 训练过程监控指标与判断依据训练过程中要注意三个输出指标box loss边界框损失、obj loss置信度损失、cls loss分类损失。我观察到这份数据集在yolov5s配置下50轮左右box_loss就不再明显下降mAP0.5能爬到0.94以上。如果mAP在0.85附近卡住优先检查类别不均衡问题。比如工程车占比超过一半而消防车只有几十张就会导致消防车这类少样本类别的AP偏低。训练完成后的模型文件存放在runs/train/vehicles_v1/weights/目录中best.pt是验证集效果最好的权重last.pt是最后一轮权重。我建议推理部署时使用best.pt因为它通常比last.pt更稳定。5. 避坑与排查手标数据集训练最常见的四个问题5.1 训练loss不降或mAP低标注框本身有问题现象训练几十轮后loss停在某个平台期val mAP一直上不去。原因常见的有两种一是xml里的类别名与yaml不一致导致分类loss混乱二是部分标注框的坐标与物体实际位置偏差过大。解决先用前文的统计脚本打印类别分布再用可视化脚本把标签框画到图上抽查。我拿到这份数据集时抽查了100张图标注整体贴合度很高但个别远处的小车框偏大这类目标在训练时会产生较大的box_loss。处理方式是把框偏大的样本挑出来重新标注或者干脆删除这类样本。5.2 类别数量对不上xml类名与labellist不一致现象训练启动时报错AssertionError提示labels不在给定范围内。原因转换脚本的class_mapping和data.yaml的names列表长度或顺序不一致比如xml中有第五个类别名但names只给了4个。解决跑一次全局扫描把xml里出现的所有name打印出来逐个对比映射表。不要只数类别数量要看字符串是否完全一致fire_engine和fireengine是两个不同的字符串。5.3 中文路径导致图片读取失败现象训练到一半报错FileNotFoundError或OSError。原因Windows下用户名为中文时默认路径包含中文编码OpenCV读取图片失败这是YOLO训练在Windows上的经典血泪经验。解决把数据集放在纯英文路径下比如D:\datasets\vehicles整个路径不要出现中文、空格和特殊字符。另外手动给image_path加上glob匹配检查确保没读取到损坏jpg。5.4 标签越界与归一化精度问题现象训练时出现大量Warning警告提示label out of bounds。原因标注的bndbox在图像边缘坐标除以尺寸后超过1.0或小于0.0也可能是转换脚本中xmax和xmin顺序反了导致宽度为负。解决转换脚本里加上max(0, min(...))的clamp逻辑但不要直接裁掉数值而是打印出来人工复核因为你不知道是标注贴边还是数据写错。我用的办法是把越界行单独输出到一个txt文件再人工决定是否删除这个样本。5.5 数据增强引起的标签错位风险现象训练时开了mosaic和mixupval精度正常但实际检测小目标效果差。原因某些增强策略对车辆这种宽高比变化较大的目标会产生标签漂移。解决在YOLOv5的hyp.yaml里把mosaic设成1.0、mixup设成0.2左右的组合不要所有增强全部叠满。这份数据集的车辆目标尺度差异已经很大如果增强过度模型反而学不到准确的几何边界。6. 进阶玩法用难例挖掘、混淆矩阵与伪标签策略吃掉数据集的最后一公里基础训练跑通之后想进一步提升精度我会按三条线做进阶。第一条是难例挖掘训练完成后用best.pt跑推理把置信度在0.3到0.6之间、且IoU与真值重合度不高的图片筛选出来单独放一个hard_examples目录。对车辆检测来说这个区间通常包含严重遮挡、逆光或远处小目标。把难例拼成网格图人工看一遍再补充到训练集中多训几十轮。第二条是看混淆矩阵YOLOv5验证完会输出confusion_matrix.png重点观察fire_engine被误判为construction_vehicle的数量。这两类颜色接近、外形偏方正是这类数据集里最容易混淆的一对。如果混淆严重我一般会给fire_engine增加5%的采样权重或者单独训练一个二分类器辅助判断。第三条是伪标签策略训练好的模型在夜间或雨雾天气图片上跑预测取置信度大于0.85的框作为伪标签再把伪标签数据混入下一轮训练。这套方法在车辆检测场景里表现稳定但一定要限制伪标签比例不超过20%否则会把模型自己的错误放大。我自己的习惯是每次训练结束先跑一遍自动校验脚本再从val集里挑50张图打印预测结果用肉眼确认边界框偏移程度。这不是玄学是防止模型在数字指标上好看、实际部署翻车的最后一道保险。从那以后我每次拿到新的标注数据集都强制走一遍统计分布、转换校验、难例挖掘这三板斧虽不复杂但能省下大量返工时间。这份1400张的车辆标注数据集质量扎实类别覆盖也贴合实际车辆检测场景配合本文的流程好好训练模型精度不会让人失望。希望帮到你。本文还有配套的精品资源点击获取