
简介本资源面向计算机视觉方向的毕业设计与课程实践者提供一套基于YOLOv5的电动自行车头盔佩戴检测系统完整实现方案可用于自动识别图像或视频流中骑行者是否佩戴安全头盔。压缩包共94个文件约23.74MB包含13个Python源码文件、24个YAML配置、26张JPG示例图片、6个Shell脚本及说明文档、Dockerfile、ipynb教程等覆盖模型训练、验证、推理与部署全流程。资源内含标注数据集、已训练完成的权重文件以及环境配置说明读者可据此复现训练过程、直接部署检测模型并借助示例图片与视频验证识别效果。目前已有101人学习下载适合需要快速搭建头盔检测原型、完成课程设计或毕业设计的中级学习者参考。1. 电动自行车头盔佩戴检测为什么 YOLOv5 仍是性价比最高的起点早晚高峰的路口电动车流里戴没戴头盔靠人眼盯根本盯不过来。基于 YOLOv5 的电动自行车头盔佩戴检测系统要解决的就是这件事从监控画面或视频流里自动框出骑行者判断头部是否佩戴头盔输出结构化结果供后续告警或统计。它适合两类人一类是想找一个完整可复现的目标检测落地项目练手的开发者另一类是需要快速搭出原型验证可行性的工程团队。选 YOLOv5 而不是更新的版本理由很实际——生态成熟、训练脚本稳定、部署路径清晰从 conda 环境配置到树莓派 5 上跑自己训练的模型社区里踩过的坑基本都能搜到答案。这个系统的核心难点不在模型本身而在数据集标注质量和「戴头盔/未戴头盔」两类目标的区分度上这一点后面会反复提到。2. 从数据集到标注头盔检测的样本工程怎么做2.1 为什么头盔检测的数据集不能直接拿通用数据集凑通用目标检测数据集里「人」是一个大类但头盔检测需要的是更细粒度的区分同一个骑行者头部区域要单独框出来并且打上「戴头盔」或「未戴头盔」的标签。这意味着你不能拿 COCO 或者 VOC 直接训练必须自己标注或者找专门的头盔数据集。我一般会按这个思路组织数据每张图里骑行者整体可以作为一个辅助类别但真正参与训练的主类别是两个——helmet戴头盔和no_helmet未戴头盔。有些方案会把「人」也作为一个类别一起训练让模型先学会定位人再判断头部状态。两种做法各有取舍只训两个头盔类别模型更专注但容易把远处模糊的头部误判加上「人」这个类别召回率会高一些但标注成本翻倍。数据来源上常见做法是路口监控截图、行车记录仪片段抽帧、公开的电动车骑行视频。这里有个血泪经验不要只用白天晴天样本。夜间、逆光、雨天的样本如果缺失模型上线后在这些场景下会集体翻车把深色帽子误判成头盔、把头盔反光误判成没戴。建议至少保证 20% 的样本来自非理想光照条件。2.2 标注规范与目录结构一次说清 YOLO 格式的四个边界坑YOLOv5 用的是 YOLO 格式标注每张图对应一个同名.txt文件每行是类别索引 x_center y_center width height全部归一化到 0 到 1 之间。下面是一个标注转换和目录组织的脚本示例import os import cv2 import xml.etree.ElementTree as ET # 类别映射顺序必须和训练时的 data.yaml 一致 CLASS_MAP {helmet: 0, no_helmet: 1} def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 读取图片尺寸用于归一化 img_name root.find(filename).text img cv2.imread(os.path.join(img_dir, img_name)) h, w img.shape[:2] lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: continue # 跳过不关心的类别 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) # 边界裁剪防止标注框超出图像范围 xmin, ymin max(0, xmin), max(0, ymin) xmax, ymax min(w, xmax), min(h, ymax) x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{CLASS_MAP[cls_name]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) out_path os.path.join(out_dir, os.path.splitext(img_name)[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))这段代码的逻辑很直白解析 VOC 格式的 XML把绝对坐标转成归一化中心点加宽高。参数上要注意三个点。第一CLASS_MAP的顺序必须和data.yaml里的names完全一致否则训练出来的模型会把头盔和没戴头盔搞反。第二归一化前一定要做边界裁剪标注时手抖画出图像范围的框如果不裁掉归一化后会出现负数或大于 1 的值YOLOv5 训练时不会报错但会静默产生错误的梯度。第三坐标保留 6 位小数足够再多是浪费。目录结构建议这样组织dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml的内容path: ./dataset train: images/train val: images/val nc: 2 names: [helmet, no_helmet]这里有个容易踩的坑train和val路径是相对于path的不是相对于 yaml 文件本身。很多人写绝对路径导致换机器就找不到数据建议统一用相对路径。2.3 数据增强策略头盔检测该开哪些、该关哪些YOLOv5 默认开启了 mosaic、HSV 增强、随机翻转等。对头盔检测来说mosaic 增强要谨慎。mosaic 会把四张图拼成一张对于小目标密集的场景有帮助但头盔检测里骑行者的头部区域本身就不大拼接后可能出现头部被截断的情况反而引入噪声。我的做法是训练前期开 mosaic后期最后 10 个 epoch 关掉让模型在完整图像上收敛。HSV 增强里的色调偏移不要开太大。头盔颜色是重要特征蓝色头盔和红色头盔如果色调被大幅扰动模型可能学不到颜色相关的判别信息。建议hsv_h控制在 0.015 以内hsv_s和hsv_v可以适当放大到 0.7 和 0.4。翻转增强要注意水平翻转没问题垂直翻转绝对不要开。现实中没有人倒着骑车垂直翻转会让模型学到错误的头部朝向先验。3. 训练配置与调参让头盔检测模型真正收敛3.1 环境配置与最小训练命令环境配置是新手最容易卡住的地方。YOLOv5 对 PyTorch 和 CUDA 版本有要求我一般用 conda 建一个干净环境conda create -n helmet python3.9 -y conda activate helmet # 根据你的 CUDA 版本选择对应的 PyTorch这里以 CUDA 11.8 为例 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 --weights yolov5s.pt --source data/images/bus.jpg能正常输出检测结果图说明环境没问题。接下来开始训练python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data ./dataset/data.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name helmet_v1参数逐个说。--img 640是输入分辨率头盔检测里头部区域偏小如果显存够建议提到 800 甚至 1024小目标召回会明显改善。--batch 16是批大小显存不够就往下调但不要低于 8否则 BatchNorm 统计量不稳定。--weights yolov5s.pt用预训练权重做迁移学习比从头训快得多收敛也更稳。--hyp选 low 增强配置因为头盔检测场景相对固定不需要太激进的增强。3.2 三个必调超参数与学习率策略YOLOv5 的超参数有一大堆但头盔检测真正需要动的就三个。第一个是lr0初始学习率。默认 0.01如果 loss 在前几个 epoch 就震荡得厉害降到 0.001。判断标准很简单看train/box_loss曲线如果它上下跳动幅度超过整体下降趋势就是学习率大了。第二个是anchor尺寸。YOLOv5 默认的 anchor 是基于 COCO 数据集聚类出来的头盔检测的目标尺寸分布和 COCO 差别很大。建议在训练前用自己的数据集重新聚类 anchorpython utils/autoanchor.py --data ./dataset/data.yaml --weights yolov5s.pt这个脚本会输出一组适配你数据的 anchor 尺寸把它替换到模型配置文件里。这一步对召回率的影响经常被低估我实测过重新聚类 anchor 后no_helmet类的召回率能提升 5 到 8 个百分点。第三个是label_smoothing标签平滑。默认 0.0头盔检测里如果标注有少量噪声比如边界框画得不够紧可以设到 0.05 到 0.1让模型对标注误差更鲁棒。但不要超过 0.1否则模型会变得过于保守置信度普遍偏低。学习率策略上YOLOv5 默认用 cosine 退火加 warmup。warmup 阶段让学习率从很小慢慢升到lr0避免训练初期梯度爆炸。这个默认开着就行不用动。但warmup_epochs可以调如果数据集很小比如只有几千张warmup 设 1 到 2 个 epoch 就够数据集大就设 3 到 5。3.3 训练过程监控看哪些指标、什么时候该停训练启动后runs/train/helmet_v1/目录下会生成一堆文件。重点看三个results.csv、results.png和confusion_matrix.png。results.csv里每行是一个 epoch 的指标。核心关注metrics/mAP_0.5和metrics/mAP_0.5:0.95。头盔检测这种两类问题mAP_0.5 到 0.85 以上基本可用到 0.92 以上算不错。但不要只看 mAP还要看val/box_loss和val/obj_loss是否还在下降。如果 mAP 还在涨但 val loss 开始上升说明过拟合了该停了。confusion_matrix.png是排查类别混淆的利器。如果helmet被大量预测成no_helmet或者反过来说明两类样本的特征区分度不够要么是标注有问题要么是数据里某一类样本太少。我遇到过一次no_helmet的召回率始终上不去最后发现是标注时把「戴了深色帽子」也标成了no_helmet模型学混了。早停策略上YOLOv5 有--patience参数默认 50 个 epoch 没提升就停。头盔检测我一般设 20 到 30因为数据集规模通常不大50 太浪费。另外建议开--save_period 10每 10 个 epoch 存一次权重方便回滚到最佳检查点。4. 推理部署与性能优化从 PyTorch 到实际可用4.1 模型导出与推理脚本训练完得到best.pt但实际部署时 PyTorch 模型往往太重。YOLOv5 支持导出 ONNX、TensorRT、CoreML 等格式。最通用的是 ONNXpython export.py --weights runs/train/helmet_v1/weights/best.pt --include onnx --img 640 --batch 1导出后得到一个.onnx文件可以用 ONNX Runtime 推理速度比原生 PyTorch 快不少而且不依赖 PyTorch 环境。如果部署在 NVIDIA 边缘设备上导出 TensorRT 引擎性能更好python export.py --weights best.pt --include engine --device 0 --half--half是 FP16 量化速度能再提升一截精度损失通常在 1 个百分点以内头盔检测这种任务完全可以接受。推理脚本的核心逻辑import cv2 import torch # 加载模型指定设备 model torch.hub.load(./yolov5, custom, pathbest.pt, sourcelocal) model.conf 0.4 # 置信度阈值 model.iou 0.45 # NMS 的 IoU 阈值 cap cv2.VideoCapture(test_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame) # 提取检测结果按类别过滤 for *box, conf, cls in results.xyxy[0].tolist(): label helmet if int(cls) 0 else no_helmet # 这里可以加告警逻辑比如连续多帧检测到 no_helmet 就触发 annotated results.render()[0] cv2.imshow(result, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()model.conf和model.iou这两个参数需要根据实际场景调。路口监控场景下我一般把conf设到 0.4 到 0.5太低会引入大量误报太高会漏掉远处的小目标。iou设 0.45 是通用值如果发现同一个头部被框了两次降到 0.4。4.2 边缘设备部署树莓派 5 上的实测经验树莓派 5 上部署自己训练的 YOLOv5 模型是很多人的目标。实测下来树莓派 5 的 CPU 跑 YOLOv5s 的 ONNX 模型640 分辨率下单帧推理大约 200 到 300 毫秒也就是 3 到 5 FPS做实时视频分析比较吃力。几个优化方向一是降分辨率。把输入从 640 降到 416 甚至 320速度能翻倍但小目标召回会下降。头盔检测里如果摄像头距离路口不远320 也够用。二是用 NCNN 格式。YOLOv5 支持导出 NCNN这是专门为移动端和嵌入式优化的推理框架在 ARM 上比 ONNX Runtime 快不少python export.py --weights best.pt --include ncnn --img 320三是抽帧处理。不需要每帧都推理隔 3 到 5 帧处理一次配合简单的目标跟踪比如 IOU 匹配来维持帧间的检测连续性。这样在 5 FPS 的推理速度下也能覆盖 15 到 25 FPS 的视频流。4.3 后处理与告警逻辑怎么避免误报刷屏检测模型输出的是逐帧结果但实际业务需要的是「某个人没戴头盔」这个事件。如果直接拿单帧结果告警视频里同一个人会被反复告警而且偶尔一帧的误检也会触发。常见做法是加一个简单的跟踪和投票机制。对每一帧的no_helmet检测框用 IOU 和上一帧的框做匹配同一个目标连续 N 帧比如 5 帧都被判定为no_helmet才触发一次告警。这样能过滤掉大部分单帧误检。另外可以加一个区域过滤。只对画面中特定区域比如非机动车道内的检测结果做告警人行道上的行人即使被误检也不影响。这个用简单的多边形区域判断就能实现不需要额外模型。5. 避坑与排查头盔检测落地时最容易翻车的五个地方5.1 现象训练 loss 正常下降但 mAP 始终在 0.3 以下原因通常出在标注格式上。YOLO 格式要求坐标归一化到 0 到 1如果标注时用了绝对坐标或者归一化时除错了尺寸比如用了缩放后的图片尺寸而不是原图尺寸模型学到的就是错误的位置信息。另一种可能是data.yaml里的names顺序和标注文件里的类别索引对不上。解决写一个校验脚本遍历所有标注文件检查每行的五个值是否都在合法范围内类别索引是整数且在nc范围内后四个值在 0 到 1 之间。同时可视化几张标注后的图片肉眼确认框的位置和标签是否正确。5.2 现象模型在验证集上表现很好但实际视频里漏检严重原因一般是训练数据和实际场景的分布不一致。验证集是从训练集同一批数据里随机划分的分布自然一致。但实际视频可能来自不同的摄像头、不同的角度、不同的光照。如果训练数据里全是正面视角实际视频是俯拍角度模型就懵了。解决训练数据要尽量覆盖实际部署场景的多样性。如果已经训练完了才发现这个问题最经济的做法是从实际视频里抽几百帧标注后做微调训练学习率调小比如 0.0001只训 10 到 20 个 epoch。5.3 现象no_helmet类的误报率特别高把深色帽子、头发都判成没戴头盔原因是「没戴头盔」这个类别的视觉特征太杂了——可能是黑色头发、可能是棒球帽、可能是头巾。模型很难从这些负样本里学到统一的判别边界。解决在负样本里增加难例。专门收集那些被误报的帧标注成正确的类别如果戴了帽子但没戴头盔应该标no_helmet如果其实戴了头盔但被误判检查标注是否准确。另外可以考虑加一个「头部」类别作为中间监督让模型先定位头部再判断头盔状态。5.4 现象导出 ONNX 后推理结果和 PyTorch 不一致原因通常是导出时的输入尺寸和推理时的输入尺寸不一致或者预处理方式不同。YOLOv5 导出 ONNX 时默认做了 letterbox 填充推理时如果直接 resize 而不做 letterbox坐标映射就会错位。解决推理时严格复用 YOLOv5 的预处理逻辑包括 letterbox 的缩放比例和填充偏移。如果自己写预处理务必确认缩放后的坐标能正确映射回原图。5.5 现象树莓派上跑几帧就卡死或内存溢出原因是树莓派内存有限如果每帧都创建新的推理会话或者不释放中间张量内存会迅速耗尽。另外OpenCV 的VideoCapture如果没设置缓冲区大小会预读大量帧占满内存。解决推理会话只创建一次复用。VideoCapture设置CAP_PROP_BUFFERSIZE为 1只保留最新帧。推理完的中间变量及时用del释放必要时手动调gc.collect()。6. 进阶技巧用测试时增强和模型集成再压榨几个点训练和部署都跑通之后如果还想再提升精度有两个成本不高但效果稳定的手段。第一个是测试时增强TTA。推理时对同一张图做多种变换水平翻转、多尺度缩放分别推理后把结果做 NMS 融合。YOLOv5 的detect.py里加--augment就能开启。实测在头盔检测上TTA 能把 mAP_0.5 提升 1 到 2 个百分点代价是推理时间翻 3 到 4 倍。如果对实时性要求不高比如离线视频分析这个提升是白捡的。第二个是模型集成。训练两个不同 backbone 的模型比如 YOLOv5s 和 YOLOv5m推理时把两个模型的检测框合并再做 NMS。集成能显著降低单模型的系统性偏差尤其是no_helmet这种容易混淆的类别。我一般用加权框融合WBF代替普通 NMS效果更好ensemble-boxes这个库直接能用。from ensemble_boxes import weighted_boxes_fusion # boxes_list 是多个模型的检测框列表格式为 [x1, y1, x2, y2] 归一化坐标 # scores_list 和 labels_list 对应每个模型的置信度和类别 boxes, scores, labels weighted_boxes_fusion( boxes_list, scores_list, labels_list, weights[1, 1], iou_thr0.5, skip_box_thr0.3 )参数上iou_thr控制融合时的重叠阈值头盔检测建议 0.5 到 0.6。skip_box_thr过滤低置信度框设 0.3 能去掉大部分噪声。weights可以按模型在验证集上的表现分配表现好的给更高权重。最后一个习惯每次训练完把results.csv、confusion_matrix.png和data.yaml一起归档到一个以日期命名的文件夹里。我吃过亏三个月后想复现某个版本的结果发现当时的超参数和数据集划分已经找不到了只能从头再训一遍。这个习惯看起来笨但能省下大量后悔药。希望帮到你。本文还有配套的精品资源点击获取