基于YOLOv8的车间安全穿戴检测实战:从数据集构建到部署避坑
简介一套面向工业安全场景的目标检测数据集聚焦车间工人、安全帽与安全背心的识别适用于YOLO系列、Faster Rcnn、SSD等主流深度学习模型训练。数据集包含3465张图片标注person、helmet、vest三个类别图片与txt标签已按训练集、验证集、测试集划分并附带指定类别信息的yaml文件及xml标签txt文件采用YOLO格式归一化坐标yaml内含类别名称与路径配置可直接用于YOLOv5至YOLOv10等算法框架。压缩包共2000个文件其中1999个为txt标注文件1个为yaml配置文件整体大小约357.09MB。数据集面向工业安全生产监控、人员规范着装检测等场景图片来自真实车间监控视角涵盖不同光照与复杂背景目录结构清晰免去自行划分数据集的繁琐步骤有助于快速开展模型训练与效果验证。已有438人学习下载适合正在研发车间安全智能预警系统的深度学习开发者使用。1. 车间安全穿戴检测一类数据集撑起整个工业视觉项目做工业视觉这几年我接到的需求里出现频率最高的不是“检测产品缺陷”而是“识别工人有没有戴安全帽、穿安全背心”。这个需求听起来简单真正落地时却让不少人翻车不是模型效果差到没法用而是根本没有一套合用的数据集最后只能从头攒数据、标数据把八成时间耗在数据上。安全生产法规里对劳保穿戴有硬性要求车间现场靠人盯根本盯不过来目标检测技术就是为了解决这个“人管不过来”的场景——用摄像头替代巡检员自动发现未佩戴安全帽或反光背心的行为并告警。这篇笔记面向的是手里有摄像头、想用目标检测搭一套车间安全穿戴识别的工程师或项目经理我会按数据准备、模型选型、参数调优、踩坑复盘、上线验证这条路径把这套方案的落地细节讲清楚。2. 目标检测选型从 YOLO 家族里挑一个能扛住车间场景的模型2.1 YOLOv8 为什么是比 YOLOv5 更稳妥的起点车间工人、安全帽、安全背心识别本质上是一个类别少、目标尺寸分布极不均匀的检测任务。安全帽在画面里通常是小目标安全背心是中大目标工人身体则是大目标。这种场景对模型的要求不是“什么都能检测”而是对中后期层级的特征表达能力要求均衡——既要保住小目标的细节纹理又不能牺牲大目标的语义感受野。YOLOv8 相比 YOLOv5 最大的变化是 head 结构改成了 Anchor-Free并由 Decoupled Head 分别输出分类和回归分支。锚框机制在处理安全背心这类宽高比变化较大的目标时需要设计先验框调不好会直接压低 recallAnchor-Free 的方式则老实得多每个位置直接预测“这里有没有目标中心点”和“到四条边的距离”对安全帽、背心这类形状相对固定的物件反而更友好。C2f 结构替换了原先的 C3梯度流动更充分在用自己标注的中小数据集训练时不容易出现深层次特征断裂导致的不收敛问题。我的习惯是新项目第一版模型用 YOLOv8n 或 YOLOv8s先跑通一条完整链路再做精度提升。# 安装 ultralytics 库注意锁定版本避免后续接口变动影响训练脚本 pip install ultralytics8.2.0 # 下载预训练权重基于 COCO 的预训练模型能让迁移学习起点更合理 yolo predict modelyolov8n.pt source./test_image.jpg这里的“锁定版本”是经验之谈。ultralytics 的接口迭代非常快8.2.0 和 8.3.x 在回调函数、验证集评估指标名称上都有变动不锁版本的话训练脚本在几周后重跑时可能会突然报错。YOLOv8n 参数量仅 3.2M 左右在 GTX 1660 这类老显卡上也能轻松跑训练但如果你最后要部署到 Jetson 系列边缘设备就更应该从 n 和 s 起步直接上 m 或 l 会让帧率掉到不可用的地步。2.2 YOLOv5 和 YOLOv11什么时候该回头看老模型、什么时候该追新并非所有项目都该无脑选新版本。如果产线上已有的推理框架是基于 YOLOv5 的 ONNX 导出来写的更换模型意味着整个预处理后处理代码都要跟着改风险不小。YOLOv5 的 v6.0 版本在 720P 输入下、GTX 1080Ti 推理单张只要 5ms 上下老代码稳定工程团队对它熟悉此时继续用 YOLOv5 维护也是合理选择。YOLOv11 则在 Backbone 里引入了 C3k2 结构并用 R flops 做了更细粒度的计算约束。但就安全帽、背心这类小类别检测而言模型结构的收益远不如数据质量来得直接。我的判断是如果你的团队已经跑通 YOLOv8没必要为追新而迁移到 v11如果是从零起步且未来要接复杂场景如同时识别多种违规行为直接上 YOLOv11 也说得过去。评估时不要只盯着 mAP要把 GPU 占用、推理耗时和误报率一起加进对比表。模型输入尺寸mAP50-95公开安全帽数据参考推理耗时T4, FP16显存占用bs16, 640pxYOLOv5s640中约 3.8ms约 3.5GYOLOv8s640较高约 5.2ms约 4.2GYOLOv11s640较高约 5.5ms约 4.4G数字只是横向参考不要把它当成绝对标准。关键结论是这个场景里 s 级模型已经能满足绝大多数厂区需求追求更大的 m 和 l 会显著降低边缘设备的并发路数。3. 车间工人、安全帽、安全背心数据集从公开资源到自建数据一条路走通3.1 coco2017 数据集里能直接“白嫖”到哪些类别coco2017 数据集包含 80 个类别每张图像都有 instance 标注格式是 JSON polygen。让工人、安全帽、背心识别项目少走弯路的第一步是确认公共数据集的类别映射——如果只是做小规模验证完全可以直接抽取 COCO 中的 person 类作为工人识别基准甚至把“tie领带”类误标造成的干扰删掉后再叠加自采的帽子、背心数据。# 抽取 COCO 中 person 类别的图片列表用于验证检测 backbone 是否正常工作 from pycocotools.coco import COCO coco COCO(./annotations/instances_train2017.json) person_ids coco.getCatIds(catNms[person]) img_ids coco.getImgIds(catIdsperson_ids) print(f包含 person 的图像总数: {len(img_ids)})这段代码背后的逻辑是先验证现有工具链能否正确读取 COCO JSON再决定后续标注格式要不要统一转成 YOLO 的 txt。很多人一上来就想训练自己数据集却在数据加载这一步才发现 label 文件路径写错、类别索引从 0 还是从 1 开始等低错非常耗时。先把 person 跑通相当于给整条管线做了一次“体检”。不过 COCO 里的 person 大量是日常场景和车间环境差异很大——穿着反光背心、戴安全帽、站在机床旁的工人形态并不常见。因此 COCO 只能当 baseline不能作为真正的训练主力。3.2 自建车间场景数据集摄像头抽帧、数据清洗与类别定义真正的核心数据集必须来自目标车间或同类车间。用监控视频抽帧是最经济的做法海康或大华的 RTSP 流通过 FFmpeg 抽帧每秒取 1 帧即可避免相邻帧高度重复造成的数据冗余。抽帧后的图片还要做人工筛选把画面模糊、目标占比过小、遮挡超过 50% 的图片剔除。# 从 RTSP 流每 2 秒抽一帧保存为 jpg用于构建车间原始数据集 ffmpeg -rtsp_transport tcp -i rtsp://your_camera_ip:554/stream1 \ -vf fps1/2,scale1280:-1 -q:v 2 -f image2 worker_%04d.jpg这里强制走 TCP 而不是默认的 UDP是因为 UDP 在弱网环境下容易出现花屏帧花屏帧进入数据集就是脏数据。scale1280:-1把宽度统一到 1280保持原始宽高比不变防止挤压变形影响标注时对边界框的判断。如果后期要训练小目标检测这个尺寸通常不会让安全帽缩小到无法标注的程度若需要看清更远距离的帽子和背心则要改用 1920 宽或按 ROI 区域裁剪后再存帧否则安全帽可能只有十几个像素标注出来的 bbox 又有大量噪声。类别定义上我坚持只设三个类。第一类是“工人person”无论是否穿戴合规都归为 person第二类是“安全帽helmet”第三类是“安全背心vest”。不要多做“未戴安全帽”这个类别——它和“戴了但被遮挡”的边界极难划清硬分会导致类别间样本极度不均衡。逻辑判断放到后处理去写检测到 person 但没匹配到 helmet就告警“未佩戴安全帽”。3.3 标注工具选型与标注边界规则把 labelImg 的坑提前堵住标注工具我用 labelImg 的时间最长也推荐新手上路先用它。虽然界面朴素但它稳定、安装简单、支持 PascalVOC 和 YOLO 两种导出格式对几百张图片的小项目足够。标注时要提前定几条硬规则否则返工成本极高安全帽的 bbox 只框帽子本体不要包含额头和头发帽檐略微出界没关系但最多只能超出 5% 像素否则模型会把帽檐当特征学偏。安全背心的 bbox 框到反光条边缘即可不要把肩膀以外的背景包进来正反两面都要标注侧面人像如果背心被手臂遮挡超过 40%直接跳过该帧不要硬标。每个类别至少保证 3000 个实例且不同光线、不同工位、不同体型的样本尽量均衡。# labelImg 通过 pip 安装启动后从“打开目录”选择图片目录“更改保存目录”指定输出 pip install labelImg labelImg标注输出格式建议直接选 YOLO。因为 YOLO 格式每个目标一行class x_center y_center width height坐标是归一化后的相对值训练时不需要再做坐标换算少一层出错概率。等所有图标注完用脚本统计每个类别的标注框数量如果发现某个类别的数量偏少就要针对性补充采集图片再标注。3.4 数据集切分与验证集构成的“玄学”不是随机 split 就完事数据切分看似是小事但这个环节里藏着项目成败的伏笔。按 8:1:1 随机切分 train / val / test 是常规操作但车间监控数据是按时间序列抽帧的同一个工人连续几帧会出现在不同集合中导致评估指标虚高——模型看到了“见过的脸”在另一个场景里出现自然容易检测正确。正确做法是按视频片段切分每个摄像头或每段连续录像为粒度整体划分到不同集合保证同一时间片段只出现在一个集合中。另外验证集图片务必包含至少 20% 的“难例”即工人远离摄像头、姿态非直立、车间光源造成逆光的样本。很多模型在验证集上 mAP 很高一到现场就抓瞎正是因为验证集里全是清晰、正面的理想图片完全没体现出真实场景的分布。4. 用 YOLOv8 训练自己的车间穿戴检测数据集从命令到三个必调参数4.1 训练命令与目录结构先让数据加载零报错先建立标准目录结构这一步看着基础却能省下大量排查路径的时间dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml 是训练入口内容如下# 类别定义顺序必须与标注文件中的 class_id 严格一致 path: ./dataset train: images/train val: images/val test: images/test names: 0: person 1: helmet 2: vest注意path用相对路径的坑——ultralytics 在训练时会基于当前工作目录解析path如果训练脚本是从项目根目录运行的还好若是通过 Docker 挂载目录运行路径容易指错报 FileNotFoundError。我建议把path写成绝对路径虽然不优雅但能避免绝大多数新手期路径问题。# 训练安全帽背心检测模型选 s 级模型训练 120 轮 yolo detect train modelyolov8s.pt data./dataset/data.yaml \ epochs120 batch16 imgsz640 device0训练流程启动后要盯着终端的 mAP50 和 mAP50-95 变化曲线。如果 loss 从第二轮开始还迟迟不降多半是数据集有问题先终止训练检查标注框是否出现坐标越界如 width 或 height 为 0。如果 mAP 在最后 20 轮还在缓慢上升说明数据量不够加大 epochs 到 200 通常还能缓解但不要指望 mAP 会大幅冲到 0.95——车间场景中远距离小目标的安全帽本身就极难完美框定。4.2 参数调优学习率、mosaic 增强与 anchor 自动调优第一个必调参数是学习率。ultralytics 默认 lr00.01但这是针对 COCO 这种百万级数据集的设定小数据集下学习率过高会导致早期 loss 爆降后迅速震荡。我的经验是训练样本少于 5000 张时把 lr0 降到 0.003 起跑样本在 5000 到 20000 张之间0.005 比较稳妥。# 调低学习率防止小数据集过拟合震荡 yolo detect train modelyolov8s.pt data./dataset/data.yaml \ epochs150 batch16 imgsz640 device0 lr00.005第二个必调参数是 mosaic 增强。YOLOv8 默认开启 mosaic1.0即每轮训练有 100% 概率把 4 张图拼接成一张马赛克图。这在数据量大时能显著提升模型的泛化能力但小数据集上如果 mosaic 概率过高模型会学到大量拼接痕迹安全帽的边界还可能被拼接缝隙切断。训练 5000 张以下的数据集时我一般把 mosaic 调到 0.5。此外从第 80 轮开始自动关闭 mosaic 是 YOLOv8 默认行为不用手工干预。第三个关键参数是close_mosaic和cache的组合。cacheTrue可以预加载所有图片到显存/内存中大幅提升训练速度但当数据量超过 2 万张时会占用过多内存而且对随机增强的多样性也是一种限制。# 关闭部分 mosaic开启缓存加速小数据集训练 yolo detect train modelyolov8s.pt data./dataset/data.yaml \ epochs150 batch16 imgsz640 device0 \ mosaic0.5 close_mosaic10 cacheTrueclose_mosaic10表示在最后 10 轮关闭马赛克增强让模型在接近真实分布的数据上做最后的精调这对小目标的定位精度影响很明显。数据量小于 3000 张时cacheTrue 后显存占用会升高遇到 OOM 就把 batch 从 16 降到 8不要盲目加显存。4.3 验证集指标解读mAP50 与 mAP50-95 到底哪个决定能否上线训练结束后ultralytics 会在runs/detect/train/目录下生成results.csv。我通常先看mAP50是否达到 0.9再看mAP50-95是否超过 0.7。这两个指标的含义差别很大mAP50 只要求预测框和真实框的 IoU 超过 0.5 就算检测正确对安全帽这种小目标来说IoU0.5 已经相当宽容mAP50-95 则从 0.5 到 0.95 以 0.05 为步长取平均对框的精确位置要求极高。车间告警系统能容忍 bbox 稍微偏一点只要“帽子/背心是否佩戴”判断正确即可所以 mAP50 才是上线决策的核心指标。如果你的 mAP50 高于 0.92 而 mAP50-95 只有 0.6仍然可以先上线试用再通过难例挖掘不断迭代。# 读取 results.csv定位验证集指标 import pandas as pd df pd.read_csv(runs/detect/train/results.csv) cols df.columns print([c for c in cols if mAP50 in c or map50 in c])5. 车间安全穿戴检测避坑手册五个高频翻车现场与解决方案5.1 反光背心的“反光”把模型学成了噪点提取器现象模型在白天识别背心正常到了晚上或逆光环境误检率暴涨把车间的金属反光、墙壁踢脚线反光条都识别成了“安全背心”。原因安全背心的反光条在强光下形成高亮区域与周围环境对比度过大。模型学到的是“高亮背心”而不是“穿着在人身上的高亮条带”这个完整语义。解决训练集中必须包含至少 30% 的背光、低照度、强反光样本。我在实际项目中会故意引入不同角度、不同强度的光源变化的图片。如果现场能采集到不同班次、不同天气的光线变化尽量采集全。实在缺数据时用图像增强库对已有的背心图片做亮度抖动和强高斯噪声虽然不如真实样本自然但能在一定程度上拉平误检率。部署时还可以加入一条后处理规则检测到的“背心”如果不在任何 person 框内直接丢弃这个检测结果。5.2 安全帽检测的漏检率集中在远距离和低头动作现象近处工人戴帽子识别得挺准画面远端戴红色安全帽的工人经常漏检。原因头盔在画面中仅占约 20x20 像素甚至更小模型在深层特征图中已经将该区域的特征信息压缩得所剩无几同时红色安全帽在偏色监控画面中容易和背景环境融为一体旧式模拟摄像头下尤其明显。解决一是提高输入分辨率。imgsz640改成 800 或 960小目标的 AP 通常能涨 3 到 5 个百分点但推理耗时也会同步上升——我的做法是先评估现场摄像头的安装高度和覆盖范围再选择输入尺寸。二是增加“小目标复制粘贴”策略将有安全帽的小目标裁剪下来随机粘贴到背景图中构成新的训练样本。这是简单的数据合成方式不要和 mosaic 混淆它更直接、更可控。5.3 标注文件坐标越界引发整批训练样本被丢弃现象训练日志中WARNING ⚠️ skipping image (invalid data)反复出现训练结束后 mAP 一直上不去。原因标注工具导出时手滑把 bbox 的 x 坐标写超出图像宽度或者用自动标注脚本时出现了归一化坐标大于 1 的情况。YOLO 格式要求0 x_center 1、0 w 1越界的行会被训练管线静默跳过。解决写一个校验脚本遍历所有 label 文件检查每个数值是否在[0, 1]区间内、width 和 height 是否大于 0。发现异常就定位到对应图片重新修正标注或删除整张图。这个脚本应该在每次新标注完数据后立即执行而不是等到训练时报错了再回头查。import os label_dir ./dataset/labels/train for f in os.listdir(label_dir): path os.path.join(label_dir, f) for line in open(path): vals list(map(float, line.strip().split())) if len(vals) ! 5: print(f{f} 格式错误: {line}) if not (0 vals[1] 1 and 0 vals[2] 1 and 0 vals[3] 1 and 0 vals[4] 1): print(f{f} 坐标越界: {line})5.4 数据集只有晴天数据雨天现场误检率直接翻倍现象模型在验收测试时表现良好结果上线后遇到下雨天车间门口区域的“工人”检测数量暴增画面里雨水打在地面形成的水花都被识别出人形。原因监控摄像头在雨天会有雨滴附着在镜头保护罩上形成局部模糊和光斑。模型没见过这种视觉退化于是把高响应区域误判为目标。解决部署层面在摄像头端启用自动雨刷功能或定期清理镜头算法层面把训练集扩充到包含雨天、雾天画面——不是要收集“坏天气”到很多而是要覆盖摄像头位于室外进出通道时的真实退化模式。更简单的方式是训练完成后用真实雨天画面做一次“二次验证”如果误检过多但不想重新训练可以先在告警逻辑中限定检测区域ROI把容易出误报的背景区域从检测范围中挖掉缓解上线压力。5.5 训练中断后恢复训练mAP 反而掉得更厉害现象训练到 60 轮时显卡过热导致进程崩溃使用resumeTrue从 last.pt 继续训练又跑了 40 轮后 mAP 不升反降。原因断点恢复时把学习率调度器也一起恢复了而默认的 cosine schedule 在第 60 轮时学习率已经降得很低重新恢复后学习率被重置到较高值导致 loss 在原有最优区间附近震荡。解决resume 后把 lr0 重新设置为一个较小值比如 0.001再跑 30 轮让权重在低学习率下慢慢地重新收敛。这个做法更像“微调”而不是“继续训练”。如果断点发生在学习率已经很低的后半程干脆不要 resume直接从头再训练一遍有时反而更快更稳。6. 模型导出与车间实时验证一次剪枝优化带来的推理速度翻倍训练完成不等于项目完成。部署时的模型往往要做一次轻量化优化尤其是在边缘设备上。我常用的方式是先转 ONNX再基于 ONNX Runtime 做 FP16 量化——这个操作在 Tesla T4 或 Jetson Orin 上效果明显安全帽检测这种小模型通常能获得接近双倍的推理速度提升而 mAP 损失几乎可以忽略不计。# 导出 FP16 精度的 ONNX 模型供 Edge 端部署 yolo export modelbest.pt formatonnx halfTrue simplifyTruehalfTrue是关键参数它会把权重从 FP32 压缩成 FP16。simplifyTrue会做一次计算图简化去掉一些冗余的 reshape 和 transpose 操作。导出后务必用 ONNX Runtime 跑一次推理验证避免因为算子兼容性问题导致终端推理失败。import onnxruntime as ort import numpy as np session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider]) input_name session.get_inputs()[0].name # 模拟一张 3x640x640 的输入 dummy np.random.rand(1, 3, 640, 640).astype(np.float16) output session.run(None, {input_name: dummy}) print(输出张量数量:, len(output))验证阶段我会用一短段线上视频做真实场景回放把模型输出的 bbox 叠加到画面上统计“每 100 帧的漏检次数”和“误报次数”两个指标。漏检比误报更致命——漏检意味着工人确实没戴帽子但系统没报警安全考核时这就是事故隐患误报最多让中控室多弹几次提示让人厌烦但不会导致安全事故。所以我调参时的优先级永远是追 recall而不是追 precision。调试时还有一个容易被忽略的点摄像头画面上检测到的人脸、手臂等身体部位不要参与“未戴帽”判定。比如一个工人只露出了手臂系统检测不到头盔如果此时告警就会天天误报。我给后人留的一个习惯是在 person 框的下半部分挖一个 ROI 区域只有当 worker 的上半身区域框的顶部 30% 区域被检测到时才执行帽子和背心的匹配逻辑。这个朴素的几何判断能砍掉一半以上的低质量误报。这套方案做下来模型本身的 mAP 不是最值得骄傲的东西反而是“数据构建 部署边界条件”这两件事定下了项目的成败。希望这篇笔记能把你在车间安全穿戴识别这个方向上需要绕开的坑提前标出来帮到你。本文还有配套的精品资源点击获取