☰
YOLOv9头盔检测实战:数据集标注、训练参数与重参数化部署全解析
2026/9/28 17:11:32 网站建设 项目流程

简介:面向智慧交通场景的电动车骑行人员头盔佩戴检测系统,基于YOLOv9目标检测框架,提供完整源码、训练好的模型权重及评估曲线。系统适用于毕业设计、课程设计及实际项目开发,支持自定义数据集训练与迁移学习,能够解决道路安全监控中头盔佩戴状态的自动识别问题,帮助用户在真实道路场景下高效部署。资源包共一百八十八个文件,涵盖八十余个Python脚本、三十个YAML配置、测试图片、编译文件以及三个模型权重文件等,总大小约六十九点三二MB,目录结构清晰,便于按模块查阅。压缩包内还附有详细运行教程、评估曲线和结果统计数据,可对照文档在主流Python环境中完成环境配置、数据准备、模型训练与检测推理全流程。目前已有238人学习下载,适合计算机、人工智能、电子信息等相关专业的学生或开发者作为毕业设计、课设或项目预研的完整参考方案。

1. 不只是跑通 YOLOv9:这份头盔检测资源先要看清三个前提

做道路电动车头盔佩戴检测,最花时间的从来不是模型训练本身,而是数据对不对、路径全不全、参数适不适合你的显卡。这套基于 YOLOv9 的 Python 检测源码,集成了官方训练流程、可运行的train_dual.py/detect_dual.py脚本、配套权重与评估曲线,拿到的第一反应不该是直接点运行,而是先确认三件事:预训练权重是哪个版本、自带的 yaml 数据集是不是头盔数据、你的机器显存能承受多大的 batch。搞清楚这三点,你才能真正把这份资源用成一套能复现的检测系统。适合做毕业设计快速起步,也适合刚接触目标检测的工程师当工程模板改。

2. 环境复现与目录结构:train_dual.py、detect_dual.py、yolov9-s.pt 各司其职

2.1 先把 Python 环境装到不会翻车的状态

这份项目是标准的 PyTorch 工程,依赖集中在requirements.txt里。我一般建议用 Anaconda 建独立环境,不要直接装到 base 环境里,否则后面装 torch 版本冲突时你会很被动。

conda create -n yolov9 python=3.9 -y conda activate yolov9 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

这里我额外解释两点。第一,Python 版本选 3.9 而不是 3.11。YOLOv9 官方代码对 Python 3.10 以上存在一些隐性的 torch 算子兼容问题,碰上了报错不好查;3.9 是官方测试最多的版本。第二,-i后面跟清华源,是因为默认 PyPI 源在国内慢且容易中断,这个不是你网速的问题,是源的问题,换源能少等十分钟以上。

装完后验证一下 torch 能不能调用 GPU:

python -c "import torch; print(torch.__version__, torch.cuda.is_available())"

如果输出True,说明 CUDA 可用;如果是False,说明你装的是 CPU 版 torch,后续--device 0会直接报错。这时候要重装:

pip uninstall torch torchvision -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

提示:NVIDIA 驱动版本建议在 450 以上,CUDA 版本装 11.8 或 12.1 都行。这一步别省,训练一半才发现显卡没调用上,那才是真的浪费时间。

2.2 理清目录里各文件的作用再动手

环境配好后,先别急着跑,花两分钟认清项目的几个关键文件到底干什么。很多新手翻车,就是因为把训练脚本和推理脚本的参数搞混了。

文件/目录作用关键参数
train_dual.py训练入口,支持从零训练和微调--weights、--data、--batch、--epochs
detect_dual.py推理入口,图片/视频/摄像头均可--weights、--source、--conf-thres
data/banana_ripe.yaml示例数据集配置train/val 路径、类别名称
models/detect/yolov9-c.yaml模型结构定义depth_multiple、width_multiple
hyp.scratch-high.yaml超参数配置lr、mosaic、hsv 增强参数
reparameterization.ipynb重参数化转换,部署前必跑加载训练权重并合并分支

这里最容易被忽略的是reparameterization.ipynb这个 Notebook。YOLOv9 训练时网络带有辅助分支,推理时如果不先做重参数化合并,速度会慢一截。后面我会专门讲这个文件怎么用,现在你只需要知道:训练完生成 best.pt 后,不要直接拿去部署,要先过一遍重参数化。

2.3 模型选型和权重文件的坑

