VOC与YOLO双格式电瓶车检测数据集:从手工标注到YOLO训练实战
2026/9/14 1:35:30 网站建设 项目流程

简介:面向目标检测入门与电梯场景安全监控需求,这份电瓶车进入电梯检测数据集可帮助训练电梯内电瓶车识别模型,适合算法学习者、安防项目开发者做模型验证与原型演示。数据集包含两百张图片,同时提供VOC格式的XML标注与YOLO格式的TXT标注,压缩包内共计六百零二份文件,整体大小约十一兆,便于快速下载使用。标注工作全部使用LabelImg完成,类别为electric scooter,所有对象均手工绘制矩形框,共标注二百一十个目标框,位置准确、规则统一,可直接用于YOLO系列、Faster R-CNN等常见检测框架的训练和评估。已有二百零四人浏览学习,作为一份小样本实践资源,既可用于检测算法效果摸底,也可用于数据增强、迁移学习等实验基础,帮助快速搭建电瓶车检测项目的初始训练集。

1. 电梯口的电瓶车检测,先过数据集这一关

小区物业和写字楼对电瓶车进电梯这件事盯得很紧,摄像头装了不少,但真正能自动报警的没几个。原因不在摄像头,而在“认得出电瓶车”的目标检测模型从哪来。这个压缩包给的就是最底层的那块砖:200张真实电梯场景的jpg图片,一个类别electric scooter,手工用labelImg画了210个矩形框,同时给出Pascal VOC的xml和YOLO的txt两种标注格式。VOC格式方便复核和改错,YOLO格式可以直接喂给yolov5、yolov8这类训练管线。对刚入门目标检测的人来说,它是一份能看清“手工标注长什么样”的标准样本;对正在做电梯监控类项目的工程师,它又是预训练前值得合并进训练集的真实场景数据。

2. VOC与YOLO双格式标注,从XML到TXT的坐标换算逻辑

2.1 两种格式为什么同时存在

VOC格式的核心是xml文件,一个标注框对应一个<object>节点,里面写清楚类别名<name>,以及<bndbox>子节点下的四个坐标xminyminxmaxymax。这四个值都是像素绝对值,优点是肉眼直接能看出框的位置,labelImg打开就能继续编辑。YOLO格式则完全是另一套写法,每行代表一个目标,依次是类别id、x中心坐标、y中心坐标、框宽、框高,后四个值全部除以图片宽高完成归一化,范围在0到1之间。

大多数开源训练仓库直接吃YOLO格式的txt,不需要写代码解析xml;但如果要检查标注质量、做数据清洗、合并新标注数据,VOC的xml反而更友好。所以我一般会同时保留两种格式,xml作为“原件”,txt作为“生成物”。一旦发现某个框标歪了,在labelImg里改完xml,再重新生成txt,避免出现两份文件信息不一致的情况。

压缩包里一个典型xml的结构大致是这样:

<annotation> <filename>dianpingche_xyxr_189.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>electric scooter</name> <bndbox> <xmin>327</xmin> <ymin>214</ymin> <xmax>588</xmax> <ymax>641</ymax> </bndbox> </object> </annotation>

对应到YOLO脚本,这一行变成0 0.357 0.594 0.204 0.593。坐标换算不是拍脑袋写的,而是固定公式:x_center = (xmin + xmax) / 2 / widthy_center同理用高度算;w = (xmax - xmin) / widthh = (ymax - ymin) / height。用上面的数字带进去,x_center等于(327 + 588) / 2 / 1280 = 0.357,y_center等于(214 + 641) / 2 / 720 = 0.594,宽高分别是0.2040.593。YOLO格式不关心框的左上右下角点,只关心中心点和宽高,因此在后续读代码时不要拿VOC的思维去套。

含义VOC字段YOLO第N列是否归一化
类别object/name第0列否,类别id
中心x(xmin+xmax)/2第1列是,除以width
中心y(ymin+ymax)/2第2列是,除以height
框宽xmax-xmin第3列是,除以width
框高ymax-ymin第4列是,除以height

顺带一提,KITTI标注转YOLO是另一套逻辑,KITTI多了truncated、occluded这类属性字段,坐标转换时只取2D框部分,核心换算公式与上面完全一样,区别在于字段位置而不是几何关系。

