拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于深度学习的电动自行车头盔佩戴检测系统:YOLO与PyTorch实战指南

基于深度学习的电动自行车头盔佩戴检测系统:YOLO与PyTorch实战指南

简介:基于YOLOv5与DeepSORT的电动自行车头盔佩戴检测系统,是一份面向人工智能及深度学习开发者的完整工程。针对骑行场景中头盔佩戴状态的实时识别与多目标追踪问题,整合了目标检测与重识别技术,适合智慧交通、安全监控等场景落地,也适合有Python基础的学生进行算法实践与二次开发。压缩包共186个文件,容量133.57MB,包含55个Python脚本、22个YAML配置、7个模型权重文件、45张PNG图片及HTML/JS/CSS展示页面等,其中Python脚本覆盖训练与推理逻辑,YAML用于模型结构配置,pt文件提供预训练权重。已有422人学习浏览。借助该工程可直接复现头盔检测与追踪过程,获得模型结构定义、预训练权重、训练调参代码和前端监控界面,既能用于理解YOLOv5目标检测与DeepSORT多目标追踪机制,也为后续算法改进和业务系统集成提供了可行基础。

1. 基于深度学习的电动自行车头盔佩戴检测系统:先搞懂它解决什么问题

这类名字带“.zip”的项目,我每年都要帮人盘一遍。它解决的问题很具体:摄像头拍下电动自行车经过的画面,系统自动判断骑车人有没有戴头盔,没戴就抓拍、报警,戴了就正常通行。用深度学习的检测系统来做,比普通图像处理稳定得多——能扛逆光、夜间、侧脸和遮挡,准确率普遍能做到95%以上。这套东西适合三类人:毕业设计选型的学生、园区或工地出入口想加一道自动监管的工程师、还有刚接触目标检测想跑通一个完整项目的算法入门者。它不是一个只能跑demo的黑匣子,而是把数据、标注、训练、部署完整串起来的系统工程。

2. 模型选型:为什么头盔检测绕不开YOLO和PyTorch

2.1 头盔佩戴检测的本质是定位,不是单纯分类

头盔佩戴检测听起来是“判断戴没戴”,但真正落到系统里,必须同时输出“人在哪”和“这个人是否戴盔”,否则没法联动抓拍或统计车流。所以它天然是一个目标检测任务,不是图像分类任务。如果你用分类网络把整张图分成“戴盔”和“没戴盔”,只要图里有一个人没戴,整张图就报废,漏报率极高。

目标检测的主流路线有三类:两阶段检测器(Faster R-CNN)、单阶段检测器(YOLO/SSD)和基于Transformer的端到端检测器(DETR/RT-DETR)。头盔这类小目标多、实时性要求高的场景,两阶段虽然精度上限高,但速度太慢;DETR那套在CPU和低端边缘设备上跑不动;YOLO系单阶段检测器在速度和精度的平衡上最友好,因此绝大多数落地系统选它。

我的经验是,别在模型选型上过度纠结,把精力放到数据和标注上。用YOLOv8n或者YOLOv5s,对头盔检测这种语义简单的任务,已经能跑到mAP50在0.9以上。真正决定上限的是标注质量、类别定义和夜间样本数量。另外,类别设计也有讲究。常见方案是直接分两个类:0表示“戴着头盔的人”,1表示“没戴头盔的人”,框画在人身上;另一种是把head和helmet分别检测,再通过IoU判断头盔是否戴在头上。后者能处理“手里拿着头盔”的干扰,但标签和后处理复杂度高。对于电动自行车出入口这种场景,二分类方案足够了。

2.2 YOLOv5和YOLOv8怎么选:一张表说清

常见的新手问题是用YOLOv5还是YOLOv8。这里给一个可抄作业的对比:

模型主干是否anchor-free显存占用(bs=16,640)推荐场景
YOLOv5sCSPDarknet否约6GB老项目维护、N卡部署资料多
YOLOv8n改进CSPDarknet是约4GB快速验证、边缘部署
YOLOv8s改进CSPDarknet是约6GB精度优先的小型系统
RT-DETRResNet+Transformer是约8GB不追求速度、只做离线分析

如果只是毕设或者出demo,我一般直接用ultralytics库配YOLOv8n,因为它的训练命令和导出工具一条命令搞定,不用手写很多胶水代码。如果要求严谨的工程交付,YOLOv5的生态更稳定,TensorRT部署资料也多。注意,这里的“n/s”是模型尺寸,不是版本后缀。头盔目标相对小,直接上n可能会有漏检,可以先跑一版s,如果速度不够再换回n。