项目默认用yolov9-s.pt,对应配置文件是models/detect/yolov9-c.yaml。这里有个容易迷惑的地方:yolov9-s.pt是官方在 COCO 上预训练好的权重,你下载后直接把它当作初始权重微调即可。但要注意,s是小模型,c 是全尺寸模型,e是超大模型——如果你显存只有 6G,用yolov9-e.pt连训练都会爆显存。

我的经验是:6G 显存用yolov9-s,batch 设 4;8G 显存用yolov9-s,batch 设 8;12G 以上再考虑yolov9-c。别一上来就追求高精度,先把流程跑通,再换大模型提升指标。

3. 从 banana_ripe.yaml 换成头盔数据:YOLO 标注格式与数据集配置的四个注意点

3.1 先看清自带数据集不是头盔

打开data/banana_ripe.yaml,你会看到:

train: data/images/train val: data/images/val names: 0: very-ripe 1: immature 2: mid-ripe

这是香蕉成熟度检测的数据集。如果你直接用这份配置训练头盔检测,模型只会学会分辨香蕉的成熟阶段,和头盔没有半点关系。下载资源里如果自带的 best.pt 是在这 3 个类别上训出来的,你拿它去测头盔图片,输出的一定是混乱的分类结果。拿到资源的第一步,是确认权重和数据集的类别定义是否匹配你的场景。头盔检测需要的是helmet和no_helmet两个类别,或者更细分为person、helmet、no_helmet,取决于你的需求。

3.2 YOLO 标注格式与 labelimg 使用

头盔数据集需要准备 YOLO 格式的标注文件。每个.jpg图片对应一个同名.txt文件,里面每一行代表一个目标:

class_id x_center y_center width height

注意四个坐标值都是归一化到 0~1 的浮点数,不是像素坐标。我用 labelimg 标注时,会先在PascalVOC模式下标矩形框,然后再批量转成 YOLO 格式。其实 labelimg 直接支持 YOLO 模式,保存时选择YOLO格式即可,坐标自动归一化,不需要手动算。

标注时的遗漏提醒:电动车骑行人员的头部,无论是否戴头盔,都要标注出来。如果只标注戴了头盔的,模型会把「没戴头盔」当作背景,测试时永远检测不到违规目标。正确做法是:

0 x_center y_center width height # 戴了头盔 1 x_center y_center width height # 没戴头盔

类别 0 是 helmet,类别 1 是 no_helmet。哪个在前面无所谓,但必须和 yaml 文件里的 names 顺序保持一致。

3.3 自建 yaml 文件的四个注意点

参考 banana_ripe.yaml,在data/下新建helmet.yaml:

train: D:/helmet_dataset/images/train val: D:/helmet_dataset/images/val names: 0: helmet 1: no_helmet

第一个注意点,train 和 val 路径写绝对路径。相对路径在你自己电脑上能跑,换一台机器就报 FileNotFoundError。第二个注意点,names里的类别顺序必须和标注文件里的 class_id 严格对应,如果你标注时 0 是 no_helmet 而这里 0 是 helmet,训练不会报错,但测试时输出的标签全错。第三个注意点,每个类别图片数量要在 300 张以上,且正负样本比例别过分失衡,我建议头盔和未戴头盔的样本比例控制在 1:1 到 1:1.5 之间。第四个注意点,val目录不能和train目录重叠,否则评估曲线会虚高,测出来的 mAP 不具备参考价值。

3.4 数据校验:训练前用脚本查一遍标注

自建数据集最容易翻车的是标注文件写错,坐标越界、类别 id 超范围都是重灾区。训练前先跑一段校验代码:

import os def check_labels(img_dir, label_dir, num_classes=2): for f in os.listdir(label_dir): if not f.endswith('.txt'): continue img_file = f.replace('.txt', '.jpg') if not os.path.exists(os.path.join(img_dir, img_file)): print(f'[WARN] {img_file} 缺少对应图片') with open(os.path.join(label_dir, f), 'r') as fp: for line in fp: parts = line.strip().split() if len(parts) != 5: print(f'[ERROR] {f} 某行不是5个字段: {line}') continue cls, cx, cy, w, h = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if cls >= num_classes: print(f'[ERROR] {f} 类别ID越界: {cls}') if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 < w <= 1 and 0 < h <= 1): print(f'[ERROR] {f} 坐标越界: {line}') check_labels('helmet_dataset/images/train', 'helmet_dataset/labels/train')

逻辑很简单:逐行校验每行 5 个字段、类别在合法范围内、归一化坐标不越界。出现任何[ERROR]都先修好再训练,不然后面 loss 异常你都不知道该查数据还是查模型。

