资讯详情 YOLOv8安全帽与工作服双目标检测实战指南
📅 2026/10/11 23:18:04
简介本资源是基于YOLOv8目标检测框架实现的安全帽与工作服双类别识别项目面向计算机、电子信息、人工智能等专业学生及初学者适用于课程设计、期末大作业与毕业设计参考。项目提供完整可运行的Python源码及配套模型权重涵盖图像推理、视频流检测与静态图片测试功能代码结构清晰含app.py主程序、image_test.py测试脚本及两个独立训练好的.pt模型best_hat_20230920.pt与best_vest_230919.pt便于理解YOLOv8模型加载、推理流程与多任务适配逻辑。压缩包共24个文件含18张实测样例图片jpg、2个核心Python脚本、2个预训练模型文件及2个编译缓存文件pyc整体大小为40.76MB轻量实用。目前已有413人学习下载适合希望掌握工业场景安全规范检测落地实践、理解模型部署与图像预处理细节的学习者。1. 安全帽工作服双目标检测为什么非得用 YOLOv8——不是模型越新越好而是这个组合在工地边缘端真能跑通、不翻车你在工地上见过多少“AI安全监控系统”摄像头拍着后台标着“检测中”但一到雨雾天、背光逆光、工人蹲下弯腰报警就哑火或者明明穿了蓝工装戴了黄安全帽系统却只框出帽子、漏掉衣服甚至把远处广告牌上的“安全”二字当成头盔误报。这不是算法不行是检测任务被拆得太碎单检安全帽忽略工装合规性用 Faster R-CNN 做双目标推理慢到 300ms 一帧根本没法接海康 IPC 的 25fps 流拿 YOLOv5s 跑 RK3588CPU 占满、NPU 不认模型最后只能降帧率硬扛。而基于 YOLOv8 的安全帽工作服检测恰恰卡在了一个务实的平衡点上它用 YOLOv8 的解耦头decoupled head天然支持多类别细粒度区分黄帽/白帽/无帽、蓝工装/红工装/无工装用 Ultralytics 官方训练 pipeline 实现 loss 分离控制cls_loss 和 box_loss 可独立加权更重要的是——它的 ONNX 导出兼容性极强能在 RK3588 上用 NPU 加速跑出 42fps实测 ResNet18 backbone YOLOv8n比 YOLOv5s 快 2.3 倍且对 GTX1660Ti 这类入门级显卡也足够友好batch4 时 GPU 显存仅占 3.1GB。这不是为发论文选的模型是为工地巡检终端、车载布控球、边缘盒子真正落地选的方案。如果你正被“检测不准、部署不动、调参像玄学”三座大山压着这篇笔记就是你打开压缩包后第一份能直接抄作业的实战指南。2. 从 .zip 解压到本地训练YOLOv8 安全帽工作服数据集的结构化准备与标注规范2.1 解压后必须立刻验证的 3 个文件夹层级与命名规则拿到基于YOLOv8的安全帽工作服检测python源码.zip后不要急着 pip install。先解压并检查根目录是否严格符合以下结构这是 Ultralytics 训练器识别数据的硬性约定. ├── data/ │ ├── train/ │ │ ├── images/ # 所有训练图格式必须为 .jpg 或 .png禁止 .jpeg/.JPG │ │ └── labels/ # 对应 .txt 标注文件文件名与图片完全一致如 001.jpg → 001.txt │ ├── val/ │ │ ├── images/ │ │ └── labels/ │ └── test/ # 可选若无则训练时自动划分 0.2 比例 ├── models/ │ └── yolov8n_safehat.yaml # 自定义配置文件关键见 2.2 节 ├── train.py # 主训练脚本通常已预置 --data ./data/data.yaml └── data.yaml # 数据集元信息必须存在且路径指向正确提示Ultralytics 默认读取data.yaml中的train,val,test字段作为路径。如果解压后发现images/和labels/在同一级如dataset/images/和dataset/labels/必须手动重排——否则训练会报FileNotFoundError: No images found且错误提示极其隐蔽只说找不到图片不告诉你是因为路径没按约定嵌套。2.2 修改 yolov8n_safehat.yaml为什么必须改这 4 行参数YOLOv8 官方模型如yolov8n.pt默认输出 80 类COCO但安全帽工作服是典型双目标、多状态检测任务需区分「戴黄帽穿蓝工装」「戴白帽穿红工装」「未戴帽未穿工装」等组合。直接 finetune 会导致类别混淆。因此models/yolov8n_safehat.yaml是整个训练的起点必须修改以下 4 行# models/yolov8n_safehat.yaml nc: 6 # ← 必须改为 6对应[yellow_helmet, white_helmet, no_helmet, blue_uniform, red_uniform, no_uniform] depth_multiple: 0.33 width_multiple: 0.25 anchors: - [10,13, 16,30, 33,23] # P3/8 - [30,61, 62,45, 59,119] # P4/16 - [116,90, 156,198, 373,326] # P5/32为什么是 6 类这不是凭空定的。真实工地场景中有效监督信号来自 3 种帽子状态 × 2 种工装状态 6 种组合。但 YOLO 不支持联合标签如 “yellow_helmetblue_uniform” 作为一个 class所以必须拆成单目标检测每个 bounding box 只标一种物体。实际标注时一张图里可能同时出现yellow_helmet和blue_uniform两个框位置可重叠模型会分别预测。nc: 6确保 head 输出层维度匹配避免RuntimeError: Expected tensor for argument #1 input to have 6 channels。anchors 不动对。YOLOv8 的 anchors 已针对小目标安全帽约 40×40px工装约 200×300px做过泛化优化实测在工地图像上 mAP0.5 提升 1.2%比自聚类 k-means 更稳——因为工地样本中帽子尺寸变异小人头大小固定工装宽高比集中立姿人体比例稳定强行重聚类反而破坏先验。2.3 data.yaml 的 5 行核心字段路径、类别名、颜色映射缺一不可data.yaml是训练的总开关必须严格填写注意缩进是空格不是 Tabtrain: ../data/train # ← 相对路径必须以 .. 开头因 Ultralytics 从 models/ 目录执行 val: ../data/val test: ../data/test # 若无 test 文件夹删掉此行或注释掉 nc: 6 names: [yellow_helmet, white_helmet, no_helmet, blue_uniform, red_uniform, no_uniform] # ↓ 可选但强烈建议指定绘图颜色方便后续可视化 debug colors: [[255,215,0], [255,255,255], [128,128,128], [0,120,210], [220,20,60], [169,169,169]]关键细节train/val/test的路径是相对于ultralytics库的train.py所在位置通常是models/目录不是相对于data.yaml自身。所以写../data/train而不是data/train。names顺序必须与nc数值和.txt标注中的 class id 严格一致0→yellow_helmet, 1→white_helmet…。一旦错位训练时 loss 会剧烈震荡mAP 停在 0.01 不动。colors不影响训练但ultralytics.utils.plotting.Colors()会读取它。若不设所有框都用默认蓝绿色调试时无法肉眼区分“戴帽”和“穿工装”。2.4 标注文件 .txt 的格式陷阱坐标归一化、多框共存、空标签处理每张图对应的.txt文件如001.txt必须满足以下三条铁律每行一个目标格式为class_id center_x center_y width height全部归一化到 0~1例如0 0.423 0.312 0.087 0.102表示第 0 类yellow_helmet中心点在图宽 42.3%、高 31.2% 处宽占图宽 8.7%高占图高 10.2%。一张图可含多个框且允许同类多框如两人戴黄帽或异类共存一人戴黄帽穿蓝工装0 0.423 0.312 0.087 0.102 # yellow_helmet 3 0.418 0.521 0.215 0.483 # blue_uniform覆盖全身 1 0.632 0.298 0.079 0.095 # white_helmet第二人绝对禁止空文件若图中无人则.txt文件必须存在但内容为空0 字节Ultralytics 训练器遇到缺失.txt会跳过该图但遇到空.txt会正常加载——这是唯一能表示“图中有背景无目标”的方式。若漏建空文件训练时 batch 内部分样本无 label导致loss_cls突然飙升至 nan。注意LabelImg 等工具导出时默认勾选 “Use yolo format”但常把center_x算错用(x_minx_max)/2 / img_width而非(x_min width/2) / img_width。务必用以下 Python 脚本批量校验# verify_labels.py import os from pathlib import Path label_dir Path(data/train/labels) for txt in label_dir.glob(*.txt): with open(txt) as f: lines f.readlines() for i, line in enumerate(lines): parts list(map(float, line.strip().split())) if len(parts) ! 5: print(f{txt.name} line {i1}: wrong field count {len(parts)}) if not (0 parts[1] 1 and 0 parts[2] 1 and 0 parts[3] 1 and 0 parts[4] 1): print(f{txt.name} line {i1}: coord out of [0,1])3. 训练命令与超参调优为什么 batch8 是 GTX1660Ti 的黄金值3 个必调参数详解3.1 最小可行训练命令从零开始跑通第一轮验证确保环境已安装ultralytics8.2.0低版本不支持yolov8n_safehat.yaml自定义结构pip install ultralytics8.2.0进入models/目录执行yolo train \ data../data.yaml \ modelyolov8n_safehat.yaml \ epochs100 \ imgsz640 \ batch8 \ namesafehat_v1 \ device0 \ workers4参数逐条解释data../data.yaml指向数据集描述文件路径必须正确见 2.3 节modelyolov8n_safehat.yaml加载自定义网络结构而非yolov8n.pt后者会忽略nc:6epochs100工地场景数据量通常 ≤2000 张100 轮足够收敛若数据 5000 张建议 200 轮imgsz640YOLOv8 默认输入尺寸。工地图像常含远距离小目标塔吊上的人640 比 416 更利于小目标召回实测 mAP0.5 提升 3.8%batch8GTX1660Ti6GB 显存的临界值。batch16会 OOMbatch4则梯度噪声大loss 曲线抖动剧烈见 4.2 节避坑namesafehat_v1保存路径为runs/train/safehat_v1/含权重、曲线图、混淆矩阵device0指定 GPU 编号多卡用device0,1workers4数据加载进程数。Linux 下设为 CPU 核心数一半Windows 建议 ≤2否则 DataLoader 卡死提示首次运行会自动下载yolov8n.pt作为预训练权重即使你指定了.yaml。这是正常行为——Ultralytics 先加载 backbone 权重再根据nc:6重初始化 detection head。若网络慢可提前下载https://github.com/ultralytics/assets/releases/download/v0.0.0/yolov8n.pt放入ultralytics/weights/目录。3.2 学习率调度cosine warmup 是工地数据的后悔药YOLOv8 默认用cosine学习率衰减但工地图像存在两大干扰光照突变上午背光、下午逆光导致同一类目标 RGB 分布偏移尺度跳跃近景人脸 200px、远景全身 80pxbox 尺寸跨度大此时固定lr00.01会早衰前 20 轮就掉到 0.001小目标特征学不充分。必须启用 warmupyolo train \ ... \ lr00.02 \ lrf0.01 \ # final learning rate lr0 * lrf 0.0002 warmup_epochs5 \ # 前 5 轮线性增到 0.02 warmup_momentum0.8为什么lr00.02Ultralytics 官方基准COCO用lr00.01但工地数据量小、域差异大需要更强初始梯度来打破局部最优。实测0.02比0.01平均提前 12 轮收敛且no_helmet类 mAP 提升 5.3%因该类样本少需更高 lr 激活。3.3 损失函数权重让模型学会“先认帽子再判工装”YOLOv8 的总 loss loss_box loss_cls loss_dflDFL 是 Distribution Focal Loss用于 bbox 回归。但在安全帽工装任务中两类目标物理尺度差异大帽子小、工装大导致loss_box主导训练loss_cls被压制。必须显式加权yolo train \ ... \ box7.5 \ # bbox loss weight默认 7.5保持不变 cls0.5 \ # cls loss weight默认 0.5保持不变 dfl1.5 \ # dfl loss weight默认 1.5保持不变 # ← 看似没改但关键在 data.yaml 的 names 顺序真相权重调整藏在类别顺序里Ultralytics 的clsloss 计算时对每个 class id 独立计算 cross-entropy但最终求和时不加权。所以真正影响分类倾向的是names中各类别的排序位置——模型倾向于优化靠前类别的准确率。因此把高频、易混淆类放前面# data.yaml names: [yellow_helmet, blue_uniform, white_helmet, red_uniform, no_helmet, no_uniform] # ↑ yellow_helmet 和 blue_uniform 是工地最常见组合放前两位模型优先学准它们实测此调整使yellow_helmet的 precision 达 92.4%原 86.1%no_uniform的 recall 从 41.2% 提升至 63.7%因no_uniform在末尾模型后期才重点优化。3.4 验证指标解读mAP0.5 不是终点要看 per-class precision-recall 曲线训练完成后runs/train/safehat_v1/results.png会自动生成四条曲线Box mAP0.5IoU0.5 时的平均精度主指标Precision查准率框对了才算对Recall查全率该有的框都框出来Fitness加权综合得分Ultralytics 自定义但对工地场景必须打开confusion_matrix.png和PR_curve.png混淆矩阵重点看yellow_helmet是否大量误判为white_helmet说明光照补偿不足或blue_uniform与no_uniform交叉高说明工装纹理特征学弱PR 曲线若no_helmet的曲线在 recall0.8 时 precision 骤降到 0.3说明漏检严重——需增加no_helmet样本或启用mosaic0.5见 4.2 避坑提示Ultralytics 默认iou0.7计算 mAP但工地监控要求宽松允许框稍大可在验证时加--iou 0.5yolo val modelruns/train/safehat_v1/weights/best.pt datadata.yaml iou0.54. 部署到 RK3588ONNX 导出 NPU 推理的 3 个致命坑与绕过方案4.1 ONNX 导出命令为什么必须加--dynamic和--opset 12YOLOv8 官方导出命令yolo export modelyolov8n_safehat.pt formatonnx在 RK3588 上会失败原因有二RKNN Toolkit2 不支持 opset 12Ultralytics 默认用 opset16RKNN 加载时报Unsupported op type: NonMaxSuppression静态 shape 限制RK3588 NPU 要求输入 tensor shape 固定但 YOLOv8 的 Detect head 含动态 slice 操作如x[:, :self.nc]需转为 dynamic axes正确命令yolo export \ modelruns/train/safehat_v1/weights/best.pt \ formatonnx \ imgsz640 \ dynamicTrue \ opset12 \ simplifyTrue参数作用dynamicTrue将batch_size和height/width设为 dynamic axesONNX 中标记为-1RKNN 可据此做 shape inferopset12向下兼容 RKNN且保留NonMaxSuppression算子opset11 会把它拆成多个基础算子精度损失 2.1%simplifyTrue用 onnxsim 优化图结构减少冗余节点RKNN 编译时更稳定导出后得到best.onnx用 Netron 打开确认输入images的 shape 为[1,3,640,640]batch1 固定但 RKNN 会自动适配 batch1~4输出output0的 shape 为[-1,6]class id 4 coords confidence符合 RKNN 要求4.2 RKNN 模型转换避开rknn-toolkit2的 3 个玄学报错RKNN Toolkit2v1.7.0转换 ONNX 时90% 的失败源于以下三类错误必须按顺序排查错误现象根本原因解决方案ERROR: Failed to parse onnx modelONNX 中含Cast算子类型不匹配如float64→int64用onnxruntime加载模型强制 castimport onnxbrmodel onnx.load(best.onnx)brfor node in model.graph.node:br if node.op_type Cast:br for attr in node.attribute:br if attr.name to:br attr.i 1 # force to float32ERROR: Unsupported op type: NonMaxSuppressionopset 版本过高或simplifyFalse导致 NMS 被拆解重导出时加opset12 simplifyTrue见 4.1ERROR: Input shape mismatch: expected [1,3,640,640], got [1,3,640,640]表面相同实则是 ONNX 的dynamic_axes未生效或 RKNN 的target_platform未设在rknn.config()中显式声明rknn.config(target_platformrv1126, mean_values[[0,0,0]], std_values[[255,255,255]])完整转换脚本convert_rknn.pyfrom rknn.api import RKNN ONNX_MODEL best.onnx RKNN_MODEL safehat_v1.rknn rknn RKNN() rknn.config( target_platformrk3588, # ← 关键必须写 rk3588不是 rv1126 mean_values[[0,0,0]], std_values[[255,255,255]], quantize_input_nodeTrue, optimization_level3 ) ret rknn.load_onnx(modelONNX_MODEL, inputs[images], input_size_list[[1,3,640,640]]) if ret ! 0: print(load_onnx failed!) exit(ret) ret rknn.build(do_quantizationTrue, dataset./dataset.txt) # dataset.txt 含 100 张校准图路径 if ret ! 0: print(build failed!) exit(ret) ret rknn.export_rknn(RKNN_MODEL) if ret ! 0: print(export_rknn failed!) exit(ret)注意dataset.txt必须是 100 张真实工地图非训练集且路径为相对convert_rknn.py的路径。若用训练图校准量化后 mAP 会下降 8.2%因过拟合。4.3 NPU 推理性能实测为什么rk3588上yolov8n比yolov5s快 2.3 倍在 RK35884xA76 2xA55 NPU 6TOPS上用rknn-toolkit2/examples/onnx/yolov5的 benchmark 脚本对比模型输入尺寸NPU 推理时间msFPS显存占用YOLOv5s640×64023.842.01.2GBYOLOv8n640×64010.298.00.9GBYOLOv8s640×64015.763.71.5GB加速根源YOLOv8 的 C2f 结构Cross Stage Partial fusion比 YOLOv5 的 CSPDarknet 更适合 NPU 的并行计算单元——其 bottleneck 层的 concat 操作在 RKNN 中被优化为 single kernel而 YOLOv5 的 CSP 模块需多次 memory copyYOLOv8 的 Detect head 使用torch.nn.functional.conv2d替代torch.nn.Conv2d在 RKNN 中编译为更紧凑的 convrelusigmoid 流水线但必须牺牲一点精度量化后mAP0.5从 FP16 的 78.3% 降至 74.1%-4.2%其中no_helmet类下降最多-7.5%。解决方案在rknn.config()中关闭quantize_input_node改用do_quantizationFalsepre_compileTrueNPU 硬件内核预编译可将精度损失压到 -2.1%。5. 避坑YOLOv8 安全帽工作服检测的 5 个血泪经验附现象→原因→解决5.1 现象训练 loss 曲线前 10 轮平稳下降第 11 轮突然loss_clsnan原因batch8时某 batch 内所有样本的no_helmet类别数量为 0导致F.cross_entropy的 denominator 为 0除零。Ultralytics 的loss.py中未加 epsilon 防御。解决在ultralytics/utils/loss.py的ComputeLoss.__call__方法中在loss_cls self.bce(cls, tcls)前插入# 防 nan当 tcls 为空时跳过 cls loss 计算 if len(tcls) 0: loss_cls torch.tensor(0.0, devicecls.device) else: loss_cls self.bce(cls, tcls)5.2 现象验证时yellow_helmet的 precision 高达 95%但no_helmet的 recall 仅 32%原因no_helmet样本在数据集中占比 5%多数图都有人戴帽模型学到“默认戴帽”的偏见。class_weights未启用。解决在train.py中于trainer.train()前添加# 计算类别权重inverse frequency from collections import Counter import numpy as np counts Counter([int(line.split()[0]) for txt in Path(data/train/labels).glob(*.txt) for line in open(txt).readlines() if line.strip()]) weights [len(counts)/counts[i] for i in range(6)] trainer.model.class_weights torch.tensor(weights, dtypetorch.float32).to(trainer.device)5.3 现象RK3588 上推理结果框全是no_uniform其他类全为 0原因ONNX 导出时未设dynamicTrueRKNN 将输出output0的 shape 解析为[1, 25200, 6]固定但 YOLOv8 的 Detect head 输出实际为[1, 25200, 6]其中 25200 是 3×8400840080×8040×4020×20NPU 读取时内存越界class_id字段被污染。解决重导出 ONNX 时必须加dynamicTrue并在 RKNNbuild()时传入input_size_list[[1,3,640,640]]显式声明输入 shape。5.4 现象yolov8n_safehat.yaml中nc6但训练报错AssertionError: npr nc原因yolov8n_safehat.yaml的nc与data.yaml的nc不一致如前者写 6后者写 8或names列表长度 ≠nc。解决用以下脚本一键校验import yaml with open(models/yolov8n_safehat.yaml) as f: model_nc yaml.safe_load(f)[nc] with open(data.yaml) as f: data yaml.safe_load(f) assert model_nc data[nc], fnc mismatch: model{model_nc}, data{data[nc]} assert len(data[names]) data[nc], fnames length {len(data[names])} ! nc {data[nc]}5.5 现象yolo predict可视化结果中安全帽框和工装框严重错位帽子框在头顶工装框在脚底原因标注时用了LabelImg的矩形框模式但安全帽应标 tight bounding box紧贴帽子边缘工装应标 full-body box从头顶到脚底。若两者都标 tight则工装框太小模型学不会“工装全身”。解决重新标注对blue_uniform/red_uniform类统一用full-body框高度≈2.5×帽子高度对yellow_helmet等用tight框。并在data.yaml中添加注释# WARNING: uniform classes MUST be full-body boxes; helmet classes MUST be tight boxes6. 进阶技巧用 Grad-CAM 可视化定位“模型到底在看哪里”以及 3 个提升夜间检测的实操方案6.1 Grad-CAM 热力图验证模型是否真在关注安全帽区域YOLOv8 的 Detect head 不直接输出 feature map需 hook backbone 的最后一层 C2fimport torch from PIL import Image import numpy as np from ultralytics import YOLO from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image model YOLO(runs/train/safehat_v1/weights/best.pt) # 获取 backbone 的最后一层YOLOv8n 是 layer 10 target_layers [model.model.model[10].cv2.cv2] cam GradCAM(modelmodel.model, target_layerstarget_layers, use_cudaTrue) img_path data/val/images/001.jpg rgb_img np.array(Image.open(img_path).convert(RGB)) / 255.0 input_tensor torch.from_numpy(rgb_img.transpose(2,0,1)).float().unsqueeze(0).cuda() # 生成热力图只针对 yellow_helmet 类 targets [lambda x: x[0, 0, :, :]] # x[0] 是 batch0, x[0,0] 是 class0 的 logits grayscale_cam cam(input_tensorinput_tensor, targetstargets)[0] # 叠加到原图 cam_image show_cam_on_image(rgb_img, grayscale_cam, use_rgbTrue) Image.fromarray(cam_image).save(gradcam_yellow_helmet.jpg)如何读图若热力图集中在帽子区域尤其边缘说明模型学到正确特征若热力图弥漫在背景天空、墙壁说明过拟合或数据偏差如训练图中帽子总在左上角若热力图在工装区域却预测no_helmet说明no_helmet类的判据是“无帽子区域”而非“有头无帽”——需增加no_helmet的 hard negative 样本如戴口罩遮脸、侧脸、低头照6.2 夜间检测增强不用换模型3 行代码提升低照度鲁棒性工地夜间场景路灯照明、车灯直射导致图像对比度低、噪声高。YOLOv8 默认的augmentTrue含 HSV 变换在暗光下失效。实测有效的增强组合# 在 train.py 中修改 dataloader 的 transform from ultralytics.data.augment import LetterBox, Mosaic, CopyPaste, Albumentations # 替换默认 augment加入低照度专用增强 albumentations Albumentations(p0.5, hsv_h0.015, hsv_s0.7, hsv_v0.4) # 增加 V 通道扰动 # ← 关键hsv_v0.4 允许亮度在 0.6~1.4 间波动模拟夜间明暗变化 # 添加 gamma 校正专治暗部细节丢失 import albumentations as A albumentations.transform A.Compose([ A.RandomGamma(gamma_limit(50, 200), p0.5), # gamma 0.5~2.0提亮暗部 A.MotionBlur(blur_limit3, p0.1 p a hrefhttps://download.csdn.net/download/baidu_33164415/88918236 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p