☰
YOLOv5电动车头盔检测:3类别数据集标注与训练避坑指南
2026/10/26 15:47:20 网站建设 项目流程

简介:这份资源面向计算机视觉学习者与目标检测项目开发者,提供道路上电动车骑行者是否佩戴头盔的YOLOv5格式数据集,可直接用于训练与验证,省去自行采集与标注的成本。数据按YOLOv5标准目录组织,图像为1920×1080的RGB实拍图片,标注涵盖戴头盔、未戴头盔及整体标注共3个类别,训练集3646张图片与3646个标签文件,验证集911张图片与911个标签文件,并附类别说明文本。压缩包共2000个文件,以1999个txt标注文件和1个py可视化脚本为主,整体约502.44MB,解压后即可投入训练。其中show.py可随机读取一张图片绘制边界框并保存到当前目录,无需修改即可运行,便于快速检查标注质量。目前已有957人学习下载,适合需要头盔检测、安全骑行监测或目标检测课程实践的用户参考使用。

1. 电动车头盔检测数据集:3 个类别背后藏着多少标注暗坑

路口抓拍回传的图里,骑手头上那顶头盔到底戴没戴,是很多做智慧交通的团队绕不开的一个检测需求。YOLOV5 数据集:道路上电动车是否佩戴头盔目标检测数据集(3类别),说的就是这件事——用 YOLO 系列做骑乘者头部区域的目标检测,把画面里的人分成戴头盔、没戴头盔、以及可能存在的其他干扰类,最终服务于非机动车违法抓拍、事故定责辅助、园区骑行安全管理这类场景。它适合两类人:一类是手里已经有一批路口或卡口图片、想快速训出一个能跑通的原型;另一类是准备自建数据集、想先搞清楚 3 类别方案在标注和训练上会踩哪些坑。这篇不聊虚的,从类别定义、标注规范、YOLOv5 训练配置一路讲到验证和排错,中间该贴的命令和参数都给你,照着能复现。

2. 3 类别怎么定:头盔检测的类别设计与标注规范

2.1 为什么是 3 类而不是 2 类

很多人第一反应是二分类:戴了、没戴。真到标注现场就会发现,画面里大量头部是模糊的、被遮挡的、背对镜头的,还有戴的是遮阳帽、工地安全帽、毛线帽。如果强行塞进「没戴头盔」这一类,模型会学到一堆矛盾特征,误报率直接起飞。所以常见的 3 类别设计是:

类别 ID类别名含义典型样本
0helmet明确佩戴骑行头盔正面、侧面清晰可见头盔轮廓
1no_helmet明确未佩戴头盔的骑乘者头部裸露头发、戴普通帽子
2uncertain无法判定或非目标头部严重遮挡、背对、非骑乘者头部

第 2 类不是凑数,它是给标注员一个「弃权」出口。没有这个出口,标注员只能二选一,噪声全进训练集。有了它,训练时可以选择忽略该类,或者单独观察它的分布来判断数据质量。

提示:如果你的业务只关心违法抓拍,可以把 uncertain 在训练时通过--ignore-class思路过滤掉,但标注阶段一定要保留,方便回溯。

2.2 标注框该框头还是框人

这是血泪经验里翻车最多的地方。框整个人,模型学的是人体检测,头盔只是人体上的一个小区域,小目标特征被稀释;框整个头,头盔和头几乎重合,戴与不戴的差异集中在头顶那一小块。我一般会这样定:

  • 框的范围是骑乘者头部外接矩形,包含头盔本体和面部,不含肩膀。
  • 头盔和头部不要分开标两个框,一个头一个框,类别由头盔状态决定。
  • 后视镜、车把、挡风被一律不标,避免引入无关目标。

标注工具用 labelImg 或 CVAT 都行,导出 YOLO 格式。YOLO 格式每行是class_id x_center y_center width height,全部归一化到 0~1。下面这段脚本用来检查标注文件里有没有越界或空框:

import os def check_labels(label_dir, img_w=1920, img_h=1080): bad = [] for name in os.listdir(label_dir): if not name.endswith('.txt'): continue path = os.path.join(label_dir, name) with open(path) as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) != 5: bad.append((name, i, '字段数不对')) continue cls, x, y, w, h = parts x, y, w, h = map(float, (x, y, w, h)) # 归一化坐标必须在 0~1,且宽高不能为 0 if not (0 <= x <= 1 and 0 <= y <= 1): bad.append((name, i, '中心点越界')) if w <= 0 or h <= 0 or w > 1 or h > 1: bad.append((name, i, '宽高异常')) return bad if __name__ == '__main__': issues = check_labels('./labels/train') for it in issues[:20]: print(it) print('总问题数:', len(issues))

