ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

安全背心识别数据集:YOLO训练双格式标注与避坑实战

安全背心识别数据集:YOLO训练双格式标注与避坑实战 简介这是一份面向目标检测任务的安全背心识别标签数据集共标注vest、no-vest两个类别覆盖4185张图片的标签信息适用于YOLO系列、Faster R-CNN、SSD等主流深度学习模型。包内已将训练集、验证集和测试集的标签文件按目录分开并提供指定类别信息的yaml配置文件训练时可直接调用省去自行划分和配置类别的步骤。资源共2000个文件以txt标签为主1999个另含1个yaml类别配置文件压缩包大小约254.18MB整体轻量实用。目前已有244人学习下载适合需要安全着装检测数据做训练验证的开发者、学生及研究人员标准的标签格式和预划分结构可帮助快速上手YOLOv5至YOLOv10等算法训练与效果评估。1. 安全背心识别数据集二分类目标检测里最容易被低估的坑工地和厂区的安全背心检测表面上是个二分类目标检测问题框出穿背心的人vest和没穿背心的人no-vest。但真正用 YOLO 训练过这个任务的人都知道难的不是识别“穿了背心”而是 no-vest 这个类别是个开放集合——没穿背心的人、远处模糊的人、只露出半个身子的人全都要归进这一类。这份数据集把这个问题处理得比较干净4185 张图片YOLO txt 和 VOC xml 双格式标注train / validation / test 三套划分已做好yaml 配置也带上了拿来就能直接喂给 YOLOv5 到 YOLOv10 系列训练。对正在做工地安全帽、反光衣、人员合规检测的工程师来说省掉的是最枯燥的整理标签和划分数据集那一步。2. 数据集结构与两种标注格式YOLO txt 和 VOC xml 怎么配合用2.1 文件结构双格式标注与三套划分打开压缩包先看文件清单里面是大量类似train-2032-_jpg.rf.4d4d54f4fe57910a7b6c8ba1c985dfa1.txt这样的标签文件。命名里带.rf.加一串哈希这是 Roboflow 导出的典型特征。.rf.后面那串哈希是标注平台的图片唯一标识图片本身则叫train-2032-_jpg.jpg。训练脚本读取标签时只认图片名前缀所以这串哈希不会影响 YOLO 训练但会坑到第一次自己写数据加载脚本的人后面避坑章节会细说。整体结构可以这样理解内容说明图片文件JPG 格式与标签文件同名前缀标签文件 AYOLO 格式 txt每行一个目标class x_center y_center width height标签文件 BVOC 格式 xml包含 object 节点和 bndbox 坐标配置文件data.yaml声明 nc2 和类别名数据划分train / validation / test 三套已按比例分好两种标注格式并存是这份数据集一个比较省心的设计。YOLO 系训练直接吃 txtFaster RCNN 和 SSD 这类框架常用 VOC 的 xml。我一般拿到这种双格式数据会先写一个脚本交叉校验两种格式的标签是否一致防止标注平台导出时某一张图的格式转换出了问题。校验的思路很简单解析同一张图的 txt 和 xml把坐标都换算成像素值对比每个目标的中心和宽高偏差超过一个阈值就标记出来。import os import xml.etree.ElementTree as ET def parse_yolo_txt(txt_path, img_w, img_h): objects [] with open(txt_path, r) as f: for line in f.readlines(): parts line.strip().split() cls int(parts[0]) x_center float(parts[1]) * img_w y_center float(parts[2]) * img_h width float(parts[3]) * img_w height float(parts[4]) * img_h objects.append((cls, x_center, y_center, width, height)) return objects def parse_voc_xml(xml_path): objects [] 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) for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2 y_center (ymin ymax) / 2 width xmax - xmin height ymax - ymin objects.append((name, x_center, y_center, width, height)) return img_w, img_h, objects这段代码把两种格式解析成统一的内存结构。参数上要注意YOLO txt 存的坐标是归一化到 0~1 的必须乘回图片宽高VOC xml 里是绝对值不需要换算。交叉校验时还要建立一个类别名和类别 id 的映射表因为 xml 里存的是字符串vest而 txt 里存的是数字 0 或 1直接比较会误报不一致。2.2 YOLO txt 标签格式归一化坐标与类别 idYOLO 格式的 txt 标签是这份数据集训练时的主力格式。每行五个数字第一个是类别 id0 代表 vest1 代表 no-vest后面四个是归一化后的中心点 x、y 和框宽高。所谓归一化就是除以图片的原始宽高所以无论图片是 640x640 还是 1920x1080标签值都落在 0 到 1 之间。这里有一个值得注意的边界情况yaml 文件里类别顺序必须和 txt 里的 id 一致。data.yaml 里写names: [vest, no-vest]那么 id 0 永远是 vestid 1 永远是 no-vest。有人训练前手动改了 yaml 里的 names 顺序但没有重新生成 txt 标签结果模型把背心识别成了 no-vest整个训练白费。建议拿到数据先跑一个统计脚本看看每个类别的目标数量分布顺便校验标签格式是否合法。目标检测训练最怕标签里有空文件、越界坐标或者类别 id 超出范围。import os from collections import Counter label_dir labels/train class_counter Counter() error_files [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue filepath os.path.join(label_dir, fname) with open(filepath, r) as f: lines f.readlines() if len(lines) 0: error_files.append((fname, empty file)) continue for line in lines: parts line.strip().split() if len(parts) ! 5: error_files.append((fname, finvalid format: {line.strip()})) continue cls int(parts[0]) if cls not in (0, 1): error_files.append((fname, fclass id out of range: {cls})) continue coords [float(x) for x in parts[1:]] if any(c 0 or c 1 for c in coords): error_files.append((fname, fcoordinate out of [0,1]: {coords})) continue class_counter[cls] 1 print(class distribution:, dict(class_counter)) print(error files:, len(error_files)) for item in error_files[:10]: print(item)这个脚本的价值在于把标签问题暴露在训练之前。常见错误是坐标越界尤其是用标注平台导出的数据有时会多一个空格或者漏一个数字导致解析失败。另一个值得留意的点是空标签文件——某些图片里一个目标都没有YOLO 训练时会忽略这种图片但如果数量太多等于训练时白白跳过了不少样本。2.3 VOC xml 与 YOLO txt 互转迁移到 Faster RCNN 时用得上如果你打算用这份数据训 Faster RCNN 或者 SSDVOC 格式的 xml 就能直接派上用场。但有些框架吃的是 COCO 格式的 json或者要求 txt 放在特定目录下这时候就需要自己做格式转换。我一般保留一份 xml 作为原始标注需要什么格式就临时转而不是把原始数据改来改去。下面这个函数把 xml 转成 YOLO txt适合要重新划分数据集、或者想清理标签时使用。转换的逻辑核心是从 xml 的 size 节点读图片宽高把 bndbox 的绝对坐标换算成归一化坐标。import os import xml.etree.ElementTree as ET CLASS_MAP {vest: 0, no-vest: 1} def convert_xml_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) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt txt_path os.path.join(output_dir, txt_name) with open(txt_path, w) as out_f: for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue cls_id CLASS_MAP[name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h out_f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n)转换前先确认 CLASS_MAP 和 yaml 里的 names 顺序一致否则类别 id 会错位。还有一个细节是输出目录要先创建好脚本里没有自动建目录的逻辑。如果图片里有目标超出了图片边界坐标换算后可能出现宽度或高度为负数这种 xml 建议直接人工检查原始标注不要硬转。3. 直接训练 YOLOv8yaml 配置、训练命令与参数调整3.1 data.yaml 配置路径、类别与验证集这份数据集的 yaml 文件已经写好了但路径大概率需要改。Roboflow 导出的 yaml 里通常写的是绝对路径换一台机器就失效。我拿到后的习惯是先改成相对路径把图片根目录用一个变量管理起来。path: ./datasets/safety_vest train: train/images val: validation/images test: test/images nc: 2 names: [vest, no-vest]yaml 里关键的几个字段是 path、train、val、nc、names。path是数据集根目录train 和 val 填相对于根目录的路径。注意这里 train 指向的是图片目录不是标签目录YOLO 会自动在同级目录下找 labels 子目录。如果你把图片放在train/images那么标签必须在train/labels目录名不能乱改。YOLOv8 对标签目录的查找规则是固定的拿到图片路径把/images/替换成/labels/把扩展名.jpg替换成.txt。很多新手把标签和图片混放在同一个目录训练时日志里会疯狂提示找不到标签。还有一个容易被忽略的点test 目录在训练时不会被用到但 val 目录如果图片太少训练过程的 mAP 曲线会抖得没法看这个在避坑章节单独展开。3.2 训练命令与超参数YOLOv8 是当前训练这类数据最省心的选择ultralytics 库封装好了数据加载、增强、评估全流程命令行就能跑。我用的是yolov8n.pt作为预训练权重起步因为安全背心检测的视觉特征和 COCO 里的人物检测有重叠迁移学习能加快收敛。yolo detect train \ modelyolov8n.pt \ datadatasets/safety_vest/data.yaml \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectruns/train \ namevest_exp1参数说明如下参数建议值说明modelyolov8n.ptn 是 nano训练最快显存充足可换 yolov8s.ptepochs100100 轮够看到收敛趋势早停会提前截断imgsz640默认输入尺寸如果原图小物体多可换 832batch16取决于显存8GB 以下建议降到 8patience20连续 20 轮 mAP 不提升就早停省时间project / nameruns/train/vest_exp1输出目录方便多组实验对比训练开始后重点看终端里每个 epoch 输出的box_loss、cls_loss和mAP50-95。前 10 轮 loss 下降不明显是正常的预训练权重需要先适应新数据的分布。如果 loss 从第一轮就开始剧烈震荡或者 mAP 一直趴在地上不动大概率是标签路径没配对不是模型问题。安全背心数据集有一个特殊之处图片大多来自监控视角人比较小背心和人的比例在整张图里占比不大。这种情况可以把 imgsz 从 640 提到 960小目标召回率会有明显改善。代价是训练速度下降显存占用上升8GB 显存跑 960 分辨率加 batch 8 比较勉强。3.3 类别不均衡vest 与 no-vest 的比例问题二分类目标检测里vest 和 no-vest 的目标数量往往不均衡。安全背心数据集里绝大多数标注框是 vest因为标注时聚焦在穿了背心的人身上no-vest 的框数量明显少。这个不均衡会导致模型偏向预测 vest表现为 recall 高但 precision 低一堆没穿背心的人被漏检。处理不均衡的常见方案有两种。第一种是调整损失权重ultralytics 里没有直接暴露这个参数但可以给每个类别单独设置 loss 系数需要改源码第二种是数据增强对 no-vest 的样本做随机裁剪、旋转、亮度变化变相增加这个类别的样本量。我一般建议先不要动权重直接拿原始数据训一轮看 confusion matrix 里 no-vest 的漏检率到底是多少。有时候真实场景里 no-vest 本身出现频率就低模型把它误检成 vest 的概率虽然高但实际部署时可以通过提高置信度阈值来过滤比改损失函数更简单可控。4. 避坑训练安全背心识别模型的四个真实翻车点4.1 标签文件名对不上.rf. 哈希后缀的坑现象训练日志提示大量 warning类似WARNING: no labels found in labels/train/xxx.jpg训练能跑但 mAP 极低甚至从第一轮开始 loss 就不降。原因这份数据的标签文件名是train-2032-_jpg.rf.4d4d54f4....txt包含 Roboflow 的哈希后缀而图片名是train-2032-_jpg.jpg。如果你在整理数据时把图片和标签分别拷贝到不同目录图片名被重命名了但标签文件还保留着.rf.后缀YOLO 按图片名去找对应的 txt后缀对不上自然找不到。解决用脚本批量去掉标签文件名里的哈希部分让 txt 和 jpg 严格同名。注意去掉的是从.rf.开始的那一段而不是整个后缀。cd labels/train for f in *.txt; do newname$(echo $f | sed s/\.rf\.[a-f0-9]*//) mv $f $newname done这个命令把train-2032-_jpg.rf.4d4d54f4fe57910a7b6c8ba1c985dfa1.txt改成train-2032-_jpg.txt。sed 里的[a-f0-9]*匹配哈希串。改完跑一遍统计脚本确认 txt 数量和 jpg 数量一致再开始训练。4.2 names 顺序错位yaml 与 txt 类别 id 不一致现象模型训练正常loss 正常下降验证集 mAP 也不低但推理时把穿了反光背心的人标成 no-vest没穿的人标成 vest两类完全反了。原因有人打开 data.yaml觉得names: [vest, no-vest]顺序不对改成[no-vest, vest]没有同步修改所有 txt 标签里的类别 id。txt 里原本 id 0 是 vestyaml 改完之后 id 0 变成了 no-vest结果整个模型学到的类别语义全偏了。解决yaml 的 names 顺序是最终标准txt 里每个目标的第一个数字必须和 names 的下标严格对应。要改 names 顺序就必须重写所有标签文件。import os def flip_class_in_txt(txt_path, old_idx, new_idx): with open(txt_path, r) as f: lines f.readlines() new_lines [] for line in lines: parts line.strip().split() if int(parts[0]) old_idx: parts[0] str(new_idx) new_lines.append( .join(parts)) with open(txt_path, w) as f: f.write(\n.join(new_lines) \n)这个脚本把指定类别 id 替换成新 id。跑完再重新统计类别分布确认两个类别的数量比例没有异常变化。4.3 no-vest 是开放类别mAP 虚高但现场误报多现象验证集 mAP50 能到 0.9 以上但拿到工地现场实测误检率很高路过的行人、远处晃动的人影、推车的人都会被框出来。原因no-vest 这个类别的定义是“没穿安全背心”但它本质上是 vest 的补集包含各种姿态、服饰、距离、遮挡下的人。数据集里 no-vest 的样本再丰富也覆盖不了全部现场情况模型学到的是“看起来不像 vest”就归为 no-vest一旦现场出现数据集里没见过的场景就容易乱报。解决部署时提高置信度阈值比如从默认的 0.25 提到 0.5并且只保留 vest 类别的检测结果把 no-vest 类别从输出里过滤掉。如果你的业务目标是“发现没穿背心的人并告警”那么正确做法是只检测 vest倒过来推理没穿的人而不是直接依赖 no-vest 的检测框。4.4 验证集图片太少val 曲线剧烈震荡现象训练过程中 mAP 曲线像锯齿一样剧烈抖动上一个 epoch 0.85下一个 epoch 0.6再下一个又回到 0.8。loss 曲线相对平滑。原因这份数据集的 validation 目录下只有几十张图片。验证集样本太少每张图里的检测结果对整体 mAP 的影响都很大换一个 epoch 的模型预测结果差异就足够让 mAP 跳好几个百分点。解决把 validation 和 test 合并或者把 train 里的一部分图片挪到 validation 里保证验证集至少有 100 张以上。也可以直接把 test 目录当作验证集来用因为它们本来就不参与训练。cp -r datasets/safety_vest/test/images/* datasets/safety_vest/validation/images/ cp -r datasets/safety_vest/test/labels/* datasets/safety_vest/validation/labels/合并后重新统计 validation 图片数量确认在合理范围。如果不想动原始划分还有一个临时做法训练时把 val 参数直接指向 test 目录代价是测试集不再干净后续评估需要重新留数据。5. 验证模型的三种手段混淆矩阵、现场视频帧与阈值调整5.1 用混淆矩阵检查类别混淆训练完不要只看 mAP先看混淆矩阵。YOLOv8 训练结束后会在输出目录生成confusion_matrix.png重点观察 vest 和 no-vest 之间的误检比例。如果 no-vest 被误判成 vest 的比例超过 10%说明模型把大量未穿背心的人当成了正样本部署前必须先处理。from ultralytics import YOLO model YOLO(runs/train/vest_exp1/weights/best.pt) metrics model.val(datadatasets/safety_vest/data.yaml) print(metrics.box.map50)验证结果里map50是 IoU 阈值 0.5 下的 mAPmap50-95是更严格的多阈值平均。对安全背心这类场景我更关注map50因为实际部署时告警框不需要特别精确的边界只要框到人身上就行。5.2 用真实视频帧做压力测试数据集的测试集只能证明模型在“和训练集同分布”的数据上表现好现场监控画面的光照、角度、人流密度完全是另一回事。我会从现场录一段 5 分钟的视频抽帧后跑推理统计误检和漏检。from ultralytics import YOLO model YOLO(runs/train/vest_exp1/weights/best.pt) results model.predict(sourcesite_video.mp4, conf0.45, imgsz640, saveTrue)conf参数是置信度阈值建议从 0.4 开始调。如果误检多就往上加漏检多就往下减。安全背心检测的告警宁可漏一次再复查也不建议误报洗脑因为现场值班人员对高频误报会产生习惯性忽略。5.3 ONNX 导出与部署推理验证通过后把模型导出成 ONNX 格式方便用 TensorRT 或者 OpenVINO 部署到工地的边缘设备。YOLOv8 官方库直接支持导出n 模型导出后大约 12MB 左右在 Jetson Nano 上能跑实时。yolo export modelruns/train/vest_exp1/weights/best.pt formatonnx dynamicFalse imgsz640导出后用 onnxruntime 做一次推理验证确认输出格式符合预期。安全背心检测场景对延迟敏感度中等640 输入在 CPU 上单帧推理大约 40 到 80 毫秒足够用。我做这类项目到最后都会走一遍同样的流程先跑标签校验脚本再训练一个短周期看 loss 趋势然后看混淆矩阵最后拿现场视频实拍测一轮。那次 names 顺序搞反导致整个上午白训之后我每次拿到新数据集都会强制先打印 yaml 里的 names 和任意一个 txt 的类别 id 对照一遍两分钟能避免半天返工。这篇数据集从标注格式到划分都做得比较规整按上面的流程走坑基本能绕开。希望帮到你。本文还有配套的精品资源点击获取
返回列表