简介:面向目标检测初学者与头盔佩戴自动识别需求,提供一套按YOLOV5目录结构整理的道路电动车头盔检测数据集。共3个类别:戴头盔、没戴头盔,以及行人整体标注,图片为1920×1080分辨率的RGB道路场景,适合直接接入YOLOV5训练流程,免去格式转换与手工整理。资源共2000个文件,主体为1999个txt标注文件,配合1个show.py可视化脚本,压缩后502.44MB,数据划分清晰:训练集包含3646张图片与对应标签,验证集包含911张图片与对应标签,另有txt类别说明文件。其中show.py无需修改即可运行,随机读取一张图片就能绘制出边界框并保存到当前目录,方便快速核验标注效果。目前已有955人学习下载,适合用于毕业设计、课程项目或算法练手,省去采集与标注的重复工作。
1. 为什么“3类别”的电动车头盔数据集,反而比二分类更难用
很多人在找“道路上电动车是否佩戴头盔目标检测数据集”时,默认以为拿到一个分好类的压缩包、跑通YOLOv5训练就完事了。但真正做过这个方向的人会告诉你:头盔检测的难点从来不是“有没有戴”,而是“戴的是什么”和“没戴到什么程度”。这个数据集标成3类——通常是helmet(正确佩戴)、head_without_helmet(完全没戴)、head_with_helmet_bad(戴了但没系扣或挂在脖子上)——意味着你训练出来的模型不仅要识别“有无”,还得判断“佩戴是否规范”。这对标注一致性、正负样本比例、以及YOLOv5的anchor设置都提出了比二分类更苛刻的要求。
这篇文章面向的是真正要拿这个数据集去跑训练、调参、甚至部署到边缘设备上的从业者。我会从数据集目录结构讲起,直接给你一份能跑的YOLOv5训练配置,再把3类别最典型的几个坑——类别不平衡、头盔小目标漏检、误检高发场景——逐一拆开。你跟着走一遍,至少能少翻三次车。
2. 拿到数据集后先别急着训:目录结构、标注格式与类别核对
2.1 数据集的常见目录组织方式
这类“道路电动车头盔检测”数据集一般会按YOLO格式或VOC格式交付。以最常见的YOLO格式为例:
helmet_dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ ├── img_0002.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── img_0001.txt │ │ ├── img_0002.txt │ │ └── ... │ ├── val/ │ └── test/ ├── classes.txt └── dataset.yamlclasses.txt里通常就三行:helmet、head_without_helmet、head_with_helmet_bad。注意:有些数据集会把类别顺序写成head_without_helmet在前、helmet在后,如果你直接用别人的dataset.yaml而不核对,训练出来的模型类别顺序就是乱的,推理时张冠李戴。
标签文件里每行对应一个目标框:class_id center_x center_y width height。这里的center_x、width全部是相对于图像宽高的归一化值。我刚拿到这类数据集时,习惯先用Python快速扫一遍所有标签,看类别分布和边界框尺寸分布——这一步能省掉后面一大半调参的力气。
2.2 用脚本核对样本数与类别平衡
3类别里最典型的分布是:helmet样本最多,head_without_helmet次之,head_with_helmet_bad最少。有的数据集里第三类可能只占5%不到。这种分布会让YOLOv5在训练时严重偏向多数类。
import os from collections import Counter label_dir = "helmet_dataset/labels/train" counter = Counter() for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue with open(os.path.join(label_dir, fname), "r") as f: for line in f: cls_id = int(line.split()[0]) counter[cls_id] += 1 print(counter) # 输出示例: Counter({0: 5210, 1: 2870, 2: 430}) # 如果类别0是helmet,类别2只有400出头,就要考虑采样策略这段代码做的事很简单:遍历训练集所有标签文件,统计每个类别的目标框总数。逻辑说明:我统计的是“目标框数量”而不是“图片张数”,因为一张图里可能有5个不戴头盔的人,导致类别比例看上去均衡,实际目标框却失衡。参数说明:如果发现最少的类别占比不足10%,建议在训练时开启--image-weights参数,YOLOv5会根据每个类别的样本权重动态调整采样比例。
2.3 边界框尺寸分布决定anchor策略
YOLOv5默认的anchor是针对COCO数据集设计的,COCO数据集80类、物体普遍大而居中。但电动车的头盔在道路监控画面里往往只占很小一块,尤其是远景电瓶车,头盔像素可能只有20x20。这时候要重新聚类anchor。
import numpy as np from pathlib import Path def load_bboxes(label_paths): all_wh = [] for lp in label_paths: with open(lp, "r") as f: for line in f: parts = line.split() w = float(parts[3]) h = float(parts[4]) all_wh.append([w * 640, h * 640]) # 假设图像resize到640 return np.array(all_wh) label_files = list(Path("helmet_dataset/labels/train").glob("*.txt")) wh = load_bboxes(label_files) print(f"边界框数量: {len(wh)}, 平均尺寸: {wh.mean(axis=0)}")参数说明:640是YOLOv5默认的imgsz训练尺寸,如果你后续部署用imgsz=416,这里改成416。跑完这段脚本,你大概会看到平均框宽高在[35, 30]左右——明显小于COCO的平均水平。这类数据集我一般直接换用k-means聚类出的anchor,而不是吃YOLOv5默认值。
3. 用YOLOv5训练3类别头盔检测:从环境配置到启动训练
3.1 环境配置与依赖安装
YOLOv5训练环境本身不算复杂,但版本兼容问题足够让人折腾一天。我当前的常用搭配是:Python 3.9-3.10、PyTorch 1.13+、CUDA 11.7。如果你用最新版PyTorch 2.x,某些旧版YOLOv5代码会报torch.insert的坑,建议直接拉最新的官方仓库。
# 创建conda环境并安装依赖 conda create -n yolov5 python=3.10 -y conda activate yolov5 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这段命令里最值得留意的是--index-url。逻辑说明:默认PyPI源装的PyTorch是CPU版本,训练时根本用不上GPU,跑起来慢得让人怀疑人生。参数说明:CUDA版本以你自己的显卡驱动为准,如果是RTX 4090这类新卡,需要安装适配CUDA 11.8以上或12.x的PyTorch,否则会报no kernel image is available。
装完依赖后,用官方自带的detect.py跑一次推理验证环境:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg如果能看到输出图里正确框出人、车、bus,说明环境基本通了。这一步没跑通就不要继续往下走,否则后面训练出的问题你会分不清是环境问题还是模型问题。
3.2 编写dataset.yaml并做数据集划分
YOLOv5要求数据集配置一个YAML文件。路径建议都用绝对路径,而不是相对路径——相对路径在这个项目里是我踩过最多次的坑:
# helmet.yaml path: /home/user/helmet_dataset # 数据集根目录 train: images/train # 训练集相对path的路径 val: images/val # 验证集 test: images/test # 测试集,可省略 nc: 3 # 类别数 names: ['helmet', 'head_without_helmet', 'head_with_helmet_bad']逻辑说明:path字段是YOLOv5从5.0版本之后引入的写法,用来统一拼接train、val、test路径。如果你把train写成/home/user/helmet_dataset/images/train这种全路径,同时path又写了根目录,YOLOv5会把两段拼起来,直接报路径不存在。参数说明:names的顺序必须和标签文件里的class_id一一对应,这里最容易翻车的就是把这个顺序搞错。
数据集划分如果原包没有给好,用train_test_split脚本快速处理:
import os import random from shutil import copyfile image_files = os.listdir("helmet_dataset/images") random.seed(42) random.shuffle(image_files) train_split = int(len(image_files) * 0.8) val_split = int(len(image_files) * 0.9) # 留10%做测试 for i, fname in enumerate(image_files): src_img = f"helmet_dataset/images/{fname}" src_label = f"helmet_dataset/labels/{fname.replace('.jpg', '.txt')}" if i < train_split: dst = "split/train" elif i < val_split: dst = "split/val" else: dst = "split/test" copyfile(src_img, f"{dst}/images/{fname}") copyfile(src_label, f"{dst}/labels/{fname.replace('.jpg', '.txt')}")说明:这个切分逻辑是按整图划分,不是按目标框划分。按图划分能避免同一张图的内容同时出现在训练集和验证集,造成验证指标虚高。random.seed(42)保证每次运行得到相同划分结果,方便复现。
3.3 启动训练并盯住关键指标
万事俱备,启动训练:
python train.py \ --data helmet.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache \ --name helmet_v5s参数说明:
--weights yolov5s.pt:用COCO预训练权重做迁移学习。头盔检测属于目标检测的下游任务,COCO里虽然没头盔类别,但特征的通用性足以让模型快得多收敛。如果从零训练--weights '',100个epoch大概率还在过拟合边缘挣扎。--cache:把图片一次性加载进显存,能显著缩短训练时间。代价是显存占用暴涨,我现在用的RTX 3090 24G才能配合batch=16和imgsz=640跑得动。显存小的机器建议去掉或用--cache ram。--name helmet_v5s:指定本次训练的日志和权重输出目录,默认为runs/train/exp,多个实验跑下来会非常混乱。
训练过程中每分钟刷一次的指标矩阵里,我第一个看的不是mAP,而是train/box_loss和val/box_loss。这两个值在训练初期如果完全不下降,大概率是标签坐标归一化出了问题——通常是标注文件的坐标范围写成了像素值。第二步看metrics/precision和metrics/recall的差距,如果precision高但recall低,说明模型倾向于保守,漏检多;反过来则误检多。头盔检测场景里漏检比误检严重得多——漏检一个不戴头盔的骑手,等于这个模型在实际部署中形同虚设。
3.4 显存不足时的降级方案
如果你用的显卡只有8G显存(比如RTX 3060 Laptop),上面这条命令大概率会直接爆发显存溢出。我一般从三个方向降:
python train.py --data helmet.yaml --weights yolov5s.pt --img 512 --batch 8 --epochs 100 --name helmet_v5s_512减小--img首当其冲,从640降到512,显存占用直接少一半。不过注意,训练尺寸变了,推理时最好也保持同样尺寸,否则尺度不一致会影响精度。其次把--batch降到8,最后可以考虑换yolov5s.yaml为yolov5n.yaml——这个配置文件控制的是网络宽度和深度,n版参数只有s版的三分之一,精度会掉一些,但换来的是训练效率和边缘部署的友好性。
4. 3类别检测的四个必调参数:anchor、类别权重、IoU阈值与NMS设置
4.1 用自动anchor让模型匹配头盔尺度
YOLOv5的train.py每次训练都会在启动时重新计算anchor,除非你显式传--noautoanchor禁用。这个机制会根据当前数据集的边界框尺寸,用k-means聚类出更适合的anchor。不过它只在首个epoch做一次,如果数据分布太偏,它聚类出的anchor也可能不够理想。
我通常先跑一遍自动anchor训练,然后查看runs/train/exp/opt.yaml里记录的anchor值,再手动改models/yolov5s.yaml里的anchor定义,锁定后训练:
# anchors: 3 scales x 3 aspect ratios anchors: - [10, 13, 16, 30, 33, 23] # P3/8 小目标 - [30, 61, 62, 45, 59, 119] # P4/16 中目标 - [116, 90, 156, 198, 373, 326] # P5/32 大目标 (原COCO默认)头盔检测数据集中,中大型anchor(P5那组)几乎用不上,我把这组换成更小尺寸,比如[20, 25, 30, 55, 55, 80]。逻辑说明:YOLOv5在三个不同尺度的特征图上分别做预测,P3负责小目标,P5负责大目标。道路监控画面里的头盔通常落在P3和P4,与其让P5那组大anchor占着权重,不如把小anchor尺寸往小压。改完之后训练,你能明显看到小目标类别的recall拉上来。
4.2 类别不平衡:给少数类加loss权重
3类别数据集的常见问题是head_with_helmet_bad样本太少。YOLOv5官方没有直接提供类别权重参数,但可以在train.py里改loss.py的部分代码——这种做法比较侵入,另一种思路是直接在数据层面处理。我更推荐做离线数据增强,把少数类对应的图像旋转、左右翻转、亮度抖动后复制进训练集。注意增强时标签坐标要跟着变换,用albumentations库里的RandomBrightnessContrast配合BboxParams可以省掉自己写变换逻辑的麻烦。
4.3 推理阶段的IoU阈值与NMS设置
训练是一回事,部署时调参数又是另一回事。detect.py里最值得改的是这两个参数:
python detect.py \ --weights runs/train/helmet_v5s/weights/best.pt \ --source test_images/ \ --conf-thres 0.25 \ --iou-thres 0.45 \ --img 640参数说明:--conf-thres是置信度阈值,低于这个值的目标直接被丢弃。头盔检测这种安全相关场景,我推荐设0.15到0.25——宁可多一些误检,也要保证不戴头盔的人被框出来。--iou-thres是NMS过程中判断两个候选框是否是同一目标的IoU阈值,默认0.45,如果画面上人挨着人、电动车并排骑行,导致头盔框互相交叠,把这个值调大到0.5以上,可以减少重叠框被抑制后只保留一个的情况——但调太大也会让两个不同目标被合并成一个框。
常见的调参失误:有人为了提高precision把--conf-thres调到0.5,结果原本就难检的head_with_helmet_bad几乎全军覆没。这类“戴了但没系好”的正样本,特征上与“正确佩戴”非常接近,置信度天然偏低。阈值设太高等于把这个类别删掉了。
4.4 超参数:让误检和漏检更可控
YOLOv5的data/hyps/hyp.scratch-low.yaml里有一些全局超参数,头盔检测里最值得动的是hsv_h、hsv_s、hsv_v——这是训练时对图像做HSV色彩增强的强度。道路监控画面受天气和光照影响极大,阴天、逆光、夜间场景下头盔颜色和轮廓都会发生变化。我把默认的hsv_h: 0.015、hsv_s: 0.7、hsv_v: 0.4稍微调高到0.02、0.8、0.5,让模型在训练中见到的颜色变化更多样,减少对特定颜色的过拟合。
另外fliplr: 0.5是水平翻转增强的比例,保持默认就好——头盔左右对称,翻转不会生成错误的监督信号。
5. 3类别头盔检测的避坑合集:从标注到部署的5个教训
5.1 类别标注不一致:同一顶头盔能被标成两类
现象:训练完验证集mAP不低,一测试发现模型把“戴头盔的人”和“没戴头盔的人”反复横跳,同一张图换个角度就分类翻转。
原因:数据集标注阶段对“戴了但没系扣”的边界定义不清晰。有的标注员把帽子、鸭舌帽也标成了helmet,有的把挂在车把上的头盔标成了head_with_helmet_bad——这根本不是人头位置,目标框和人体完全不匹配。
解决:训练前把所有标签按类别抽出来可视化一遍。用cv2.rectangle把目标框画在图片上,人眼扫一遍,把明显标错的图直接删掉或重新标注。这个过程得有心理准备,1000张图可能得花两三个小时。但跳过这一步,后续所有调参都是在烂数据上瞎折腾。
5.2 验证集指标虚高,实际场景全崩
现象:训练时val mAP到0.85,部署到真实道路监控,准确率掉到一半。
原因:验证集和测试集图片来自同一个视频流的不同帧,高度相关。模型在验证集上看到的场景和实际部署场景差异极大——数据集里几乎全是白天光线充足的画面,而你部署的位置可能有树荫、雨雾、夜晚路灯。
解决:用--source单独拉一个真实场景的视频文件做鲁棒性测试,不要只看mAP。尤其是夜间场景,如果训练集里没有晚上骑电动车的图片,模型对暗光下的头盔基本睁眼瞎。我一般会从数据集外补充夜间和逆光图片,用训练好的模型打伪标签,再手工修正,混入训练集做一次微调。
5.3 后处理阶段漏检大量小目标
现象:大尺寸头盔全部检测正常,距离远一点、像素小一点的头盔全部漏掉。
原因:小目标在YOLOv5的P3特征图上对应的特征较少。如果训练时--img用640,推理时NMS的阈值又偏严,小目标经过两次下采样后只剩下极小的特征激活区域,置信度天然上不去。
解决:把推理尺寸从640提升到960或1280,但显存和时间消耗也会同步涨。我是先做anchor聚类,如果聚类结果中小框占比超过一半,直接换yolov5s6.yaml这种6个输出尺度的模型结构;如果数据和算力都有限,就老老实实把训练和推理的--img都提到960。注意这会让训练时间几乎翻倍。
5.4 骑手手臂与摩托车把手造成误检
现象:头盔检测模型把路人的手机、背包、甚至路边园丁的草帽识别成了头盔。
原因:3类别中的helmet是一个相对宽泛的语义——圆形的、深色的、在人头顶的物体。数据集中有些图的头盔外形差异大,比如粉色头盔、半盔和全盔,模型学到的特征是“圆形+颜色深+在人头上”,导致手机的凸起、背包的圆角也被激活。
解决:检查数据集中头盔的形态多样性是否覆盖了目标场景。如果不够,补充数据或者做更激进的颜色/几何增强。另外一条路是在模型后接一个基于位置约束的过滤逻辑——头盔必须出现在人的头部区域,如果检测框中心点距离人体框顶部太远,直接丢弃。这类后处理通常用pip install shapely算IoU来过滤。
5.5 训练中断后继续训练导致loss暴涨
现象:训练到epoch 40时显存溢出或断电,用--resume继续训练,loss曲线直接飞到天上。
原因:训练中断时保存的last.pt是断点处的优化器状态和权重,但如果你在中断后改了--batch或--img,学习率调度器和优化器状态对不上了。
解决:--resume时保持和上次完全一致的batch和img参数。如果非要改,建议从best.pt重新训,而不是从last.pt续跑。另外,给train.py传--workers 0可以避免某些Windows环境下dataloader的线程问题导致的中断。
6. 把头盔检测模型部署到边缘设备:模型降级与推理加速的实用做法
这个项目最常见的落地场合是电动自行车出入口的监控摄像头,现场往往只有一台Jetson Nano或树莓派。这些设备的算力跑不动YOLOv5s原始模型,需要做量化裁剪。我当前的推荐做法是先用yolov5s.pt训练,验证通过后导出为torchscript或engine格式,再做INT8量化。YOLOv5官方仓库自带的export.py可以直接把PyTorch权重导出为TensorRT引擎:
python export.py \ --weights runs/train/helmet_v5s/weights/best.pt \ --include engine \ --imgsz 640 \ --half参数说明:--half开启FP16精度,显存占用减半,推理速度几乎翻倍,精度损失在头盔检测这种语义不那么精细的任务上基本可以忽略。如果你用TensorRT,--int8需要额外的校准数据集,做法是在data/helmet.yaml里指定calib: images/val,然后导出时加--int8。
部署时我用的是TensorRT的Python API加pycuda做异步推理。整条pipeline包括:读帧、预处理(resize到640再Normalize)、推理、后处理(解anchor、NMS)。实际在Jetson Nano上,FP16的YOLOv5s能跑到15-20 FPS,如果卡到10 FPS以下,把--imgsz降到480甚至416——头盔检测在小尺度下掉点还在可接受范围,但流畅度直接影响抓拍成功率。
一个人逃不过的经验:头盔检测的能不能用,七分在数据,两分在调参,一分在算力。我做过不止一次把大量时间花在调NMS阈值和anchor上,最后发现数据集本身有几百张标注错乱的图。所以拿到这个数据集,第一件事不是跑train.py,而是花半天把标签可视化检查一遍。等模型在真实场景里稳定跑上一周,你再回头看,会发现最值得投入的永远是数据质量本身。希望帮到你。
本文还有配套的精品资源,点击获取