☰
YOLO安全帽手套检测数据集实战:三种标签格式转换与YOLOv8训练避坑指南
2026/9/28 7:27:43 网站建设 项目流程

简介:本资源为面向目标检测学习者的YOLO安全帽与手套检测数据集,适用于工地安全监控、工业场景违规穿戴识别等实际项目,也适合作为课程设计、毕业设计或算法练手的训练素材。压缩包共421.34MB、约2000个文件,其中以1987个xml标注文件为主体,另含少量txt列表、html教程与py脚本,覆盖VOC、COCO、YOLO三种标签格式,可直接接入YOLO系列模型训练。数据均来自真实场景,经labelimg标注,标注框质量较高,并附数据集划分脚本,可按需生成训练集、验证集与测试集。内容还包含环境搭建、训练教程及划分脚本等配套材料,帮助读者快速跑通从数据准备到模型训练的完整流程。目前已有331人学习下载,适合希望低成本获取高质量安全穿戴检测数据、快速验证算法效果的开发者与研究者。

1. 安全帽手套检测数据集:5000 张图、三种标签格式,拿到手怎么用

工地现场的安全帽和手套检测,是目标检测落地里最典型的“小目标 + 遮挡 + 光照乱”场景。你手上如果有一个 5000 张图片、同时带 VOC、COCO、YOLO 三种格式标签、还附了划分脚本和训练教程的压缩包,那它其实已经帮你省掉了最耗时的数据整理环节。但真正决定这套数据能不能跑出可用模型的,不是图片数量,而是标签格式转换有没有对齐、类别映射有没有错位、训练集验证集划分有没有泄漏。这篇笔记就围绕“YOLO安全帽手套检测数据集”这个具体对象,把三种标签格式的差异、划分脚本的写法、YOLOv8 训练参数怎么设、以及我实际踩过的坑讲清楚。适合刚拿到数据集准备训练的新手,也适合想检查自己数据管线是否规范的熟手。

2. 三种标签格式到底差在哪:VOC、COCO、YOLO 的坐标逻辑

2.1 坐标原点与归一化方式决定你转换时会不会翻车

VOC 格式的标注文件是 XML,坐标用绝对像素值,原点在左上角,记录的是xmin, ymin, xmax, ymax。COCO 格式用 JSON,坐标同样是绝对像素值,但字段是[x, y, width, height],注意这里是宽高而不是右下角坐标。YOLO 格式则是 TXT,每行一个目标,格式为class_id x_center y_center width height,全部是相对于图片宽高的归一化值,范围 0 到 1。

这三种格式里,VOC 和 COCO 都是绝对坐标,YOLO 是归一化坐标。很多人转换时只除了图片宽度,忘了高度也要除,或者把xmax直接当width用,结果训练时框全偏了。我一般会先写一个校验脚本,随机抽 20 张图把三种格式的框画出来叠在原图上,肉眼确认对齐后再批量转换。

2.2 用 Python 把 VOC 转成 YOLO 的最小脚本

假设你的 VOC 标注在annotations_xml/下,图片在images/下,类别只有helmet和glove两类。下面这个脚本可以直接抄:

import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射,必须和后续训练时的 data.yaml 一致 CLASS_MAP = {'helmet': 0, 'glove': 1} def voc_to_yolo(xml_path, img_path, out_txt_path): tree = ET.parse(xml_path) root = tree.getroot() # 读取图片真实宽高,不要用 XML 里的 size,有些标注工具会写错 with Image.open(img_path) as im: img_w, img_h = im.size lines = [] for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in CLASS_MAP: continue cls_id = CLASS_MAP[name] 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) # 归一化中心点和宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 裁剪到 0-1,防止标注越界导致训练报错 x_center = min(max(x_center, 0), 1) y_center = min(max(y_center, 0), 1) w = min(max(w, 0), 1) h = min(max(h, 0), 1) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_txt_path, 'w') as f: f.write('\n'.join(lines)) # 批量处理 for xml_file in os.listdir('annotations_xml'): if not xml_file.endswith('.xml'): continue stem = xml_file[:-4] img_file = stem + '.jpg' voc_to_yolo( os.path.join('annotations_xml', xml_file), os.path.join('images', img_file), os.path.join('labels_yolo', stem + '.txt') )

这段代码的关键点有三个:第一,图片宽高从PIL.Image读,不信任 XML 里的size字段,因为有些标注工具在图片旋转后不会更新 size;第二,归一化后做了 0 到 1 的裁剪,越界框在 YOLO 训练时会被静默忽略或报 NaN;第三,类别映射用字典显式定义,避免不同标注人员写的Helmet、helmet、安全帽混在一起。