2.3 环境搭建:PyTorch加ultralytics的最小配置

环境不用装一堆东西,PyTorch、ultralytics、OpenCV三件套就够了。这里给一个Linux或者Windows都可以用的conda安装命令:

conda create -n helmet python=3.9 -y conda activate helmet pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 --extra-index-url https://download.pytorch.org/whl/cu117 pip install ultralytics opencv-python

先解释逻辑:conda创建独立的Python 3.9环境,避免把系统Python搞乱;pip安装指定版本的PyTorch,cu117表示适配CUDA 11.7,这个版本对RTX 30系和40系驱动都友好。如果你显卡驱动不支持11.7,需要去PyTorch官网查对应CUDA版本,不要盲目抄。pip下载慢的话,可以在命令末尾加-i https://pypi.tuna.tsinghua.edu.cn/simple。

安装完先验证一下GPU是否被识别:

import torch print(torch.__version__) print(torch.cuda.is_available())

如果输出False,大概率是CUDA/显卡驱动版本不匹配,或者装成了CPU版。先用CPU版跑通小规模训练也没有问题,只是慢。头盔数据集一般不会太大,CPU训练一晚也能出结果,这条血泪经验后面避坑章节会展开。

3. 数据准备:把头盔标注成YOLO格式的4个关键步骤

3.1 数据来源和数量:别迷信公开数据集,自己补拍才管用

头盔佩戴检测没有特别统一的标准数据集。很多公开安全帽数据集以工地大黄色安全帽为主,电动自行车的头盔是半盔、全盔、彩色头盔,形状差异大,直接迁移会翻车。常见做法是混合三路数据:

  • 公开数据集:比如SCUT-HEAD、Global Safety Helmet Dataset里头的head类,主要用来预训练和补充负样本;
  • 自己用手机或摄像头录街道骑行视频,抽帧得到图像;
  • 如果设备到位,做合成:把头盔抠图贴到背景上做马赛克增强,但注意不要破坏光照一致性。

数量经验:戴头盔和不戴头盔两类目标各不少于3000个框,这个数量级对YOLO来说足够。图像总数在5000到10000张之间,不要太多到训练时间爆炸,也不要少到类别不平衡。如果你做毕设,时间有限,可以先用公开数据集跑通流程,再补2000张自己的场景数据。数据里一定要包含夜间、逆光、雨天、侧脸这些难例,否则白天现场效果很好,一入夜就大面积漏检。

3.2 标注规范:两类还是分部位,先想清楚再动手

标注前必须定类别方案,否则返工到崩溃。我推荐用二分类方案:

  • 类别0:helmet_ridden(戴着头盔的人)
  • 类别1:no_helmet(没戴头盔的人)

注意框的是“人+头盔”整体,不是单独框头盔。这样模型直接输出人的位置和是否戴盔,省掉后处理。如果采用多类别方案,把person、helmet、head分开检测,再通过几何关系判断头盔和head的重叠率,能处理“手里拿着头盔”的干扰,但标签和后处理复杂度高,不建议第一次做就选它。

标注工具我用LabelImg,因为它直接输出Pascal VOC XML,稍后转成YOLO txt很方便。标注时遵循几个原则:被严重遮挡的目标不标;只有上半身出现在画面里也标,但像素面积小于30x30的忽略;夜间看不清的不硬标。这些原则看起来琐碎,实际上决定模型泛化能力。还有一条很容易忽略:不要用同一个文件既有VOC又有YOLO格式,LabelImg会自动生成classes.txt,这个文件里的类别顺序必须和CLASS_MAP一致,否则转出来的txt类别索引是乱的。

3.3 把VOC标注转成YOLO格式:Python脚本直接用

LabelImg默认保存为VOC格式,而YOLO训练需要的是每个图片一个txt文件,每行是“类别 cx cy w h”,坐标相对图像宽高。写转换脚本时最容易错的是归一化和类别索引,贴一个我常用的版本:

import xml.etree.ElementTree as ET import os CLASS_MAP = {"helmet_ridden": 0, "no_helmet": 1} def convert_voc_to_yolo(xml_file, out_txt): tree = ET.parse(xml_file) root = tree.getroot() width = int(root.findtext("size/width")) height = int(root.findtext("size/height")) lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in CLASS_MAP: continue bndbox = obj.find("bndbox") xmin = float(bndbox.findtext("xmin")) ymin = float(bndbox.findtext("ymin")) xmax = float(bndbox.findtext("xmax")) ymax = float(bndbox.findtext("ymax")) x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height # 防止越界,写死一点更稳 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) lines.append(f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(out_txt, "w", encoding="utf-8") as f: f.write("\n".join(lines)) if __name__ == "__main__": xml_dir = "Annotations" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(".xml"): continue convert_voc_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(out_dir, xml_name.replace(".xml", ".txt")))

