简介电动车安全帽头盔数据集面向目标检测与道路交通安全视觉任务精选约1000张电动车骑行场景图片并全部完成标注可用于训练头盔佩戴识别、骑乘人员检测等模型适合深度学习初学者、算法工程师及智慧交通项目开发者直接使用。压缩包共1528个文件764个png图像与764个xml标注文件一一对应采用VOC格式标注解压后即可接入YOLO、Faster R-CNN、SSD等主流检测框架免去格式转换步骤资源整体约390.97MB规模适中既能支撑快速原型验证也可作为数据扩充的基础。目前已有2794人浏览学习资源热度和可用性得到一定市场验证。借助这批数据读者可省去手动采集与标注的耗时环节直接开展模型训练、验证集评测、精度调优等流程同时可基于现有标注信息结合数据增强构建更大规模数据集服务于电动车违章监测、路口安全预警、头盔佩戴自动巡检等落地场景。1. 电动车安全帽头盔数据集为什么公开数据跑得通、真实场景总翻车做电动车安全帽头盔检测的视觉项目门槛往往不在模型而在数据集。我见过好几个团队在公开的头盔检测数据集上跑出接近满分的 mAP一拿到真实路口场景就漏检频出半盔被当成遮阳帽、夜间戴深色盔的骑手直接消失、雨天帽兜被误判成头盔。这套数据集的本质不是一堆图片和框而是你对「戴没戴头盔」这个业务问题的定义——类别怎么定、头部怎么框、模糊目标怎么处理、正负样本怎么配比全都会变成模型上线后的翻车点。把这条链路想清楚比调任何训练参数都重要。这篇文章写给要自己搭数据集、给路侧或园区安防类项目做头盔识别验证的开发者。接下来我会把从数据采集、类别定义、格式转换到训练验证和数据闭环的完整做法讲清楚每个环节都给可直接抄作业的命令和参数。2. 从零搭一套头盔检测数据集类别定义与目录结构先定死2.1 类别怎么定两类头部框是起步阶段的最优解头盔检测看起来是标准的目标检测问题但类别定义直接决定模型能从数据里学到什么。项目里常见的方案有三种单类别只管框头盔两类别框住头部区域、标记戴盔与未戴盔三类别同时标骑手、头盔和头。实际部署里最常见的思路是两级检测——第一级先在画面里检出骑手或头部区域第二级判断这个区域里有没有头盔。对应到数据侧最稳的标注方式是只标头部框类别分成 helmet戴盔头部框和 no_helmet未戴盔头部框两类。头部框比头盔框的语义更稳头盔被车把、雨棚遮挡时只框头盔容易漏而头部框至少能把目标锚住。不建议直接给骑手全身框。全身图里既有头盔又有衣服模型很容易学到「这人在骑车」而不是「这人戴了盔」换一个场景视角就失效。三类别方案能同时输出骑手位置、头盔位置和头位置信息最全但标注工作量接近翻倍多人交叉场景还容易自相矛盾。我的建议是初期只做两类头部框跑通流程之后再按业务需要补 person 类。有一点要提前定死类别顺序一旦确定就不能改训练配置和推理配置里的 names 必须严格一一对应中途改类别顺序等于重新标注。数据规模也影响类别选择单类别头盔框方案两千张图就能看到像样的 mAP但上线后误检多两类头部框方案需要更多负样本才稳三类别方案最好有一万张以上的图片预算。2.2 采集策略公开数据当种子自采数据补场景数据来源走两条腿。第一条是公开渠道开源图库里的共享单车、校园通勤照片加上安全宣传材料里的电动车图片能快速凑出几千张先把标注和训练流程跑通。这类图片的拍摄视角多为平视街拍和路侧高位相机的俯视视角差异很大所以公开数据只能当种子。第二条是自采在真实路口、小区出入口、学校门口用手机横屏连拍或者录像抽帧贴近监控视野。时间段覆盖早高峰、午后、夜间天气覆盖晴天、阴天、小雨角度覆盖平视、俯视、侧向。数量上给一个可抄作业的底限5000 张图起步每张图平均 2 到 4 个目标戴盔与未戴盔的正负样本尽量各占一半否则分类损失会一边倒。场景记录要特别覆盖外卖骑手、后座乘客、带儿童座椅的家长——这些是真实项目里最多变的样本。自采时注意隐私合规不拍车牌特写公开渠道的图片要确认授权再进训练集。采集负样本千万别省。现实场景里戴盔率高的路口不戴盔的是少数如果按真实比例采样no_helmet 类可能只占 10%模型学完会倾向于把所有头都判成戴盔。常见做法是刻意压低戴盔率采样比例把 no_helmet 提到 40% 到 50%上线后再用阈值把误报压回去。2.3 目录结构与标注协议训练前把规则写进文档目录结构建议把 images 和 labels 平级分开train、val、test 按同一批文件名一一对应不要按子目录打散。helmet_dataset/ ├── images/ │ ├── train/ # 约 4000 张 │ ├── val/ # 约 600 张 │ └── test/ # 约 400 张按路口独立划分 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── annotation_rule.md └── data.yamlval 和 test 要按路口维度划分而不是全局随机洗牌。同一个路口出现在训练集又出现在测试集评估结果会虚高因为模型记住了背景纹理。标注协议要在开工前写进 annotation_rule.md下面几条是我项目里直接用过的规则可以直接抄。规则项建议值头部框范围紧贴头盔或头皮外沿的矩形不含脖子头盔被遮挡遮挡超过一半不标能看到一半以上照常标 helmet无盔头部只要人眼能分辨就标 no_helmet哪怕是远景极小目标短边小于 12 像素或人眼难断不标帽兜、围巾一律不算头盔标 no_helmet后座乘客看得清头部就标不做丢弃这套协议最关键的一句话是「人眼能分辨就标」。它保证标注员和模型面对的是同一套判断标准也是后面排查漏检的依据。标注工具选能导出 VOC 格式的桌面工具就行支持标签热键和自动保存适合批量操作不建议用在线众标平台处理这种小目标密集图坐标精度难以保证。所有图至少过一遍双标注抽检两个人标同一批 30 张图计算框的 IoU 一致率低于 90% 就返工。这一步看着费时间实际能省掉后面所有怀疑模型的排查时间。3. 把标注转成 YOLO 能吃的格式VOC 转 YOLO 的脚本与三个参数坑3.1 三种标注格式的选型表标注格式直接影响后续训练流程能不能顺利跑通。下面这张表是我做格式选型时的对照头盔检测这类小目标密集任务格式转换越简单越好。格式坐标形式典型导出工具适合场景VOC xml像素绝对值 xmin/ymin/xmax/ymax桌面标注工具默认导出人工标注、转换前的中间格式YOLO txt归一化 x_center/y_center/w/h各开源检测框架偏好训练直接使用单行一条框COCO json绝对像素加区域掩码在线平台导出实例分割或统一数据管理选型建议从桌面标注工具直接导 VOC再用脚本转 YOLO。不要手写 YOLO txt坐标归一化容易写错也不建议直接从在线平台导 YOLO 格式不同平台的导出字段顺序偶尔不一致这一条是真踩出来的。COCO json 适合本来就做实例分割的团队头盔检测只用矩形框用不到掩码徒增转换复杂度。3.2 VOC 转 YOLO 脚本与关键参数下面的脚本只处理 helmet 和 no_helmet 两个类别其他类别直接跳过。输入是 VOC 格式的 xml 目录输出是同名 txt。import os import xml.etree.ElementTree as ET CLASS_MAP {helmet: 0, no_helmet: 1} def voc_to_yolo(xml_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_name)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAP: continue # 只保留头盔相关类别 b obj.find(bndbox) xmin max(0, float(b.find(xmin).text)) ymin max(0, float(b.find(ymin).text)) xmax min(img_w, float(b.find(xmax).text)) ymax min(img_h, float(b.find(ymax).text)) bw, bh xmax - xmin, ymax - ymin if bw 2 or bh 2: continue # 丢弃小于2像素的退化框 x_c (xmin xmax) / 2 / img_w y_c (ymin ymax) / 2 / img_h lines.append(f{CLASS_MAP[name]} {x_c:.6f} {y_c:.6f} {bw/img_w:.6f} {bh/img_h:.6f}) with open(os.path.join(out_dir, xml_name[:-4] .txt), w) as f: f.write(\n.join(lines)) voc_to_yolo(annotations, labels/train)逻辑说明脚本先取图片宽高做归一化分母然后遍历 xml 里的 object 节点把类别名映射成数字再把边界框转成中心点加宽高的格式。越界裁剪放在最前面避免负值和超出图宽的值进入训练。三个参数坑要特别留意。第一个是类名对不上VOC 里如果写的是中文名或者 cap 这类别名CLASS_MAP 匹配不上会把整张图丢成空文件训练不报错但 mAP 直接归零。第二个是空文件处理一张图没有目标时 txt 是 0 字节训练能接受空标签但评估时空标签会拉低召回统计保留空文件不要删。第三个是 basename 一致性图片和标签必须严格同名大小写、扩展名后缀都不能差否则训练时找不到对应标签。3.3 转换自检脚本坐标、分布、可视化三步走转换完不能直接开训先跑一遍自检脚本。def check_labels(label_dir, class_num2): bad, cls_count [], [0] * class_num for f in sorted(os.listdir(label_dir)): p os.path.join(label_dir, f) with open(p) as fp: for line in fp: t line.split() if len(t) ! 5: bad.append((f, 字段数不对)) # 缺字段 continue c, x, y, w, h map(float, t) cls_count[int(c)] 1 if not (0 x 1 and 0 y 1): bad.append((f, 中心点越界)) if not (0 w 1 and 0 h 1): bad.append((f, 宽高越界)) if w * h 0.0001: bad.append((f, 框面积过小)) return bad, cls_count bad, cls_count check_labels(labels/train) print(bad, cls_count)自检脚本检查四件事每行是不是 5 个字段、中心点是不是落在 0 到 1 之间、宽高是否合理、框面积是否小到失去意义。输出的类别分布能直接看出正负样本比如果 no_helmet 占比低于 20%别急着训先回上一章补数据。我还会顺手统计框的宽度分布。头盔框宽度集中在整图宽度的 5% 到 15% 属于正常如果出现大量超过 30% 的框多半是标注时把骑手全身框进来了这类错误会带偏小目标检测。自检通过后抽 20 张图把框叠加画出来目检一次这一步虽然土但能拦住绝大多数低级错误。4. 用 YOLO 训一个头盔检测基线最小配置与关键参数4.1 data.yaml 先写对names 顺序就是类别生死线数据准备好之后第一份要写的配置文件是 data.yaml。path: /workspace/helmet_dataset train: images/train val: images/val test: images/test nc: 2 names: 0: helmet 1: no_helmetpath 建议用绝对路径相对路径在换机器跑的时候容易找不到目录。train、val、test 填的是 images 下的子目录不需要写 labels 目录检测框架默认去同名路径下找 txt。names 的类别顺序必须和第 3 章转换脚本里的 CLASS_MAP 完全一致这里错了训练出来的模型类别含义和推理结果就全对不上。nc 写上更明确虽然多数框架会按 names 自动推断但显式写出来能少一次排查。4.2 训练启动命令与参数实测建议训练命令用 YOLO 的官方命令行干净可控。yolo detect train \ datahelmet.yaml \ modelyolo11n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience15 \ seed0参数建议值说明modelyolo11n.pt轻量权重起步显存够再换 s 版本imgsz640速度与精度的默认平衡点小目标多可试 960batch16按显存调8GB 卡跑不动就降到 8epochs100配合 patience 15 早停够了lr00.01数据量小于五千张建议降到 0.005头盔在画面里属于中小目标imgsz 调到 960 通常能涨几个点 mAP但推理速度会明显下降路侧设备如果只有几十毫秒的预算要谨慎。batch 取 2 的幂次对显存利用率最好。lr0 玄学成分不小数据量小的时候大学习率容易震荡降一半更稳。数据增强参数先全部用默认值等 baseline 跑出来再动一上来就调 mosaic 只会让问题更难定位。4.3 评估别只盯 mAP每类 AP 和 recall 才是业务指标训练结束后评估脚本要按类别拆开看。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) m model.val(datahelmet.yaml, splittest) print(fmAP50: {m.box.map50:.3f}) print(fmAP50-95: {m.box.map:.3f}) for i, ap in enumerate(m.box.ap50): print(fclass {int(i)} AP50: {ap:.3f})评估时 splittest 要求 data.yaml 里写了 test 项否则框架会静默退回 val结果会偏乐观。头盔检测业务上 mAP50 是主指标IoU 阈值压到 0.5 而不是 0.75是因为密集场景下小目标的位置标注本身有抖动阈值过严意义不大。更关键的是各类别的 recall。漏报一个未戴盔的人比误报一个戴盔的人代价高得多所以训练完先看 no_helmet 的 recall 和 AP。打开混淆矩阵如果 helmet 被误判成 no_helmet说明正样本头盔不够典型补全盔和半盔的正面样本如果 no_helmet 被误判成 helmet说明负样本难例不足要去采集遮阳帽、帽兜这类近似目标而不是调损失权重。5. 头盔数据集训练最常踩的 5 个坑现象、原因与排查方法5.1 半盔被当成遮阳帽类别混淆的根源在样本占比现象训练完的模型把戴遮阳帽、棒球帽的骑手误检成 helmet置信度还很高。原因训练集里半盔样本占比低模型学到的其实是「头顶有东西等于头盔」这个粗糙特征。半盔本身外形和棒球帽在 2D 图像上高度相似只有侧面轮廓能区分而侧面的半盔样本往往最少。解决补拍半盔的正面、侧面、背面样本单独抽查半盔类别的混淆矩阵确认错分来自哪些拍摄角度如果业务上对半盔和全盔没有区分需求可以都并入 helmet。如果反复压不住就把半盔拆成一个独立类别训练推理时再合并输出。5.2 连帽衫帽兜算不算头盔标注规则留白最坑现象冬季穿连帽衫、雨天穿雨衣的骑手帽兜被模型高置信判成 helmet截图给业务方看非常尴尬。原因标注规则里没定义帽兜归属标注员凭感觉标有人标 helmet 有人标 no_helmet模型学到的是矛盾的标签分布。帽兜的轮廓和半盔确实接近正面看尤其像。解决在标注规则里写死——只有硬质保护头盔算 helmet连帽衫帽兜、围巾一律 no_helmet。如果业务场景明确要求拦截帽兜就单独拆出 hood 类别做三分类。这些难样本按 10% 到 20% 的比例重复采样让模型多看到几次别被常规样本带偏。5.3 工地安全帽混进电动车场景合并还是拆类现象戴黄色工程安全帽的骑手被漏检或者普通自行车头盔被识别成安全帽两个类别来回串。原因工地安全帽和自行车头盔在颜色、帽檐形状上接近而数据集偏重市区通勤场景工地安全帽样本太少模型没见过自然就归到相近类别里。解决先定业务口径。城市通勤场景一般把两类合并成一个 helmet对外输出只关心戴没戴。工地周边巡检场景则拆出 hardhat 独立类别专门采集白、黄、蓝三色安全帽的各角度样本每个颜色都要有正面、侧面和俯视视角否则拆类后依然互相干扰。5.4 夜间逆光下深色头盔消失这问题调参救不回来现象白天 mAP 能到 90 以上夜间直接掉到 70 上下戴深色头盔的骑手几乎全漏。原因头盔是塑料深色材质夜间和背景融为一体640 分辨率下小目标只有几十个像素对比度再一低特征就完全被淹没了。这类问题不是模型不收敛而是数据里根本没有夜间样本。解决采集夜间红外补光或闪光灯补光样本训练时打开 HSV 通道增强模拟低照度把亮度通道的扰动范围加大推理侧对亮度直方图做自适应拉伸而不是手动调曝光。这里有个血泪经验调阈值、加注意力模块都试过最后还是老实回去补了夜间数据才见效。5.5 漏标与错标标注质量先于模型参数现象训练损失很低但验证集漏检多模型对某些图「视而不见」。原因标注阶段大量小目标被漏标模型把带框区域当成负样本学多人重叠时头部框边界乱画模型学到的是歪框。漏标比错标的危害更大因为它悄无声息地污染了正负样本边界。解决双标注抽检一致性低于 90% 的批次返工统计无框图占比某个路口无框率异常就回原图核查多半是漏标更换标注工具后抽 20 张图对比坐标框防止工具之间的坐标偏移。标注质量的坑不会在训练初期暴露往往在业务方拿现场图来质疑的时候才炸开所以前置抽检必须做。6. 数据集上线前的验证与数据闭环让误检样本反哺训练集6.1 用一段真实路口视频做端到端验证顺手压掉抖动误检静态测试集只能证明模型认识这些图片不能证明模型能用。我每次训练结束都会去部署点位附近拍 10 分钟左右的视频抽帧逐帧跑推理统计漏检、误检和置信度抖动。真实监控里最常见的现象是单帧闪烁——某一帧误检一个头盔下一帧又消失截图看不出问题在视频里非常扎眼。处理办法是 3 帧滑窗投票同一目标连续两帧以上命中才保留输出。from collections import Counter, deque class FrameVote: def __init__(self, window3, min_hits2): self.queue deque(maxlenwindow) self.min_hits min_hits def update(self, frame_dets): # frame_dets: [{id: 跨帧目标id, conf: 置信度}, ...] self.queue.append(frame_dets) if len(self.queue) self.queue.maxlen: return [] # 队列未满不做决策 hits Counter() for dets in self.queue: for d in dets: hits[d[id]] 1 return [d for d in frame_dets if hits[d[id]] self.min_hits]window 越大延迟越高路口场景 3 帧就够。跨帧的目标 id 用 bbox 中心点欧式距离小于阈值来匹配不要用目标检测器自带的 id小目标场景下它不稳定。6.2 误检样本回流训练集数据闭环比调参更值钱数据闭环是这个数据集项目里最值得养成的习惯。每两周把现场新拍到的误检图挑 100 到 200 张补标后加进训练集用小学习率 lr00.001 续训 20 到 30 个 epoch千万不要从头训。数据集会一点点向真实部署场景靠拢误检率是能画出下降曲线的。这个做法本质上是把现场当成标注数据的源让数据集自己长大。我自己的教训是有一回赶上线漏了梅雨季的样本回流结果连续几周在雨天路口误检率翻倍最后乖乖回去补数据。从此养成每周抽 20 分钟翻误检图的习惯把新规律写进标注规则文档这比调任何训练参数都值希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?