2.2 用Python脚本校验坐标越界与负宽高

手工标注最常见的坑不是框画歪,而是坐标越界。标注时手一抖,xmax就可能超出图片宽度几个像素;或者拖拽时鼠标顺序反了,出现xmax小于xmin的负宽框。这两类问题在VOC格式下用labelImg看不出来,但转成YOLO格式后,归一化坐标会出现大于1或小于0的值,训练时锚点匹配直接错乱。

我会在拿到数据集后先跑一遍校验脚本:

import xml.etree.ElementTree as ET import os annot_dir = "annotations" for xml_name in sorted(os.listdir(annot_dir)): if not xml_name.endswith(".xml"): continue tree = ET.parse(os.path.join(annot_dir, xml_name)) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) for obj in root.iter("object"): 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 xmin < 0 or ymin < 0 or xmax > w or ymax > h: print(f"{xml_name}: 越界框 {xmin},{ymin},{xmax},{ymax}") if xmax <= xmin or ymax <= ymin: print(f"{xml_name}: 非正宽高 {xmin},{ymin},{xmax},{ymax}")

这段脚本做了两件简单但重要的事:第一,逐个访问xml里的size/widthsize/height,拿到图片真实尺寸;第二,对每个<object>下的<bndbox>做边界检查。越界的框用clip操作夹紧到[0, w-1][0, h-1]区间即可,负宽高的框则需要回到labelImg里打开原图重新拖拽。实际跑数据时,200个xml也就几秒钟,别跳过这一步。

从这个数据集自带的txt数量也可以反推一个事实:标注数210,图片200张,说明有少数图片里同时框了多辆电瓶车。单类别、单目标为主的分布,会让训练时每张图的“正样本密度”偏低,batch里容易出现大量只有背景的候选区域,这个特点在后面调损失函数时要特意留意。

3. 200张样本的训练前准备:划分、增强与YOLO数据配置

3.1 训练集验证集怎么划分才不会翻车

200张图的规模非常小,一个直观问题是:训练集和验证集怎么分。按常见的8比2划分,训练集160张,验证集40张。但这里有个容易被忽略的细节:电梯场景里同一个角度、同一辆电瓶车在连续帧里高度相似,如果划分时不做随机洗牌,验证集里可能混入大量与训练集“长得一模一样”的图片,评估结果虚高。

推荐的做法是设定固定随机种子,把图片路径和标注文件同步打乱后切分:

import os import random image_dir = "images" train_ratio = 0.8 random.seed(42) # 固定种子,保证可复现 images = [f for f in os.listdir(image_dir) if f.lower().endswith(".jpg")] random.shuffle(images) split = int(len(images) * train_ratio) train_imgs = images[:split] val_imgs = images[split:] with open("train.txt", "w") as f: for img in train_imgs: f.write(os.path.abspath(os.path.join(image_dir, img)) + "\n") with open("val.txt", "w") as f: for img in val_imgs: f.write(os.path.abspath(os.path.join(image_dir, img)) + "\n")

这个脚本输出的train.txt和val.txt可以直接被yolov5的train.py读取。注意yolov5在加载标签时,是拿图片路径前缀自动去找labels目录下同名txt的,所以图片名和标注txt文件名必须一一对应,切分时只写jpg路径即可,不需要手动复制标注文件。如果发现某些图片没有对应xml或txt,训练时yolov5会静默跳过,但这样会让batch里有效图片变少,最好在切分前先做一次存在性检查。

从另一个角度说,200张的训练集对模型来说几乎不可能学出很强的泛化能力。它更适合做三件事:验证数据管线是否跑通、对比不同预处理策略的效果、作为更大训练集的种子数据。想直接部署到电梯监控里,实测误检率可能不低,这符合数据集说明里“不对训练精度作保证”的定位。

3.2 电梯场景下的数据增强参数怎么调

yolov5默认的增强参数面向COCO等自然图像,直接用在电梯场景并不合适。电梯轿厢内部是金属壁面,灯光偏冷,电瓶车经常出现在画面角落且伴随镜面反光,这种光照环境需要更强的色彩抖动来模拟不同梯厅的灯管差异。

我一般会在hyp.scratch-low.yaml基础上做如下调整:

