简介:这份资源面向计算机视觉方向的本科生与研究生,提供一套可直接运行的电动自行车头盔佩戴识别检测方案,适用于毕业设计、期末大作业或课程实践。项目基于YOLOv5算法构建,包含完整源码与训练好的模型文件,代码注释详尽,新手也能快速理解检测流程与训练逻辑。压缩包共187个文件,约134.13MB,其中55个Python脚本承载数据加载、模型训练与推理主流程,22个yaml配置文件定义数据集与网络结构,7个pt权重文件可直接用于检测,另有png效果图、js前端页面及Dockerfile等部署辅助文件,目录结构清晰。目前已有385人学习下载。资源附带项目手册与说明文档,读者可据此完成环境搭建、模型推理与结果可视化,并参考前端页面实现检测结果展示,快速复现一个导师认可的高分项目。
1. 电动自行车头盔佩戴识别:从毕设选题到能跑通的检测系统
电动自行车头盔佩戴识别检测,说白了就是给摄像头装一个"自动抓拍未戴头盔"的大脑。它要解决的核心问题很具体:在早晚高峰的非机动车道上,从密集的车流里把骑行人框出来,再判断他头上有没有头盔。这件事在"一盔一带"政策落地后成了刚需,物业、园区、交管辅助场景都有真实需求,也是 yolov5 训练自己数据集里最典型的二分类落地案例。很多人冲着"高分毕设"四个字来,但真正卡住他们的从来不是模型结构,而是数据集怎么标、头盔这种小目标怎么调参、模型文件怎么在普通显卡上跑出可用帧率。这篇笔记就按我实际做过的路径,把源码、模型文件、训练参数和部署踩坑一次讲透,适合要交毕设的学生,也适合想把这套东西接到自己项目里的工程师。
2. 头盔识别为什么选 YOLOv5 而不是别的检测框架
2.1 从任务特性反推框架选型
头盔佩戴识别本质是一个"小目标 + 二分类 + 实时性"的组合任务。说它是小目标,是因为头盔在整张图里占比往往不到 5%,尤其是远景摄像头拍到的骑行人,头部区域可能只有几十个像素。说它是二分类,是因为我们通常只关心"戴了"和"没戴"两种状态,不需要区分头盔品牌、颜色、款式。说它要实时,是因为路口抓拍场景下,30 帧的视频流你至少得跑到 15 帧以上才有实用价值。
YOLOv5 在这三个维度上的平衡是它成为主流选择的原因。它的 Anchor 机制对小目标有专门的多尺度检测头,P3 层特征图专门负责小物体;它的单阶段结构保证了推理速度;它的工程化程度高,数据增强、超参数进化、模型导出这些环节都有现成脚本。相比之下,Faster R-CNN 精度可能略高但速度跟不上,YOLOv8 更新但对毕设场景来说生态资料反而没 v5 全,遇到问题不好搜。所以"基于 yolov5 实现头盔佩戴识别"这个选题,选型上是站得住的。
2.2 数据集构建:头盔识别的标注规范
数据集是这套系统里最容易被低估的部分。我见过太多人直接拿网上的头盔数据集开训,结果模型在自家场景里翻车。原因很简单:不同场景的摄像头角度、光照、头盔样式差异极大,通用数据集覆盖不了你的实际分布。
自己标数据时,类别设计有两种常见方案。第一种是两类:helmet(戴头盔)和no_helmet(未戴头盔),直接对头部区域做检测。第二种是三类:person、helmet、no_helmet,先检测人再判断头部状态。我一般推荐第一种,因为两类方案标注量小、模型收敛快,而且推理时不需要做人和头盔的关联匹配,后处理简单。缺点是当画面里人很多时,头部区域密集容易漏检,这时候可以配合人体检测做级联。
标注工具用 labelImg 或 X-AnyLabeling 都行,导出 YOLO 格式的 txt。每条标注是类别id 中心x 中心y 宽 高,坐标都归一化到 0~1。这里有个血泪经验:头盔框一定要贴着头部轮廓标,不要框到肩膀,也不要只框头盔顶。框太大会让模型学到"人+头盔"的混合特征,框太小会丢失头盔边缘信息,两种情况都会让验证集 mAP 虚高但实际场景拉胯。
数据划分按 8:1:1 走训练/验证/测试。如果样本量少于 2000 张,建议把验证集比例提到 0.15,因为小数据集上验证集太小会导致指标波动大,你根本判断不出模型到底有没有变好。
2.3 目录结构与配置文件落地
YOLOv5 的数据配置靠一个 yaml 文件驱动。假设你的项目根目录是helmet_detection,标准结构如下:
helmet_detection/ ├── datasets/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data/ │ └── helmet.yaml ├── models/ │ └── yolov5s_helmet.yaml └── runs/helmet.yaml的内容:
# 数据集路径,建议写绝对路径避免相对路径踩坑 path: /home/user/helmet_detection/datasets train: images/train val: images/val test: images/test # 类别数,两类方案就是 2 nc: 2 # 类别名称,顺序必须和标注时的 id 对应 names: 0: helmet 1: no_helmet这里有个容易翻车的点:names的顺序必须和标注时用的类别 id 严格一致。如果你标注时 0 是 no_helmet、1 是 helmet,但 yaml 里写反了,训练照样能跑,loss 照样下降,但推理结果会完全颠倒——戴头盔的判成没戴。这种错误不看可视化结果根本发现不了,属于典型的"黑匣子"式 bug。
模型配置文件yolov5s_helmet.yaml直接复制官方的yolov5s.yaml,把nc改成 2 即可。如果你想用更大的模型提升小目标精度,可以换成yolov5m.yaml或yolov5l.yaml,但要注意显存和速度的权衡。我实测在 1080p 输入下,yolov5s 在 RTX 3060 上能跑到 60+ FPS,yolov5m 大概 35 FPS,yolov5l 只有 20 FPS 左右。头盔识别这种场景,yolov5s 通常够用,除非你的摄像头特别远、头盔特别小。
3. 训练参数怎么调:头盔小目标的超参数实战
3.1 从预训练权重开始的迁移学习
头盔识别数据集通常不大,从零训练几乎不可能收敛到可用精度。标准做法是加载 COCO 预训练权重做迁移学习。YOLOv5 官方提供了 yolov5s.pt 等权重文件,直接指定即可:
python train.py \ --weights yolov5s.pt \ --cfg models/yolov5s_helmet.yaml \ --data data/helmet.yaml \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --workers 8 \ --project runs/train \ --name helmet_exp1参数逐个说清楚。--weights yolov5s.pt加载预训练权重,这是收敛速度的关键,没有它你可能要训 300 轮才看到效果。--epochs 100是训练轮数,头盔这种二分类任务通常 80~150 轮就收敛了,看验证集 mAP 曲线,如果连续 20 轮不涨就可以停。--batch-size 16受显存限制,8G 显存跑 640 分辨率大概能到 16,显存不够就降到 8,但 batch 太小会让 BN 层统计不稳定,建议配合--accumulate做梯度累积。--imgsz 640是输入分辨率,头盔是小目标,理论上 1280 能提升精度,但速度会掉一半以上,640 是精度和速度的平衡点。
--workers 8是数据加载线程数,设成 CPU 核心数就行,设太大反而会因为线程切换开销变慢。--device 0指定第一块 GPU,多卡用0,1。
3.2 针对小目标的三个必调参数
头盔识别的难点在小目标,有三个参数直接决定你能不能检出远处的骑行人。
第一个是--anchor。YOLOv5 默认的 anchor 是基于 COCO 数据集聚类的,对头盔这种小目标偏大。正确做法是用你的数据集重新聚类 anchor:
python utils/autanchor.py --data data/helmet.yaml --imgsz 640 --device 0这个脚本会输出 9 组 anchor 尺寸,把它们替换到模型 yaml 里的anchors字段。我实测重新聚类后,小目标召回率能提升 5~8 个百分点。注意聚类时--imgsz要和训练时一致,否则 anchor 尺度对不上。
第二个是--hyp。YOLOv5 的超参数在data/hyp.scratch.yaml里,头盔场景我一般改这几个:mosaic: 0.5(马赛克增强概率,太高会让小目标被裁切得太碎)、scale: 0.3(缩放增强幅度,小目标经不起大比例缩放)、fl_gamma: 1.5(focal loss 的 gamma,缓解正负样本不平衡,因为大部分头部区域是戴头盔的,no_helmet 是少数类)。
第三个是--img-weights。这个参数控制训练时多尺度训练的概率,默认 0.0 关闭。头盔识别建议开到 0.5,让模型适应不同距离的目标尺度,对远景小目标提升明显。
3.3 训练过程监控与早停判断
训练启动后,重点看runs/train/helmet_exp1/下的几个文件。results.csv记录了每轮的 loss 和 mAP,results.png是可视化曲线。判断训练是否正常,看三条线:train/box_loss应该稳定下降,如果震荡剧烈说明学习率太大或 batch 太小;val/box_loss如果先降后升,说明过拟合了,该早停;metrics/mAP_0.5是主指标,头盔识别通常能到 0.85 以上算合格,0.92 以上算优秀。
早停用--patience 30,意思是验证集指标连续 30 轮不提升就停止。这个值别设太小,小数据集上指标波动大,设 10 可能会在正常波动时误停。
训练完成后,最优权重在runs/train/helmet_exp1/weights/best.pt,最后轮次的权重是last.pt。部署用 best.pt,继续训练用 last.pt。
4. 模型推理与部署:从 detect.py 到实际场景
4.1 用 detect.py 快速验证效果
训练完第一件事是拿测试集跑一遍,确认模型没训废:
python detect.py \ --weights runs/train/helmet_exp1/weights/best.pt \ --source datasets/images/test \ --img-size 640 \ --conf-thres 0.4 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name helmet_test--conf-thres 0.4是置信度阈值,低于这个值的框会被丢弃。头盔识别建议设 0.4~0.5,设太低会有一堆误检,设太高会漏掉远处的小目标。--iou-thres 0.45是 NMS 的 IoU 阈值,控制重叠框的合并,头盔密集场景可以降到 0.4 减少漏检。--save-txt会把检测结果存成 txt,方便你统计准确率。
跑完后去runs/detect/helmet_test/看可视化图片。重点检查三类错误:远景小头盔漏检、相似颜色误检(比如白色帽子被当成头盔)、密集场景框重叠。这三类错误分别对应不同的调参方向,漏检调低 conf 或重新聚类 anchor,误检补充负样本,重叠调 NMS 阈值。
4.2 导出 ONNX 与 TensorRT 加速
PyTorch 模型直接部署速度不够,生产环境一般导出 ONNX 或 TensorRT。导出 ONNX:
python export.py \ --weights runs/train/helmet_exp1/weights/best.pt \ --include onnx \ --img-size 640 640 \ --batch-size 1 \ --opset 12--opset 12是 ONNX 算子集版本,12 兼容性最好,别用太新的版本,很多推理引擎还不支持。--batch-size 1是部署时的批大小,如果你要做多路视频,可以设成实际并发数。
导出 TensorRT 需要先装 TensorRT 和 pycuda,然后:
python export.py \ --weights best.pt \ --include engine \ --img-size 640 640 \ --batch-size 1 \ --device 0 \ --half--half开启 FP16 半精度,速度能提升 1.5~2 倍,精度损失通常在 1% 以内,头盔识别这种任务完全可以接受。TensorRT 引擎和硬件绑定,换显卡要重新导出,这是部署时容易忽略的点。
4.3 视频流推理的工程化改造
detect.py 是单张图片推理,实际场景要处理视频流。核心改造是把推理循环抽出来,配合 OpenCV 的 VideoCapture:
import cv2 import torch import numpy as np # 加载模型,注意 map_location 防止 GPU 模型加载到 CPU 报错 model = torch.hub.load('./', 'custom', path='best.pt', source='local') model.conf = 0.4 model.iou = 0.45 cap = cv2.VideoCapture('rtsp://your_camera_stream') # 设置缓冲区为 1,避免处理旧帧导致延迟累积 cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while cap.isOpened(): ret, frame = cap.read() if not ret: break # YOLOv5 接受 RGB 输入,OpenCV 读的是 BGR,必须转换 img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = model(img_rgb, size=640) # 渲染结果 annotated = np.squeeze(results.render()) cv2.imshow('helmet', annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这段代码的关键在CAP_PROP_BUFFERSIZE设为 1。默认缓冲区会缓存多帧,导致你处理的是几秒前的画面,实时抓拍场景下这是致命的。另外torch.hub.load的source='local'参数必须加,否则会尝试联网下载,内网环境直接卡死。
如果要接多路摄像头,别用多线程各自加载模型,显存会爆。正确做法是一个模型实例配一个推理队列,多路视频帧进队列,单线程批量推理。批量大小根据显存调,RTX 3060 上 640 分辨率大概能 batch 8。
5. 头盔识别部署避坑:五条踩坑记录
5.1 验证集 mAP 很高但实际场景全错
现象:训练完 mAP_0.5 到 0.95,信心满满拿去实测,结果画面里戴头盔的全判成没戴。
原因:最常见的是类别 id 映射反了,yaml 里 names 顺序和标注时不一致。其次是数据集分布和实际场景差异太大,比如训练集全是白天正脸,实测是夜间侧脸。
解决:先拿一张训练集里的图跑推理,如果训练集都对但实测错,就是分布问题,补充实际场景数据重训。如果训练集也错,检查 names 顺序,用model.names打印确认。
5.2 小目标漏检严重,远处骑行人完全检不到
现象:近处的人能检出,超过 15 米的骑行人一个框都没有。
原因:默认 anchor 尺寸偏大,P3 检测头对小目标特征提取不足。另外输入分辨率 640 时,远处头盔可能只有 10 几个像素,特征图上下采样后信息基本丢失。
解决:重新聚类 anchor,把输入分辨率提到 960 或 1280,或者在数据增强里加copy-paste小目标增强。如果硬件允许,换 yolov5m 或加一个 P2 检测头专门处理超小目标。
5.3 推理速度远低于预期,1080p 只能跑 5 帧
现象:论文里写的 60 FPS,实际部署只有 5 FPS。
原因:没导出 TensorRT,用的还是 PyTorch 原生推理。或者视频解码占用了大量 CPU,GPU 在等数据。
解决:导出 TensorRT FP16 引擎,速度能提升 3~5 倍。视频解码用硬解码,OpenCV 的CAP_PROP_HW_ACCELERATION或者直接用 GStreamer 管道。另外检查是不是每帧都在做 cvtColor 和 resize,这些操作能放到 GPU 上就放 GPU。
5.4 夜间和逆光场景误检率飙升
现象:白天正常,晚上路灯下把树影、反光当头盔。
原因:训练集缺少夜间样本,模型没学过暗光下的特征分布。逆光时头部区域过曝,头盔和背景对比度消失。
解决:补充夜间和逆光数据,至少占训练集的 20%。数据增强里加hsv_v调整亮度,范围开到 0.6。如果条件允许,用红外摄像头做夜间补充,但要注意红外图像和可见光图像的域差异,最好分开训练或做域适应。
5.5 模型文件换机器后加载失败
现象:在自己电脑上跑得好好的 best.pt,拷到服务器上torch.load报错。
原因:PyTorch 版本不一致,或者模型保存时用了 GPU 而加载环境只有 CPU。另外 YOLOv5 的torch.hub.load依赖目录结构,单独拷一个 pt 文件可能找不到模型定义。
解决:加载时加map_location='cpu',跨版本用weights_only=False。部署时把整个 YOLOv5 仓库一起拷过去,或者导出 ONNX/TensorRT 这种自包含格式,彻底摆脱 PyTorch 环境依赖。
6. 把头盔识别做到能交付的几个进阶技巧
模型训出来只是起点,真正决定这套系统能不能用的是后处理和数据闭环。分享几个我实际项目里验证过的技巧。
第一个是基于跟踪的投票机制。单帧检测难免有抖动,同一辆车这帧判戴、下帧判没戴,抓拍系统就没法用。做法是接一个轻量跟踪器(比如 ByteTrack),给每个骑行人分配 ID,累计连续 N 帧的检测结果做多数投票,只有超过 70% 的帧判为 no_helmet 才触发抓拍。这样误报率能降一个数量级。代价是引入 2~3 帧延迟,但抓拍场景完全能接受。
第二个是置信度分级输出。别只输出"戴/没戴",把置信度也带上。conf > 0.8 直接判定,0.5~0.8 标记为待人工复核,< 0.5 丢弃。这样既保证了自动化率,又给人工留了兜底。实际部署时,待复核的样本正好是难例,定期拿回来重新标注加入训练集,形成数据闭环,模型会越用越准。
第三个是输入分辨率自适应。固定 640 在近景浪费算力、远景不够用。可以根据画面中检测到的人体框大小动态调整:如果上一帧检测到的头部框平均小于 32 像素,下一帧切到 1280 输入;大于 64 像素就切回 640。这样在保证远景精度的同时不牺牲近景速度。实现上用一个滑动窗口统计最近 30 帧的框尺寸,加个滞回区间防止频繁切换。
第四个是模型量化到边缘设备。如果要在 RK3568 或树莓派上部署,PyTorch 模型跑不动,需要量化成 INT8。YOLOv5 支持导出 RKNN 格式,流程是 PyTorch → ONNX → RKNN,量化时准备 200~500 张校准图,覆盖各种光照场景。INT8 量化后模型体积缩小 4 倍,速度提升 2~3 倍,精度损失控制在 2% 以内。注意量化后的模型对输入归一化方式敏感,预处理必须和量化时一致,否则精度会崩。
最后说个习惯:每次训练完,我都会把这次的数据集版本、超参数、mAP、实际场景抽检结果记到一个表格里。头盔识别这种任务,调参的收益往往不如补数据,但如果你不记录,就永远不知道上次那个 0.93 的 mAP 是怎么来的,也没法复现。我吃过这个亏,一个效果很好的模型因为没记录 hyp 配置,后来怎么调都调不回去,只能重训。希望这些经验能帮你少走点弯路,把这个方向真正做扎实。
本文还有配套的精品资源,点击获取