简介:面向目标检测与电动车安全治理场景的YOLOv5数据集,聚焦道路上电动车骑行者头盔佩戴识别,共3个类别:戴头盔、未戴头盔、整体标注,适合目标检测入门练习及校园、园区等场景的安全监测项目。数据集按训练/验证划分,训练集3646张1920×1080 RGB图像,验证集911张,配套txt标签与图片同名,内含类别序号和归一化坐标,无需额外处理即可直接用于YOLOv5训练。压缩包内共2000个文件,主要文件类型为txt标签文件,另附1个Python可视化脚本,可随机读取图片绘制边界框并保存到当前目录,无需改动即可运行,便于核对标注质量。资源包大小约502MB,已有955人浏览学习。对正在积累头盔检测训练数据的开发者而言,这份资源能省去自行采集与标注的时间,直接进入YOLOv5的训练、调参与效果评估环节。
1. 电动车头盔检测这个场景,为什么非要一份专门的YOLOV5数据集
电动车骑手戴没戴头盔,是路口执勤、小区门禁、园区安防最常被问到的一类问题。监控画面里人车混杂,靠人眼盯屏幕做台账,一天下来疲劳度高得吓人。换成目标检测就简单了:喂一份已标注好的数据集,让YOLOv5学会区分“戴头盔的骑手”和“不戴头盔的骑手”,每帧画面直接出框、出置信度。这份YOLOV5数据集就是为道路上电动车佩戴头盔场景整理的,共3个类别,采用YOLO标准txt标注,解压后可以直接进 yolov5 训练自己的数据集。适合安防工程师接监控方案,也适合目标检测初学者拿来练完整流程。这个场景的真正难点不在网络结构,而在标注口径、类别均衡和现场光照,下面按数据体检、训练、部署这条线把坑逐一说清。
2. 拆开看这份头盔检测数据集:3个类别、目录结构与标注格式
2.1 数据目录长什么样:images 与 labels 的对应关系
拿到压缩包后,先别急着解压就训练。头盔检测数据集的目录结构在不同渠道分发时会有差异,最通用的是 YOLOv5 官方推荐的 images / labels 配对方式:
helmet_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ # 部分数据包没有 test,只有 train/val ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classes.txt图片文件与标签文件靠“同名不同后缀”对应,比如IMG_0234.jpg对应IMG_0234.txt,一个 txt 里每一行就是一个目标框。classes.txt 里一行一个类别名,按索引顺序排列,第 0 行就是 txt 里的类别编号 0。这个 3 类别数据集最常见的拆法是helmet(头盔目标)、head(未佩戴头盔时的头部目标)、person(骑手或车身),但不同打包渠道可能把类别顺序调换,也可能把第三类定义为rider,所以拿到手第一步永远是打开 classes.txt 确认索引顺序,再决定 data.yaml 怎么写。
标签 txt 内部格式是 YOLO 归一化坐标,每行五个值:class_id x_center y_center width height,所有坐标都除以了图片宽高,取值在 0 到 1 之间。例如1 0.4823 0.3571 0.2146 0.1723表示类别 1 的框中心位于图片横向 48.23%、纵向 35.71% 处,框宽占整图 21.46%、高占 17.23%。比起 COCO 的 JSON 标注或 VOC 的 XML,这种格式对训练脚本最友好,YOLOv5 直接读入不需要做任何格式转换。
2.2 拿到手先做一遍“体检”:类别分布与标注质量统计
直接拿一个标注好的数据集训练之前,我会用脚本先统计两个东西:每个类别到底有多少个框、框的平均尺寸有多大。这一步能提前暴露类别不均衡和小目标过多的问题,等训练出来发现 mAP 上不去再回头查,浪费的时间更多。
import os from pathlib import Path from collections import Counter data_root = Path("./helmet_dataset") for split in ["train", "val"]: label_dir = data_root / "labels" / split cls_counter = Counter() widths, heights = [], [] for txt in label_dir.glob("*.txt"): lines = txt.read_text().strip().splitlines() for line in lines: parts = line.split() if len(parts) < 5: continue cls = int(parts[0]) w, h = float(parts[3]), float(parts[4]) cls_counter[cls] += 1 widths.append(w) heights.append(h) print(f"[{split}] 各类别框数量: {dict(cls_counter)}") if widths: print(f"[{split}] 平均框宽: {sum(widths)/len(widths):.4f}, " f"平均框高: {sum(heights)/len(heights):.4f}, " f"总框数: {len(widths)}")这段脚本遍历 labels 下所有 txt,统计每个类别的目标数量,并算出归一化后的平均框宽高。逻辑上要注意两点:一是行内值少于 5 个的跳过,避免读到空行或损坏标签;二是归一化宽高乘上图片实际像素才有意义,比如 640×640 的图,平均框高 0.17 大约是 109 像素,这属于中等偏小的目标,训练时--img 640是合理的,如果平均框高低于 0.08,就要考虑把输入分辨率提高到 768 或 960。
提示:如果发现某一个类别框数不到另一个类别的十分之一,先解决数据问题再训练。YOLOv5 虽然内置了按标签频率反比的类别权重,但极端不均衡下自动权重也压不住。
体检完类别分布,建议再抽查几张图,把标注框直接画回原图看边界。常见做法是用 OpenCV 把 txt 转成矩形,重点看头盔类别的框是否把头发区域一起包进去了,这直接决定后面训练时框的稳定性。检查脚本通常我只会跑前 3 张确认标注口径,不会全量跑,因为画框图占磁盘空间且人眼看多了会疲劳。
3. 把数据集跑进 YOLOv5 训练:环境、data.yaml 与超参数选择
3.1 环境准备和目录整理
YOLOv5 官方仓库对新手最友好的地方在于依赖固定,clone 下来装 requirements 就能跑。我一般用 conda 隔离一个专用环境,避免 pycocotools 或 OpenCV 版本和系统环境互相打架。
git clone https://github.com/ultralytics/yolov5 cd yolov5 conda create -n yolo python=3.10 -y conda activate yolo pip install -r requirements.txtPython 版本建议 3.9 或 3.10,太新的 3.12 在某些旧版 torch 配套下容易出现torchvision编译不匹配的问题。装完后把数据放进datasets/helmet_dataset/,目录结构保持第 2 章的 images/labels 配对形态即可。如果你的数据包是普通的文件夹,没有按 train/val 分好,最简单的方式是手动按 8:2 比例挪图片,同时带着同名 txt 一起挪:先shuf打乱文件名列表,再取前 80% 进 train,后 20% 进 val,并且每张图都要确认 labels 对应目录里有同名 txt,否则训练时那边读不到标注会直接跳过这张图。
3.2 data.yaml 怎么配、超参怎么给
训练前先写好训练集的描述文件,YOLOv5 用这个文件定位图片路径和类别数。
train: ./datasets/helmet_dataset/images/train val: ./datasets/helmet_dataset/images/val nc: 3 names: 0: helmet 1: head 2: personnc必须和实际标注类别数一致,names的索引顺序必须与 classes.txt 完全一致。这里最容易犯的错是类别顺序不一致:你以为是 0=helmet,数据实际是 0=person,训练出来的混淆矩阵完全没法看。路径建议用相对路径,这样整个项目目录拷贝到别的机器不用改配置。
训练命令我通常这样起:
python train.py \ --data helmet.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --workers 4 \ --cache disk--weights yolov5s.pt表示加载 COCO 预训练权重做迁移学习,头盔和 person 这些类别在 COCO 里有相近语义,收敛速度比从头训快很多。--cache disk是缓存到磁盘而非内存,避免几千张图一次性塞进内存导致机器卡死。--batch 16在 8GB 显存的卡上配合 640 分辨率比较稳妥,12GB 以上可以试 24 或 32。
3.3 训练结束后的指标怎么读
训练结束终端会打印一个结果表,核心看这几项:
| 指标 | 含义 | 对头盔检测的意义 |
|---|---|---|
| mAP@0.5 | IoU 阈值 0.5 下的平均精度 | 判断“框有没有框住目标” |
| mAP@0.5:0.95 | 0.5~0.95 多个阈值平均精度 | 判断“框得准不准” |
| Precision | 预测框中真正是目标的占比 | 误报越少越高 |
| Recall | 真实目标被召回的比例 | 漏检越少越高 |
对头盔检测这个业务场景,我优先看 Recall 而不是 Precision。原因很直接:漏掉一个没戴头盔的骑手,比多报一个戴盔框更严重。如果 Recall 明显低于 Precision,说明模型学得太保守,把很多置信度略低的真实目标过滤掉了,这时可以降低--conf阈值或者补数据。如果 mAP@0.5 过了 0.9 但 mAP@0.5:0.95 不到 0.6,多半是框边界不贴合,回到标注口径上看头盔框有没有把头发包进去。
4. 训练头盔检测模型翻车排查:五个常见坑与调整办法
4.1 类别不均衡:模型只会认“戴盔”,漏检“未戴盔”
现象:训练到 40 轮左右 mAP 不再上升,混淆矩阵里“未戴头盔的 head”大量被预测成“helmet”,输出图上一排绿色框,哪怕骑手只是戴了一顶遮阳帽也会被标成头盔。
原因:数据集中头盔目标远多于裸露头部目标,交叉熵损失天然偏向多数类,模型把“头顶有东西”一律学成了戴盔。
解决:先用第 2 章的统计脚本确认两类框的数量差异,如果超过 5 倍,就把少类图片做水平翻转、亮度扰动后复制一份混进 train 目录,或者单独多拍一些没戴头盔的现场图补进去。数据层面解决比调损失权重靠谱,YOLOv5 的cls_pw虽然会按标签频率反比调整,但极端不均衡时数据分布才是根因。
4.2 头盔框得太小:检测不稳定、容易串到头发边
现象:val 的 box_loss 到 80 轮还在 0.08 以上徘徊,稍远距离的骑手频繁漏检,画出来的框忽大忽小。
原因:标注时只把头盔本体贴边圈起来,目标框高度只有整图的 8% 左右,在 640 分辨率下只有五十几个像素,锚框匹配不稳定,而且头盔边缘和头发颜色接近。
解决:重新统一标注口径,把头盔连同额头、发际线下沿一起包进框里,让框的高度大约是头盔高度的 1.1 到 1.2 倍。如果数据是买来的不能重标,就把--img提高到 768 或 960,等效给每个目标多出约 20% 的像素,小目标漏检会有肉眼可见的改善。
4.3 验证集高分、现场全飘:光照差异导致场景迁移失败
现象:训练集 val mAP 0.93,导出模型跑到现场监控上,下午 4 点逆光方向的画面全部误检,白色头盔在强光下被识别成光头。
原因:数据集大多是白天顺光拍摄,监控点位是低机位仰拍,加上逆光,形成了明显的域偏移,模型学到的颜色分布和现场对不上。
解决:训练时打开 HSV 增强,把默认的饱和度扰动从 0.7 调到 0.9,再给随机亮度加一点负方向的范围;更有效的方法是采集现场几百帧低光照、逆光画面,用原模型先跑一遍伪标签,人工修正后混进数据集做增量微调,而不是重头训练。
4.4 一个骑手被拆成两个框:NMS 和置信度阈值没有配合
现象:同一帧画面里,一个人头顶同时出现 helmet 框和 head 框,检测结果里两个目标重叠超过 60%。
原因:模型同时输出两类预测,默认 NMS 按类别分开抑制,不同类别之间的重叠框不会互相合并,导致同一目标双框。
解决:检测时加--agnostic-nms,对所有类别统一做非极大值抑制;如果做了跟踪统计,还要在业务逻辑里写一条规则:head 框中心落入 helmet 框内时,按戴盔目标计数,一个骑手只计一次。
4.5 --cache 爆内存和 autoanchor 重算变慢:训练参数没理解透
现象:加--cache后内存被打满系统卡死;或者训练日志里出现autoanchor重算锚框,训练速度明显下降。
原因:--cache默认把全部训练图片读入内存,数据集图片多且分辨率大时,内存占用轻松超过几十 GB。autoanchor 会在训练开始前重新聚类锚框,它对标注正常的数据收益不大,但耗时明显。
解决:显存够但内存紧张时用--cache disk,把缓存落到磁盘;如果体检时已经确认目标尺寸分布没有异常,直接加--noautoanchor跳过重算,能省下训练前几十分钟的等待时间。
5. 从训练到现场:detect 参数、统计脚本与轻量化部署
5.1 detect.py 的后处理参数:置信度、IoU 与 agnostic NMS
训练完成后,用 best.pt 做推理时,后处理参数直接影响现场效果。很多新手直接用默认参数跑视频,得到的结果要么满屏误报,要么漏得厉害。
python detect.py \ --weights runs/train/exp/weights/best.pt \ --source test_video.mp4 \ --conf 0.35 \ --iou 0.45 \ --agnostic-nms \ --img 640 \ --save-txt \ --save-conf--conf 0.35是置信度下限,低于这个值的目标直接丢弃,现场宁缺毋滥时可以调到 0.5;--iou 0.45是 NMS 的 IoU 阈值,阈值越低合并越激进,两个重叠框更容易合为一个;--save-txt会输出与图片同名的 YOLO 格式标签文件,每行格式比训练标签多一列置信度,也就是cls cx cy w h conf,做统计脚本时要注意第六列的存在。
5.2 头盔率统计:逐帧计数前先想清楚去重
最常见的错误是把视频每一帧的目标数加起来当统计结果,一辆车在画面里停留 30 帧就会被计 30 次。我一般先每隔 8 到 10 帧抽一帧,再只统计画面中间区域的目标,最后按帧序列做简单的重叠去重:如果当前检测框中心与上一帧某个框中心距离小于框宽的一半,认为是同一目标,不重复计数。
import cv2 from pathlib import Path labels_dir = Path("runs/detect/exp/labels") person_count = helmet_count = 0 last_centers = [] for txt in sorted(labels_dir.glob("*.txt")): centers = [] for line in txt.read_text().strip().splitlines(): parts = line.split() cls = int(parts[0]) conf = float(parts[5]) cx, cy = float(parts[1]), float(parts[2]) if conf < 0.35: continue centers.append((cls, cx, cy)) if cx < 0.2 or cx > 0.8: # 只统计画面中线区域 continue if cls == 2: # 与上一帧中心距离小于 0.05,视为同一目标 if any(abs(cx - pcx) < 0.05 and abs(cy - pcy) < 0.05 for _, pcx, pcy in last_centers): continue person_count += 1 elif cls == 0: if any(abs(cx - pcx) < 0.05 and abs(cy - pcy) < 0.05 for _, pcx, pcy in last_centers): continue helmet_count += 1 last_centers = [(c, x, y) for c, x, y in centers] helmet_rate = helmet_count / max(person_count, 1) print(f"人员目标约 {person_count}, 头盔目标约 {helmet_count}, " f"头盔率约 {helmet_rate:.2%}")这段脚本是简化版去重逻辑,适合先跑通统计流程。实际项目中用 ByteTrack 或 DeepSORT 做多目标跟踪会更准,但阈值逻辑是一样的。
提示:头盔率定义没有标准答案,有的项目把“头盔目标数 / 人员目标数”叫头盔率,有的项目要求按车辆数算,先和业务方确认口径再写统计公式。
5.3 轻量化方案:yolov5n / ONNX 与边缘设备部署
树莓派这类边缘设备跑 YOLOv5 是有可能的,但得先想清楚模型规格。一般做法是先对比 n/s/m 三个档位,再决定是否导出 ONNX 用推理引擎运行。
| 模型 | 640 输入下精度趋势 | 显存占用参考 | 适合场景 |
|---|---|---|---|
| yolov5n | 比 s 低 2~4 个点 | 约 2GB | 树莓派、嵌入式盒子 |
| yolov5s | 基准水平 | 约 3~4GB | 普通工控机 |
| yolov5m | 比 s 高 2~3 个点 | 约 5~6GB | 服务器端批量分析 |
树莓派 4B 上我试过 yolov5n 转 ONNX 后配合半精度推理,单帧耗时大约在 300 到 500 毫秒这个量级,做 1 秒抽帧统计勉强够用。需要注意两点:一是导出保存的 ONNX 要确认输出层的形状,避免动态尺寸导致推理报错;二是 ONNX Runtime 的输入要做相同的 letterbox 预处理,不然推理结果会偏。
6. 把模型变成标注工具:用伪标签把你的新场景数据快速扩出来
6.1 批量预测输出 YOLO 格式伪标签
换一个路口或换一批摄像头后,原有数据集的分布就不够用了。新场景重新标注成本高,我会先用手头 best.pt 对新场景图片批量预测,生成伪标签,再人工抽检修正。命令和普通检测一致,只是不需要保存可视化结果图,只保存 txt。
python detect.py \ --weights runs/train/exp/weights/best.pt \ --source ./new_scene_images \ --conf 0.5 \ --iou 0.5 \ --save-txt \ --save-conf \ --project ./pseudo \ --name batch1伪标签目录在./pseudo/batch1/labels,每行格式是cls cx cy w h conf,多了一列置信度。训练新模型前必须把第六列去掉,YOLOv5 读训练标签只认前五列,带着置信度列会出现解析错误。
6.2 伪标签筛选策略与再训练注意点
伪标签不能无脑全收。我会按三个标准筛:置信度低于 0.5 的删掉,这类大概率是边界错框;与新场景光照风格明显不符的框删掉,比如强逆光下把树影当头盔的情况;每个类别抽 10% 用 labelImg 或 CVAT 人工复核,重点看头盔框是否把头发包进去,这是最容易错的口径。修正后的伪标签和原数据集混合,从原 best.pt 继续微调 30 到 50 轮,比从 COCO 权重重新训收敛快得多。如果你计划换 YOLOv8 训练,这套目录和 txt 标注可以直接复用,data.yaml 改成 YOLOv8 的写法即可,类别顺序不变。
最早我拿外部数据集直接丢进去训,结果夜间带队测试时被一排开远光的电动车搞崩了,那批逆光图让我意识到数据分布比网络结构更急。从那以后,我每次拿到外部数据集都强制先跑一遍统计脚本和伪标签抽检,再进正式训练,这个习惯帮我省下的重训时间比下载数据本身还多。这份 3 类别头盔数据集是个不错的起点,但它的价值在于给你一套可用的标注分布,而不是替你解决现场光照和统计口径。把第 2 章的体检和第 4 章的坑过一遍,再谈部署,你会少走很多弯路。希望帮到你。
本文还有配套的精品资源,点击获取