
简介这份课程资源面向计算机视觉方向的毕业设计学生与深度学习入门者提供一套基于YOLOv5的电动自行车头盔佩戴检测系统完整实现方案用于自动识别图像或视频流中骑行者是否佩戴安全头盔。压缩包共94个文件、约23.74MB包含13个Python源码文件、24个YAML配置、26张JPG示例图片、6个Shell脚本及说明文档、Dockerfile、ipynb教程等覆盖模型训练、验证、推理与部署全流程。资源内含标注数据集、已训练好的模型权重以及环境配置说明读者可据此复现训练过程、直接部署检测模型并借助配置文档完成基础支撑环境的安装与调试。目前已有101人学习下载适合需要完整项目参考、快速上手目标检测实战的学习者。1. 电动自行车头盔检测从人工盯屏到 YOLOv5 自动识别的落地路径早晚高峰的路口一个摄像头每小时能拍下上千张电动自行车通行的画面靠人眼去判断谁没戴头盔既不现实也不可持续。基于 YOLOv5 的电动自行车头盔佩戴检测系统要解决的就是这件事把「有没有戴头盔」变成一个目标检测问题让模型自动框出骑行者头部区域并分类。这套方案适合三类人想找一个完整 CV 项目练手的在校学生、需要快速验证交通场景 AI 可行性的工程师、以及手里有摄像头资源想做智能化改造的集成商。它不需要你从零标注几万张图也不需要自己搭训练框架核心工作集中在数据集组织、YOLOv5 配置调参和推理部署三个环节。下面按「先跑通再优化」的顺序把每个环节拆开讲清楚。2. 数据集准备头盔检测的标注策略与格式转换2.1 为什么头盔检测的数据集不能直接拿通用行人数据凑通用行人检测数据集里人的头部区域通常只是一个模糊的边界框没有区分「戴头盔」和「没戴头盔」的细粒度标签。而头盔检测的核心难点恰恰在于骑行者的头部在画面中占比小、遮挡多、光照变化剧烈且「戴」与「没戴」的视觉差异集中在头顶和面部边缘的几十个像素里。如果你拿 COCO 里 person 类的标注直接训练模型学到的只是「这里有人」而不是「这个人头上有没有头盔」。常见做法是自建数据集采集渠道有三个路口监控截图、行车记录仪视频抽帧、公开道路场景视频。采集时要注意覆盖不同时段早中晚、不同天气晴阴雨、不同头盔颜色和款式。我一般会建议至少收集 3000 到 5000 张原始图像其中正样本戴头盔和负样本未戴头盔比例控制在 1:1 到 1.5:1 之间。如果负样本太少模型会倾向于把所有头部都判成戴头盔召回率虚高但误报严重。标注工具用 LabelImg 或 CVAT 都可以关键是标签体系要提前定死。推荐两类标签helmet和no_helmet。不要标成head再在后期用规则判断那样会把分类逻辑和检测逻辑耦合在一起后期换模型或加类别时非常痛苦。2.2 从 LabelImg 标注到 YOLOv5 格式的完整转换脚本LabelImg 默认输出 Pascal VOC 格式的 XML 文件而 YOLOv5 需要的是每张图对应一个.txt文件每行格式为class_id x_center y_center width height且坐标要归一化到 0 到 1 之间。下面这个脚本可以直接把 VOC 格式批量转成 YOLO 格式同时生成训练所需的目录结构。import os import xml.etree.ElementTree as ET import shutil from pathlib import Path # 类别映射根据你的标注类别顺序修改 CLASS_MAP {helmet: 0, no_helmet: 1} def convert_voc_to_yolo(xml_path, img_width, img_height): 将单个 VOC XML 转为 YOLO txt 行列表 tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: continue cls_id CLASS_MAP[cls_name] 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) # 归一化并计算中心点与宽高 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines def batch_convert(voc_dir, output_dir, img_dir): 批量转换并生成 YOLO 数据集目录 voc_dir Path(voc_dir) output_dir Path(output_dir) img_dir Path(img_dir) labels_dir output_dir / labels images_dir output_dir / images labels_dir.mkdir(parentsTrue, exist_okTrue) images_dir.mkdir(parentsTrue, exist_okTrue) for xml_file in voc_dir.glob(*.xml): stem xml_file.stem img_file img_dir / f{stem}.jpg if not img_file.exists(): print(f跳过 {stem}找不到对应图片) continue # 读取图片尺寸这里用 PIL也可以用 cv2 from PIL import Image with Image.open(img_file) as im: w, h im.size lines convert_voc_to_yolo(xml_file, w, h) if not lines: continue # 写 label 文件 with open(labels_dir / f{stem}.txt, w) as f: f.write(\n.join(lines)) # 复制图片到统一目录 shutil.copy(img_file, images_dir / img_file.name) print(f转换完成共处理 {len(list(labels_dir.glob(*.txt)))} 个标注文件) if __name__ __main__: batch_convert( voc_dir./annotations/xml, output_dir./dataset, img_dir./annotations/images )这段代码的逻辑分三步解析 XML 拿到每个目标的类别和边界框坐标按图片实际宽高做归一化计算最后把图片和标签分别复制到images和labels两个平行目录。参数方面CLASS_MAP必须和你在 LabelImg 里用的类别名完全一致大小写敏感img_width和img_height来自原图不能用标注工具里显示的缩放后尺寸否则归一化坐标会整体偏移。转换完成后建议随机抽 5 到 10 张图用 YOLOv5 自带的utils/plots.py里的可视化函数画框验证一遍确认框的位置和类别都对得上。2.3 数据集划分与 data.yaml 配置的四个关键参数转换完成后按 8:1:1 划分训练集、验证集和测试集。划分时要注意同一段视频抽出的帧不能同时出现在训练集和验证集里否则验证指标会虚高。下面是一个标准的data.yaml配置# data.yaml path: ./dataset # 数据集根目录 train: images/train # 训练集图片路径相对 path val: images/val # 验证集图片路径 test: images/test # 测试集图片路径可选 nc: 2 # 类别数 names: [helmet, no_helmet] # 类别名称顺序必须与 CLASS_MAP 一致四个关键参数里path建议用相对路径方便整个项目打包迁移nc必须等于names列表长度写错会在训练启动时报维度不匹配names的顺序决定了推理结果里类别 ID 对应的含义一旦训练完成就不能改否则所有预测标签都会错位。另外如果图片和标签不在同一级目录下YOLOv5 默认会去images同级的labels目录找同名.txt这个约定不要随意打破。3. YOLOv5 训练配置从环境搭建到超参数调整3.1 环境配置的版本锁定与常见依赖冲突YOLOv5 对 PyTorch 和 CUDA 版本比较敏感我一般会锁定一套经过验证的组合Python 3.8、PyTorch 1.12.1、CUDA 11.3、cuDNN 8.2。如果你用 conda可以直接建一个独立环境conda create -n helmet_yolo python3.8 -y conda activate helmet_yolo # 安装 PyTorch根据你的 CUDA 版本选择对应命令 pip install torch1.12.1cu113 torchvision0.13.1cu113 --extra-index-url https://download.pytorch.org/whl/cu113 # 克隆 YOLOv5 仓库并安装依赖 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这里最容易翻车的地方是requirements.txt里的opencv-python版本和系统里已有的 OpenCV 冲突表现是import cv2时报libGL.so.1找不到。解决办法是先pip uninstall opencv-python再装opencv-python-headless后者不依赖图形界面库适合服务器环境。另一个坑是numpy版本YOLOv5 某些版本要求numpy1.24如果你环境里已经是 1.24 以上会在做数据增强时抛np.int弃用错误降级即可。3.2 用 train.py 启动训练命令行参数逐项拆解环境就绪后训练命令本身不复杂但每个参数都影响最终效果。下面是一条我常用的启动命令python train.py \ --data ./data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0 \ --workers 4 \ --project runs/train \ --name helmet_exp1 \ --cache逐项说明--weights yolov5s.pt表示从官方预训练权重开始微调小模型在头盔这种单一场景下通常够用如果误报率高再换yolov5m.pt--img 640是输入分辨率头盔目标较小的话可以提到 1280但显存占用会翻倍--batch-size 16在 8GB 显存下比较稳妥如果报 OOM 就降到 8 并配合--accumulate做梯度累积--epochs 100是上限实际看验证集 mAP 曲线连续 20 轮不升就可以停--cache把图片缓存到内存能明显加快每个 epoch 的读取速度但数据集超过 1 万张时慎用内存可能扛不住。训练启动后终端会实时打印每轮的 box_loss、obj_loss、cls_loss 和验证集的 Precision、Recall、mAP0.5。重点盯 mAP0.5 和 Recall头盔检测场景下Recall 比 Precision 更重要因为漏检一个没戴头盔的人比误报一个戴了头盔的人代价高得多。如果 Recall 上不去优先检查负样本是否足够、标注框是否把整个头部都包住了。3.3 超参数微调学习率、锚框和增强策略的取舍YOLOv5 默认的超参数在data/hyp.scratch.yaml里头盔检测场景下我一般会改三个地方。第一是学习率默认lr00.01对微调来说偏大改成0.001能让预训练权重保留更多通用特征收敛更稳。第二是锚框如果你的数据集里头盔目标普遍偏小用python utils/autanchor.py --data data.yaml重新聚类生成适配的锚框尺寸替换掉默认的 COCO 锚框对小目标召回提升明显。第三是数据增强mosaic默认开启能提升小目标检测能力但如果你的图片里骑行者经常被遮挡可以适当降低mosaic概率到 0.5同时开启mixup增加样本多样性。改超参数不用直接动源码在训练命令里加--hyp ./my_hyp.yaml指向你的自定义文件即可。每次只改一个参数跑 20 轮看验证曲线确认有效再改下一个。同时改多个参数出了问题根本不知道是哪个引起的这是血泪经验。4. 推理与部署模型导出、加速和实际场景验证4.1 用 detect.py 跑通单张图和视频流推理训练完成后权重保存在runs/train/helmet_exp1/weights/best.pt。先用单张图验证模型是否正常工作python detect.py \ --weights runs/train/helmet_exp1/weights/best.pt \ --source ./test_images \ --img 640 \ --conf-thres 0.4 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name helmet_test--conf-thres 0.4是置信度阈值低于这个值的框会被丢弃。头盔检测里这个值不要设太高0.3 到 0.4 之间比较合适太高会漏掉模糊或小目标。--iou-thres 0.45控制 NMS 合并重叠框的力度如果发现同一个人头被框了两次把这个值调低到 0.3 到 0.4。--save-txt会把检测结果按 YOLO 格式存下来方便后续做统计或二次处理。视频流推理把--source换成视频文件路径或摄像头编号比如0表示默认摄像头即可。如果要做实时检测建议把--img降到 416 或 320牺牲一点精度换帧率。在树莓派 5 上部署自己训练的 YOLOv5 模型时直接跑 PyTorch 权重帧率可能只有个位数需要先导出成 ONNX 或 NCNN 格式再做推理加速。4.2 导出 ONNX 和 TensorRT 的适用场景与命令模型导出用export.py不同格式对应不同部署环境# 导出 ONNX通用性最好适合服务器和边缘设备 python export.py --weights best.pt --include onnx --img 640 --batch 1 # 导出 TensorRTNVIDIA 显卡上推理速度最快 python export.py --weights best.pt --include engine --img 640 --batch 1 --device 0 # 导出 NCNN适合树莓派等 ARM 设备 python export.py --weights best.pt --include ncnn --img 640ONNX 的优点是跨平台Windows、Linux、ARM 都能跑配合 ONNX Runtime 推理速度比原生 PyTorch 快 1.5 到 2 倍。TensorRT 在 NVIDIA 显卡上能再快 2 到 3 倍但只能在同代或更新架构的显卡上运行换机器要重新导出。NCNN 是腾讯开源的推理框架在树莓派上表现不错但导出后需要自己写 C 推理代码Python 端支持不如 ONNX 完善。选哪个取决于你的部署硬件服务器选 ONNX 或 TensorRT嵌入式设备选 NCNN 或 ONNX Runtime。4.3 用混淆矩阵和 PR 曲线判断模型是否真的可用训练日志里的 mAP 只是一个汇总指标要判断模型能不能上线还得看混淆矩阵和 PR 曲线。YOLOv5 训练结束后会在runs/train/helmet_exp1/下生成confusion_matrix.png和PR_curve.png。混淆矩阵里重点看no_helmet被误判成helmet的比例这个值高说明模型对未戴头盔的特征学得不够需要补充负样本或提高负样本的损失权重。PR 曲线看的是在不同置信度阈值下 Precision 和 Recall 的权衡如果曲线整体偏向左下角说明模型区分能力不足得回头检查标注质量或增加数据量。实际验证时我习惯拿一段没参与训练的路口视频跑一遍人工数 100 个骑行者对比模型输出算一下漏检和误报的实际数量。这个数字比任何验证集指标都更有说服力因为验证集里的图片分布和真实场景总有差异。5. 避坑与排查头盔检测项目里最容易翻车的五个地方5.1 训练 loss 正常下降但 mAP 一直不涨现象box_loss 和 obj_loss 都在降但验证集 mAP0.5 卡在 0.3 左右不动。原因通常是标注框不准确比如框只包住了头盔顶部而没包住整个头部或者把背景里的圆形物体误标成了头盔。解决方法是随机抽 20 张训练图用utils/plots.py把标注框画出来肉眼检查重点看框的边界是否紧贴目标边缘。如果标注没问题再检查data.yaml里nc和names是否与标注类别完全对应类别 ID 错位会导致模型学到的分类边界完全混乱。5.2 推理时所有头部都被判成 helmet现象模型在测试图上把没戴头盔的人也框成了 helmetno_helmet 类别几乎不出现。原因是训练集里负样本太少或者负样本的多样性不够比如全是戴帽子的人没有真正光头的样本。解决办法是补充至少 500 张真实的未戴头盔图片涵盖不同发型、不同角度、不同光照。如果补充数据后仍然偏向 helmet可以在损失函数里给 no_helmet 类别加权重修改train.py里的cls_pw参数默认是 1.0调到 2.0 到 3.0 能明显提升负样本的召回。5.3 导出 ONNX 后推理结果和 PyTorch 不一致现象PyTorch 权重跑出来框的位置和类别都对导出 ONNX 后用 ONNX Runtime 跑框的位置偏移或者类别置信度整体偏低。原因通常是导出时的--img尺寸和推理时的输入尺寸不一致或者 ONNX 的--dynamic选项没开导致 batch 维度固定。解决方法是导出和推理都用同一个--img值如果要做动态 batch导出时加--dynamic推理时确保输入 tensor 的 shape 和导出时一致。另外ONNX Runtime 的providers设置也会影响结果优先用CUDAExecutionProvider回退到CPUExecutionProvider时数值精度可能有微小差异。5.4 视频推理帧率过低达不到实时要求现象用detect.py跑视频每帧耗时超过 100ms帧率不到 10 FPS。原因是输入分辨率太高比如 1280或者模型太大yolov5l。解决办法分三步先把--img降到 416帧率通常能翻倍如果还不够换yolov5s权重重新训练小模型在头盔这种单一场景下精度损失有限最后考虑导出 TensorRT 或 ONNX 做推理加速。另外--half选项开启 FP16 推理在支持 Tensor Core 的显卡上能再快 30% 左右但精度会有轻微下降需要验证是否可接受。5.5 换一批新场景图片后模型集体失效现象在训练集同分布的路口图片上表现很好换到另一个路口或夜间场景后漏检和误报同时飙升。原因是模型过拟合了训练场景的光照、背景和摄像头角度。解决办法是在训练集里加入目标场景的图片做微调哪怕只有 200 到 300 张也能显著提升泛化能力。如果目标场景图片实在拿不到可以在训练时加强数据增强把hsv_h、hsv_s、hsv_v的扰动范围调大模拟不同光照条件。夜间场景还要特别注意训练集里如果几乎没有夜间图片模型在夜间基本不可用这是数据覆盖问题不是调参能解决的。6. 把模型塞进现有业务流一个可复用的后处理技巧模型输出只是一堆框和类别要变成业务系统能用的数据中间还需要一层后处理逻辑。我一般会在detect.py的基础上加一个统计模块按帧累计检测结果输出每个时间窗口内的头盔佩戴率。下面这段代码展示了如何从 YOLOv5 的推理结果里提取结构化数据import cv2 import torch from pathlib import Path def process_frame(model, frame, conf_thres0.4, iou_thres0.45): 对单帧做推理并返回结构化结果 # YOLOv5 模型推理 results model(frame, size640) detections results.pandas().xyxy[0] # 转为 DataFrame # 过滤低置信度 detections detections[detections[confidence] conf_thres] # 按类别统计 stats {helmet: 0, no_helmet: 0} for _, row in detections.iterrows(): cls_name row[name] if cls_name in stats: stats[cls_name] 1 # 计算佩戴率 total stats[helmet] stats[no_helmet] rate stats[helmet] / total if total 0 else 0.0 return stats, rate, detections # 使用示例逐帧处理视频并累计统计 cap cv2.VideoCapture(road_test.mp4) model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) window_stats {helmet: 0, no_helmet: 0} frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break stats, rate, dets process_frame(model, frame) window_stats[helmet] stats[helmet] window_stats[no_helmet] stats[no_helmet] frame_count 1 # 每 100 帧输出一次统计 if frame_count % 100 0: total window_stats[helmet] window_stats[no_helmet] if total 0: print(f帧 {frame_count}: 佩戴率 {window_stats[helmet]/total:.2%}) window_stats {helmet: 0, no_helmet: 0} cap.release()这段代码的关键在于results.pandas().xyxy[0]这一行它把 YOLOv5 的原始输出转成了带列名的 DataFrame列包括xmin、ymin、xmax、ymax、confidence、class、name后续做过滤和统计都很方便。参数conf_thres和iou_thres建议和detect.py里保持一致避免两套逻辑产生不同结果。统计窗口设为 100 帧是个经验值对应 25 FPS 视频大约 4 秒既能平滑瞬时波动又不会太滞后。如果要把这套逻辑接入现有的交通管理平台建议把process_frame封装成一个独立服务通过 HTTP 或消息队列接收图片帧返回 JSON 格式的统计结果。这样模型更新时只需要重启推理服务不影响上层业务。另外实际部署时记得加一个「模型版本」字段到返回结果里方便回溯不同版本的表现差异。我自己就吃过亏换了新模型后没记录版本过了一周发现指标下降根本不知道是模型问题还是数据分布变了。希望这些经验能帮到你。本文还有配套的精品资源点击获取