逻辑说明:逐行解析 YOLO 标注,检查字段数、归一化范围和宽高合法性。参数上img_w/img_h其实用不到,因为 YOLO 是归一化坐标,这里保留是为了提醒你——如果你用的是 VOC 的绝对坐标转过来,务必先除以原图宽高。跑完如果问题数不为 0,先修标注再谈训练,否则后面 loss 不降你会怀疑人生。

2.3 数据划分与类别均衡

3 类别里 uncertain 往往占比不低,如果直接随机划分,可能出现验证集里某一类几乎为空。建议按类别分层抽样,训练:验证:测试大致 8:1:1。可以用 sklearn 的train_test_split配合stratify,但前提是每个标注框都能归到一个主类别。更简单的做法是先按图片级的主类别(一张图里出现最多的类别)分层,再划分。

另外,no_helmet 通常是少数类,因为违法样本本身少。不要一上来就过采样复制图片,容易过拟合。优先考虑:在 loss 里给少数类更高权重,或者用 mosaic、mixup 增强时让少数类图片参与更多批次。YOLOv5 的hyp.scratch-low.yaml里cls参数控制分类损失权重,可以适当调高。

3. 用 YOLOv5 训练头盔检测模型:从环境到第一个权重

3.1 环境配置与数据目录结构

YOLOv5 对环境不算挑剔,Python 3.8+、PyTorch 1.8+ 就能跑。我一般用 conda 建独立环境,避免和系统里的其他 torch 版本打架:

conda create -n helmet python=3.9 -y conda activate helmet # 按你的 CUDA 版本装 torch,这里以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

装完先跑python detect.py --source data/images确认环境没问题。数据目录按 YOLOv5 约定组织:

helmet_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/

然后写数据配置文件helmet.yaml:

path: ../helmet_dataset train: images/train val: images/val test: images/test nc: 3 names: ['helmet', 'no_helmet', 'uncertain']

nc是类别数,必须和 names 长度一致,写错会直接报维度不匹配。path用相对路径时注意是相对于 yolov5 根目录还是 yaml 所在目录,YOLOv5 不同版本行为略有差异,稳妥起见用绝对路径。

3.2 训练命令与关键超参数

最小可跑命令:

python train.py \ --data helmet.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name helmet_exp

参数逐个说:--weights yolov5s.pt用预训练权重,小目标场景也可以试 yolov5m;--img 640是输入分辨率,头盔在整图里偏小,如果显存够,建议上 960 或 1280,小目标召回会明显改善;--batch 16按显存调,8G 显存跑 640 大概能到 16;--epochs 100是起步值,看 mAP 曲线早停;--hyp用 low 增强配置,数据量少时比默认配置更稳。

训练过程中重点看三个指标:metrics/mAP_0.5、val/box_loss、val/cls_loss。如果 box_loss 一直震荡不降,多半是标注框有问题;如果 cls_loss 高但 box_loss 正常,说明类别特征难分,考虑加数据或调cls权重。

3.3 推理与结果查看

训练完权重在runs/train/helmet_exp/weights/best.pt。推理:

python detect.py \ --weights runs/train/helmet_exp/weights/best.pt \ --source ../test_images \ --img 960 \ --conf-thres 0.35 \ --iou-thres 0.5 \ --save-txt

--conf-thres是置信度阈值,头盔检测里 no_helmet 容易漏,可以先把阈值降到 0.25 看召回,再根据误报情况回调。--save-txt会输出每张图的检测框坐标,方便你写脚本统计各类别数量。--iou-thres控制 NMS,密集骑乘场景可以适当调低到 0.45,减少框重叠。

4. 头盔检测避坑与排查:5 个真实翻车现场

4.1 现象:mAP 卡在 0.3 上不去,loss 也不降

原因:最常见的是标注框把整个人都框进去了,头盔区域占比太小,模型学不到判别特征。其次是类别名和 yaml 里顺序不一致,比如标注时 helmet 是 0,yaml 里写成了 1。

解决:随机抽 20 张训练图,用python detect.py跑一遍看框的位置,如果框明显偏大,回去重标。再核对 yaml 的 names 顺序和标注文件里的 class_id 是否一一对应。改完重新训练,mAP 通常能到 0.6 以上。

4.2 现象:验证集 mAP 很高,实际路口图一跑全是误报