这段代码做的事情:遍历一个目录下所有XML,读图像宽高,把每个object的bbox从左上右下坐标转成中心点加宽高,再除以宽高完成归一化。注意三个细节。第一,类别名必须和CLASS_MAP里的完全一致,包括大小写,写错会静默跳过;第二,YOLO格式要求类别索引从0开始,第1个字段是整数,后面是浮点;第三,坐标可能会因为标注工具误差越界,加一个min/max裁剪可以减少训练警告。

3.4 数据增强和划分:不要只加翻转,要加夜间和遮挡

YOLO内置增强在配置文件里就能开,比如mosaic、mixup、hsv_h/hsv_s/hsv_v、fliplr。对于头盔检测,最有用的增强是:

  • mosaic:4张图拼一起,增加小目标出现频率;
  • 夜间亮度变化:把hsv_v的range拉大,模拟暗光;
  • 随机裁剪和仿射变换:增强戴盔的人被杆子、手部遮挡的情况。

划分数据集时注意不要随机分,而要按照视频来源/时间分。比如你录了三个视频,应该让同一段视频的帧只出现在train或val中,否则验证集是“作弊”的,测出来mAP虚高。这是我踩过的坑之一。划分比例7:2:1或8:2都可以,但val至少要有300张以上,否则mAP波动太大。

4. 训练与调参:让头盔检测模型在100轮内收敛到90% mAP

4.1 数据配置:写对helmet.yaml,别让训练静默跑偏

ultralytics训练需要自己的YAML配置,指定训练集、验证集和类别信息。常见错误是把图片和标签放在同一个文件夹,或者路径写成绝对路径换机器后失效。我一般在项目根目录放一个helmet.yaml,内容这样:

path: ./helmet_dataset train: images/train val: images/val names: 0: helmet_ridden 1: no_helmet

path是数据集根目录,train和val是相对根目录的图片路径。labels与images并列,ultralytics会自动到images/train同级的labels/train里找txt。这里有一个隐藏规则:图片路径下必须存在对应的同名txt,否则训练时该图片直接跳过,而且不报错,导致mAP虚高。新手最容易在这地方翻车。

4.2 训练命令和关键参数:从预训练权重开始,别从零训练

训练不要自己从头搭模型,用官方预训练权重做迁移学习。命令如下:

yolo detect train \ data=helmet.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ optimizer=AdamW \ patience=15 \ workers=8

逐行说明:data指向刚才写的yaml;model用yolov8n.pt,第一次运行会自动下载COCO预训练权重,如果你网络环境没法访问,可以先把权重下载好后放到目录里;epochs设100,头盔数据集小,100轮足够,多反而过拟合;imgsz用640,如果你的摄像头画面是1080P且头盔占比小,可以提高到960或1280,但显存占用指数上涨;batch=16是一个均衡值,8G显存能跑;lr0=0.01配合AdamW,是ultralytics默认推荐,SGD则需要调低到0.001;patience=15表示15轮mAP没有提升就早停,防止晚上睡觉后训练跑过头。

训练日志里重点看两类指标:box_loss和cls_loss是否单调下降,val的mAP50是否稳步上升。如果loss降了但mAP不动,大概率是标签和图片对不上,或者类别定义有问题。训练过程中不要频繁改参数,等一轮结束再看趋势。

4.3 验证与评估:不只盯着mAP,要看漏检和误检

训练完生成best.pt和last.pt,验证命令是:

yolo detect val model=best.pt data=helmet.yaml batch=32

输出包含mAP50和mAP50-95。对于头盔检测这个场景,mAP50比mAP50-95更实用,因为最终决策只要IoU大于0.5就算命中。如果mAP50在90%以上,基本可以进入部署环节。但mAP是整体统计,掩盖了小目标漏检,建议额外看一下混淆矩阵和PR曲线。混淆矩阵里最容易出现的是类别0和类别1互相混淆,这说明两类目标在视觉上太像——常见原因是标注时把“戴着头盔但头盔很小”标成了no_helmet。

如果目标是做系统,我还会把验证结果的预测图导出来,人工看几百张。这不是玄学,目标检测项目的数据bug,很多都靠人工翻图才能发现,比如标签错位、漏标、遮挡框乱跳。

4.4 断点续训和最终权重选择

训练如果因为断电中断,不用从头开始:

