965张头盔检测数据集:VOC/YOLO双标注与YOLOv8训练实战

📅 发布时间:2026/9/14 2:46:33
965张头盔检测数据集:VOC/YOLO双标注与YOLOv8训练实战
简介面向电动车、摩托车头盔佩戴检测应用的数据集资源适合目标检测方向学习者、课程设计或实际项目开发者使用。包内共2000个文件压缩包约132.29MB包含965张街边场景图片以及对应的968个xml格式VOC标签、969个txt格式YOLO标签并附带一个将VOC标签转为JSON格式的Python脚本便于不同检测框架直接调用。数据集类别聚焦头盔与人头两类目标手工标注精准背景多样且分布均匀能够支撑YOLO、SSD、Faster R-CNN等主流算法的训练与验证。目前已有1820人学习。借助配套标签转换脚本从数据整理到模型训练可顺畅衔接尤其适合快速搭建头盔佩戴检测demo或作为算法对比实验的数据基础。1. 965张双标签头盔数据集为什么同时备好VOC与YOLO两份标注在电动车头盔佩戴检测项目里最常卡住进度的不是模型结构而是标注数据。这个965张的街拍数据集覆盖头盔与头部两类目标图片多来自城市道路抓拍背景里有树、招牌、护栏、车窗反光光照和角度都不规整反而比校园或园区场景更适合训练真实抓拍模型。每张图带Pascal VOC的XML标注和YOLO的TXT标注各一份还有把VOC转成COCO JSON的Python脚本。对做课程设计、比赛demo和快速验证的人来说省去用LabelImg重新标注再导出的时间直接进训练环节对做实际项目落地的人来说两份格式意味着可以在YOLO与Detectron2之间自由切换转换脚本则补上了数据工程里最琐碎的一环。2. VOC XML与YOLO TXT的坐标体系差异及数据集目录组织从实际使用角度先盘清楚两种标注格式后面跑到训练和转换脚本时才不会被坐标系绕晕。2.1 Pascal VOC的绝对坐标标注结构VOC格式的标签是XML文件每个目标用一个object节点表达。打开其中一张图对应的文件核心字段是name与bndbox。name保存类别名在这个数据集里就是helmet与head。戴头盔时人头被头盔罩住检测目标是helmet没戴头盔时检测目标是裸露的head。bndbox里的xmin、ymin、xmax、ymax表示目标框左上角和右下角在原始图像上的像素坐标属于绝对坐标必须对应原始图片尺寸才能画框或裁剪。以某张1280×720的样本为例片段如下annotation filenameP10_helmetou_65.jpg/filename size width1280/width height720/height depth3/depth /size object namehelmet/name bndbox xmin432/xmin ymin218/ymin xmax584/xmax ymax346/ymax /bndbox /object /annotation实际标注文件里同一张图往往有多个object节点分别对应一辆电单车上不同位置的骑乘人员。读取这类XML我习惯用Python标准库xml.etree.ElementTree不需要额外装依赖root.find(filename)拿图片名root.findall(object)遍历目标列表再对每个object取name和bndbox的四个子节点。之所以不推荐用正则硬匹配是因为XML标签的顺序在不同标注工具下会有差异ElementTree按节点名取值更稳定。2.2 YOLO归一化坐标的写入方式YOLO的TXT标注与VOC完全两套坐标系。每行一个目标五个值分别是class_id、x_center、y_center、width、height后四个都做了归一化除以原始图片的宽和高。如果类别顺序是helmet0, head1内容类似0 0.396875 0.391667 0.118750 0.177778 1 0.107813 0.295833 0.076563 0.175000第一行表示helmet中心位于图片横向39.69%、纵向39.17%位置框宽为图片宽度的11.88%高为图片高度的17.78%。第二行对应head。这个设计的优点是无论模型把输入缩放到640还是1280标注都不需要重新计算缺点是排查问题时必须先反归一化再可视化否则人眼很难判断框落在哪里。维度VOC XMLYOLO TXT坐标单位像素绝对值相对尺寸的0~1浮点数框表示xmin, ymin, xmax, ymaxx_center, y_center, width, height类别字段字符串name整数class_id依赖图片尺寸是尺寸影响坐标解析否天然支持任意尺寸典型使用框架老版Faster R-CNN / SSDYOLO全系这张对比表在换框架时最有用。比如把数据集从YOLO转回Detectron2或MMDetection这两个框架的注册器只吃COCO或VOC不支持TXT就必须把归一化坐标还原成像素坐标并重新封装。2.3 从bndbox到YOLO坐标的换算逻辑如果数据集只给了VOC格式自己算YOLO坐标也很直接。核心公式是def voc_bbox_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h return x_center, y_center, box_w, box_hxmin xmax除以2得到框中心的像素横坐标再除以img_w完成归一化框宽直接用xmax - xmin除以图片宽度。反向恢复像素框时用x_center * img_w - box_w * img_w / 2得到xmin。这种双向换算在手动写可视化工具、做TTA增强测试时经常会用到建议放进自己的工具模块里复用。值得说明的是整个换算过程只用加减乘除不涉及取整坐标精度不会因为归一化损失。提示检查bndbox是否越界时重点看xmax img_w或ymax img_h的情况街拍图经过旋转压缩后在标注阶段容易出现这类边角误差。3. 基于YOLOv8的头盔检测训练数据划分与参数调试3.1 965张样本的划分策略965张街拍图不算大但类别集中在helmet与head目标尺寸和角度覆盖比较典型微调训练完全够用。常见做法是按8:2切训练集和验证集约772张训练、193张验证。不能简单地用随机数切文件而要先shuffle再切或者用sklearn.model_selection.train_test_split设random_state。这个数据集没有严重的类别不均衡按文件列表直接切就行但务必要让同一张图的XML、TXT和JPG进入同一个集合。cd helmet_965_dataset mkdir -p images/train images/val labels/train labels/val python - EOF import os, shutil from sklearn.model_selection import train_test_split images [f for f in os.listdir(images) if f.endswith(.jpg)] train_imgs, val_imgs train_test_split( images, test_size0.2, random_state42) for split, img_list in [(train, train_imgs), (val, val_imgs)]: for img in img_list: shutil.copy(fimages/{img}, fimages/{split}/{img}) stem img.rsplit(., 1)[0] if os.path.exists(flabels/{stem}.txt): shutil.copy(flabels/{stem}.txt, flabels/{split}/{stem}.txt) if os.path.exists(fVOC/Annotations/{stem}.xml): shutil.copy(fVOC/Annotations/{stem}.xml, flabels/{split}/{stem}.xml) EOFrandom_state42固定随机种子方便复现同一份划分。test_size0.2表示验证集占20%。脚本末尾同时拷贝TXT和XML是为了后面转换脚本能基于训练目录单独生成COCO JSON避免把验证集图片混进训练用的标注文件。如果后续要扩展数据新图片要先归入训练集再整体重切防止验证集里混入已见样本。3.2 编写data.yaml并启动训练YOLOv8用YAML文件描述数据集路径、训练和验证子目录、以及类别名称。放在数据集根目录path: /home/user/helmet_965_dataset train: images/train val: images/val names: 0: helmet 1: headpath是绝对路径Windows下写成D:/datasets/helmet_965_dataset这样的正斜杆。names和TXT里class_id的顺序必须严格一致否则模型学到的类别语义会整体错位。我一般会在训练前先跑一个batch做sanity check确认第一个epoch的loss在正常下降而不是直接训完100个epoch才发现路径写错。启动训练pip install ultralytics yolo detect train datahelmet_965_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectruns/helmet_detect \ nameexp1 \ device0modelyolov8n.pt加载COCO预训练权重进行微调n是nano版本显存占用小街拍头盔目标不算极小nano在demo场景通常够用。显存足够可以换yolov8s.pt或yolov8m.ptmAP一般多1到3个点。以COCO预训练权重为基准三个常见规模的参数对比如下模型权重参数量单卡显存占用参考推理速度参考yolov8n.pt3.2M约3GB最快yolov8s.pt11.2M约4GB快yolov8m.pt25.9M约6GB中等imgsz640是默认值如果目标在原图中占比较小可以放大到960或1280代价是训练速度和显存占用上涨。patience20表示验证集指标连续20轮不提升就提前停止防止训练到后期过拟合。device0指定第一块GPU无GPU时删掉这个参数用CPU同时把epochs降到30左右控制耗时。3.3 损失曲线与YOLO训练健康度判断训练过程中最值得看的是train/box_loss、val/box_loss和metrics/mAP50三组曲线。YOLOv8的边框回归损失默认使用CIoU同时把重叠面积、中心点距离和宽高比放进优化目标曲线下降平稳性比YOLOv5的GIoU更好。头盔是刚性目标形状变化不大CIoU的组合惩罚在这种场景下收敛很快。mAP50指IoU阈值为0.5时的平均精度头盔检测这种大目标任务在标注质量可靠的情况下mAP50通常能到0.85以上。如果训练损失持续下降而验证损失反弹要怀疑过拟合适当缩短epoch或调大几何增强幅度。如果mAP50始终在低水平震荡优先检查标签里的漏标和错框再考虑堆训练时间。注意街拍场景的运动模糊会让个别样本loss长期偏高这是正常噪声不要因为单轮震荡就频繁中断训练。4. VOC转COCO JSON看懂脚本逻辑再做自定义改造4.1 转换脚本的输入输出结构数据集附带的VOC转JSON脚本目标是COCO格式包含images、annotations、categories三个顶层字段。categories声明类别id与名称的映射images记录图片id、文件名、宽高annotations记录每个目标框的image_id、category_id、bbox、area。COCO的bbox是[x_min, y_min, width, height]与VOC的[xmin, ymin, xmax, ymax]不兼容新接触的人容易直接把四个值照抄过去导致框整体偏大。三种坐标系的关系可以概括成一张表坐标系字段含义VOC bndboxxmin, ymin, xmax, ymax左上角与右下角像素坐标COCO bboxx, y, width, height左上角像素坐标与框宽高YOLO txtx_center, y_center, w, h归一化中心点与框宽高一张图对应一个images条目图里每个目标对应一个annotations条目同一个image_id会出现在多个annotation里。这个结构是目标检测、实例分割类模型的标准入口Faster R-CNN系列、Mask R-CNN、Detectron2和MMDetection都吃这套格式。4.2 核心代码与逐段说明以这个数据集的目录组织为例整理后的转换脚本如下import os import json import xml.etree.ElementTree as ET from PIL import Image def voc_to_coco(annot_dir, img_dir, out_path, categories[{id: 1, name: helmet}, {id: 2, name: head}]): images, annotations [], [] ann_id 1 valid_names {cat[name] for cat in categories} for img_id, xml_name in enumerate(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() filename root.find(filename).text img_path os.path.join(img_dir, filename) if not os.path.exists(img_path): print(f[skip] 图片不存在: {filename}) continue with Image.open(img_path) as im: width, height im.size images.append({ id: img_id, file_name: filename, width: width, height: height }) for obj in root.findall(object): name obj.find(name).text if name not in valid_names: print(f[skip] 未知类别: {name}, {filename}) continue cat next(c for c in categories if c[name] name) 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) w, h xmax - xmin, ymax - ymin annotations.append({ id: ann_id, image_id: img_id, category_id: cat[id], bbox: [xmin, ymin, w, h], area: w * h, iscrowd: 0 }) ann_id 1 coco { images: images, annotations: annotations, categories: categories } with open(out_path, w, encodingutf-8) as f: json.dump(coco, f, ensure_asciiFalse, indent2) print(f完成: {len(images)} 张图片, {len(annotations)} 个目标框) if __name__ __main__: voc_to_coco(VOC/Annotations, VOC/JPEGImages, helmet_coco.json)with Image.open同时拿到宽高并自动关闭文件句柄不依赖XML里的size节点避免XML尺寸写错时坐标错位。Windows下如果不加with大量图片循环时容易出现句柄占用导致的读取异常。os.path.exists拦截文件名不匹配的图片遇到漏图直接跳过保证后续生成的JSON不会被残缺记录污染。如果转换别的数据时出现w0 or h0的空框在annotations.append前加一个if w 0 or h 0: continue过滤掉即可手工标注通常没有这个问题但程序生成的结果经常会出现。4.3 类别映射与路径兼容问题COCO的category_id习惯从1开始YOLO的class_id从0开始。如果同一份数据在两个框架间来回使用需要写一个class_map做显式映射不要依赖默认顺序。另一个高频坑是Windows下路径分隔符os.path.join可以自动处理但YAML里写绝对路径时注意转义。转换完成后可以用json.load重新读一遍统计annotations数量是否与原始VOC所有object节点数一致不一致就去查日志里skip掉的文件。提示JSON文件较大时indent2会显著增加体积生产环境可以用indentNone压缩输出调试阶段保留缩进更容易排查问题。JSON格式化工具在对比转换前后结构时很好用先看categories再看images最后检查annotations里的image_id是否都存在于images列表。5. 佩戴检测落地时的样本筛选技巧5.1 统计类别分布识别不平衡头盔检测里最常见的问题是helmet特别多、head特别少导致模型对没有头盔的人头召回率偏低。训练前先跑一段统计python - EOF from collections import Counter import os c Counter() for txt in os.listdir(labels/train): if not txt.endswith(.txt): continue for line in open(flabels/train/{txt}): c[int(line.split()[0])] 1 print(c) EOF如果head样本数明显少于helmet考虑对含head的图片做水平翻转或小角度旋转多生成几个变体再放进训练集。不要直接复制原图那样只会让模型记住重复特征。5.2 排查模糊与遮挡样本街拍图里最影响精度的三类坏样本是运动模糊、强光过曝、头盔被雨棚或车窗部分遮挡。用训练日志中val损失最高的20张图生成一个可视化grid人工看一遍是标签错还是场景本身就难。标签错就回到XML或TXT修改坐标场景难就保留它们虽然会拖低mAP却恰好是部署时漏检的重灾区。能在训练阶段保留这些困难样本后期上线的鲁棒性会好很多。5.3 推理阶段用置信度阈值控制误报模型部署时把置信度阈值从默认的0.25提升到0.4~0.5能在不损失太多召回的情况下显著减少误报。用验证集跑一组不同conf值画出precision-recall曲线拐点附近就是精度与召回率的平衡点。例如针对这个数据集我遇到过conf0.35时precision约0.72、recall约0.81把conf拉到0.45后precision升到0.88recall只掉到0.76的情况。数据分布不同数值会有差异但调阈值本身的成本远低于改模型结构。用Ultralytics在验证集上测阈值命令是yolo detect val datahelmet_965_dataset/data.yaml \ modelruns/helmet_detect/exp1/weights/best.pt \ conf0.45conf0.45会直接以这个阈值计算指标。把conf分别设为0.25、0.3、0.35、0.4、0.45、0.5各跑一遍记录precision和recall就能确定投放阈值。对于部署在嵌入式设备或路侧摄像头上的场景这个标定结果直接决定业务线的误报上限值得花几分钟多跑几组。本文还有配套的精品资源点击获取