4. 训练参数别照抄:epochs、batch-size、close-mosaic 背后的取舍逻辑

4.1 用命令行方式训练,而不是改脚本

项目提供了两种训练方式:直接运行train_dual.py或命令行传参。我强烈建议用命令行,因为参数语义更清晰,且不会因为改了脚本后忘记改回来而影响后续推理。

python train_dual.py \ --weights yolov9-s.pt \ --cfg models/detect/yolov9-c.yaml \ --data data/helmet.yaml \ --hyp hyp.scratch-high.yaml \ --epochs 100 \ --batch-size 8 \ --device 0 \ --img 640 \ --close-mosaic 15 \ --name helmet_run

这套参数的核心含义:

  • --weights yolov9-s.pt:加载 COCO 预训练权重做迁移学习。yoloes-s.pt不写的话默认从零训练,收敛速度会慢很多。
  • --epochs 100:头盔检测是二分类任务(helmet / no_helmet),100 epoch 在小数据集上足够;如果你的数据量和场景复杂(夜间、雨天、多角度),建议 200。
  • --batch-size 8:8G 显存跑yolov9-s的推荐值。如果你只有 6G,改成 4;有 12G,可以上 16。
  • --img 640:输入分辨率。头盔头部目标相对较小,不建议降到 416,但显存实在不够时可以降到 512。
  • --close-mosaic 15:最后 15 个 epoch 关闭 mosaic 数据增强。mosaic 会把 4 张图拼在一起,最后阶段关闭它能让模型在接近真实分布的图片上微调,AP 会涨一点。

训练开始后,注意观察两个信号。第一个是 loss 刚开始会急剧下降然后缓慢下降,这是正常的。第二个是如果 loss 在前 20 个 epoch 纹丝不动,基本可以断定数据有问题,赶紧 Ctrl+C 去查标注文件。不用等 100 个 epoch 跑完再骂自己数据集没配好,前 20 个 epoch 足够暴露问题。

4.2 断点续训与训练结果产物

训练中断是常态,别慌。YOLOv9 会每个 epoch 保存last.pt,默认放在runs/train/helmet_run/下。续训命令只需要把--weights改成runs/train/helmet_run/last.pt即可:

python train_dual.py \ --weights runs/train/helmet_run/last.pt \ --data data/helmet.yaml \ --epochs 100 \ --batch-size 8 \ --device 0 \ --close-mosaic 15

训练完成的runs/train/helmet_run/目录里,你会看到best.pt、last.pt、results.png和results.csv。其中best.pt是验证集上 mAP 最高的权重,推理就用它。results.csv用 Excel 直接打开,能看到每个 epoch 的 Precision、Recall、mAP50、mAP50-95 变化曲线。这里有个血泪经验:如果你看到val_mAP50很高但val_mAP50-95很低,说明模型对遮挡目标的框定位不稳,需要增加标注质量,而不是盲目加 epoch。

4.3 超参数别乱动的原因

不少新手喜欢把hyp.scratch-high.yaml里的 learning rate 调成 0.01 或 0.001,理由是「网上说这么调更好」。这个文件是 YOLOv9 官方在 COCO 上反复调过的,里面的lr0: 0.01、mosaic: 1.0、hsv_h: 0.015都对应特定的收敛行为,瞎改只能让模型更难收敛。真正值得你动的只有close-mosaic这一个参数。

5. 推理验证与实测避坑:三个确认信号,加上六个真实踩坑记录

5.1 推理脚本怎么改才能测出自己的头盔模型

训练完成后修改detect_dual.py或直接命令行推理:

python detect_dual.py \ --weights runs/train/helmet_run/best.pt \ --source test_imgs/ \ --conf-thres 0.25 \ --iou-thres 0.45 \ --imgsz 640

--source可以传图片文件夹、单张图片、视频文件路径,也能传0表示调用摄像头实时检测。测试图片我建议选 20 张以上、包含不同角度、不同光线、正负样本各半的图片。--conf-thres 0.25表示置信度低于 0.25 的检测框会被过滤,--iou-thres 0.45是 NMS 的 IoU 阈值。如果你的场景误检多,把conf提到 0.4;如果漏检多,降到 0.15。推理结果保存在runs/detect/下。

三个确认模型真的能用的信号:第一,测试集上的 mAP50 在 0.85 以上;第二,单张图上能稳定框出不戴头盔的骑行者,并且不会把行人框成 no_helmet;第三,对未见过的场景(比如雨天夜景)有一定泛化能力。如果第三条不满足,说明数据多样性不够,不是模型的锅。