参数默认值建议值理由
hsv_h0.0150.03电梯内灯管色温不同,色调偏移加大
hsv_s0.50.7金属反光会让饱和度剧烈变化
hsv_v0.40.5亮度抖动增强,适配逆光和暗梯厅
flipud0.50.0电瓶车不会倒置,上下翻转是负迁移
fliplr0.50.5左右翻转保留,场景对称性高
mosaic1.00.5200张小样本,mosaic过强会割裂车辆整体轮廓

上下翻转flipud必须关掉,这是最容易被新手忽略的一项。电瓶车倒过来在物理世界里不存在,模型一旦把“车把在上”当作特征,遇到车辆侧翻或人推车调整姿态时就容易漏检。mosaic从1.0降到0.5的原因是小数据集本身只有200张,拼接时一张图被切成四块,原有的真实语义被破坏,前几轮训练里模型连最基本的轮廓还没学会,反而被局部纹理带偏。

色彩增强的幅度可以适当加大,电梯监控经常是24小时连续工作,白天窗外强光、夜里红外人脸补光,同一个摄像头在不同时段拍出来的色调差别很大。hsv_v提高到0.5后,网络对亮度变化更不敏感,实测对暗光帧的鲁棒性提升明显。

3.3 数据配置文件要写清楚类别名

yolov5和yolov8的数据配置文件结构不完全一致,但核心字段相同。这个数据集只有一个类别,配置文件可以直接写成:

path: ./ train: train.txt val: val.txt nc: 1 names: 0: electric scooter

nc是类别数,这里只有1类;names列表的顺序决定了训练输出中类别id与名称的对应关系。如果后面合并了自己的电瓶车数据,比如想区分“电动自行车”和“电动三轮车”,nc要改成2,names要补上新类别名,同时所有标注txt的第0列要对应修改。这个类别名用的是electric scooter,和中文语境的“电瓶车”对应,但实际部署报警时往往希望显示成中文,训练后推理阶段再做一层名称映射即可,不影响训练过程。

验证一下划分后的数据量:160张训练图、40张验证图,训练时如果batch设为16,一个epoch是10个step,跑100个epoch刚好1000步。这个训练量级几分钟就能跑完一版,非常适合做实验对比。

4. YOLO训练参数与损失收敛判断,小数据集的收敛边界

4.1 训练命令与超参数选择

用yolov5训练这套数据时,命令不复杂:

cd yolov5 python train.py \ --data dataset.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --patience 20 \ --cache ram

参数含义:--weights yolov5s.pt用的是yolov5s预训练权重,虽然数据只有200张,但预训练权重里的底层特征对轮廓、边缘、颜色都有很好的先验,比随机初始化收敛快得多;--img 640是输入分辨率,电梯监控画面里电瓶车大概占画面宽度的20%到40%,640像素能保住车把和后视镜这类小结构;--batch 16在多数16GB显存显卡上都能跑,这个体量也不值得用更大的batch;--cache ram一次性把200张图读进内存,避免每轮epoch反复读磁盘。

--patience 20的意思是验证集损失连续20个epoch不下降就早停。小数据集跑到60到70轮时往往已经开始过拟合,表现在训练损失还在降、验证损失反而上升。早停让训练自动停在验证损失最低点,省掉反复手动调epoch数的时间。

如果你用的是AMD显卡,yolov5在Windows下有DirectML分支可以跑推理,但训练还是建议走NVIDIA CUDA。这套数据量不大,真正耗时的是反复实验标注和改进策略,把时间花在环境折腾上不划算。yolov5和yolov8之间的差异主要集中在模型结构和训练策略,数据集的YOLO txt格式两者通用,换版本时不需要改标注。

4.2 三类损失曲线分别看什么

yolo损失函数由三部分组成:box_loss衡量预测框和真实框的位置偏差,obj_loss衡量目标存在置信度,cls_loss衡量分类是否正确。这个数据集只有一个类别,分类任务极度简单,cls_loss基本不是瓶颈,重点盯前两个。

小数据集训练时,box_loss前30轮会有明显的快速下降,从初始的几个点降到0.5左右,之后曲线变平。如果验证集上的box_loss在某个点后开始反弹,说明模型开始死记训练集中的电瓶车姿态,而不是学习泛化特征。这时候再往下练只会加剧过拟合,正确的做法是回到第3章把增强幅度调大,或者增加新标注数据。

