ARTICLE DETAIL

资讯详情

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

YOLOv9安全帽反光背心检测:2000张标注图训练与部署指南

YOLOv9安全帽反光背心检测:2000张标注图训练与部署指南 简介本资源面向计算机视觉初学者与安全防护场景的算法开发者提供安全帽与安全服反光背心目标检测的YOLOv9标注数据集可用于训练与验证工地、厂区等场景下的穿戴合规检测模型。压缩包共2000个文件约193.07MB其中1955个txt为YOLO格式标注文件44个jpg为对应样本图像另有1个yaml配置文件用于声明类别与数据路径整体已完成自动定向与1280x720黑边缩放预处理开箱即可接入训练流程。目前已有855人学习下载说明该数据集在安全穿戴检测方向具备一定参考价值。读者可借助现成标注快速复现YOLOv9训练、调整类别配置并验证检测效果省去从零采集与标注的时间成本适合课程设计、毕业项目或工业安全监测原型开发使用。1. 安全帽与反光背心检测2000 张标注图怎么喂给 YOLOv9 才不白标工地出入口的摄像头每天抓拍几万张图安全员盯屏幕盯到眼花漏掉一个没戴安全帽的工人可能就是一条人命。安全帽和安全服反光背心检测本质上是把「人有没有穿戴合规防护装备」这件事交给目标检测模型去判断。YOLOv9 是这套方案里目前比较顺手的选择2000 多张已标注图是起点不是终点。这篇文章面向的是手里已经有一批标注数据、想跑通训练和推理的工程师也面向正准备标数据、想知道怎么标才不返工的人。我会把数据组织、配置修改、训练命令、推理验证和踩坑记录按顺序讲清楚你照着做能复现遇到问题能定位。2. YOLOv9 检测安全帽与反光背心的数据准备2000 张图怎么组织才不返工2.1 先想清楚类别定义再动手标2000 多张图被标记听起来不少但如果类别定义混乱这批数据基本报废。安全帽和反光背心检测最常见的类别划分有两种一种是按穿戴状态分比如helmet、no_helmet、vest、no_vest另一种是按人和装备分比如person、helmet、vest。我一般推荐第一种因为模型直接输出「有没有戴」比先检测人再判断装备状态要省事后处理逻辑也简单。但这里有个坑如果一张图里同一个人既没戴安全帽也没穿反光背心你是标一个no_helmet加一个no_vest还是标一个no_helmet_no_vest我的血泪经验是尽量拆成独立类别不要合并。合并类别会让模型学到「没戴帽子的人通常也没穿背心」这种伪相关换一个工地场景就翻车。类别数控制在 4 到 6 个之间比较稳太少区分度不够太多小样本类别训不动。标注格式方面YOLOv9 沿用 YOLO 系列的 txt 格式每行class_id x_center y_center width height坐标归一化到 0 到 1。如果你手里是 VOC 的 xml 或者 COCO 的 json需要转一道。转换脚本网上一搜就有但要注意两个边界一是图片宽高要从原图读不能从 xml 里的 size 字段读因为有些标注工具会写错二是归一化后坐标要 clip 到 0 到 1否则训练时会出现越界框。2.2 目录结构和 data.yaml 的写法YOLOv9 官方仓库对目录结构有约定我一般按下面这样组织dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml是训练入口内容如下path: /home/user/dataset train: images/train val: images/val test: images/test nc: 4 names: 0: helmet 1: no_helmet 2: vest 3: no_vest这里path写绝对路径最稳写相对路径时容易因为启动目录不同而找不到文件。nc是类别数必须和 names 的长度一致否则训练启动时会报索引越界。names的顺序要和标注时 class_id 的映射一致这个映射一旦定了就不要改改了就得重新标。2000 张图按 7:2:1 切分训练集 1400 张验证集 400 张测试集 200 张。如果某些类别样本特别少比如no_vest只有 100 张那验证集里可能只有十几张指标波动会很大。这时候可以考虑分层采样保证每个类别在验证集里至少有 30 个实例。常见做法是写个脚本统计每个类别的实例数再按比例分配不要直接随机切。2.3 数据增强的取舍别把反光背心增没了YOLOv9 默认开启 mosaic、mixup、随机翻转等增强。对安全帽检测来说mosaic 和翻转一般没问题但 mixup 要小心。mixup 会把两张图按透明度叠加反光背心的荧光色被叠之后可能变得不像背心模型学到的特征会偏。我一般把 mixup 关掉mosaic 保留但把概率从 1.0 降到 0.5 左右。另外HSV 增强里的饱和度调整幅度不要太大。反光背心的核心特征是高饱和度的荧光黄或荧光橙饱和度一降和普通衣服就分不开了。我通常把hsv_s设在 0.5 以内hsv_v设在 0.3 以内。这些参数在data/hyps/hyp.scratch-high.yaml里改或者训练时用命令行覆盖。提示数据增强的目的是模拟真实场景的变化不是制造不存在的样本。反光背心的颜色是判别性特征增强时要保护这个特征。3. YOLOv9 训练配置与启动从 yolov9-c.yaml 到第一个 checkpoint3.1 模型选型c 还是 es 还是 mYOLOv9 官方提供了多个规模的模型常见的是yolov9-c、yolov9-e以及轻量级的yolov9-s、yolov9-m。安全帽检测这个任务2000 张图不算多我建议从yolov9-s或yolov9-c起步。yolov9-e参数量大小数据集上容易过拟合除非你有上万张图否则没必要。如果你要在边缘设备上跑比如 Jetson 或者 RK3588那yolov9-s更合适推理速度快精度损失在可接受范围内。我实测过yolov9-s在 640 输入下Jetson Orin Nano 能跑到 30 FPS 以上够用。如果只是服务器端跑yolov9-c精度更好训练时间也多不了太多。模型配置文件在models/detect/下面yolov9-s.yaml和yolov9-c.yaml都在。不要直接改官方文件复制一份到自己的目录再改方便回溯。改的地方主要是nc也就是类别数要和data.yaml里的nc一致。YOLOv9 的 head 部分会根据nc自动调整输出通道但有些版本需要手动改训练启动时报维度不匹配就是这个问题。3.2 训练命令与关键参数启动训练的命令如下python train_dual.py \ --workers 8 \ --device 0 \ --batch 16 \ --data data.yaml \ --img 640 \ --cfg models/detect/yolov9-s.yaml \ --weights \ --name helmet_vest_v1 \ --epochs 100 \ --patience 20 \ --lr0 0.01 \ --lrf 0.01 \ --cos-lr \ --close-mosaic 10逐项说明--workers是数据加载线程数设成 CPU 核心数的 2 倍左右太多会抢内存。--batch是批大小16 是 8GB 显存下的安全值显存够可以加到 32。--img是输入分辨率640 是默认值如果小目标多比如远处的人可以加到 960但显存和训练时间都会涨。--weights 表示从零训练如果要做迁移学习可以填预训练权重路径。--patience 20是早停耐心值验证指标 20 轮不提升就停省时间。--close-mosaic 10表示最后 10 轮关闭 mosaic让模型在真实分布上收一收这个技巧对精度提升有帮助。--lr0是初始学习率0.01 是 SGD 的常用值如果用 Adam 可以降到 0.001。--lrf是最终学习率比例--cos-lr开启余弦退火。这套组合在 2000 张图的数据集上比较稳不会震荡。训练过程中要看几个指标box_loss、cls_loss、dfl_loss是否稳定下降mAP0.5是否上升。如果cls_loss不降可能是类别不平衡或者标注有问题。如果mAP震荡厉害把学习率降一半试试。3.3 训练日志与 checkpoint 管理YOLOv9 默认把结果存到runs/train/下面每次训练一个子目录里面有weights/、results.csv、confusion_matrix.png等。weights/best.pt是验证集上最好的模型weights/last.pt是最后一轮的。我一般用best.pt做推理但如果发现best.pt是早期轮次后期过拟合了那就得看results.csv里的曲线手动选一个合适的 checkpoint。results.csv里记录了每轮的损失和指标可以用 pandas 读出来画图。重点看metrics/mAP_0.5和metrics/mAP_0.5:0.95前者是 IoU 0.5 的 mAP后者是 0.5 到 0.95 的平均。安全帽检测里mAP0.5到 0.85 以上算可用0.9 以上算不错。如果只有 0.6 左右大概率是数据问题不是模型问题。注意不要只看best.pt的最终指标要结合混淆矩阵看。如果no_helmet被大量误判成helmet那是漏检工地场景里漏检比误检危险得多。4. 推理与部署把 best.pt 跑成能用的检测服务4.1 单图推理与批量推理训练完之后先用单图推理验证效果python detect.py \ --weights runs/train/helmet_vest_v1/weights/best.pt \ --source test_images/ \ --img 640 \ --conf 0.4 \ --iou 0.5 \ --device 0 \ --save-txt \ --save-conf--conf 0.4是置信度阈值低于这个值的框不输出。安全帽检测里我一般设 0.4 到 0.5太低会有一堆误检太高会漏掉远处的小目标。--iou 0.5是 NMS 的 IoU 阈值重叠度高于这个的框会被合并。--save-txt会把检测结果存成 txt方便后续分析。批量推理时--source指向图片目录YOLOv9 会遍历所有图片。如果图片多建议开--nosave只存 txt不存可视化图省磁盘。推理结果里的 txt 格式和标注格式一样可以直接拿来和真值对比算误检率和漏检率。4.2 用 Python 脚本做视频流推理实际工地场景是视频流不是单张图。下面是一个用 OpenCV 读摄像头、YOLOv9 推理的脚本import cv2 import torch from models.common import DetectMultiBackend from utils.augmentations import letterbox from utils.general import non_max_suppression, scale_boxes import numpy as np # 加载模型 device torch.device(cuda:0 if torch.cuda.is_available() else cpu) model DetectMultiBackend(runs/train/helmet_vest_v1/weights/best.pt, devicedevice) model.eval() # 打开摄像头 cap cv2.VideoCapture(0) names model.names while cap.isOpened(): ret, frame cap.read() if not ret: break # 预处理letterbox 保持比例填充到 640x640 img letterbox(frame, 640, stride32, autoTrue)[0] img img.transpose((2, 0, 1))[::-1] # HWC - CHW, BGR - RGB img np.ascontiguousarray(img) img torch.from_numpy(img).to(device).float() / 255.0 img img.unsqueeze(0) # 推理 pred model(img) pred non_max_suppression(pred, conf_thres0.4, iou_thres0.5) # 后处理把框画回原图 for det in pred: if len(det): det[:, :4] scale_boxes(img.shape[2:], det[:, :4], frame.shape).round() for *xyxy, conf, cls in det: label f{names[int(cls)]} {conf:.2f} cv2.rectangle(frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0, 255, 0), 2) cv2.putText(frame, label, (int(xyxy[0]), int(xyxy[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow(Helmet Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码的关键点是letterbox预处理和scale_boxes后处理。letterbox把原图等比缩放到 640短边补灰边保证不拉伸。scale_boxes把检测框从 640 尺度映射回原图尺度否则框的位置会偏。non_max_suppression做 NMS去掉重叠框。conf_thres和iou_thres和命令行推理保持一致。如果要做告警可以在检测到no_helmet或no_vest时触发一个信号比如存图、发消息、亮灯。我一般会加一个计数器连续 N 帧检测到违规才告警避免单帧误检导致误报。4.3 模型导出与边缘部署如果要在边缘设备上跑需要把 PyTorch 模型导出成 ONNX 或 TensorRT。YOLOv9 官方提供了export.pypython export.py \ --weights runs/train/helmet_vest_v1/weights/best.pt \ --include onnx \ --img 640 \ --batch 1 \ --device 0 \ --simplify--include onnx导出 ONNX--simplify会调用 onnx-simplifier 简化计算图。导出后可以用 onnxruntime 推理也可以用 TensorRT 进一步加速。TensorRT 导出需要额外装tensorrt和pycuda导出命令把--include改成engine即可。边缘部署时要注意输入尺寸和预处理必须和训练时一致。训练用 640推理也用 640不要中途改。如果边缘设备算力不够可以降到 416 或 320但精度会掉需要重新评估。提示导出 ONNX 后用onnxruntime跑一遍验证输出和 PyTorch 一致再上边缘设备。不一致的话大概率是预处理或后处理对不上。5. 避坑与排查2000 张标注图训练时最容易翻车的 5 个地方5.1 现象训练 loss 不降mAP 一直在 0.1 左右原因标注文件的 class_id 和 data.yaml 里的 names 顺序对不上。比如标注时helmet是 0no_helmet是 1但 data.yaml 里写反了。模型学到的映射是错的loss 自然不降。解决写个脚本统计标注文件里出现的 class_id和 data.yaml 的 names 逐一核对。确认无误后再训练。这个坑我踩过两次每次都是因为中途改了类别顺序没同步。5.2 现象验证集 mAP 很高但实际推理一堆误检原因训练集和验证集来自同一批图片分布太像模型过拟合了。2000 张图如果都是同一个工地、同一个时间段拍的验证集不能反映真实场景。解决验证集要尽量包含不同光照、不同角度、不同工地的图片。如果数据来源单一至少按时间切分用后拍的图做验证。另外推理时的置信度阈值可以调高一点0.5 到 0.6压一压误检。5.3 现象no_helmet 类别召回率特别低漏检严重原因no_helmet 的样本太少或者标注时把「没戴帽子」的人头漏标了。安全帽检测里没戴帽子的人头往往比较小标注时容易忽略。解决统计每个类别的实例数如果 no_helmet 少于 500 个考虑补充数据或者用过采样。另外检查标注时有没有把「人」和「没戴帽子的人头」混淆。我一般会单独看一遍 no_helmet 的标注框确认框的是人头而不是整个人。5.4 现象反光背心在夜间或逆光下检测不到原因训练数据里夜间和逆光样本太少模型没学过这些场景。反光背心在夜间车灯照射下会过曝颜色特征和白天完全不同。解决补充夜间和逆光数据或者在数据增强里加亮度、对比度的随机调整。但要注意增强不能替代真实数据如果夜间场景很重要必须实拍。另外可以考虑用红外摄像头但那是另一套方案了。5.5 现象训练到一半显存爆了报 CUDA out of memory原因batch 太大或者 mosaic 增强时拼了太多图显存峰值超了。YOLOv9 的 mosaic 会把 4 张图拼成一张显存占用是单图的 4 倍。解决把 batch 降到 8 或 4或者把--img从 640 降到 512。如果还爆检查--workers是不是太高数据加载线程也会占显存。我一般用nvidia-smi盯着显存训练启动后前 100 轮显存波动最大稳定后就没事了。6. 把 2000 张图的检测精度再往上推一档三个我常用的技巧第一个技巧是难例挖掘。训练完第一版模型后用best.pt在训练集上跑一遍推理把置信度低或者误检的图挑出来人工重新标一遍再加到训练集里。2000 张图里通常有 100 到 200 张是难例补进去之后 mAP 能涨 2 到 3 个点。这个操作我一般做两轮第三轮收益就很小了。第二个技巧是测试时增强也就是 TTA。推理时把图片水平翻转、多尺度缩放分别推理再合并结果。YOLOv9 的detect.py里没有直接开 TTA 的参数但可以自己写脚本实现。TTA 能把 mAP 再推 1 到 2 个点代价是推理时间翻倍。如果对实时性要求不高比如只做事后审核TTA 值得开。第三个技巧是类别平衡采样。如果no_helmet和no_vest的实例数远少于helmet和vest可以在数据加载时给少数类更高的采样权重。YOLOv9 的 dataloader 里没有直接支持但可以自己写一个 sampler或者在生成训练列表时把少数类的图片重复几遍。注意不要重复太多否则过拟合。下面是一个简单的难例挖掘脚本框架import os import cv2 import torch from models.common import DetectMultiBackend from utils.augmentations import letterbox from utils.general import non_max_suppression, scale_boxes import numpy as np model DetectMultiBackend(runs/train/helmet_vest_v1/weights/best.pt, devicecuda:0) model.eval() hard_examples [] img_dir dataset/images/train label_dir dataset/labels/train for img_name in os.listdir(img_dir): img_path os.path.join(img_dir, img_name) frame cv2.imread(img_path) img letterbox(frame, 640, stride32, autoTrue)[0] img img.transpose((2, 0, 1))[::-1] img np.ascontiguousarray(img) img torch.from_numpy(img).to(cuda:0).float() / 255.0 img img.unsqueeze(0) pred model(img) pred non_max_suppression(pred, conf_thres0.25, iou_thres0.5) # 读取真值 label_path os.path.join(label_dir, img_name.replace(.jpg, .txt)) if not os.path.exists(label_path): continue with open(label_path) as f: gt_lines f.readlines() # 如果预测数量少于真值数量或者置信度普遍偏低标记为难例 if len(pred[0]) len(gt_lines): hard_examples.append(img_name) elif len(pred[0]) 0 and pred[0][:, 4].mean() 0.5: hard_examples.append(img_name) print(f难例数量: {len(hard_examples)}) with open(hard_examples.txt, w) as f: f.write(\n.join(hard_examples))这个脚本的逻辑是用低置信度阈值推理如果预测框数量少于真值框数量说明有漏检如果预测框平均置信度低于 0.5说明模型不确定。这两种情况都算难例。把难例挑出来重新标注再训练一轮精度会有明显提升。参数方面conf_thres0.25是故意设低的目的是让模型多输出一些框方便和真值对比。实际部署时用 0.4 到 0.5。iou_thres0.5保持不变。难例文件存成 txt后续可以写脚本把这些图复制到单独目录人工复核。我自己的习惯是每训练完一版模型先看混淆矩阵再看难例最后决定是补数据还是调参。2000 张图不算多但把难例挖透效果不比 5000 张随机图差。这个方向值得做因为工地安全检测的容错率很低漏检的代价比误检大得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表