ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLOv9头盔佩戴检测实战:数据准备、模型训练推理与TensorRT部署

2026/9/28 17:09:30 拓冰建站 浏览量
YOLOv9头盔佩戴检测实战:数据准备、模型训练推理与TensorRT部署 简介面向道路电动车骑行安全场景这套基于YOLOv9的检测项目提供头盔佩戴识别从数据准备、模型训练到推理部署的完整闭环覆盖智慧交通、毕业设计、程序开发等需求适合计算机视觉方向的在校学生与开发者参考。压缩包共188个文件以83个Python脚本、30个YAML配置、训练好的pt权重、评估结果csv及jpg样本图为主整体约69.32MB目录层级分明便于直接导入PyCharm按模块查阅。资源内含详细运行教程覆盖Anaconda环境配置、依赖安装、自定义数据集准备、train_dual.py训练参数调整与detect_dual.py测试推理借助YOLOv9-s预训练权重和已有模型可快速跑通检测流程也能在自有数据集上继续训练并查看评估曲线节省大量调参排错时间。目前已有238人学习下载适合需要成套代码、模型和文档来完成课题或工程演示的读者。1. YOLOv9 头盔佩戴检测第一次跑通这个项目要解决的三个问题早高峰路口交警每天要看上千辆电动车判断骑车人有没有戴头盔。基于 YOLOv9 的摄像头能在车流进入视野的两三秒内完成检测框出“带头盔的人”和“没头盔的人”——这就是头盔佩戴检测系统。它本质上是一个二分类目标检测任务从视频帧里找出目标输出带置信度的边界框再按业务规则决定是否告警。这个方向对刚接触目标检测的 Python 开发者很友好开源数据集能打底训练好的权重可以直接跑推理评估曲线能看清模型真实水平后续的优化路径也明确。这篇笔记按拿到项目源码之后的推进顺序把环境搭建、模型推理、训练评估和部署落地里的关键细节过一遍。2. 为什么选 YOLOv9 做头盔检测模型结构与数据集准备做头盔佩戴检测的人十有八九先用 YOLOv5 或 YOLOv8 试过一轮然后被特定场景逼着换更强的模型。我自己第一次跑这个任务用的 YOLOv8n近处识别效果还行骑车人一远、画面里目标变小漏检率就上来了。换成 YOLOv9 之后 mAP50 有明显提升尤其远处小目标这一块。先理解它为什么强再决定怎么用否则后面调参都是玄学。2.1 YOLOv9 的两个核心改动PGI 与 GELAN 解决什么问题YOLOv9 最大的变化是引入了可编程梯度信息Programmable Gradient InformationPGI和广义高效层聚合网络Generalized Efficient Layer Aggregation NetworkGELAN。先说 GELAN。它是在 CSPNet 和 ELAN 基础上做的网络结构改进核心思路是在不增加推理开销的前提下让不同层的特征融合得更充分。头盔检测里最典型的场景是一辆电动车从路口远端驶来骑车人头部在画面里只占几十个像素这时候浅层细节特征和深层语义特征都要用上。GELAN 的多分支融合正好能保留这些小目标的纹理信息漏检率会明显低于直接用深层网络压出来的特征图。PGI 解决的问题更底层——深度网络训练时梯度在反向传播过程中会逐渐丢失或产生偏差导致浅层网络学不到东西。PGI 的做法是设计一条辅助可逆分支在训练阶段为主干提供可靠的梯度信号推理阶段这条分支可以完全拿掉不影响速度。所以同一个数据集YOLOv9 训练出来的模型对难样本的拟合能力通常比 v8 强最直接的表现就是 mAP50-95 这个指标更高而不是只有 mAP50 好看。做这个系统选模型不能只看论文指标还要看落地场景。路口摄像头的算力有限如果目标是从视频流实时检测就得在精度和帧率之间找平衡。YOLOv9 有 c、e、t 几个规格c 版是精度和速度权衡最均衡的e 版精度更高但推理时间变长。我一般先用 c 版跑通全流程确认数据集没问题再决定要不要换更重的模型。2.2 数据集怎么搭类别设计和标注格式决定系统上限模型决定了下限数据集决定了上限。头盔佩戴检测常见的数据集分三类公开的骑车人头盔数据集、路采视频抽帧标注、以及从通用行人数据里二次筛选。拿到这个项目 zip 时先看里面数据集类别定义再看标注格式这样训练和推理脚本才能对得上。类别设计上我强烈建议用两类helmet戴头盔和head未戴头盔的头部。不要单独设person类。原因有两点一是去掉 person 类之后 anchor 分配更集中模型会把注意力放在头部区域而不是躯干二是后处理逻辑更简单——只有 head 类目标的置信度超过阈值时才告警helmet 类只记录不告警。如果三类混在一起还得在代码里额外做“person 和 head 是否重叠”的判断徒增复杂度。标注格式用 YOLO 的 txt 格式每行一个目标五个值类别编号、归一化后的中心点 x、中心点 y、框宽、框高。坐标全部除以图片宽高范围 0 到 1。例如0 0.5234 0.3871 0.1182 0.1567 1 0.2652 0.4123 0.1034 0.1465第一行的0代表 helmet后面四个数是归一化坐标第二行1代表 head。用标注工具导出时注意确认导出的是 YOLO 格式而不是 COCO 或 VOC 的 JSON/XML因为 YOLOv9 源码直接读 txt。2.3 从 VOC/COCO 转成 YOLO 训练格式目录结构与配置文件如果你手头的数据集是 VOC 或者 COCO 格式需要先转成 YOLO txt。转换脚本不复杂核心逻辑是读取 XML 或 JSON 里的标注框数据换算成归一化坐标按类别编号写入 txt 文件。下面是按 VOC XML 转 YOLO 的常见写法import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_dir, out_dir, classes): for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name xml_file.replace(.xml, .txt) with open(os.path.join(out_dir, txt_name), w) as f: for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n) classes [helmet, head] convert_voc_to_yolo(xml_labels, labels, classes)这段脚本的思路是遍历每个 XML 标注文件拿到图片尺寸后把每个目标的边界框做归一化然后写成 YOLO txt。注意两个边界坑一是 XML 里width和height标签的位置有些数据集的树形结构和标准 VOC 不一样建议先打印root的结构确认二是坐标要除以图片原始尺寸如果你在标注前做过缩放必须先写回原图尺寸再归一化否则模型训练会一直报框不对齐的错误。转完之后目录结构按 YOLO 惯例组织dataset/ images/ train/ val/ labels/ train/ val/ helmet.yamlhelmet.yaml是训练入口内容如下path: ./dataset train: images/train val: images/val nc: 2 names: [helmet, head]path建议写相对路径这样换机器不用改绝对路径。nc是类别数量names的排列顺序必须和标注 txt 里的类别编号一一对应顺序错了模型训练不报错但评估和推理的语义会全部错位。3. 用 Python 跑通推理最小命令与摄像头实时检测脚本拿到 zip 解压后第一步不是看代码而是先把环境配好然后用训练好的模型跑通一张图片的推理。这一步通了后面所有操作才有底。环境配不对后续每一步都在跟报错纠缠这是最消耗耐心的环节。3.1 环境与依赖Python 版本、CUDA、推理框架的搭配头盔检测项目的主流 Python 环境是 3.8 到 3.10太高或太低都可能跟依赖包冲突。CUDA 版本和 PyTorch 的搭配按官方矩阵来CUDA 11.8 配 PyTorch 2.0 及以上CUDA 12.1 配 PyTorch 2.1 及以上。依赖推荐版本说明Python3.8 ~ 3.103.11 部分依赖编译会翻车PyTorch2.0.x 或 2.1.x对应 CUDA 11.8 / 12.1torchvision与 PyTorch 同版本版本不一致会导致模型加载异常numpy1.24 左右过高版本可能和旧版 opencv 冲突opencv-python4.8带 GUI 支持别装 headless 版创建虚拟环境是必做的一步别省python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt装完依赖先验证 GPU 和 PyTorch 是否真的可用import torch print(torch.__version__) print(torch.cuda.is_available())如果torch.cuda.is_available()返回 False先查 CUDA 驱动版本再查 PyTorch 是不是装了 CPU 版本。这个坑很隐蔽——pip install torch默认装的就是 CPU 版必须从 PyTorch 官方的匹配表里找对应 CUDA 版本的安装命令。确认 GPU 可用后再继续。3.2 一条命令跑通单张图片推理环境就绪后用 zip 里训练好的模型对单张图片做一次完整推理。常见做法是用 YOLOv9 官方仓库的detect.py或者用 ultralytics 的统一接口加载权重。先看官方仓库的方式python detect.py --weights best.pt --source test.jpg --conf 0.5 --img 640 --device 0命令含义逐条拆开--weights指定训练好的模型权重文件--source是输入图片路径--conf是置信度阈值--img是推理输入尺寸--device 0表示用第一块 GPU。输出结果默认存在runs/detect/exp/下里面是画好检测框的图片和对应的置信度。--conf 0.5这个阈值直接影响实际效果。头盔检测里如果你更在意漏报阈值可以降到 0.35如果误报太多比如把路人头上的帽子当成头盔阈值拉到 0.6 以上。不过阈值只是后处理手段治标不治本真正要解决误报还是得靠训练数据。如果用 ultralytics 接口跑推理写法更 Pythonic适合后续要接业务逻辑的场景from ultralytics import YOLO model YOLO(best.pt) results model(test.jpg, conf0.5, imgsz640, device0) boxes results[0].boxes.xyxy.cpu().numpy() clses results[0].boxes.cls.cpu().numpy() scores results[0].boxes.conf.cpu().numpy() for box, cls, score in zip(boxes, clses, scores): label helmet if cls 0 else head print(f{label}: {score:.2f} {box})这段代码把检测结果转成 numpy 数组然后逐框打印类别、置信度和坐标。逻辑说明results[0]取第一张图片的结果boxes.xyxy是左上右下坐标格式做业务判断时直接用这个数组就行比如cls 1 and score 0.5就触发未戴头盔告警。3.3 摄像头或视频流实时检测的最小脚本单张图片跑通后下一步就是接视频或摄像头。写一个可以直接跑的最小脚本逻辑很简单循环读帧、推理、画框、显示import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(0) # 0 表示默认摄像头视频文件则传路径 while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame, conf0.5, imgsz640, device0)[0] annotated results.plot() cv2.imshow(Helmet Detection, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逐帧推理的逻辑没有秘密但有一个参数值得注意imgsz。摄像头画面通常是 1920×1080直接送进模型会非常慢。640 是精度和速度的平衡点缩到 416 可以换取接近翻倍的帧率代价是远距离小目标漏检变多。如果你是要装在路口杆子上用推荐 640 起步看实测帧率再决定降不降。results.plot()会把检测框、类别和置信度都画在帧上适合调试时看效果。真正上线时不要用 plot因为绘制本身消耗 CPU直接读取boxes、clses、scores做逻辑判断后推给后端更高效。4. 训练自己的模型并读懂评估曲线关键参数与调优预训练模型能跑通但不同城市的头盔样式、不同路口的摄像角度、不同季节的着装都会让通用权重水土不服。这时候就该用自己的数据微调。训练本身不难难的是理解评估曲线知道什么时候该调数据、什么时候该调参数。4.1 训练命令与关键超参数头盔检测的训练跟通用目标检测没有本质差别命令如下python train.py --data helmet.yaml --weights yolov9-c.pt --batch 16 --epochs 100 --img 640 --device 0--weights用官方预训练权重做迁移学习起点比从随机初始化开始训练收敛快得多准确率也更高。--batch 16是个保险值显存不够就降到 8显存充足可以上 32。--epochs先设 100观察评估曲线走势再决定加不减。超参数里最影响最终效果的是学习率和数据增强。YOLOv9 默认的lr0是 0.01如果 batch 比较大32 以上建议降到 0.005。学习率太大表现为 loss 曲线剧烈抖动不收敛太小则模型欠拟合、收敛慢。常用调整方案是先跑 30 个 epoch 看 loss 走势再把学习率按十倍缩放实验。参数推荐值影响batch8 ~ 32太小梯度噪声大太大容易过拟合epochs100 ~ 200100 左右看趋势别一开始就 300lr00.005 ~ 0.01控制收敛速度和稳定性img640增大提升小目标精度消耗显存optimizerSGD 或 AdamWSGD 更稳AdamW 收敛快但调参要更细训练过程中每轮结束会在runs/train/exp/下生成权重文件和评估图best.pt是验证集 mAP 最高的权重last.pt是最后一轮的权重。部署时只用best.pt。4.2 评估曲线怎么读PR 曲线、loss 曲线、混淆矩阵zip 里的“评估曲线”一般是训练过程中自动生成的三个文件results.png、PR_curve.png和confusion_matrix.png。这三个图是判断模型能不能用的关键依据。results.png里包含 box_loss、cls_loss、dfl_loss 三条下降曲线以及 mAP50 和 mAP50-95 两个上升曲线。Loss 下降正常说明模型在学下降到一定程度后保持平稳或小幅波动属于正常收敛。如果 loss 几乎不降检查学习率如果 loss 不断下降但 mAP 不涨大概率是训练数据本身的问题比如标注框不齐、类别不均衡。PR_curve.png是最值得盯的图。它横轴是召回率漏检的反面纵轴是精确率误报的反面mAP50 就是曲线下的面积。曲线越靠近右上角模型越好。头盔检测的实际标准是 mAP50 至少 0.85 才能考虑上线低于 0.8 说明数据还有明显短板。confusion_matrix.png能直接告诉你类别之间的混淆情况。头盔检测常见的现象是 helmet 和 head 互相混淆——数据里很多戴头盔的照片露了后脑勺或者浅色头盔和偏暗肤色的头部在低分辨率下看起来接近。如果混淆矩阵里这两类的交叉值高于 0.1优先补数据而不是调模型。评估命令也要跑一遍拿到数值化的指标python val.py --weights runs/train/exp/weights/best.pt --data helmet.yaml输出里重点看mAP0.5和mAP0.5:0.95。前者是项目上线的及格线后者是模型综合能力的体现差距大说明框回归精度不够该检查标注质量。4.3 从断点继续训练与类别不均衡的处理训练到一半发现数据有问题或者想接着上一次训练继续跑不需要重新开始。训练命令加一个--resume即可python train.py --resume runs/train/exp/weights/last.pt断点续训会读取之前的状态包括学习率、进度和优化器参数直接接着最后一步的节奏跑不会从头来。类别不均衡是头盔检测数据集最容易踩的坑。路采数据里戴头盔的样本远多于不戴的模型会偏向 helmet 类导致 head 类漏检率高。有两个实际有用的处理方式一是在数据层面做重采样把 head 类的图片复制并做随机翻转、亮度调整后再混入训练集二是在 loss 层面调整类别权重对 head 类适当加大权重。先用重采样效果不够再动 loss两个手段同时上容易引入新问题。5. 头盔检测项目避坑5 个典型问题与排查记录这个项目方向做的人多踩坑记录也丰富。以下 5 个问题是我做头盔检测时遇到过的真实情况每个都按现象、原因、解决的顺序列出来方便对照排查。5.1 红色电动车挡泥板被识别成头盔现象推理画面里红色挡泥板、红色车筐甚至红色消防栓都被框成 helmet置信度还不低。原因训练数据里红色头盔占比偏高模型把“红色 圆形轮廓”当成了头盔的充分条件。目标检测模型本质在学特征统计数据里某个颜色占比过高它就会把这个颜色当成强特征。解决清洗训练集时统计颜色分布确认红色头盔样本占比不超过总样本的 30%再补充黄色、白色、黑色、蓝色头盔的照片让特征分布多样化。数据增强里适度调高 hue 扰动也能让模型减少对特定颜色的依赖。5.2 夜间和逆光场景漏检率高现象白天 mAP50 能到 0.9晚上一测漏检率直接翻倍hazy 夜色下连人带车一起漏掉。原因训练集里白天样本占绝大多数夜间样本光照低、对比度差模型没学过这种特征分布。头盔和头部的纹理在低照度下趋同分类边界变模糊。解决从路采视频里专门抽夜间和逆光时段的数据单独标注后并入训练集如果没有夜间数据先用图像增强手段把白天数据压暗、加噪声、调色温生成一批伪夜间样本做预训练。最后再用少量真实夜间数据微调。没有真实夜间数据光靠数据增强撑不住。5.3 训练 loss 不下降曲线在 300 轮左右开始反弹现象box_loss 前 50 轮缓慢下降然后进入平台期到 300 轮之后反而开始上升mAP 也随之下滑。原因这是典型的过拟合伴随学习率过大。数据量小几千张模型容量大训练后期模型开始在训练集上背答案验证集表现自然变差。解决先看训练集和验证集的 loss 差距如果训练 loss 持续下降、验证 loss 上升就是过拟合。措施按顺序来加数据增强、把lr0调低到 0.003、用更小的模型规格从 c 降到 t。最大 epoch 控制在 150 以内别盲目拉长训练。5.4 权重文件加载直接报错模型结构和参数不匹配现象导入best.pt推理时抛 KeyError 或者unexpected key in module state_dict有的还会提示模型类和权重不匹配。原因YOLOv9 官方仓库和 ultralytics 的权重格式不完全兼容训练时用的也是官方仓库脚本推理端换成了 ultralytics 接口或者反过来网络结构定义不一致导致参数名对不上。解决确认这个 zip 里训练和推理用的是同一套框架。用官方仓库训练就用官方detect.py推理如果要用 ultralytics 接口需要把权重先用转换脚本转成兼容格式。别混着用这是最常见也最容易犯的错。5.5 摄像头实时检测帧率不到 10 FPS现象模型在单张 640 图片上推理只要 30ms接上摄像头之后整体帧率掉到个位数画面卡顿严重。原因复现脚本把模型加载、推理和显示都放在同一个循环里每次循环都重新做了预处理如果加上了results.plot()绘制逻辑绘制耗时也会占掉大半帧间隔。解决模型和摄像头初始化放循环外面循环里只做推理和必要的绘制精简版。视频流处理分辨率先降到 1280×720 再喂给模型。如果还是卡考虑把推理尺寸降到 480加上帧间隔跳帧处理两帧抽一帧推理。帧率够不够最终指标是推理耗时和处理管线的重叠程度不只是模型本身的推理速度。6. 部署到真实路口TensorRT 加速与工程化习惯模型训练完、评估曲线也达标了离真正常跑起来还差最后一步部署。路口摄像头往往是边缘盒子算力有限直接把 PyTorch 模型丢上去跑即使 GPU 是车规级的也难扛住多路视频流并发。常见的做法是先把 PyTorch 权重导出为 ONNX再用 TensorRT 转成 engine 做推理加速。yolo export modelbest.pt formatonnx imgsz640 trtexec --onnxbest.onnx --saveEnginebest_fp16.engine --fp16第一行导出 ONNX第二行用 trtexec 工具转成 FP16 精度的 TensorRT engine。FP16 是推理加速最实用的一档精度损失在 1% 以内速度提升通常在 2 倍以上。如果你的部署环境 GPU 显存很紧可以再试 INT8 量化但需要标定数据集调起来费时间精度也需要重新验证。转完之后用 Python 加载 engine 做推理from ultralytics import YOLO engine YOLO(best_fp16.engine) results engine(frame, conf0.5, imgsz640)这个接口跟前面的推理脚本完全一致换一个权重文件路径而已。实际部署时还有两个工程习惯值得坚持一是先对视频流做跳帧帧间画面变化不大时直接复用上一帧检测结果能省下大量算力二是对 head 类目标做连续 N 帧确认逻辑单帧检测到未戴头盔不立即告警连续 3 帧都命中再触发误报率能压到极低。我做这类项目有个自己的习惯每次调整完数据和参数训练结束后第一件事不是看 loss而是看 confusion matrix 以及 PR 曲线尾部——尾部就是最难识别的那批样本提醒我数据集还有哪些边界情况没覆盖到。这个习惯帮我避开过好几次“模型看似达标、上线立刻露馅”的尴尬。这个方向值得做的事情很多跑通只是起点把误报率压到业务能接受的范围内才算真正做完。希望帮到你。本文还有配套的精品资源点击获取