5.2 避坑记录:十个问题九个出在数据和路径上

我把实操中遇到过的典型问题整理成六个踩坑记录,每条都是血泪换来的。

踩坑一:FileNotFoundError: No such file or directory: 'data/helmet.yaml'

现象:训练命令执行后立刻报错,说 yaml 文件不存在。原因:当前工作目录不在项目根目录,PyCharm 默认工作目录可能指向别处。解决:在 PyCharm 的 Run Configurations 里把 Working directory 改成项目根目录,或者直接cd到项目根目录再执行命令。

踩坑二:训练不报错,但 loss 完全不降

现象:loss 一直稳定在某个值附近,没有任何下降趋势。原因:数据路径配了 train 和 val 指向同一批图片,模型在「背答案」,验证集 loss 自然不会降。解决:确认val目录和train目录图片不重叠,且标注文件路径与图片路径一一对应。

踩坑三:训练正常但检测时一张图都没框出来

现象:detect_dual.py跑完,输出图片上没有检测框。原因:置信度阈值设得太高,或者输入图片分辨率过大导致小目标漏检。解决:先降到--conf-thres 0.05看是否有框;如果有,再逐步调到合理值。同时把imgsz调整到 640,过大的原始图片会压缩后丢失细节。

踩坑四:类别输出和预期完全不一致

现象:模型明明训练的是头盔,输出结果却是 banana/very-ripe。原因:--weights指到了官方预训练权重或原项目自带的 banana 模型,而不是你训练生成的best.pt。解决:检查--weights路径,确认是runs/train/helmet_run/best.pt。这个错误看起来很蠢,但相当多的人在高强度改参时会犯。

踩坑五:显存不足 CUDA out of memory

现象:训练跑了几十个 step 后直接报 OOM。原因:batch-size 太大,或者训练中期的 mosaic 拼接把图像尺寸放大了 4 倍显存占用。解决:先降--batch-size,如果还报,就把--close-mosaic从 15 改成 5,让 mosaic 提前关闭;再不行就降--img 512。

踩坑六:检测结果框的位置时准时不准

现象:同一个视频里,有些帧检测框贴得很准,有些帧偏了很多。原因:没有做重参数化,模型还带着辅助分支在推理,速度慢且不稳定。解决:跑reparameterization.ipynb把权重合并后再推理,见下一章。

6. 重参数化与落地:把训练权重收拢成推理模型的一个关键习惯

YOLOv9 的网络结构包含主干网络和辅助可逆分支。训练时这两个分支协同优化梯度的传播,但推理时辅助分支只是负担。官方方案是训练完成后做重参数化,把辅助分支合并到主干网络里,得到更精简的推理模型。这个操作在工程上不是可选项,如果省掉,一是推理速度会慢 20%~40%,二是模型行为在连续帧上容易出现轻微抖动。

项目根目录的reparameterization.ipynb就是干这件事的。打开 Notebook,逐格运行,核心逻辑是加载最好的权重文件,调用融合方法,导出一个合并后的模型:

import torch from models.yolo import Model # 加载训练时的模型结构 model = Model(cfg='models/detect/yolov9-c.yaml', ch=3, nc=2) # 训练好的权重里有额外的 aux 分支,推理时需要切换模式 ckpt = torch.load('runs/train/helmet_run/best.pt', map_location='cpu') model.load_state_dict(ckpt['model'].float().state_dict()) # 关键:融合辅助分支,这一步就是重参数化 model.eval() fake_input = torch.randn(1, 3, 640, 640) traced = torch.jit.trace(model, fake_input) torch.jit.save(traced, 'runs/train/helmet_run/best_traced.pt')

不是所有模型都能用torch.jit.trace直接搞定,如果报算子兼容错误,更通用的做法是直接保存融合后的权重:

# 进入检测模式会触发模块内部的 reparameterize model.eval() torch.save({'model': model}, 'runs/train/helmet_run/best_rep.pt')

融合完成后,用detect_dual.py指定新权重跑一遍同样的测试图片,对比前后两版推理的 fps 和检测框坐标输出,你会发现框更稳、延迟更低。从那以后,我每次训完模型都强制自己先做重参数化,再谈测试——不是多此一举,而是权重合并后才会暴露真正的泛化能力。希望这点经验能帮你在做头盔检测落地时少走一次弯路。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询