首页 / 资讯中心 / 文章详情

工地安全识别数据集构建与YOLO训练避坑指南

工地安全识别数据集构建与YOLO训练避坑指南 ★ FEATURED ARTICLE
简介这份资源面向计算机视觉学习者、算法工程师及工地/工厂安全管理信息化开发者提供安全帽、反光衣与工作服自动识别的YOLO数据集用于训练和验证人员着装合规检测模型可支撑实时监控画面中的违规告警、抓拍与弹窗提醒等应用。压缩包共2000个文件以1999个txt标注文件和1个yaml配置文件为主txt文件承载图像标注信息yaml用于定义数据集路径与类别配置整体约952.16MB规模适中便于直接接入YOLO训练流程。目前已有1601人学习下载具备一定参考热度。借助该数据集读者可快速搭建施工工地、工厂等场景的着装检测实验完成模型训练、评估与调优并对照标注内容理解安全帽、反光衣、工作服等类别的样本分布为后续部署到OpenCVYOLO实时检测方案提供数据基础适合需要落地安全信息化管理的中高级视觉开发者。1. 工地安全识别数据集为什么你的 YOLO 模型总在反光衣上翻车做过工地视觉项目的人多半遇到过这种场景安全帽检测 mAP 能到 0.9一加上反光衣类别就掉到 0.6 出头工作服更是时好时坏。问题往往不在模型结构而在数据集本身——安全帽是刚性目标、边界清晰反光衣是柔性目标、高反光、易被遮挡工作服又和普通外套高度相似。这三类目标放在同一个 YOLO 检测头里数据分布差异极大标注口径稍有偏差训练就会变成玄学。这个方向要解决的核心问题很具体构建一套能同时覆盖安全帽、反光衣、工作服三类目标的标注数据集并让 YOLO 系列模型在上面稳定收敛。适合谁做智慧工地、园区安防、电力巡检的算法工程师以及想用 YOLO 入门工业检测的学生。下面从数据采集、标注规范、格式转换、训练调参到避坑按我实际落地的顺序讲一遍。2. 三类目标的标注口径与采集策略先定规则再动手2.1 安全帽、反光衣、工作服的边界怎么划标注口径不统一是数据集报废的头号原因。我一般会在动手标第一张图之前先写一份三页以内的标注手册把下面这些规则钉死。安全帽的判定标准相对简单只要画面中人员头部佩戴了硬质防护帽无论颜色都标helmet。未佩戴的头部标head不要标成person的一部分。这里有个容易翻车的点——安全帽被拿在手里、挂在胳膊上、放在地上都不算佩戴不标helmet。我见过不少开源数据集把手里拿的帽子也标了模型学出来就是到处乱框。反光衣的判定要复杂一些。我的规则是人员躯干部分穿着带有反光条的上衣且反光条面积在画面中可见标vest。如果只露出袖子或衣领的一小角不标。反光衣被安全绳、工具包遮挡超过一半时标vest但在后续训练中作为困难样本单独统计。这里的关键是「可见反光条」这个条件因为纯色荧光绿外套和反光衣在低分辨率下几乎无法区分。工作服的判定最容易出问题。我的做法是人员穿着统一制式工装通常是蓝色或灰色连体服/分体服且与普通外套有明显区分时标uniform。如果工人穿的是自己的夹克、羽绒服不标。实际项目中工作服和反光衣经常同时出现——工人穿工作服外面套反光背心这时候两个类别都要标允许框重叠。提示三类目标的标注顺序建议是person→helmet/head→vest→uniform先框人再框穿戴物避免漏标。2.2 采集场景的覆盖度与数量分配数据集的质量上限在采集阶段就决定了。我一般按下面的维度做场景矩阵每个格子至少采 200 张有效图。维度取值说明光照白天顺光、白天逆光、黄昏、夜间补光夜间是反光衣检测的试金石距离近景5m、中景5-15m、远景15m远景小目标决定模型上限遮挡无遮挡、部分遮挡、严重遮挡遮挡样本占比建议 20%-30%角度正面、侧面、背面、俯视俯视是塔吊监控的常见视角天气晴天、阴天、雨天、雾天雨天反光衣反光特性变化大数量上我的经验是安全帽实例至少 8000 个反光衣实例至少 6000 个工作服实例至少 4000 个。如果工作服样本太少可以考虑先用安全帽反光衣两类跑通再增量加工作服。不要一上来就三类一起训否则工作服类别会拖累整体收敛。采集设备方面工地场景我一般用 1080P 网络摄像机和手机各采一部分。手机拍的要特别注意曝光反光衣在自动曝光下容易过曝成一片白建议锁定曝光补偿 -0.7EV 左右。视频抽帧的话相邻帧差异小于阈值就丢弃避免大量重复样本。2.3 用脚本做采集后的初筛与去重采完的原始图不能直接标先跑一遍初筛。下面这个脚本做两件事剔除模糊图、剔除近似重复图。import cv2 import numpy as np from pathlib import Path from skimage.metrics import structural_similarity as ssim def blur_score(img_path): 拉普拉斯方差值越小越模糊 img cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) if img is None: return 0 return cv2.Laplacian(img, cv2.CV_64F).var() def dedup_images(img_dir, blur_thresh80, ssim_thresh0.92): 先按模糊度过滤再按 SSIM 去重 img_dir Path(img_dir) files sorted(img_dir.glob(*.jpg)) kept, hashes [], [] for f in files: score blur_score(f) if score blur_thresh: continue # 模糊图直接丢 img cv2.imread(str(f), cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (256, 256)) is_dup False for h in hashes: if ssim(img, h) ssim_thresh: is_dup True break if not is_dup: kept.append(f) hashes.append(img) return kept if __name__ __main__: result dedup_images(./raw_images, blur_thresh80, ssim_thresh0.92) print(f保留 {len(result)} 张)blur_thresh80是我在工地场景下的经验值低于这个数的图基本看不清反光条。ssim_thresh0.92控制去重力度调低到 0.88 会保留更多相似样本调高到 0.95 去重更狠。注意 SSIM 比较的是灰度缩略图对光照变化不敏感所以不会误删不同光照下的同一场景。跑完这个脚本原始图通常能砍掉 30%-40%标注成本直接降下来。3. 从 LabelImg 到 YOLO 格式标注流程与转换脚本3.1 标注工具选型与团队协作规范单人或小团队LabelImg 够用轻量、支持快捷键。三人以上协作我建议直接上 CVAT 或 Label Studio因为需要审核机制和标注一致性检查。不管用哪个工具类别名必须统一成英文小写helmet、head、vest、uniform、person。不要用中文不要用SafetyHelmet这种驼峰YOLO 训练时类别名不一致会直接报错。标注文件默认是 PASCAL VOC 的 XML 格式每个 XML 对应一张图。团队协作时我会要求标注员每标完 200 张就导出一份统计各类别实例数、平均框面积、宽高比分布。如果某一类平均框面积突然变小说明标注员开始偷懒标小框需要及时纠正。3.2 VOC 转 YOLO 格式的完整脚本YOLO 需要的是每张图一个.txt每行class_id x_center y_center width height全部归一化到 0-1。下面这个脚本处理转换同时做边界检查和类别映射。import xml.etree.ElementTree as ET from pathlib import Path import shutil # 类别映射顺序决定 class_id CLASSES [helmet, head, vest, uniform, person] CLASS_TO_ID {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(xml_path, out_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip().lower() if name not in CLASS_TO_ID: continue # 未知类别跳过避免训练时报错 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) if xmax - xmin 2 or ymax - ymin 2: continue # 过滤无效框 xc (xmin xmax) / 2 / img_w yc (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{CLASS_TO_ID[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) out_path Path(out_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines)) def batch_convert(xml_dir, out_dir, img_dir): Path(out_dir).mkdir(parentsTrue, exist_okTrue) for xml_file in Path(xml_dir).glob(*.xml): img_file Path(img_dir) / (xml_file.stem .jpg) if not img_file.exists(): continue import cv2 img cv2.imread(str(img_file)) h, w img.shape[:2] voc_to_yolo(xml_file, out_dir, w, h) if __name__ __main__: batch_convert(./annotations, ./labels, ./images)CLASSES列表的顺序就是训练时的类别索引一旦确定不要改改了要重新生成所有标签。边界裁剪那几行是后悔药——LabelImg 有时候会标出负坐标或超出图宽的框不裁剪的话 YOLO 训练时归一化会出负数loss 直接 NaN。xmax - xmin 2过滤掉宽度小于 2 像素的框这种框多半是误标。3.3 数据集划分与 data.yaml 配置转换完标签按 8:1:1 划分训练/验证/测试。划分时要注意同一段视频抽的帧不能跨集合否则验证集精度虚高。我一般按视频源分组划分而不是按图片随机划分。# 目录结构 dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml内容如下path: ./dataset train: images/train val: images/val test: images/test nc: 5 names: [helmet, head, vest, uniform, person]nc是类别数必须和names长度一致。如果只做安全帽和反光衣两类改成nc: 2names: [helmet, vest]同时标签文件里的 class_id 也要对应改。我见过有人改了 yaml 没改标签训练时类别全乱mAP 看着还行但预测结果完全对不上。4. YOLO 训练参数怎么设从 yolov8n 到 yolov8m 的实测对比4.1 模型选型与输入分辨率工地场景我一般从 yolov8n 起步跑通流程再换 yolov8s 或 yolov8m 提精度。yolov8n 在 1080P 下推理能到 100 FPST4 TensorRT适合多路实时yolov8m 精度高 3-5 个点但 FPS 降到 30 左右。如果标题里提到 T4 1080P25 帧多路检测那基本锁定 yolov8n 或 yolov8s。输入分辨率默认 640但反光衣的细长反光条在 640 下容易糊。我的做法是训练用 640推理用 960 或 1280 做对比。如果显存够直接训练 960 效果更好但 batch size 要降到 8 或 16。4.2 关键训练参数与含义yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ box7.5 \ cls0.5 \ dfl1.5 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ degrees5 \ translate0.1 \ scale0.5 \ mosaic1.0 \ mixup0.1 \ copy_paste0.1 \ patience30 \ device0lr00.01是初始学习率yolov8 默认 0.01如果 loss 震荡厉害降到 0.005。lrf0.01是最终学习率系数余弦退火到lr0 * lrf。box7.5是框回归损失权重反光衣边界模糊时可以适当调高到 8.5。cls0.5是分类损失权重工作服和普通外套混淆严重时调到 0.8。hsv_s0.7饱和度增强对反光衣颜色变化有帮助。copy_paste0.1是分割增强检测任务里也能用增加小目标样本。mosaic1.0默认开启但最后 10 个 epoch 建议关掉让模型在真实分布上收敛。patience30是早停耐心值30 个 epoch 没提升就停。4.3 训练过程监控与指标解读训练时重点看三个指标box_loss、cls_loss、mAP50。box_loss降到 0.5 以下算正常cls_loss降到 0.3 以下。如果cls_loss卡在 0.8 不降多半是工作服和反光衣类别混淆需要检查标注。验证集上分类别看 mAP。安全帽一般能到 0.92反光衣 0.85 左右工作服 0.75 左右算正常。如果反光衣 mAP 低于 0.7先查验证集里夜间样本占比夜间反光衣过曝会导致漏检。工作服 mAP 低于 0.6考虑合并到person类别或者增加样本。注意训练日志里的instances数量要关注如果某一类实例数远少于其他类模型会偏向多数类。我的经验是各类实例数比例不要超过 3:1。5. 避坑与排查反光衣漏检、工作服误报、标签错位的处理5.1 反光衣夜间漏检严重现象白天反光衣 mAP 0.88夜间掉到 0.45大量漏检。原因夜间补光灯下反光条过曝成白色与安全帽颜色接近模型把反光衣误判为安全帽或背景。另外夜间训练样本不足模型没学到夜间反光特征。解决夜间样本单独增广用hsv_v0.6做亮度扰动模拟不同补光强度。同时在验证集里单独统计夜间子集 mAP不要只看整体。如果夜间样本实在少用copy_paste把白天反光衣实例贴到夜间背景上但要注意光照一致性贴完做一次高斯模糊融合边缘。5.2 工作服被误报为反光衣现象工人穿蓝色工装模型框出vest置信度 0.6 左右。原因标注时把带反光条的工装和纯工装都标了vest或者uniform样本里混入了反光衣。模型学到的特征边界模糊。解决重新审核标注纯工装一律标uniform只有可见反光条的才标vest。如果两类确实难分考虑合并成一个workwear类别或者用多标签分类代替检测。我一般会在训练前跑一遍标签可视化把vest和uniform的框画出来人工过一遍。5.3 标签错位导致 loss NaN现象训练第一个 epoch 就 NaN或者 loss 突然飙到几百。原因VOC 转 YOLO 时坐标没归一化或者 class_id 超出nc范围。也有可能是图片和标签文件名不匹配YOLO 读到了错误的标签。解决转换后跑一遍校验脚本检查所有坐标是否在 0-1 之间class_id 是否小于nc。文件名不匹配的问题用diff对比 images 和 labels 目录的文件名列表缺的补上或删掉。from pathlib import Path def validate_labels(label_dir, nc): for txt in Path(label_dir).glob(*.txt): for i, line in enumerate(txt.read_text().strip().split(\n)): if not line: continue parts line.split() cls_id int(parts[0]) coords [float(x) for x in parts[1:]] if cls_id nc: print(f{txt} 行{i}: class_id {cls_id} 超出范围) if any(c 0 or c 1 for c in coords): print(f{txt} 行{i}: 坐标越界 {coords})5.4 验证集精度虚高现象验证集 mAP 0.95实际部署掉到 0.6。原因训练集和验证集有同一段视频的相邻帧模型见过相似画面。或者验证集场景太单一全是白天顺光。解决按视频源分组划分数据集同一视频的帧只进一个集合。验证集要覆盖所有光照和角度我一般会手动挑 50 张困难样本单独做测试集不参与训练和调参。5.5 小目标安全帽漏检现象远景画面里安全帽只有 10x10 像素模型完全漏检。原因输入分辨率 640 下10 像素目标经过下采样后特征几乎消失。解决训练分辨率提到 960 或 1280或者用切片推理SAHI。如果显存不够用imgsz960加batch8。另一个办法是在数据增强里加scale0.3让模型多见小目标。6. 用 SAHI 切片推理把远景安全帽召回率拉上来训练完模型部署时最头疼的还是远景小目标。我试过直接上 1280 推理T4 上 FPS 掉到 15多路就不够了。后来改用 SAHI 切片推理640 模型也能把远景召回率提 20 个点。SAHI 的思路很简单把大图切成有重叠的小块每块单独推理再合并结果。对 1080P 图我一般切 640x640重叠 128 像素。这样每个安全帽在某个切片里至少占 30 像素以上模型能正常检出。from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction import cv2 # 加载 YOLO 模型 detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathruns/detect/train/weights/best.pt, confidence_threshold0.3, devicecuda:0 ) # 切片推理 result get_sliced_prediction( test_far.jpg, detection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, perform_standard_predTrue # 同时跑整图推理保留大目标 ) # 导出结果 result.export_visuals(export_dir./sahi_output) for pred in result.object_prediction_list: print(pred.category.name, pred.score.value, pred.bbox.to_xyxy())overlap_height_ratio0.2表示切片间重叠 20%太小会漏掉跨切片目标太大推理变慢。perform_standard_predTrue让整图也跑一遍避免切片把大目标切碎。实测在 1080P 工地图上SAHI 把远景安全帽召回率从 0.55 提到 0.78反光衣从 0.62 提到 0.81代价是推理时间增加约 2.5 倍。如果 T4 上跑多路可以只对画面下半部分做切片上半部分天空区域直接整图推理省一半时间。最后说个习惯我每次训完模型都会把验证集里所有漏检和误检的图单独存一个文件夹标上日期和模型版本。下次改数据集或调参时先拿这批困难样本跑一遍看有没有改善。这个习惯帮我省了很多次「改了参数但不知道有没有用」的纠结。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站