ARTICLE DETAIL

资讯详情

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

施工工人防护服检测数据集:YOLO11训练避坑指南

施工工人防护服检测数据集:YOLO11训练避坑指南 简介面向施工现场安全监管与智慧工地应用的工人防护服识别数据集包含572张真实作业场景图像和1427份txt格式标注文件并附1个yaml配置文件压缩包约71.31MB。所有标注框与图片均按通用目标检测格式整理yaml文件用于定义类别名称与路径配置整体2000个文件可直接接入YOLO11等主流训练框架大大降低数据准备成本。数据集已经过基础模型验证识别率达到95.6%可稳定判断工人是否规范穿戴防护服为施工安全智能分析提供可靠依据对复杂作业场景中的目标识别也具备较强参考价值。图片内容覆盖多种光照、角度、远近及作业姿态适合进行模型训练、精度评估、误检分析与不同算法横向对比。目前已有40人学习下载既适合目标检测入门者理解数据构造和训练流程也适合安防、建筑行业算法工程师在生产环境中微调模型、测试部署效果。1. 带标注的施工工人防护服数据集工地安全巡检的刚需还是又一个“标注即正义”的错觉工地安全帽检测做了快十年“安全帽”三个字几乎成了智慧工地 CV 方案的代名词但真正让安全员头疼的从来不是有没有戴帽子而是工人有没有穿防护服、反光衣有没有被灰土盖住、夜间低照度下那件荧光马甲还能不能被摄像头认出来。这类目标外观差异大、遮挡频繁、样本稀少通用模型很难直接迁移。带标注的施工工人防护服数据集正是把这个细分场景从“捎带手检测”变成“单独训练一个模型”的燃料配合 YOLO11 格式的标注组织方式把训练、验证、部署整条链路直接打通。它的价值不是那 95.6% 的识别率数字本身而是让你能在两周内从一个空白目录跑到一个能在工地边缘盒子上实时报警的防护服模型。这篇笔记面向的是要做工地实名制考勤、安全着装检查、或者单纯想让监控从“录下来”变成“看得懂”的算法工程师和系统集成商我会把数据集怎么组织、标签格式怎么转、YOLO11 训练参数怎么定、以及那些让识别率从 90% 卡在 95% 的隐蔽坑一次性讲透。2. 防护服识别数据集的核心构成标注维度、类别平衡与场景覆盖2.1 先搞清楚数据集里“带标注”到底标了什么带标注的施工工人防护服数据集最常见的标注层次是三类第一类是目标框标注用矩形框框出工人身体或躯干区域第二类是关键点标注在肩、肘、膝等关节位置打点用来判断穿戴姿态是否规范第三类是语义分割标注把防护服和反光条区域精确到像素级。对于 YOLO11 训练来说目标框标注是性价比最高的选择——标注成本低、类别边界清晰、模型推理速度快。关键点标注适合需要判断“反光衣是否完全覆盖躯干”的场景但数据采集和标注成本会翻倍而且 YOLO11 的关键点检测分支对关键点数量有限制超过 17 个点就要考虑改造网络结构。语义分割标注在这个场景下属于杀鸡用牛刀除非你要做的是防护服磨损检测否则不建议一开始就上。从标注维度上来说最容易被忽略的是属性标签。同一个工人穿反光衣站在晴天白天和穿棉服外套站在夜间灯光下模型看到的完全是两个目标。属性标签至少要包含光照条件白天/夜间/逆光、遮挡程度无遮挡/轻微/严重、防护服类型反光衣/阻燃服/防化服。这些属性不参与损失函数计算但在数据集划分时一定要作为分层抽样的依据否则你统计出来的 95.6% 识别率可能只是在晴天样本上刷出来的。2.2 类别体系设计别把“穿防护服”做成二分类我在做这个数据集的项目时第一版模型犯过一个典型错误只分“穿防护服”和“未穿防护服”两个类别结果模型在工地上完全没法用。原因很简单工地监控画面里背景极其复杂塔吊、脚手架、搅拌车都在动二分类模型会把所有移动的物体都当成“疑似工人”误报率高到安全员直接把报警系统关了。正确做法是至少三类起步person_with_safety_vest穿防护服的人、person_without_safety_vest未穿防护服的人、worker_equipment被识别为人的安全帽、工具包等干扰物。注意第三类不是无效类别它其实是给模型提供负样本的锚点让模型知道“看着像人但不是工人”的东西长什么样。如果你的场景里还有管理人员和技术员穿便装的情况建议再加一个person_with_normal_clothes类别把“没穿防护服”和“穿着便装但不是工人”这两个语义彻底分开。类别标签文件里需要同时保持中文标签和英文标签的映射关系因为 YOLO11 的data.yaml里类别名用英文能避开中文字符编码问题但最终的告警推送又要用中文所以从数据集标注阶段就要维护这个映射表而不是训练完再补。2.3 数据划分与样本量估算别拿 80/20 分完就开训防护服检测数据集的样本量分配我的经验是遵循“场景优先、类别次之、数量兜底”的原则。首先按工地站点划分训练集和验证集而不是按图片随机划分。同一个工地的摄像头角度、光线条件、背景结构高度相似如果同一批工人出现在训练集和验证集里模型相当于“作弊”了验证集指标会虚高 3-5 个百分点。其次每个类别在验证集中的占比要尽量接近它在真实场景中的出现频率如果夜间样本只占总体 10%那就必须保证验证集里夜间样本也占 10%否则模型的夜间识别能力你根本测不出来。样本量方面一个有效的防护服检测模型每类目标至少需要 2000 个标注框。这不是 YOLO11 的限制而是小目标检测的普遍规律——防护服在 1080P 监控画面里往往只有 30x60 像素目标框的平均绝对面积不足整张图的 2%这类小目标需要大量样本让特征提取器学会从噪声里找反光条纹理。如果你的预算只够标 500 个框那就别急着上 YOLO11先跑 YOLOv8n 把基线做出来用小模型验证数据质量比直接上大模型然后浪费算力更划算。3. 把防护服数据集转成 YOLO11 能吃的格式转换脚本与标签文件组织3.1 YOLO11 的数据集目录结构与 data.yaml 写法YOLO11 的训练入口是ultralytics包它期望的数据集目录结构如下所示。注意images和labels必须分目录存放且文件名一一对应。construction_ppe/ ├── images/ │ ├── train/ # 训练集图片jpg/png 均可 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 每个图片对应一个同名 .txt 文件 │ └── val/ └── data.yaml # 类别定义与路径配置data.yaml是 YOLO11 读取数据集配置的唯一入口一个能直接跑起来的文件长这样# data.yaml - 施工工人防护服检测配置 path: /data/construction_ppe # 数据集根目录建议用绝对路径 train: images/train val: images/val nc: 3 names: 0: person_with_safety_vest 1: person_without_safety_vest 2: worker_equipmentpath字段的坑在于如果用了相对路径YOLO11 会以当前工作目录为基准拼接路径而训练脚本往往在项目根目录执行一不小心就拼出个不存在的目录。训练报错AssertionError: train dataset not found时第一件事永远是用ls -la确认path指向的目录真实存在而不是怀疑数据集文件被删了。nc必须和names的键数量一致多写一个标签会让类别数对不上训练时 loss 曲线会莫名其妙地不下降。3.2 从 VOC/COCO 标注转 YOLO 格式Python 转换脚本很多公开的施工防护服数据集最初发布时的标注格式是 VOC XML 或 COCO JSON而 YOLO11 的ultralytics内部只接收 YOLO 格式的纯文本标签。每个目标的标签行是class_id x_center y_center width height其中坐标均为归一化数值转换脚本如下# voc2yolo.py - VOC XML 转 YOLO 格式 import xml.etree.ElementTree as ET import os def convert_bbox(size, box): VOC 的 xmin, ymin, xmax, ymax 转 YOLO 归一化坐标 dw 1.0 / size[0] # 图片宽度归一化系数 dh 1.0 / size[1] # 图片高度归一化系数 x (box[0] box[2]) / 2.0 # 中心点 x y (box[1] box[3]) / 2.0 # 中心点 y w box[2] - box[0] # 框宽度 h box[3] - box[1] # 框高度 return x * dw, y * dh, w * dw, h * dh def voc_to_yolo(xml_path, output_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) yolo_lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue # 跳过未在类别列表中的标注 cls_id class_map[cls_name] bndbox obj.find(bndbox) box [float(bndbox.find(tag).text) for tag in (xmin, ymin, xmax, ymax)] # 关键一步clip 边界防止标注超出图片范围 box[0] max(0, box[0]); box[1] max(0, box[1]) box[2] min(img_w, box[2]); box[3] min(img_h, box[3]) x_c, y_c, w, h convert_bbox((img_w, img_h), box) yolo_lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(output_dir, txt_name), w) as f: f.write(\n.join(yolo_lines)) class_map {person_with_safety_vest: 0, person_without_safety_vest: 1, worker_equipment: 2} # 对 data/train_annotations 下所有 XML 执行转换 for xml_file in os.listdir(data/train_annotations): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(data/train_annotations, xml_file), labels/train)代码里的clip边界处理是这个脚本最容易出 bug 的地方。公开数据集的标注框偶尔会越界几个像素如果不把越界值强行拉回图片范围内模型训练时计算 loss 会用这个框和真实框匹配但边界归一化坐标可能出现负数或大于 1 的值轻则警告重则训练崩溃。转换完成后千万不要直接开训随手写一个校验函数检查每个 txt 里的每行是否有超出[0, 1]区间的值这个习惯能帮你省掉半小时的排错时间。3.3 训练启动YOLO11 的最小可用命令与原理解读转换完标签训练命令本身简洁到出乎意料# 训练 YOLO11 防护服检测模型 yolo detect train \ data/data/construction_ppe/data.yaml \ modelyolo11n.pt \ epochs200 \ imgsz640 \ batch16 \ device0 \ patience30 \ project./runs \ nameppe_yolo11n一行命令的背后是 YOLO11 在替你完成四件事加载预训练权重yolo11n.pt的骨干网络参数用你提供的标签和图片构建数据加载器并按imgsz640做训练时随机缩放在每一轮迭代里用回归损失和分类损失联合更新权重根据patience30做早停验证集指标连续 30 轮不提升就自动结束。epochs200对防护服数据集来说是标配起步值但绝大多数情况下跑不到 200因为早停会在第 120-160 轮之间触发这是正常的不是模型训练失败。batch16是显存内存卡的经典解药。YOLO11n 是 nano 级模型参数最小16G 显存的显卡能很轻松跑到这个值。如果你用的是 8G 显存的卡batch8才是安全线。还有一个容易被忽视的参数device0多卡机器上如果你不指定Ultralytics 会默认只使用cuda:0但如果有两张卡且显存都是 8G分别用device0,1跑两个实验对比更高效而不是为了省事硬把 batch 翻倍。训练完成后模型权重存在runs/detect/ppe_yolo11n/weights/best.pt注意是best.pt而不是last.pt。last.pt是最后一轮迭代的权重通常有过拟合风险best.pt是验证集 mAP 最高的那个历史权重直接用这个做后续推理和部署。4. 防护服数据集训练避坑五个让识别率卡在 90% 的隐蔽坑4.1 标注框太小导致小目标梯度消失现象训练 loss 在前 20 轮正常下降之后进入平台期验证集 mAP 始终低于 90%对画面远处工人的召回率特别低。原因防护服在监控画面里属于典型小目标目标框面积小于 32x32 像素。YOLO11 的检测头有三个尺度最小尺度 stride 是 32对应特征图上的感受野较大小目标在这个层级的梯度信号弱模型学不到足够信息。解决把imgsz从 640 调到 1280小目标在特征图上占的像素更多。代价是训练时间翻倍、显存占用增加。如果你的 GPU 撑不住 1280就先把原图切成四份训练推理时再用切片拼接效果等同于放大。4.2 夜间样本占比低导致模型“断电”现象白天识别率 96%晚上直接跌到 60% 以下夜间画面里穿反光衣的工人完全不被识别。原因训练集里夜间样本占比不足 5%数据分布严重倾斜。反光衣在夜间红外补光下的纹理和白天完全不同模型从未见过这种特征的防护服。解决不要随机划分数据集按场景分层采样。把夜间样本单独挑出来强制要求训练集和验证集里夜间样本占比都达到 20%。如果夜间样本实在不够用 CLAHE 对白天图像做对比度增强合成伪夜间样本但合成样本不得超过夜间真实样本量的 30%否则模型会学到伪影。4.3 标签类别不平衡worker_equipment 样本是绊脚石现象训练时 loss 曲线震荡剧烈验证集上worker_equipment类别的精确率极低误把安全帽当成人。原因worker_equipment类别收集的样本是“干扰物”但这类样本的标注框通常很小且形态贴近真实工人头部模型在特征空间里难以区分。解决用class_weights参数给少数类别加权。YOLO11 的loss参数里没有直接暴露类别权重但你可以通过重复采样补丁的方式把worker_equipment的样本复制 2-3 份加入到训练集本质是过采样让模型每批能看到更多该类样本。4.4 数据 leak 导致验证集指标虚高现象验证集 mAP 高达 97%模型部署到新工地后识别率只有 70%。原因同一批工人在同一天被多台摄像头拍到或者同一工地的同一场景在训练集和验证集各出现了一次。模型记住了背景而不是记住防护服特征。解决划分数据时按视频片段去重。如果视频里连续 30 分钟没有切镜头整个片段只取 10 帧进训练集或验证集绝不跨集分配。检查方法很简单随机抽 10 张验证集图片看它们的背景结构是否在训练集里出现过。出现即 leak。4.5 预处理配置不匹配现象训练和推理时预处理不一致模型在验证集上正常部署后实时视频流识别率下降 15%。原因训练时开了随机翻转但推理时默认把augmentFalse模型看到的图片模式不一样。或者训练用 BGR 输入推理时用了 RGB颜色通道顺序变了。解决把predict时的augment参数也设为False并确保推理和训练代码里输入图片的通道顺序完全一致。在 ultralytics 体系下model.predict默认走的是训练时同款预处理但如果你自定义了推理脚本调cv2.imread记得cv2.cvtColor(img, cv2.COLOR_BGR2RGB)后再喂进模型。5. 复现 95.6% 识别率的验证流程从 mAP 看数据集质量5.1 mAP0.5 与 mAP0.5:0.95 的区别别被单个数字骗了95.6% 这个数字大概率是 mAP0.5 的指标也就是 IoU 阈值设为 0.5 时的平均精确率均值。对施工工人防护服检测这种场景mAP0.5 与实际业务体验更贴近因为工地告警系统不需要精确到像素级的框贴合框的中心点落在工人躯干区域就能触发报警。而 mAP0.5:0.95 这个指标更严格测试的是模型从 0.5 到 0.95 十个 IoU 阈值下的平均表现数值通常会比 mAP0.5 低 10-15 个百分点。如果一个数据集号称 95.6% 识别率但没标注清楚是哪个指标你要默认它是 mAP0.5用 mAP0.5:0.95 重新评估如果这个值也超过 85%说明这个数据集的小目标标注质量是真的高。验证流程上训练完成后第一件事不是看训练日志里的指标而是用best.pt跑一遍验证集图片推理把预测结果和标注框画在一起输出成 PNG 图肉眼扫一遍。这一步不能省因为训练日志只给你一个聚合数字不告诉你哪个画面里工人被漏检了。我习惯每训练 50 轮就保存一次中间权重用中间权重跑 20 张典型夜间图如果夜间帧的识别效果明显退化马上回滚数据增强策略而不是等训练结束才发现。5.2 一套可执行的验证脚本算 mAP、画混淆矩阵、按距离分桶通过ultralytics提供的 API可以用几行 Python 脚本完成全部验证工作。下面这段脚本会输出 mAP、导出预测结果图、并生成按目标尺寸分桶的识别率报告。# validate_ppe.py - 防护服检测模型验证脚本 from ultralytics import YOLO model YOLO(runs/detect/ppe_yolo11n/weights/best.pt) # 验证集指标 metrics model.val( datadata/construction_ppe/data.yaml, splitval, conf0.25, # 置信度阈值调高会让误报减少但漏报增加 iou0.5, # 验证时 NMS 的 IoU 阈值 save_jsonTrue, # 保存每张图片的详细预测结果 plotsTrue, # 自动画混淆矩阵和 PR 曲线 ) print(fmAP0.5: {metrics.box.map50:.4f}) # 0.956 对应文档里的 95.6% print(fmAP0.5:0.95: {metrics.box.map:.4f}) # 按尺寸分桶统计找出小目标漏检率 per_class_stats metrics.box.p # per-class precision print(Class-wise Precision:, per_class_stats)conf0.25是置信度阈值的常用起点但这个值需要根据业务导向调整。如果工地安全管理系统是自动告警安全员每天收到 50 条报警会直接静音系统此时把 conf 提高到 0.5 是合理的如果是事后查证conf 可以降到 0.15 保证不漏人。metrics.box.map50才是公开数据集报告里常说的“识别率 95.6%”所以当你拿这个数字去和同类工作对比时务必确认对方报的是哪个指标。脚本跑完后runs/detect/val目录下会有confusion_matrix.png和PR_curve.png混淆矩阵里如果person_without_safety_vest被误判为worker_equipment的比例超过 5%说明两类目标的特征边界还没拉干净优先检查标注框是否有错标。5.3 置信度阈值与 NMS 策略部署前的最后一公里验证集指标看起来漂亮不代表部署到现场效果就好因为验证时用的 NMS 参数和实时视频流的 NMS 参数必须保持一致。YOLO11 在推理时会做类别无关 NMS意思是不同类别的框如果 IoU 超过阈值也会互相抑制这在防护服场景里有一个坑工人穿反光衣站在安全帽旁边两个框的重合度很高NMS 可能把安全帽的框当成冗余框删掉导致漏检。解决方法是调低 NMS 的 IoU 阈值从默认的 0.5 降到 0.3让两个框都保留然后再在业务逻辑层合并同类框。这是在精度和召回之间的权衡没有绝对正确的参数我的习惯是拿一段 10 分钟工地监控视频做测试统计漏报率和误报率的接受度。6. 从数据集到产品用 YOLO11 集成实时视频流并做误报过滤当模型在离线验证集上稳定达到 95.6% 识别率后真正的挑战是把它接入实时视频流而不让性能滑坡。常见的做法是直接用ultralytics的model.predict(sourcertsp://..., streamTrue)跑 RTSP 流但直接把推理循环写在主程序里会遇到两个问题视频流抖帧导致跟踪丢失以及重复报警让安全员疲劳。我的方案是把推理从采集线程里解耦用队列传递帧数据再叠加一个轻量的时间窗口去重逻辑——同一目标 2 分钟内只告警一次。# ppe_stream.py - 实时视频流防护服检测与去重告警 import cv2 import queue import time from ultralytics import YOLO model YOLO(runs/detect/ppe_yolo11n/weights/best.pt) frame_queue queue.Queue(maxsize10) recent_alerts {} # 目标ID - 上次告警时间戳 def capture_loop(rtsp_url): cap cv2.VideoCapture(rtsp_url) while True: ret, frame cap.read() if not ret: continue # 流抖动跳过当前帧 frame_queue.put(frame) capture_loop(rtsp://192.168.1.64:554/stream1) # 实际部署换成真实 RTSP 地址 while True: if frame_queue.empty(): continue frame frame_queue.get() results model.predict(frame, conf0.4, iou0.3, classes[0, 1]) now time.time() for r in results: for box in r.boxes: cls_id int(box.cls[0]) if cls_id 0: continue # 穿防护服的目标不触发告警 track_id int(box.id[0]) if box.id is not None else hash(tuple(box.xyxy[0].tolist())) if track_id in recent_alerts and now - recent_alerts[track_id] 120: continue recent_alerts[track_id] now print(f[ALERT] 未穿防护服工人 detected at {time.strftime(%Y-%m-%d %H:%M:%S)})这段脚本的classes[0, 1]限定了只检测“穿防护服的人”和“未穿防护服的人”把worker_equipment彻底挡在推理流程外既省算力又减少误报。box.id来自 YOLO11 的跟踪模块如果你用的是track()而不是predict()模型会自动为每个目标分配 ID而predict()不会所以代码里做了哈希兜底。这里有个经验值实际部署时置信度阈值不要用验证时的 0.25建议至少提到 0.4因为实时流的画质波动、摄像头抖动都会产生假阳性阈值提到 0.4 能过滤掉大部分误报而真正的漏网之鱼靠后续的跨帧二次确认来补。另一个容易被忽视的集成点是区域屏蔽。工地出入口的摄像头会拍到大量穿防护服或未穿防护服的工人如果直接全画面检测出入口的集体进出会导致告警轰炸。我习惯在部署时给推理画面画一个多边形限定检测区域——比如只检测基坑边缘、材料堆放区等高风险区域出入口区域的检测框直接过滤不参与告警判断。这个逻辑用 YOLO11 的results.boxes.xyxy拿到底部中心点坐标再用cv2.pointPolygonTest判断是否落在多边形内部即可不需要改模型。最后说一下模型压缩。如果工地边缘盒子是低算力设备YOLO11n 的推理速度在 Jetson Nano 上大约 25 FPS勉强能实时跑但如果同时接 4 路视频流就会瓶颈。我一般会加一个降采样逻辑抽帧率从 25 降到 10或者把画面 resize 到 960x540 再喂给模型。实测表明 540P 输入在防护服检测场景下 mAP 只下降 1.2%但推理速度提升 50%这是性价比最高的部署优化。不要为了少 1% 的精度去堆算力工地上真正被记住的是系统有没有在工人违规时“拍下来并说清楚”而不是 96% 和 95% 的区别。希望这几段基于数据集的落地经验对你有用。防护服识别做到最后我发现真正拉开差距的不是模型结构而是对数据分布的掌控力——那些在验证集上多刷出的 0.5% 精度几乎全是靠标注质量和样本分层抠出来的。本文还有配套的精品资源点击获取
返回列表