
简介面向目标检测入门与电梯场景安全监控需求这份电瓶车进入电梯检测数据集可帮助训练电梯内电瓶车识别模型适合算法学习者、安防项目开发者做模型验证与原型演示。数据集包含两百张图片同时提供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子节点下的四个坐标xmin、ymin、xmax、ymax。这四个值都是像素绝对值优点是肉眼直接能看出框的位置labelImg打开就能继续编辑。YOLO格式则完全是另一套写法每行代表一个目标依次是类别id、x中心坐标、y中心坐标、框宽、框高后四个值全部除以图片宽高完成归一化范围在0到1之间。大多数开源训练仓库直接吃YOLO格式的txt不需要写代码解析xml但如果要检查标注质量、做数据清洗、合并新标注数据VOC的xml反而更友好。所以我一般会同时保留两种格式xml作为“原件”txt作为“生成物”。一旦发现某个框标歪了在labelImg里改完xml再重新生成txt避免出现两份文件信息不一致的情况。压缩包里一个典型xml的结构大致是这样annotation filenamedianpingche_xyxr_189.jpg/filename size width1280/width height720/height depth3/depth /size object nameelectric scooter/name bndbox xmin327/xmin ymin214/ymin xmax588/xmax ymax641/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.357y_center等于(214 641) / 2 / 720 0.594宽高分别是0.204和0.593。YOLO格式不关心框的左上右下角点只关心中心点和宽高因此在后续读代码时不要拿VOC的思维去套。含义VOC字段YOLO第N列是否归一化类别object/name第0列否类别id中心x(xminxmax)/2第1列是除以width中心y(yminymax)/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/width和size/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 scooternc是类别数这里只有1类names列表的顺序决定了训练输出中类别id与名称的对应关系。如果后面合并了自己的电瓶车数据比如想区分“电动自行车”和“电动三轮车”nc要改成2names要补上新类别名同时所有标注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对应mAP0.5--conf-thres 0.25是置信度阈值低于这个值的预测框会被过滤掉。只有210个标注框的小数据集mAP0.5:0.95这种更严格的指标会因为框边界的老鼠尾问题掉得很难看参考价值远不如mAP0.5。在业务评估时直接看mAP0.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章的随机划分这样扩出来的数据集训练出来的模型才真正对电梯轿厢的复杂光照和反光场景有适配能力。本文还有配套的精品资源点击获取