obj_loss在电梯场景里更值得关注。因为数据集中只有正样本框,没有标注“背景里有电瓶车但不需要检测”的负样本,模型对背景区域的目标置信度只能靠YOLO内部的背景采样来学习。后面几十轮里obj_loss小幅波动是正常的,但如果它持续走高,通常是输入分辨率不够,电瓶车在低分辨率帧里和墙角阴影混在一起,模型不知道该信谁。此时可以试--img 896,代价是显存占用翻倍。

训练完成后,用验证集评估:

python val.py \ --weights runs/train/exp/weights/best.pt \ --data dataset.yaml \ --iou-thres 0.5 \ --conf-thres 0.25

--iou-thres 0.5对应mAP@0.5,--conf-thres 0.25是置信度阈值,低于这个值的预测框会被过滤掉。只有210个标注框的小数据集,mAP@0.5:0.95这种更严格的指标会因为框边界的老鼠尾问题掉得很难看,参考价值远不如mAP@0.5。在业务评估时直接看mAP@0.5就好。

4.3 置信度阈值是部署前的最后一道旋钮

训练指标只能说明模型在验证集上的表现,真正部署到电梯门口时,场景变了,阈值必须重新调。电梯里如果常有小孩子骑滑板车,或者保洁推着银色的金属推车,这些物体和电瓶车在轮廓上高度相似,预测框会带着一个不高不低的置信度输出。

调试方法是把置信度阈值从0.25往上加到0.3、0.35、0.4,分别统计误检和漏检数量。误检率下降比较明显而漏检率还在可接受范围内时,就选择那个阈值。小数据集训练的模型往往置信度不够锐利,正样本的输出概率集中在0.4到0.8之间,阈值取太高会把真值也滤掉。

5. 从210个框扩展到更鲁棒的模型,难例挖掘与伪标签复核

模型跑通只是第一步,想让它在真实电梯场景里能扛住,下一步是扩数据。最有效的手段不是继续手工从零标注,而是从当前模型预测结果里找“难例”。

我一般会在验证集上跑一遍推理并保存预测结果:

python detect.py \ --weights runs/train/exp/weights/best.pt \ --source val_imgs/ \ --conf-thres 0.25 \ --save-txt

--save-txt会把每个预测框的类别、坐标和置信度写入runs/detect/exp/labels目录下的txt。接着用一段小脚本扫描这些文件,专门挑出置信度在0.3到0.7之间的框:

from pathlib import Path label_dir = Path("runs/detect/exp/labels") candidates = [] for label_file in label_dir.glob("*.txt"): for line in label_file.read_text().splitlines(): cls, x, y, w, h, conf = line.split() conf = float(conf) # 0.3以下多为背景误检,0.7以上模型已经掌握 # 中间置信度带的样本才是人工复标价值最高的 if 0.3 <= conf <= 0.7: candidates.append(label_file.stem + ".jpg") break for name in candidates: print(name)

为什么要刻意挑中间置信度带?低于0.3的预测大多是明显误检,标注价值是“教模型什么不是电瓶车”,适合作为负样本;高于0.7的框说明模型已经学得比较扎实,复标带来的增量信息很少。唯有置信度在0.3到0.7之间的那些,要么是遮挡严重,要么是角度刁钻,要么是光线诡异,是模型当前能力圈的边缘地带,把这些帧挑出来补充标注,能让模型更快学会真正的判别边界。

对于这些候选帧,不必从零开始画框。把它们交给labelImg,打开YOLO模式并加载labels.txt类别文件,模型预测出的框会原样显示为未确认的预标注。人工只需要修正画歪的边界、删掉完全错误的框,再保存即可。这样处理100张难例的速度大约比手工标注快一倍,而且标注质量更稳定。

合并新标注时要注意一件事:新图片的txt文件类别id必须和原数据集保持一致。这个数据集只有一类,id固定是0;新增图片里如果出现了一辆以上的电瓶车,txt里可以写多行,全部用0开头。全部合并后重新跑一遍第2章的校验脚本,再做一次如同第3章的随机划分,这样扩出来的数据集训练出来的模型,才真正对电梯轿厢的复杂光照和反光场景有适配能力。

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

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

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

立即咨询