2.3 COCO 转 YOLO 时最容易错的两个字段

COCO 的 JSON 里,bbox是[x, y, width, height],category_id往往从 1 开始而不是 0。转 YOLO 时需要做两件事:把category_id映射成从 0 开始的连续整数,以及把[x, y, w, h]转成归一化的中心点格式。常见做法是先用pycocotools读 JSON,再按image_id分组写 TXT。如果你不想装pycocotools,直接用json库读也行,但要注意 COCO 的images和annotations是两个独立列表,需要自己建索引。

提示:转换完成后,务必用labelimg或labelme打开几张 YOLO 格式的图检查框位置。我见过太多人直接开训,结果 mAP 一直 0.001,最后发现是 COCO 的category_id没减 1。

3. 划分脚本怎么写:别让同一张图同时出现在训练集和验证集

3.1 按图片划分而不是按标签行划分

5000 张图里,如果一张图有多个目标,YOLO 的 TXT 里就会有多行。划分时必须以图片为单位,不能按 TXT 行数随机切分,否则同一张图的目标会被拆到训练集和验证集,造成数据泄漏。我一般用splitfolders或者自己写一个基于文件名的划分脚本。

import os import random import shutil random.seed(42) # 固定随机种子,保证可复现 img_dir = 'images' label_dir = 'labels_yolo' out_dir = 'dataset' # 收集所有图片文件名(不含扩展名) stems = [f[:-4] for f in os.listdir(img_dir) if f.endswith('.jpg')] random.shuffle(stems) n = len(stems) n_train = int(n * 0.8) n_val = int(n * 0.1) # 剩下 10% 做测试集 splits = { 'train': stems[:n_train], 'val': stems[n_train:n_train + n_val], 'test': stems[n_train + n_val:] } for split, stem_list in splits.items(): img_out = os.path.join(out_dir, 'images', split) lbl_out = os.path.join(out_dir, 'labels', split) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for stem in stem_list: shutil.copy(os.path.join(img_dir, stem + '.jpg'), os.path.join(img_out, stem + '.jpg')) shutil.copy(os.path.join(label_dir, stem + '.txt'), os.path.join(lbl_out, stem + '.txt'))

这个脚本做了三件事:固定随机种子、按 8:1:1 切分、把图片和标签同步复制到对应目录。注意random.seed(42)不是可有可无的,否则每次跑出来的划分都不一样,实验无法复现。另外,如果你的数据集里有些图片没有对应的 TXT(即负样本),也要一并复制过去,YOLO 支持空 TXT 作为背景图。

3.2 划分后必须检查类别分布是否均衡

随机划分有个隐患:如果glove类别只占 5%,随机切分后验证集里可能一个glove都没有。我一般会统计每个 split 里各类别的实例数,如果偏差超过 20%,就改用分层抽样。简单做法是按每张图里出现的主要类别分组,再在组内随机划分。

from collections import Counter def count_classes(label_dir, stems): counter = Counter() for stem in stems: txt_path = os.path.join(label_dir, stem + '.txt') if not os.path.exists(txt_path): continue with open(txt_path) as f: for line in f: if line.strip(): counter[int(line.split()[0])] += 1 return counter for split, stem_list in splits.items(): print(split, count_classes(label_dir, stem_list))

打印出来如果发现val里glove数量为 0,就需要重新划分或手动调整。这个检查步骤花不了两分钟,但能避免训练完发现验证集指标完全不可信。

4. YOLOv8 训练参数怎么设:从 data.yaml 到 batch size 的实操

4.1 data.yaml 的路径和类别名必须和划分后的目录一致

YOLOv8 用data.yaml描述数据集路径和类别。假设你的目录结构是dataset/images/train、dataset/images/val、dataset/labels/train、dataset/labels/val,那么data.yaml应该这样写:

path: ./dataset train: images/train val: images/val test: images/test names: 0: helmet 1: glove

注意path是数据集根目录,train和val是相对于path的路径。很多人写成绝对路径,换台机器就报错。names的顺序必须和转换脚本里的CLASS_MAP完全一致,否则模型学到的类别会错位。

4.2 训练命令与关键参数解释

yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ device=0 \ project=runs/helmet_glove \ name=exp1

逐项说明:model=yolov8n.pt是 nano 版本,5000 张图从预训练权重开始微调足够;imgsz=640是 YOLOv8 的默认输入尺寸,如果你的图片里安全帽很小,可以提到 1280,但显存占用会翻倍;batch=16在 8GB 显存上比较稳,如果 OOM 就降到 8;lr0=0.01是初始学习率,微调时如果 loss 震荡厉害可以降到 0.001;patience=20表示 20 个 epoch 验证指标不提升就早停,省时间;device=0指定第一块 GPU,CPU 训练把这一行去掉。

