工业PPE检测实战:用YOLOv8训练安全帽与反光背心识别模型

发布时间:2026/9/14 2:02:16
工业PPE检测实战:用YOLOv8训练安全帽与反光背心识别模型 简介面向建筑、制造等高风险工业场景的PPE检测数据集整合了644张训练图、183张验证图与91张测试图共918张实际场景图片涵盖安全帽、手套、护目镜、口罩、背心及对应未穿戴状态共11个类别可用于工业安全监控、智能安防报警、安全培训与目标检测算法研究。压缩包共包含1838个文件918张现场jpg照片、918份YOLO格式txt标注另有1个yaml类别配置和1个docx说明文档整体约46.99MB目录清晰、内容配套完整。目前已有486人浏览学习适合目标检测开发者、算法工程师以及企业安全管理人员参考。每张图片都带有精确的边界框覆盖不同人员姿态与光照条件可直接用于YOLOv5/YOLOv8等主流框架训练帮助快速构建工人防护装备穿戴检测及未穿戴实时预警系统减少安全事故发生。1. 为什么PPE检测比通用目标检测更依赖数据集构成在建筑工地的实时监控画面里算法真正要回答的问题不是“画面里有没有人”而是“这个人有没有按要求戴安全帽、穿反光背心”。工业安全PPE检测数据集正是为这类问题准备的918张图片、11类目标既包含Hardhat、Glove、Vest等佩戴正样本也包含NO Hardhat、NO Glove、NO Vest等未佩戴负样本。见过不少团队拿COCO预训练模型直接上线结果没戴安全帽的工人被漏检原因很简单通用目标检测数据集里根本没有“某样东西没出现”这种负类标注。这份工程类数据集的价值也正在于此它把安防场景里真正影响判定的状态差异做成了显式类别适合正在做工业安全监控系统、想把普通摄像头升级成智能巡检设备的工程师也适合研究目标检测在长尾场景下落地的同学。2. 拆解工业安全PPE检测数据集11类标注体系与YOLO格式的映射关系2.1 类别设计把“未佩戴”也做成显式类目的工程意义第一次打开这个数据集的人第一反应通常是为什么要单独标注NO Hardhat直接把Hardhat检测出来没有人头框的区域不就是没戴吗这个思路在简单场景可行但到了真实工地会出问题。检测器的输出是有置信度阈值的。如果一个工人背对摄像头安全帽被遮挡了60%Hardhat类别的置信度可能只有0.3低于阈值就被漏掉。此时后处理逻辑会认为“没有Hardhat”从而误报“未戴”。反过来如果工人手里拎着安全帽Hardhat框和人员框重叠又容易误判为已佩戴。把NO Hardhat、NO Glove、NO Mask、NO Vest作为独立类别等于让模型直接学习“未佩戴状态”的视觉模式而不是靠缺失推断。这个设计理念也决定了训练时的损失函数设计正类和“未佩戴”负类之间的区域并不互斥画面里同一个人可能戴着手套但没戴护目镜所以每个类别要独立判定不能用一个全局状态覆盖整幅图。类别类型检测目标Hardhat正类工人头部戴有安全帽NO Hardhat负类工人头部未戴安全帽Glove / NO Glove正类/负类手部是否戴手套Goggle / NO Goggle正类/负类眼部是否戴护目镜Mask / NO Mask正类/负类口鼻是否戴口罩Vest / NO Vest正类/负类躯干是否穿反光背心Person辅助类人员本体用于上下文参照2.2 YOLO标签格式解析归一化边界框背后的坐标换算这个数据集采用YOLO格式标注每张图片对应一个同名的txt文件。文件中每一行代表一个目标一行5个数顺序固定类别ID、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。拿到一个标签文件用下面这段代码把标注画回原图是数据审查的第一步。import cv2 def draw_yolo_label(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) x_center, y_center float(parts[1]), float(parts[2]) bw, bh float(parts[3]), float(parts[4]) x1 int((x_center - bw / 2) * w) y1 int((y_center - bh / 2) * h) x2 int((x_center bw / 2) * w) y2 int((y_center bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return img这里的五个数是训练时的直接输入模型学到的就是“某个归一化位置存在某个类别”。需要注意bw和bh是边界框相对图片宽高的比例而不是像素绝对值所以YOLO格式天然不依赖输入分辨率任意尺寸的图片都能直接训练。另外Roboflow导出的数据集偶尔会出现越界坐标训练前最好做一次合法性检查。import numpy as np data np.loadtxt(label_path) assert data.shape[1] 5, 每行必须是5个数 if data.ndim 1: data data.reshape(1, -1) assert data[:, 1].max() 1.0001 and data[:, 1].min() -0.0001 assert data[:, 2].max() 1.0001 and data[:, 2].min() -0.0001 assert data[:, 3].max() 1.0001 and data[:, 4].max() 1.0001归一化坐标的理论范围是0到1但标注工具偶尔会在目标贴边时标出1.01这种值。训练框架会自动裁掉却可能让边框偏出画面影响类别判断尤其是紧贴画面边缘的NO Hardhat目标。2.3 数据划分与文件命名从Roboflow导出到项目目录数据集的命名规则是Roboflow导出的典型风格原始文件名拼接_jpg.rf.32位hash。哈希值用于避免不同来源图片重名训练时不需要处理但如果要合并多个数据集建议保留这个命名因为.rf.前面的时间戳往往对应拍摄时间对排查场景重复有参考价值。数据量划为训练集644张、验证集183张、测试集91张比例接近7:2:1。工业场景数据采集成本高918张图做迁移学习已经能跑出一个可用的PPE检测器。和鸟类目标检测的数据集、宝石检测数据集这类垂直数据集相比它最大的特点是尺度跨度极大安全帽可能只占整张图的2%人员包围框可以占到40%。这种尺度差异会直接影响后面的增强参数选择。相比通用目标检测数据集这种行业数据集的标签分布更值得先画直方图看一眼而不是直接开训。3. 用YOLOv8训练PPE检测模型环境准备、数据配置与完整命令3.1 解压并整理成YOLO标准目录结构从压缩包解压后如果已经是YOLO格式目录可以直接使用如果不是需要按下面的约定放置图片和标签。ppe_dataset/ ├── images/ │ ├── train/ │ ├── valid/ │ └── test/ └── labels/ ├── train/ ├── valid/ └── test/图片和标签一一对应文件名必须完全一致只是后缀不同。批量整理时我一般用shell命令把平铺目录拆开。mkdir -p ppe_dataset/images/{train,valid,test} mkdir -p ppe_dataset/labels/{train,valid,test} find dataset/train -name *.jpg -exec mv {} ppe_dataset/images/train/ \; find dataset/train -name *.txt -exec mv {} ppe_dataset/labels/train/ \;这里的关键是YOLO训练脚本只认目录层级不认额外子目录因此任何嵌套目录都要先摊平。另一个容易踩的坑是images和labels目录下文件数量不一致某个空标签文件被误删导致图片找不到对应标注训练时报错。提示整理完成后用ls images/train | wc -l和ls labels/train | wc -l对一下数量不一致时先解决数据问题不要急着启动训练。3.2 data.yaml配置与关键训练参数YOLOv8使用data.yaml描述数据集路径和类别清单。类别顺序必须和标签文件里的类别ID完全一致否则训练出来的模型全部错位。path: /data/ppe_dataset train: images/train val: images/valid test: images/test names: 0: Glove 1: Goggle 2: Hardhat 3: Mask 4: NO Glove 5: NO Goggle 6: NO Hardhat 7: NO Mask 8: NO Vest 9: Person 10: Vest类别名里的空格比如NO Glove在YAML里必须用引号包起来。模型推理后输出的class_id映射也依赖这份文件训练完建议原地备份一份。对于918张图片的小型工业数据集直接沿用默认参数风险很大。下面是我在这个数据集上验证过的训练命令。yolo detect train \ datappe_data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz1280 \ batch16 \ lr00.003 \ mosaic0.8 \ close_mosaic10 \ cos_lrTrue \ projectruns/ppe \ nameexp1modelyolov8s.pt会加载COCO预训练权重这对工业小目标场景很重要因为COCO里的Person类别已经帮模型学好了人体底层特征迁移到PPE检测能节省大量训练时间。imgsz1280是针对性调整原始图片里安全帽像素占比很低640分辨率下只有十几个像素1280能明显提升小目标召回。close_mosaic10表示最后10个epoch关闭马赛克增强让模型在接近真实分布的数据上收敛避免马赛克引入的碎块特征干扰。cos_lrTrue配合150个epoch可以让学习率在训练后半段平缓下降比固定步长衰减更适合小数据集。3.3 训练曲线与关键指标解读训练开始后终端会逐epoch打印box_loss、cls_loss、dfl_loss、mAP50和mAP50-95。对于PPE检测场景我更看重mAP50因为工业安防告警通常是“框到就算”对IoU精度要求没有自动驾驶那么苛刻。指标关注内容经验判断box_loss边界框回归损失持续下降最后10个epoch不反弹cls_loss分类损失决定NO Hardhat与Hardhat能否区分dfl_loss分布焦点损失影响小目标边框精度mAP50IoU0.5时平均精度单类AP差距过大会暴露类别不平衡mAP50-95多IoU阈值平均精度低说明边框贴合度不足考虑调高imgsz训练结束后的结果目录里weights/best.pt是验证集mAP最高的权重weights/last.pt是最后一个epoch的权重。评测时用best.pt而不是last.pt。测试集上的独立验证用下面命令。yolo detect val \ datappe_data.yaml \ modelruns/ppe/exp1/weights/best.pt \ splittest \ imgsz1280命令会在测试集上输出每个类别的平均精度。重点看NO Hardhat和Vest两项。如果NO Hardhat的AP明显低于Hardhat说明模型把大量未戴安全帽的头部漏检了需要回查数据集中负类的数量分布而不是继续调推理阈值。障碍物造成的干扰经常在NO Hardhat类别上体现得比Hardhat更剧烈因为安全帽外观相对统一“没戴”则意味着头部轮廓、头发颜色、背景护栏都会成为模型学习的对象。4. 提升PPE检测鲁棒性数据增强、类别权重与误检案例排查4.1 面向室外复杂光照的小目标增强策略工地摄像头画质参差不齐阴天、逆光、夜晚补光都会让整张图偏暗边缘模糊。YOLOv8默认增强参数对这个场景偏保守我会显式打开几项。# augment.yaml 片段 hsv_h: 0.02 # 色调扰动抵抗不同品牌安全帽的颜色差异 hsv_s: 0.7 # 饱和度扰动模拟阴天和强光下的色彩衰减 hsv_v: 0.5 # 明度扰动模拟逆光与暗角 scale: 0.4 # 缩放扰动让模型适应安全帽的多尺度变化 fliplr: 0.5 # 水平翻转工人左右朝向对称 degrees: 5.0 # 小角度旋转适配摄像机安装倾角hsv_v从默认的0.4提高到0.5是因为工业环境里大量逆光样本会让安全帽亮部和暗部差异极大。持续调高会增加训练时间但对最终鲁棒性贡献明显。另外不建议开启大幅旋转degrees控制在5度以内安全帽形态固定旋转过多会生成大量不真实的斜向样本。这里要说明为什么和鸟类目标检测的数据集、宝石检测数据集的增强策略不同鸟类数据集目标通常在画面中央、背景虚化增强时先做高斯模糊反而更稳宝石数据集需要保留纹理细节翻转增强要谨慎。工业PPE数据的背景是脚手架和机械设备干扰框很多大范围尺度扰动收益最高纹理类增强反而容易把安全帽边缘的颗粒噪点放大。4.2 类别不平衡从数据分布反推损失权重11个类别在918张图里的出现频率通常很不均衡。Hardhat、Person可能各出现几百次NO Goggle、Goggle也许只有几十次。不处理的话模型会把大部分学习能力放在高频类别上低频类别AP很低。我一般先跑一段统计确认不平衡程度。import glob from collections import Counter counter Counter() for label_file in glob.glob(/data/ppe_dataset/labels/train/*.txt): with open(label_file) as f: for line in f: counter[int(line.split()[0])] 1 print(counter)示例输出和对应判断如下表。类别ID目标数量判断2 Hardhat420充足9 Person380充足0 Glove210充足6 NO Hardhat95偏少10 Vest70偏少1 Goggle25严重不足5 NO Goggle20严重不足最大类和最小类差距超过20倍时需要给低频类加权。简单方式是数据采样时多读几次包含低频类的图片或者自定义分类损失权重。需要注意不能盲目给NO Goggle加太大权重护目镜目标极小误报率本来就高过大的权重会让模型把普通人脸高光识别成护目镜反而拉低精确率。4.3 混淆矩阵与两类最典型误检训练完后的runs/ppe/exp1/confusion_matrix.png里横轴是Ground Truth纵轴是预测结果。PPE数据集中最容易出现两类混淆第一是NO Hardhat被预测为Hardhat多发生在工人头顶有一块和安全帽形状相似的蓝色或黄色塑料板时第二是Person和Vest重叠模型在是否穿反光背心上犹豫不决因为反光背心紧贴人体轮廓与person框高度重合。处理这两类误检单靠调阈值不行。我会回看错误样本统计误检框的尺寸和位置。如果误检框普遍小于真实安全帽尺寸就在后处理里设置最小检测框宽高过滤掉小于某个像素数的预测框。如果是反光背心颜色与背景护栏颜色接近可以加大Vest类的对比度增强权重或者把Vest单独抽出来训练一个二分类辅助模型专门修正主模型在Person区域的判断。5. 部署到安防摄像头模型导出、TensorRT优化与实时告警验证5.1 导出TensorRT引擎并实测延迟训练出的best.pt是PyTorch权重直接跑视频流GPU利用率偏低。我通常导出成TensorRT engine再接入摄像头拉流进程。yolo export modelruns/ppe/exp1/weights/best.pt formatengine device0 halfTrue imgsz1280halfTrue让模型用FP16推理延迟通常降低30%到40%。导出时必须保持imgsz1280与训练一致否则引擎会强制resize小目标检测能力退化。导出后用下面命令压测单张图。from ultralytics import YOLO model YOLO(runs/ppe/exp1/weights/best.engine) results model.predict(test.jpg, imgsz1280, device0, halfTrue, verboseTrue)对1080p摄像头画面我会在拉流端先把每帧等比缩放到1280长边再送入推理因为原生1920x1080直接推理显存占用高却不会带来多少mAP提升。5.2 告警去抖时序状态机减少误报单帧模型输出有抖动不能每出现一次NO Hardhat就立刻告警。常见做法是保留最近N帧的检测结果做多数表决。from collections import deque history deque(maxlen15) def process_frame(frame_id, regions): history.append(regions) if len(history) 15: return for person_id, ppe_state in regions.items(): no_hat_frames sum( 1 for h in history if person_id in h and NO Hardhat in h[person_id] ) if no_hat_frames 12: raise_alarm(person_id, no_hardhat)12/15的阈值意味着视频流每秒10帧时需要1.5秒内持续检测到未戴安全帽才触发告警。这个参数根据摄像头安装位置调整如果摄像机离工人很近帧间变化大窗口可以缩短到10帧如果画面中工人移动缓慢适当加长能过滤偶发侧身造成的漏检。5.3 用测试集外的现场片段验证泛化部署时留一批现场视频不参与任何训练和验证。测试时回放一遍统计误报率。判断数据集质量最直接的方法把现场视频里的逆光、夜间红外帧混入测试集如果mAP下降超过10个点说明当前数据集的光照多样性不足需要补充采集而不是继续调参。工业PPE检测的落地瓶颈大多不在网络结构而在数据是否覆盖了目标可能出现的所有距离、角度和光照组合。本文还有配套的精品资源点击获取