资讯动态

电动自行车目标检测数据集:VOC与YOLO标注格式详解及XML转JSON脚本实践

发布时间:2026/9/8 14:43:40 来源:尧图企业网站定制
简介电动自行车识别检测数据集面向目标检测算法训练与真实梯控项目落地适合高校课程设计、毕业设计、算法竞赛及电单车入梯检测等场景。数据集共包含三千余张实拍图片背景丰富、抓拍角度多样统一标注为单一类别“电动自行车”并通过实际项目验证标注精度高、收敛效果好。压缩包约四百余兆内部含数千个标签文件与图片样本具体包括VOC格式的XML标签、YOLO格式的TXT标签以及对应的JPG图像另附一个XML转JSON的Python脚本标签体系覆盖三种主流格式经典检测网络及新框架均可直接调用。已有两千余人浏览学习。使用者拿到数据后即可开始训练无需自行转换格式附带的脚本还能将XML批量转为JSON便于扩展至MMDetection等工具链。这份资源不仅能减少数据收集与标注的时间成本还能帮助初学者理解不同标签格式之间的转换原理是课程作业、创新设计与实际项目开发中一份高质量、易上手的视觉识别资源。 早几年做电动车违规进楼检测项目时我最头疼的其实不是模型选型而是标注文件的格式。团队里有人用LabelImg导出VOC XML有人直接给YOLO训练的TXT还有人习惯把标签塞进一个JSON里给服务端用。三个格式来回倒腾写脚本的时间比调模型的时间还长。所以看到电动自行车识别检测数据集(含voc和yolo格式标签xml转json格式脚本)这种配套齐全的资源我第一反应是这才是一个数据集该有的样子。今天我就从实际使用的角度把这个数据集的构成、VOC与YOLO两套格式的门道、XML转JSON脚本的写法以及课程作业、比赛和真实项目里怎么把它用出效果一次性讲清楚。1. 电动车目标检测的实际需求与数据集定位1.1 哪些项目真正需要电动车专用检测器电动自行车识别不是闲着没事做的研究方向它背后是大量真实存在的业务场景。最常见的几类小区物业的电动车进电梯识别禁止电动车入户充电。校园、厂区内部道路的电动车逆行、超载检测。城市交通路口对电动车闯红灯、占用机动车道的违规抓拍。停车场和消防通道占用检测防止电动车堵塞安全出口。两轮车与非机动车流量统计辅助交通规划和商业选址。这些场景有个共同特点监控摄像头视角固定拍摄距离相对近目标尺寸不大但特征清晰而且背景相对单一。相比于通用目标检测里那浩浩荡荡的80类COCO一个专门针对电动车可能还包括头盔、骑行人的专用检测器在精度和推理速度上都能做到更好。所以大家才会专门寻找这种细分场景的数据集而不是拿通用模型硬扛。1.2 双格式标签加转换脚本的组合解决什么问题这个数据集最大的亮点不是图片数量而是它对下游使用的体贴。很多数据集只给一种格式比如只给VOC XML或者只给COCO JSON到了YOLO训练环节你还得另写转换脚本。这个数据集一次性给了VOC和YOLO两套标签省掉了最枯燥的格式适配阶段。另外它还附带XML转JSON的脚本这就不是实训了而是方便你继续加工。比如你把标注结果转成JSON之后可以直接喂给服务端做后处理很多推理框架的接口只认JSON。转成COCO格式方便用Detectron2、MMDetection这类框架。做数据清洗和合并多个数据集统一成一个JSON标签文件。一句话总结这个数据集的定位就是开箱即用从解压到跑通训练中间不该有格式障碍。2. VOC与YOLO标注格式解析一个XML文件和一个TXT文件的差距2.1 VOC的XML结构一张图片一个标注文档VOC格式源自PASCAL VOC挑战赛历史很久特点是一张图片对应一个XML文件。XML里记录了图片文件名、路径、尺寸以及每一个目标的类别名称和边界框坐标坐标用的是绝对像素值。annotation folderelectric_bicycle/folder filenameimg_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameelectric_bicycle/name truncated0/truncated difficult0/difficult bndbox xmin462/xmin ymin118/ymin xmax892/xmax ymax702/ymax /bndbox /object object namehelmet/name truncated0/truncated difficult0/difficult bndbox xmin510/xmin ymin130/ymin xmax608/xmax ymax224/ymax /bndbox /object /annotation从工程角度讲VOC格式的好处是「人眼可读」。任何一个懂点XML的人打开文件都能知道图片里有什么目标、目标大概在哪个位置。调试的时候特别方便标注画歪了一目了然。它的缺点是啰嗦。一个目标就要写十几行图片里目标一多文件就非常臃肿。而且XML解析本身也比解析纯文本慢批量处理几千个XML时光IO和解析就要浪费不少时间。2.2 YOLO的TXT结构五列归一化数字YOLO格式走了另一个极端——一切从简。每个标注文件是TXT文本每一行代表一个目标共五列数字类别ID 中心点x 中心点y 宽度w 高度h注意这里的坐标全是归一化到0到1之间的小数计算方式是center_x (xmin xmax) / (2 * image_width) center_y (ymin ymax) / (2 * image_height) box_width (xmax - xmin) / image_width box_height (ymax - ymin) / image_height拿上面XML里的电动车框来算center_x (462 892) / (2 * 1920) ≈ 0.3526 center_y (118 702) / (2 * 1080) ≈ 0.3796 box_width (892 - 462) / 1920 ≈ 0.2239 box_height (702 - 118) / 1080 ≈ 0.5407对应的YOLO TXT一行就是0 0.3526 0.3796 0.2239 0.5407这就是YOLO系模型YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv11等通用的标注格式。用归一化坐标的好处是不管图片缩放成640还是1280标注都不受影响——因为坐标始终是比例。坏处是调试不直观看到一行0.35 0.38 0.22 0.54脑子里很难立刻浮现出边界框长什么样。2.3 双格式数据集在目录结构上的讲究一个规范的YOLO训练数据集目录层次是固定的看一眼就能确认数据集是否正规electric_bicycle_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── classes.txtimages和labels必须同名同前缀。比如images/train/img_0001.jpg对应labels/train/img_0001.txt不能有张冠李戴的情况。data.yaml是YOLO训练时的配置文件里面声明了类别数量、类别名称和路径train: ./electric_bicycle_dataset/images/train val: ./electric_bicycle_dataset/images/val nc: 2 names: [electric_bicycle, helmet]如果数据集里只有单个类别nc就是1names也只有一个元素。拿到手的第一件事就是确认names列表和TXT里的类别ID一一对应这一步错了后面训练出来的模型预测类别就全乱了。3. XML转JSON脚本的实现逻辑从解析、映射到批量落盘3.1 转换前的映射关系设计XML转JSON不是简单地换一个格式核心是设计好JSON的结构。根据后续用途不同JSON的组织方式有三种主流选择目标框架/场景推荐JSON结构自己写后处理程序按图片名索引的字典结构COCO格式MMDetection/Detectron2COCO标准结构images、annotations、categories前端可视化展示扁平数组结构每条含图片路径和框坐标我自己写转换脚本时最常用的是按图片名索引的字典结构因为它灵活、直观后续无论转成COCO还是自己调用都比较方便{ img_0001.jpg: { width: 1920, height: 1080, objects: [ { category: electric_bicycle, bbox: [462, 118, 892, 702] } ] } }3.2 核心转换代码的解析思路用Python的xml.etree.ElementTree就能完成90%的解析工作不需要额外装库。核心逻辑就三步读XML、抽字段、写JSON。import os import json import glob import xml.etree.ElementTree as ET def voc_xml_to_dict(xml_path): 把单个VOC XML解析成字典结构 tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) width int(size.find(width).text) height int(size.find(height).text) objects [] for obj in root.iter(object): name obj.find(name).text 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) objects.append({ category: name, bbox: [xmin, ymin, xmax, ymax] }) return { filename: filename, width: width, height: height, objects: objects } def batch_convert(xml_dir, output_json_path): 批量转换目录下所有XML为单一JSON文件 result {} for xml_path in glob.glob(os.path.join(xml_dir, *.xml)): data voc_xml_to_dict(xml_path) filename data[filename] result[filename] { width: data[width], height: data[height], objects: data[objects] } with open(output_json_path, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(f转换完成共处理 {len(result)} 张图片)这个脚本看着简单但里面有两个容易被忽略的坑坑一filename字段可能和实际文件名不一致。VOC XML里的filename标签是在标注时写入的如果之后图片改名了XML里还是老名字那么转换出来的JSON索引就和图片对不上。严谨的做法是以XML文件名作为主键而不是信filename里的内容。坑二XML里可能存在没有bndbox子节点的object。有些标注工具导出的XML会在object下只有name而没有框或者在segmented标签不完整。转换脚本里应该加一层if bbox is not None的保护否则遇到脏数据就直接抛异常中断了。3.3 批量转换中的容错与路径兼容真实数据集的XML不可能每一份都干净我见过的情况包括XML里width和height写反。目标类别名称前后带空格比如electric_bicycle 。坐标值出现小数点以外的非法字符。XML编码不是UTF-8带BOM导致parse报错。所以在批量转换脚本里最好用try...except把单个文件的处理包起来失败的文件单独记到日志里不要因为一个坏文件导致整个任务中断。我一般会在脚本里加一行统计成功多少、失败多少、失败文件名列表是什么。这样转换完能立刻知道哪些数据需要人工检查。4. 三种使用场景下的数据准备路径4.1 课程作业用最小成本把模型跑起来课程设计或者毕业设计里用到电动车检测多数是为了完成一个能运行、有结果、能演示的系统。这时候目标不是刷SOTA而是把完整链路走通。拿到这个数据集后最省力的路径是解压后先确认目录结构和data.yaml与实际的图片路径是否匹配。直接用YOLOv8训练命令极简yolo detect train dataelectric_bicycle_dataset/data.yaml modelyolos.pt epochs50 imgsz640训练完成后用best.pt做推理测试截图留作报告素材。如果你用的是MMDetection或者Detectron2就先运行附带的XML转JSON脚本把VOC标签转为COCO JSON然后按框架要求组织目录。这一趟流程跑下来课程报告的数据准备和实验环境两章就有东西可以写了既有格式对比又有转换脚本代码还有不同格式的效果对比实验内容很扎实。4.2 算法比赛数据质量决定baseline上限比赛和课程作业最大的区别在于比赛对精度的要求苛刻得多。如果比赛允许使用外部数据这个数据集可以作为很好的辅助数据。但要注意两点第一数据分布差异。比赛测试集可能来自特定摄像头、特定光线、特定城市而这个数据集的拍摄环境未必一致。如果直接用这个数据集训练然后去预测比赛测试集会面临明显的domain gap。正确做法是用该数据集做pretrain在比赛提供的小样本上继续finetune或者先做数据清洗和筛选挑出与测试集风格接近的样本。第二类别定义冲突。比赛里的电动自行车可能包含电动三轮车、共享电动车而这个数据集只定义了其中一部分。合数据之前必须把类别映射表对齐否则类别ID错位训练直接崩。4.3 工业项目从能跑到能交付的差距真实项目里数据集的定位只是种子数据或冷启动数据。我做过一个写字楼非机动车管理系统现场装了6个摄像头角度五花八门有顶视的、有斜45度俯视的。拿通用电动车数据集训练出来的模型一上线误检率奇高——因为现场有大量相似的电动滑板车和自行车。最终解决方案是用现成数据集做预训练模型。在项目现场采集一周的视频抽帧并标注了大概2000张现场数据。把公开数据集和现场数据按11混合训练。用现场的硬负样本做难例挖掘迭代。这套流程下来检测精度才达到客户能接受的水平。所以这个数据集在工业项目里的正确用法不是替代现场采集而是给模型先搭一个能力骨架节省冷启动迭代的轮次。5. 双格式数据集实操中的典型坑位与排查经验5.1 类别标签不统一的问题很多电动车数据集的类别定义并不完全一致。有的把电动车和摩托车合为一类有的把骑手也单独标注出来有的把头盔单独作为一类。拿到数据集后先打开classes.txt或者XML里的name字段把所有出现过的类别列一个清单再去和目标场景对比。我遇到过最离谱的情况是同一个数据集里训练集XML中类别是ebike验证集XML中类别是electric_bicycle看起来像是语义相近的两种写法但在训练时会被当成完全不同的两个类别。这种问题不提前发现训练过程也不会报错就是验证集精度莫名其妙比预期低很多。5.2 边界框越界与归一化异常XML里的xmax、xmin偶尔会超出图片边界可能是标注时手抖也可能是目标出画了半个身体。YOLO的TXT对越界框的处理比较苛刻如果xmax大于width归一化后框宽会超过1训练时某些版本会报错某些版本会静默裁剪掉这个框。排查方法很简单写一个校验脚本遍历所有标注import os def check_labels(label_dir, img_dir): for label_file in os.listdir(label_dir): if not label_file.endswith(.txt): continue with open(os.path.join(label_dir, label_file), r) as f: lines f.readlines() for line in lines: parts line.strip().split() cid, cx, cy, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if w 0 or h 0: print(f非法框尺寸: {label_file}, {line.strip()}) if cx - w / 2 0 or cx w / 2 1 or cy - h / 2 0 or cy h / 2 1: print(f越界框: {label_file}, {line.strip()})5.3 图片EXIF方向与读取库不一致这里有个很隐蔽的坑手机或某些摄像头拍出来的JPEG带有EXIF方向信息。用PIL读取图片时PIL会自动根据EXIF纠正方向所以图片是正的但用OpenCV的cv2.imread()读取时它默认不做方向纠正图片可能是横的。标注工具如果基于PIL显示图片标注的坐标就是纠正后的坐标训练时如果读取用的是OpenCV图片本身是旋转的坐标就对不上了。解决方案有两种把数据集里的所有图片统一用脚本转成标准方向顺便去掉EXIF信息。训练管线里读取图片统一走同一个库比如全换成PIL。5.4 训练集划分中的相似样本污染最后提醒一个比赛和项目中都很常见的坑同一段视频里连续抽帧得到的图片高度相似。如果随机划分训练集和验证集不去重验证集会包含大量和训练集几乎一样的图片——验证集精度虚高看起来有98%的mAP一到真实场景就掉到80%以下。正确做法是按拍摄时间段或者场景划分确保同一个场景的不同帧只落在一个集合中。如果这个数据集采集时做了去重那说明作者很用心如果没做你就得自己动手。最简单的方式是计算图片的感知哈希相似度超过阈值的只保留一张或者按文件名前缀的时序关系分组划分。这个数据集的格式配套和转换脚本已经把上手的成本降到了最低。它解决的是目标检测项目里最枯燥、最重复的格式适配问题你要做的就是把精力花在真正重要的地方——数据清洗、类别校验、训练策略和现场迭代上。我个人的建议是拿到数据集之后不要急着训练先花半小时跑一遍格式检查脚本确认所有标注都合法、类别都统一再进训练流程。前面省掉的这半小时后面能帮你省下不止一天的排查时间。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价