注意:如果你用的是自己下载的yolov8n.pt预训练权重,确保它和你的 ultralytics 版本匹配。我遇到过旧版权重在新版代码里加载后类别数对不上,训练直接报维度错误。

4.3 训练过程中看什么指标判断有没有跑偏

启动训练后,终端会打印每个 epoch 的box_loss、cls_loss、dfl_loss和 mAP。前 5 个 epoch 看 loss 是否稳定下降,如果cls_loss一直不降,大概率是类别映射错了或者标签格式不对。10 个 epoch 后看metrics/mAP50,安全帽手套这种两类目标,5000 张图正常应该能到 0.85 以上。如果 mAP 卡在 0.1 以下,先别调参,回去检查标签。

另外,YOLOv8 会在runs/helmet_glove/exp1下生成results.csv和混淆矩阵图。混淆矩阵能直接告诉你helmet被误判成glove的比例,如果这两类互相混淆严重,说明特征区分度不够,可以考虑加更多负样本或调整 anchor。

5. 避坑与排查:标签、显存、类别不均衡的 5 个血泪记录

5.1 现象:训练第一个 epoch 就报 “No labels found”

原因:YOLOv8 默认在images/train同级的labels/train找 TXT,如果你的标签目录名是labels_yolo或者路径层级不对,它就找不到。解决:要么按 YOLO 默认结构放,要么在data.yaml里显式写train: images/train和val: images/val,YOLO 会自动把images替换成labels。如果目录名不是labels,需要额外配置,但最简单就是改成标准结构。

5.2 现象:训练 loss 正常下降,但 mAP 始终为 0

原因:最常见的是类别 ID 从 1 开始而不是 0。VOC 和 COCO 的类别 ID 经常从 1 开始,转 YOLO 时忘了减 1,导致所有标签的class_id都是 1 和 2,而data.yaml里只定义了 0 和 1。解决:转换后跑一遍grep -r "^2 " labels/看看有没有超出类别范围的 ID,有就重新映射。

5.3 现象:训练到一半突然 CUDA out of memory

原因:batch=16在 640 尺寸下对 8GB 显存刚好卡边,如果验证时开了plots=True或者同时跑其他进程,就会 OOM。解决:把batch降到 8,或者加amp=True开启混合精度。YOLOv8 默认开启 AMP,但某些旧显卡不支持,可以显式关掉再试。

5.4 现象:验证集 mAP 很高,但实际推理时框全偏了

原因:训练时用了rect=True或者imgsz不是 32 的倍数,导致 letterbox 填充后坐标还原出错。解决:推理时用yolo detect predict并确保imgsz和训练时一致,不要手动改。另外检查推理图片的 EXIF 方向,有些手机拍的图带旋转信息,PIL 读出来是正的,但 OpenCV 读出来是歪的。

5.5 现象:glove类别几乎检测不到

原因:类别不均衡,glove实例数可能只有helmet的十分之一。解决:在data.yaml里加fraction没用,正确做法是复制glove多的图片做过采样,或者用 YOLOv8 的cls损失权重参数。更直接的办法是收集更多戴手套的负样本,5000 张图里如果手套太少,模型学不到足够特征。

6. 进阶技巧:用验证集反推标签质量,以及一个我常用的检查习惯

训练完之后,别只看 mAP 数字。我习惯把验证集里预测置信度在 0.3 到 0.6 之间的图全部导出来,人工看一遍。这些“模棱两可”的样本往往暴露两类问题:要么是标注框画得太大或太小,要么是图片本身模糊导致模型不确定。把这些问题图挑出来重新标一遍,再跑一轮,mAP 通常能再涨 2 到 3 个点。

另一个技巧是用yolo detect val的save_json=True参数导出 COCO 格式的预测结果,然后用pycocotools算每个类别的 AP。如果helmet的 AP 是 0.92 而glove只有 0.65,说明手套的标注质量或数量有问题,优先补手套数据,而不是盲目加 epoch。

yolo detect val \ model=runs/helmet_glove/exp1/weights/best.pt \ data=dataset/data.yaml \ save_json=True \ conf=0.001

conf=0.001是为了让所有预测都保留下来,方便后续分析 PR 曲线。跑完会在runs/val下生成predictions.json,用 COCO API 加载后就能按类别看 AP。

最后说一个我自己的习惯:每次拿到新数据集,先花 30 分钟写一个check_dataset.py,统计图片数量、标签数量、类别分布、宽高比分布、有没有空标签、有没有越界框。这个脚本跑通之后,后面所有训练都省心。5000 张图不算大,但标签质量决定上限,模型和参数只是逼近这个上限。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询