原因:训练集和实际场景分布不一致。训练集可能是白天清晰图,实际是夜间、逆光、雨雾。或者训练集里电动车占比高,实际有大量自行车、行人头部被误检。

解决:先做一轮 hard negative mining,把实际场景里误报的图挑出来,标成背景(不标任何框)加入训练集。同时检查 uncertain 类是否被模型当成了垃圾桶,如果 uncertain 样本太杂,考虑拆成多个子类或直接剔除。夜间场景可以单独做一批增强,调低亮度、加噪声。

4.3 现象:no_helmet 召回率极低,几乎检不出

原因:少数类样本太少,模型倾向于把不确定的都判成 helmet。另外,no_helmet 和 uncertain 在视觉上边界模糊,标注一致性差。

解决:统计三个类别的框数量,如果 no_helmet 占比低于 10%,需要补数据。补不了就用--cls-pw思路调分类损失权重,或者在 hyp 里把cls从 0.5 提到 0.8。同时统一标注规范:明确「戴帽子但非头盔」归 uncertain,不要归 no_helmet。

4.4 现象:训练到一半显存爆了

原因:--img调太大,或者--batch没跟着降。YOLOv5 的显存占用和 img 的平方、batch 成正比。

解决:显存不够时优先降 batch,保持 img 不变,因为分辨率对头盔这种小目标影响更大。也可以用--multi-scale让输入尺寸在一定范围内随机,但会略微增加显存波动。实在不够就上梯度累积,--accumulate 2等效于 batch 翻倍但显存不翻。

4.5 现象:推理速度慢,达不到实时

原因:用了 yolov5l 或更大模型,或者 img 设成 1280 但硬件是边缘设备。

解决:头盔检测在路口场景通常要求 25 FPS 以上。如果目标是边缘部署,优先选 yolov5s 或 yolov5n,img 降到 640,再用 TensorRT 或 ONNX Runtime 加速。量化到 INT8 能再快一倍,但要注意校准集要覆盖夜间和逆光样本,否则精度掉得厉害。

5. 把头盔检测做扎实:验证方法与一个提点技巧

模型训完不是终点,怎么验证它真的能用才是。我一般会做三层验证:第一层是标准验证集上的 mAP 和各类别 AP,确认没有类别塌陷;第二层是分场景验证,把测试集按白天/夜间、正面/侧面、单人/多人分组,分别算指标,找出短板场景;第三层是业务指标验证,比如「未戴头盔抓拍准确率」和「漏抓率」,这比 mAP 更贴近实际。

分场景验证可以用下面这段脚本,按文件名前缀分组统计:

import os from collections import defaultdict def group_eval(label_dir, pred_dir, groups): # groups: {'day': ['day_'], 'night': ['night_']} stats = defaultdict(lambda: {'tp': 0, 'fp': 0, 'fn': 0}) for gname, prefixes in groups.items(): for name in os.listdir(label_dir): if not any(name.startswith(p) for p in prefixes): continue gt = load_boxes(os.path.join(label_dir, name)) pred = load_boxes(os.path.join(pred_dir, name)) tp, fp, fn = match(gt, pred, iou_thres=0.5) stats[gname]['tp'] += tp stats[gname]['fp'] += fp stats[gname]['fn'] += fn for g, s in stats.items(): prec = s['tp'] / max(s['tp'] + s['fp'], 1) rec = s['tp'] / max(s['tp'] + s['fn'], 1) print(f'{g}: precision={prec:.3f}, recall={rec:.3f}')

逻辑说明:按文件名前缀把图片分到不同场景组,分别统计 TP/FP/FN,再算精确率和召回率。load_boxes和match需要你自己实现,核心是按 IoU 匹配预测框和真实框。这个脚本能帮你快速定位是夜间漏检多还是多人场景误报多。

最后一个提点技巧:头盔检测里,no_helmet 的召回往往比 helmet 更重要,因为漏抓一个违法比误报一个更影响业务。可以在推理后处理阶段对 no_helmet 单独降低置信度阈值,比如 helmet 用 0.4,no_helmet 用 0.25,再配合一个简单的跟踪逻辑(同一目标连续多帧命中才输出),能明显压低误报。这个策略我在几个路口项目里都用过,比单纯调模型参数见效快。

我自己踩过最深的坑,是早期图省事把 uncertain 直接并进 no_helmet,结果模型在阴天和逆光下疯狂误报,后来老老实实把 uncertain 单独标出来,哪怕它不参与最终输出,训练稳定性也好了很多。数据标注这件事,偷的懒最后都会在调参阶段加倍还回来。希望帮到你。

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

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

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

立即咨询