ARTICLE DETAIL

资讯详情

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

YOLOv9头盔检测源码包详解:训练、推理与落地实践

YOLOv9头盔检测源码包详解:训练、推理与落地实践 简介面向计算机相关专业学生、毕业设计者及目标检测初学者提供一套基于YOLOv9的道路电动车骑行人员头盔佩戴检测系统。压缩包共188个文件以83个Python脚本、30个YAML配置文件、3个训练好的PT权重模型为主体另含大量JPG/PNG测试图、评估曲线CSV、预测结果示例及文本说明整体约69.32MB。目前已有238人学习下载。内容涵盖Anaconda环境配置、YOLO格式数据集准备、自定义yaml修改、train_dual.py训练与detect_dual.py推理的完整流程针对头盔佩戴场景给出了训练参数和阈值调整建议并附有训练日志与评估曲线供分析模型性能。自带best.pt权重可直接运行检测适合毕业设计、课设作业及企业快速落地验证代码经测试运行通过可放心使用。1. 智慧交通场景下YOLOv9 头盔佩戴检测源码包到底值不值得用城市路口每天有大量电动车穿行靠人工盯监控抽查头盔往往撑不过半小时就开始漏看。这套基于 YOLOv9 的道路电动车骑行人员头盔佩戴检测系统用 python 源码把数据集准备、模型训练、效果评估、视频推理串成了一条能落地的链路。压缩包里除了源码还有训练好的模型和评估曲线这意味着你不需要从零开始训练也能先对图片或视频做一轮预测。适合正在做交管试点、园区安全、算法毕设或课程设计的从业者。我的建议是解压后别急着跑训练先看评估曲线和权重文件再决定是直接用还是重新微调。2. YOLOv9 做头盔检测的选型逻辑GELAN 骨干、PGI 训练策略与源码包结构2.1 为什么头盔检测场景优先选 YOLOv9小目标、遮挡和密集车流的对比头盔检测看着只是目标检测的一个小分支实际落地时并不轻松。路口监控画面通常是 1080p 甚至更高分辨率一辆电动车在画面里只占很小一块骑行者头部可能只有二三十个像素。多辆车同时经过时车身、手臂、雨棚又会挡住头盔轮廓头盔颜色和路面、车身颜色接近时模型很容易把背景特征学进去。YOLOv9 在结构上做了两个关键改动PGIProgrammable Gradient Information和 GELAN 骨干网络。PGI 的核心作用是让浅层和深层的梯度信息更一致避免反向传播时小目标的信息被大目标“吃掉”GELAN 则是一种跨层特征融合结构在算力受限的前提下尽量保留多尺度语义。对头盔这种小目标、多遮挡的场景这两点比单纯堆模型参数量更实用。不过选型时也要泼一盆冷水。YOLOv9 不是在所有硬件上都比 YOLOv8 划算。如果你要部署在边缘盒子或嵌入式设备上YOLOv8 的轻量模型推理负担更小如果你有一台带 NVIDIA GPU 的服务器做路口集中推理YOLOv9-c 或 YOLOv9-e 的精度优势才能体现出来。模型小目标表现训练成本推理速度适合场景YOLOv5s一般需要更高输入分辨率低快边缘盒子YOLOv8s较好低快边缘盒子、移动端YOLOv9-c好中中路口集中推理YOLOv9-e好高较慢离线批量分析、追求精度我一般会先看源码包里训练好的模型后缀如果是best.pt且对应 YOLOv9-c那大概率是兼顾精度和速度的版本如果对应 YOLOv9-e就要做好推理时显存占用偏高的心理准备。2.2 源码包里的常见目录结构先找到权重和评估曲线别急着跑训练这种压缩包虽然叫“python源码”但内容通常分成四块源码、运行教程、训练好的模型、评估曲线。解压后不要双击 train.py先用tree或文件管理器把目录过一遍。一个常见的头盔检测项目目录大致长得像下面这样helmet_yolov9/ ├── README.md # 详细运行教程环境、命令、常见报错 ├── requirements.txt # python 依赖清单 ├── config/ │ ├── dataset.yaml # 数据配置 │ └── yolov9-custom.yaml # 模型结构配置 ├── checkpoints/ │ ├── yolov9-c.pt # 预训练权重 │ └── best.pt # 训练好的头盔检测模型 ├── runs/ │ ├── train/ │ │ └── exp0/ │ │ ├── weights/best.pt # 训练过程中的最优权重 │ │ └── results.png # loss 和指标曲线 │ └── val/ │ └── PR_curve.png # 验证集 PR 曲线 ├── scripts/ │ ├── split_dataset.py │ └── video_infer.py ├── train.py └── detect.py这个结构不一定和你拿到的包完全一致但核心文件可以对照着找README 是运行教程best.pt是训练好的模型results.png和PR_curve.png是评估曲线。如果只有best.pt而没有PR_curve.png说明训练过程可能只保存了权重没跑完整验证这种情况先别急着上线自己跑一次验证脚本更稳妥。用命令检查目录更直接cd helmet_yolov9 tree -L 2重点确认三件事模型文件是否存在且大小合理requirements.txt 是否完整README 里写的运行命令是否与 train.py、detect.py 的文件名一致。很多新手在训练阶段翻车不是因为模型结构不好而是没看 README 就凭感觉敲命令。2.3 python 环境搭建conda 创建环境、numpy 和 torch 的安装顺序如果你还没装 python先安装 Python 3.9 或 3.10安装时勾选Add to PATH。我习惯用 conda 管理项目环境这样多个项目之间互不干扰。创建环境和安装依赖的命令如下conda create -n helmet python3.10 -y conda activate helmet cd helmet_yolov9 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple如果 requirements.txt 里没有 torch或者 torch 版本不匹配需要单独安装。头盔检测项目通常依赖 torch 和 torchvision先装这两个再装其他依赖能少踩很多 numpy 编译冲突的坑pip install torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple装完以后做一次环境自检确认模型能加载到正确的设备上python -c import torch; print(torch.cuda.is_available())输出True说明 GPU 可用输出False说明当前只能跑 CPU。CPU 不是不能用但 YOLOv9 训练会很慢如果只是用训练好的模型做视频推理CPU 也能勉强跑只是帧率不会太好看。这里有一个很容易被忽略的顺序问题先装 torch 再装 numpy。因为 torch 会携带自己的 numpy 依赖顺序反了可能把 numpy 覆盖成不兼容版本导致cv2或torch加载时报错。遇到类似undefined symbol或numpy.core.multiarray的问题基本都和依赖安装顺序有关。3. 准备头盔数据集并训练 YOLOv9类别设计、dataset.yaml 与训练命令3.1 头盔检测的类别设计helmet/head 比 helmet/no_helmet 更稳标题只写了“头盔佩戴检测”但真正动手时类别怎么定义直接决定系统能不能用。常见做法有两种。第一种是helmet和head两个类别。模型先把所有人的头部位置检出来同时检出头盔位置再用几何关系判断“每个头附近有没有头盔”。这样做的好处是模型不直接学“没戴头盔”这种偏抽象的语义而是学具体物体的外观误检率通常更低而且即使某人没戴头盔模型也能通过 head 框知道他在这里。第二种是helmet和no_helmet两个类别。模型直接分类“戴了”和“没戴”少了一步匹配逻辑但对训练数据的均衡性要求很高。如果监控画面里戴头盔的人占绝大多数no_helmet样本不足模型很容易把电动车座垫、后备箱、路边水桶当成没戴头盔的目标。我一般优先选helmet/head方案。对应到 YOLO 标签分类 ID可以定义0为 helmet1为 head。如果你拿到的源码包里已经训练好了模型先用这个约定去比对它的类别名称如果类别定义不一样推理脚本里的 ID 映射也要同步改。另一个容易忽略的点是标注框的粒度。头盔检测只需要框头盔和头不要把整个人框进去。有些人为了省事把整个骑行者框成“未戴头盔”结果模型学到的是电动车和行人的整体外观换一个路口就失效。框得越贴近目标模型越容易聚焦到真正的判别特征上。3.2 数据集标注和划分YOLO txt 格式与训练/验证目录头盔检测的数据集目录通常按 YOLO 格式组织图片和标签分开存放训练集与验证集严格隔离。datasets/helmet/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/每张图片对应一个同名 txt 文件例如IMG_0001.jpg对应IMG_0001.txt。txt 里的每一行表示一个目标格式是class_id x_center y_center width height所有坐标都要归一化到 0 到 1 之间。下面是一个示例第一行是头盔第二行是头0 0.4523 0.3211 0.0812 0.1245 1 0.5211 0.3102 0.0510 0.0678注意这里x_center y_center width height的顺序不能写错写成x_min y_min x_max y_max的话训练时 loss 会很大但模型永远不收敛。标注工具用常见的 LabelImg 或 X-AnyLabeling 都行关键是导出成 YOLO 格式。标注完以后用脚本统计训练集和验证集的类别数量避免验证集里全是头盔、训练集里全是头这种极端情况。from pathlib import Path for split in [train, val]: cnt {} for txt in Path(fdatasets/helmet/labels/{split}).glob(*.txt): for line in txt.read_text().strip().splitlines(): if not line: continue cls int(line.split()[0]) cnt[cls] cnt.get(cls, 0) 1 print(split, cnt)如果发现某个类别数量严重不足需要先补数据或者做数据增强而不是直接开始训练。头盔检测对数据质量很敏感宁可用 500 张干净图片也不要硬塞 5000 张标签混乱的图片。3.3 跑通训练命令YOLOv9 train.py 参数与迁移学习数据准备好以后先看 README 里有没有写好的训练命令。多数 YOLOv9 源码包会提供一个 train.py常用训练命令如下python train.py \ --batch 16 \ --epochs 120 \ --img 640 \ --device 0 \ --data config/dataset.yaml \ --cfg config/yolov9-custom.yaml \ --weights checkpoints/yolov9-c.pt \ --hyp hyp.scratch-high.yaml \ --project runs/train \ --name helmet_exp命令里的参数要按你的实际环境调整。--batch取决于显存6G 显存建议 811G 以上可以试 16 到 32。--img决定训练分辨率640 是通用选择如果你的监控画面尺寸大、头盔目标小把--img提到 960 往往比换模型结构更有效。--weights用来加载预训练权重YOLOv9-c 是这个项目的常用选择。使用预训练权重不是玄学而是让模型从已经学会的通用特征出发再适应头盔数据集收敛更快最终精度也更高。如果你的源码包里没有yolov9-c.pt也可以直接用训练好的头盔检测模型继续微调比如--weights runs/train/exp0/weights/best.pt。训练过程中要重点观察两个地方log 里的 loss 是否在下降以及每个 epoch 结束后的 mAP 是否波动上升。如果 loss 一直不降大概率是数据配置有问题比如 dataset.yaml 里的路径写错、标签类别超出配置类别数。停机检查不要盲目加训练轮数。3.4 训练后的评估曲线怎么看loss、Precision、Recall 和 mAP训练完成后runs 目录下会生成评估曲线最常用的是results.png和PR_curve.png。这些曲线不是给别人看的成果图而是判断模型是否值得部署的第一手依据。results.png通常包含多张子图训练 loss、验证 loss、Precision、Recall 和 mAP0.5。看的时候遵循几个原则loss 曲线持续下降且验证 loss 没有明显回升说明训练过程正常Precision 高而 Recall 低说明模型检出来的基本都是对的但漏掉不少目标Recall 高而 Precision 低说明模型把很多背景当成目标常见于训练数据不足或负样本不够。PR_curve.png是更直接的判断工具。横坐标是 Recall纵坐标是 Precision曲线越靠近右上角模型越好。头盔检测这类任务如果验证集 PR 曲线在 Recall 0.8 附近时 Precision 还能保持 0.9 以上这个模型大概率能上线。如果曲线掉得很快就说明模型对遮拦、小目标、夜间场景还没学好需要针对漏检的样本补数据。后面我会专门讲怎么用 PR 曲线选推理阈值这里先记住一个结论只看 mAP 不够还要看 PR 曲线的形状因为它决定你在“少漏报”和“少误报”之间怎么取舍。4. 用训练好的模型做 Python 推理视频帧检测、置信度阈值与告警判定4.1 先用官方 detect.py 验证模型再写自己的推理脚本拿到训练好的模型后不要直接写复杂的视频处理脚本先用源码包里的 detect.py 验证一遍推理链路。这个命令能快速排查模型路径、数据格式、设备选择这些基础问题。python detect.py \ --weights runs/train/helmet_exp/weights/best.pt \ --source data/test_video.mp4 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --imgsz 640 \ --device 0 \ --save-txt \ --save-conf--source可以指向图片、视频或摄像头编号。室内测试建议先用图片因为可以逐张看检测框是否有明显错位。--conf-thres是置信度阈值0.25 是常见默认值--iou-thres是 NMS 阈值控制重叠框的合并力度。--save-txt会把检测结果保存成 txt 文件--save-conf会在结果里附带置信度。如果这一步跑通说明模型权重和运行环境没问题。接下来再针对自己的摄像头场景写代码排查范围会小很多。曾经见过有人跳过这一步直接写视频处理代码绕了半天才发现是best.pt路径写错了。4.2 用 Python API 读取摄像头或视频帧最小推理代码源码包如果基于 ultralytics 封装最省事的方法是用YOLO类直接加载训练好的模型。下面是一个最小可用的视频推理脚本import cv2 from ultralytics import YOLO model YOLO(runs/train/helmet_exp/weights/best.pt) cap cv2.VideoCapture(data/test_video.mp4) while cap.isOpened(): ok, frame cap.read() if not ok: break results model(frame, conf0.25, iou0.45, imgsz640, verboseFalse)[0] boxes results.boxes for i in range(len(boxes)): cls_id int(boxes.cls[i].item()) conf float(boxes.conf[i].item()) x1, y1, x2, y2 map(int, boxes.xyxy[i].tolist()) label f{model.names[cls_id]} {conf:.2f} cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(helmet, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码的核心逻辑是逐帧读取视频把每一帧交给模型推理再从boxes里取出类别、置信度和坐标框。conf和iou是推理参数conf越低越容易检出小目标但误报也会增多iou主要影响多人重叠时的去重效果。如果源码包里没有 ultralytics 这个依赖而是官方 YOLOv9 的实现那就继续用 4.1 里的 detect.py 命令不要强行改官方源码。两种方案的检测结果是一样的区别只在调用方式。4.3 判断谁没戴头盔helmet/head 的 IoU 匹配与 ROI 限定光画框还不够业务上要输出“谁没戴头盔”。如果模型类别是helmet/head需要把 head 框和 helmet 框做匹配。判断原则是一个 head 框附近没有对应的 helmet 框就判定为未佩戴。下面是一个不依赖复杂框架的匹配逻辑import numpy as np HELMET_CLS 0 HEAD_CLS 1 MIN_IOU 0.05 def compute_iou(box_a, box_b): x1, y1, x2, y2 box_a x3, y3, x4, y4 box_b ix1, iy1 max(x1, x3), max(y1, y3) ix2, iy2 min(x2, x4), min(y2, y4) iw, ih max(0, ix2 - ix1), max(0, iy2 - iy1) inter iw * ih area_a (x2 - x1) * (y2 - y1) area_b (x4 - x3) * (y4 - y3) return inter / (area_a area_b - inter) def find_unhelmeted(results, roiNone): boxes results.boxes cls boxes.cls.cpu().numpy().astype(int) xyxy boxes.xyxy.cpu().numpy() head_idx [i for i, c in enumerate(cls) if c HEAD_CLS] helmet_idx [i for i, c in enumerate(cls) if c HELMET_CLS] unhelmeted [] for hi in head_idx: head_box xyxy[hi] if roi is not None: cx (head_box[0] head_box[2]) / 2 cy (head_box[1] head_box[3]) / 2 if not (roi[0] cx roi[2] and roi[1] cy roi[3]): continue matched False for hj in helmet_idx: hel_box xyxy[hj] if compute_iou(head_box, hel_box) MIN_IOU: matched True break if not matched: unhelmeted.append(head_box) return unhelmetedMIN_IOU设成 0.05 并不是写错而是头盔和头部的检测框面积都很小实际重叠比例可能不高。头盔通常只盖住头顶一部分和 head 框的 IoU 达不到普通目标匹配的 0.5 标准。如果你的数据集里头盔框就是完整包住头部可以把这个值调到 0.3 左右。roi参数很有用。路侧摄像头画面里行人、自行车、路牌都可能被检测成 head但这不是我们需要关心的对象。用 ROI 限定检测中心点能直接砍掉画面边缘和马路牙子上的无效目标减少告警刷屏。5. 头盔佩戴检测落地避坑数据集、训练和推理阶段的 5 个常见问题5.1 头盔目标太小导致漏检先看输入分辨率再谈算法现象模型在测试图片上表现还行一旦处理完整的路口监控画面远处电动车上的骑行者基本不输出头盔框或者只输出 head 框。原因很多训练和推理默认使用 640 分辨率。监控原图是 1080p远处目标缩小后可能只有 20 像素缩放到 640 后特征几乎消失。这不是模型网络不够深而是输入分辨率把信息丢掉了。解决先把推理分辨率提到 960 或 1280 试一次命令里加--imgsz 1280或者用model(frame, imgsz1280)。如果显存不够把原图按区域切块推理比如把一帧切成四块 640x640检测后再把坐标映射回原图。这个操作比换模型更直接也是处理监控大图最常用的手段。5.2 夜间、逆光和反光下误检率高数据增强与预处理现象白天检测基本正常到了晚上电动车挡风玻璃反光、路灯下的白色斑块、黑色头盔都会让模型输出不稳定。有时漏检黑色头盔有时把塑料反光条当成头盔。原因训练集中白天样本占绝大多数模型实际上记住了头盔的高光和轮廓组合。夜间环境下光照分布完全不同模型学到的特征开始失效。解决训练阶段在 hyp 配置里调高hsv_h、hsv_s和hsv_v的增强幅度同时补充夜间监控截图作为训练样本。推理阶段可以对每一帧做 CLAHE 自适应直方图均衡提高暗部细节。注意这类问题不能靠单纯调低置信度解决因为低置信度会把误检和漏检同时放大。5.3 loss 降不下去、mAP 却很低数据集标签一致性排查现象训练 loss 下降很慢训练结束后 mAP0.5 也不理想PR 曲线没有明显拐点。原因最常见的不是模型问题而是标签一致性出了问题。比如有人把工地安全帽标成 helmet有人把带了但没戴好的头盔也标成 helmet还有的标签文件类别 ID 写反了helmet 和 head 互换。解决先做一次标签体检。统计每个类别数量再用 OpenCV 把标注框画到原图上随机抽 100 张检查。重点看三类错误框是否包住目标、类别是否混淆、归一化坐标是否越界。这个环节偷懒后面所有微调都是白费。5.4 只检测到驾驶员、漏掉后座乘客采样策略调整现象同一次检测中前排电动车驾驶员能被识别后座乘客的头盔或头部完全没有检测框。乘客坐在后座头部与驾驶员头盔距离很近甚至存在遮挡。原因训练集里后座样本太少模型没有见过这种重叠布局而且 NMS 在合并重叠框时可能把置信度较低的后座目标直接压掉。解决从监控视频中抽帧专门挑选双人骑行的画面补充训练集。推理时把 NMS 的iou-thres从 0.45 调高到 0.6让重叠框更容易被保留代价是同一个目标可能出现重复框。后期再用按类别过滤和置信度排序把重复框清掉。5.5 显存不足或推理速度不达标半精度、batch 与图像切块的取舍现象用训练好的模型处理视频时GPU 显存突然跑满或者一个路口画面要 2 秒才出一帧和“实时”差得很远。原因推理时默认使用 FP32 精度输入分辨率又设置得过高再加上代码里每一帧都创建新的 tensor 而没有释放内存显存很容易被打满。解决推理时开启半精度用model(frame, halfTrue)显存和速度会明显改善固定输入尺寸不要一帧 640、一帧 1280 地切换多路视频接入时优先用 ROI 把需要检测的路口区域裁出来再送入模型而不是对整个 4K 画面做检测。头盔检测的重点是头和头盔不是整条马路。6. 用评估曲线选阈值让检测结果直接变成告警事件训练好的模型里通常带着PR_curve.png它不只是用来展示成绩更是帮你决定推理阈值的依据。默认的conf0.25不一定适合你的路口如果阈值太高漏检变多阈值太低误报刷屏。一个可行的方法是用验证集导出置信度和真实标签然后遍历阈值找 F1 最高点。import numpy as np confs np.load(val_confs.npy) # 所有候选框的置信度 labels np.load(val_labels.npy) # 0 表示负样本1 表示正样本 best_thr 1.0 best_f1 0.0 for thr in np.arange(0.05, 0.95, 0.05): preds confs thr tp ((preds 1) (labels 1)).sum() fp ((preds 1) (labels 0)).sum() fn ((preds 0) (labels 1)).sum() precision tp / max(tp fp, 1) recall tp / max(tp fn, 1) f1 2 * precision * recall / max(precision recall, 1e-6) if f1 best_f1: best_f1 f1 best_thr thr print(fbest conf: {best_thr:.2f}, F1: {best_f1:.3f})如果源码包里没有val_confs.npy跑一次官方 val.py 就能导出。交通场景里如果你更介意漏报可以把阈值再往下调 0.05 到 0.1牺牲一点误报率来换召回率。告警部分也要做去抖不建议每帧都写一条日志。常见做法是连续 5 帧在同一个 ROI 区域都检测到未戴头盔才产生一次告警事件否则一个快速路过的行人就能让后台告警刷屏。有一回我把置信度直接按默认值上线结果白色塑料桶和路灯杆被模型当成头盔后台告警不停。后来养成一个习惯先看 PR 曲线定阈值再看误报样本做过滤最后才敢接入告警。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表