yolo detect train model=runs/train/exp/weights/last.pt data=helmet.yaml epochs=100

断点续训用last.pt而不是best.pt,因为best.pt是mAP最高点,但对应的学习率状态可能不是最后状态。续训后epochs是指“总轮数”,不是“剩余轮数”——这个坑我踩过,续训设100会多训100轮,导致过拟合。最终选择权重时,别迷信best.pt,如果best.pt对应的验证损失很高但mAP高,说明是阈值巧合,我一般选训练后期稳定轮次的权重,用验证视频实测效果定版本。

5. 避坑清单:头盔数据集最常见的5个翻车点

5.1 mAP一直为0:先查标签文件是不是空的

现象:训练loss下降,但是val结果全为0,输出图片上一个框都没有。 原因:ultralytics在数据目录下没找到任何标注文件时不会报错,只是静默跳过全部图片。 解决:检查labels目录里是否有txt,且txt非空;用脚本统计每个txt的行数,如果全是0,回看转换脚本的类别名是否匹配。

5.2 推理时头盔小目标漏检

现象:大头盔能检测,远处的小头盔就是预测不到。 原因:模型输入是640x640,原图压缩后小目标只有十几个像素,特征丢失。 解决:提高imgsz到960或1280;或者在预处理时对图像做切片推理(SAHI),把大图切成重叠块分别检测再合并。后者能立竿见影,但推理时间也成倍增加。

5.3 显存溢出

现象:batch=16报CUDA out of memory。 原因:显卡显存不够,或者后台有其他进程占显存。 解决:先看nvidia-smi确认显存占用;将batch降到4或8;同时开启梯度累积。注意,降batch会影响BN统计,所以尽量用更大的batch,显存不够时用累积模拟大batch。

5.4 torch.cuda.is_available()返回False

现象:训练完全在CPU上跑,慢到怀疑人生。 原因:PyTorch装成了CPU版,或者CUDA版本与显卡驱动不匹配。 解决:用python -c "import torch;print(torch.cuda.is_available())"验证;如果是CPU版,需要卸载重装。NVIDIA显卡驱动更新后,老CUDA也经常失效,所以安装前用nvidia-smi查出驱动支持的最高CUDA版本,再选PyTorch对应wheel,这也是深度学习环境配置里最常见的坑。

5.5 标注框整体偏移导致检测框错位

现象:训练后模型检测框偏左/偏上,像是整体平移。 原因:VOC转YOLO时用错了图像宽高,或者XML里的size和实际图像不一致。 解决:转换脚本里打印一行日志,对比XML的width/height和实际图像尺寸;另外很多标注工具会生成旋转框,如果坐标带theta,就需要用旋转框公式,而不是直接取min/max。头盔检测场景不需要旋转框,直接忽略theta就好。

6. 从模型到系统:ONNX/TensorRT导出与摄像头实时检测

6.1 导出ONNX和TensorRT:边缘部署的必由之路

训练得到的best.pt是PyTorch权重,生产环境不会直接用它跑,因为加载慢、显存高。我一般先导出ONNX做跨平台验证,再在目标设备上转成TensorRT:

yolo export model=best.pt format=onnx opset=12 yolo export model=best.pt format=engine device=0 half=True

ONNX的opset可以指定12,兼容性强。engine是TensorRT专有格式,half=True用FP16推理,速度能翻倍。导出后最好在Jetson或者带TensorRT的电脑上测一下速度,确认模型推理延迟低于50ms,否则实时性无从谈起。

6.2 摄像头实时检测:一版能跑通的推理脚本

部署端我用OpenCV读摄像头,配合ultralytics的predict接口做推理:

import cv2 from ultralytics import YOLO model = YOLO("best.engine") # TensorRT引擎 cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame = cap.read() if not ret: break results = model(frame, conf=0.25, iou=0.45, verbose=False) annotated = results[0].plot() cv2.imshow("helmet detection", annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这段脚本的核心是模型推理接口,conf和iou两个参数要现场调。conf设太低会出现头盔误检,设太高会漏掉小目标;iou是NMS阈值,多人聚集时设0.45比较稳。如果只需要报警,不要每帧都判断,增加一个计数窗口,比如连续3帧检测到未戴头盔再触发,否则单帧抖动会造成误报。验证方法也简单:准备一段10分钟实拍视频,逐帧统计检测框和人工标注,算出漏报率。这个数值才是系统能否投入的关键,mAP高并不代表现场能用。我自己在头盔检测上栽过的跟头,基本都写进避坑清单了。希望这个从数据到部署的完整路径能帮到你。

本文还有配套的精品资源,点击获取

返回列表