简介:YOLO安全帽手套检测数据集整合了1000张真实场景图片,面向目标检测初学者与安全施工场景应用开发者,可直接用于YOLO系列模型训练与效果验证。包内包含2000个文件,其中1000个xml标注、991个txt标签,配合少量Python划分脚本、YAML配置及HTML训练教程,分别提供VOC、COCO、YOLO三种格式标签,便于切换不同检测框架。资源包整体约50.13MB,文件按功能分类存放,目录结构较清晰。数据集使用LabelImg标注,标注质量高,场景丰富。另附训练集/验证集/测试集划分脚本,以及环境搭建和训练教程(HTML),读者可按需求自行划分数据并快速启动训练。已有544人学习下载,适合作为YOLO安全帽佩戴检测、手套识别等项目的入门数据集与教学素材。
1. 想快速跑通YOLO安全帽手套检测,先解决标签格式这一关
工地上每天有成百上千人在来回走动,单靠监控室肉眼确认谁没戴安全帽、没戴手套,本身就是件会漏掉的事。很多做智慧工地的人第一反应是“用YOLO训练一个检测模型”,但真正动手时才发现:模型不难跑,难的是没有一份能直接给训练器吃的数据——要么图片乱,要么标签格式对不上。名为“YOLO安全帽手套检测数据集”的这份资源,把1000张图片、三套标签(VOC、COCO、YOLO)和一个划分脚本打包在了一起,附带训练教程。它解决的核心问题不是“给你一个训练好的模型”,而是“让你从零把训练流程跑通”。新人可以照着它的目录结构和教程完成第一次训练,熟手也能拿它当数据格式校验的参考基准。别小看这1000张图,安全帽和手套的形态差异足够大,用它验证训练链路完全够用。
2. 三套标签同框:VOC、COCO、YOLO 的本质区别与选择逻辑
2.1 先打开文件看结构:XML、JSON、TXT 标注长什么样
拿到数据包后,第一件事不是训练,而是先认识标签文件长什么样。因为三套格式描述了同一批框,但存储方式完全不同,混着用就会出问题。
VOC 格式的文件后缀是.xml,每个图片对应一个同名 XML,核心信息保存在object标签里。一个安全帽框,看到的是类似这样的内容:
<annotation> <filename>image_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>safety_helmet</name> <difficult>0</difficult> <bndbox> <xmin>412</xmin> <ymin>233</ymin> <xmax>508</xmax> <ymax>321</ymax> </bndbox> </object> <object> <name>glove</name> <difficult>0</difficult> <bndbox> <xmin>860</xmin> <ymin>701</ymin> <xmax>1010</xmax> <ymax>842</ymax> </bndbox> </object> </annotation>这里框的坐标是绝对像素值,xmin/ymin/xmax/ymax一眼能看出物体在图中的位置,适合人读,但不适合直接喂给 YOLO。这就是为什么需要 COCO 和 YOLO 格式共存。
COCO 格式把所有标注写进一个大的 JSON 文件里,结构是三层:images存图片信息和 ID,annotations存每个框的坐标和类别,categories存类别列表。它的坐标不是四点,而是[x, y, width, height],也就是左上角坐标加框的宽高。用 Python 看这个文件最直观:
import json with open("annotations/instances_train.json", "r") as f: data = json.load(f) # 看类别表 print(data["categories"]) # 看第一张图的信息 print(data["images"][0]) # 看第一个标注框 print(data["annotations"][0])这段代码读的是 COCO 标准 JSON 结构。如果打印出的annotations[0]里category_id和bbox都存在,说明文件是完整的。这里的bbox是[x, y, w, h],而x, y是左上角坐标,千万别和 VOC 的顺序混淆。
YOLO 格式则是最“省事”的一种:每个图片对应一个同名.txt文件,一行一个目标,每行只有五个数字:类别ID、中心点 x、中心点 y、宽度、高度。注意,后四个值全部做了归一化,范围是 0 到 1。例如:
0 0.2396 0.2565 0.0500 0.0815 1 0.4870 0.7144 0.0781 0.1306第一行的第一个数字0表示安全帽,第二行的1表示手套。后面四个数分别是中心点坐标和框宽高,都除以了图片的原图宽高。这种格式几乎不需要解析,训练时直接读文本,是 YOLO 系列训练器最友好的一类标签。
2.2 训练时到底选哪套格式:选 YOLO,但为什么还要留另外两套
打开这三套文件后,很多人会陷入选择困难:既然 YOLO 格式最直接,那 VOC 和 COCO 是不是多余?不完全是。选择取决于你用哪个训练框架。
如果你用的是 Ultralytics YOLOv8 或 YOLOv5,标签路径写的是images/train和labels/train,训练器会按规则到labels目录找同名.txt文件。这条路最稳,数据包里 YOLO 后缀的标签就是给你干这个用的。
如果你后续要改用 Detectron2 或 MMDetection,这套框架的默认输入往往是 COCO JSON 或 VOC XML。比如 MMDetection 的 CocoDataset,你给它一个instances_train.json就能跑;而 VOC 数据则适合用VOCDataset直接加载。数据包里保留三套格式,相当于同一份标注给了三次选择权,换框架时不必重新标注,这是这套数据集最大的价值。
我一般会建议第一次训练的人直接走 YOLO 标签,因为省去 JSON 转码的麻烦。但要用对 YOLO 标签,得先确认两点:一是.txt文件名必须和.jpg文件名完全同名,包括前缀零;二是类别索引必须和训练时的data.yaml对齐。数据包里如果默认把0设为安全帽、1设为手套,那训练配置里也要保持同样顺序。这份“对应关系”才是跨格式转换时最常翻车的地方,后面避坑章再细说。
3. 划分脚本拆解:训练/验证/测试的比例分配与文件关联
3.1 划分脚本首先得干什么:保持图片和标签同步,防止样本泄露
数据包既然带了划分脚本,说明一个意思:你不能把1000张图全拿去训练。深度学习训练必须有验证集和测试集。验证集用来在训练过程中监控模型状态,决定什么时候停止;测试集是最后验收用的“考卷”,模型在训练时没见过它,分数才真实。
划分脚本的首要任务,是保证“图片被切走时,标签也被一起切走”。听起来是废话,但手工去分目录时最容易犯的错就是只复制了images/train,漏掉了labels/train,或者两边文件名对不上。其次要保证的是“同场景不泄露”:如果数据集中有同一批工人的连续帧照片,这些图片不能一部分进训练集、一部分进验证集,否则验证集分数虚高。判断方法很朴素——看图片文件名前缀,同一前缀的自己尽量归到同一边。
数据包里的划分脚本,常见做法是扫描images目录下的所有图片,按设定比例随机打散后,把文件名分成三组,再按分组名生成对应的标签目录。如果你在本地打开脚本调试,注意看它有没有随机种子参数,没有的话,每次划分结果都不一样,复现训练效果会很难受。
3.2 等价的划分脚本实现:随机种子、比例、双目录同步
不管数据包里给的脚本长什么样,建议你在本地保留一份自己能完全掌控的划分脚本。下面这个是我习惯用的最小实现,它同时处理images和labels,并且日志会打印每个集合的图片数和标签缺失情况:
import os import random import shutil from pathlib import Path random.seed(42) # 随机种子固定,保证多次运行结果一致 src_images = Path("datasets/images") src_labels = Path("datasets/labels") dst = Path("datasets/split") train_ratio = 0.7 val_ratio = 0.2 # test 占比由 1 - train_ratio - val_ratio 得到 for split_name in ["train", "val", "test"]: for sub in ["images", "labels"]: (dst / split_name / sub).mkdir(parents=True, exist_ok=True) for img_path in src_images.glob("*.jpg"): label_path = src_labels / (img_path.stem + ".txt") if not label_path.exists(): print(f"[WARNING] 标签缺失: {label_path.name}") continue r = random.random() if r < train_ratio: split_name = "train" elif r < train_ratio + val_ratio: split_name = "val" else: split_name = "test" shutil.copy2(img_path, dst / split_name / "images" / img_path.name) shutil.copy2(label_path, dst / split_name / "labels" / label_path.name) print("划分完成")这段脚本的逻辑是把images下的每个.jpg文件作为主索引,找到同名.txt标签,然后按随机数落入训练集、验证集或测试集。它好在哪?第一,标签缺失直接跳过并打印警告,防止脏数据混进训练;第二,random.seed(42)让结果可复现,你在不同机器上跑也能得到完全相同的划分结果。
参数上,train_ratio=0.7, val_ratio=0.2意味着测试集自动占到 0.1,也就是 1000 张图片里约 100 张做测试。如果你的数据量更小,比如不到 500 张,我一般会把train_ratio调到 0.8,给训练集更多样本。
脚本只复制文件而不移动原文件,这是一层后悔药。原目录被搞乱了,随时可以直接删掉split重新划分。比起在原目录上原地挪文件,复制方式更安全。
3.3 三个参数我建议这样调:train占比、随机种子、是否按场景分组
划分脚本里最关键的参数不是比例,而是“随机分配的粒度”。默认做法是对单张图片做随机,这对正常情况够用。但如果这套数据集本身存在同一场景连续帧,光随机就可能导致验证集里混进训练集的“近亲”。
怎么判断有没有这种问题?把划分结果打印出来,看train/images和val/images的文件名前缀是否有重复。文件名往往是按拍摄过程编号的,比如site1_0321.jpg、site1_0322.jpg。一旦发现同一个前缀既出现在训练集又出现在验证集,必须改成按前缀分组再划分:
groups = {} for img_path in src_images.glob("*.jpg"): prefix = img_path.name.split("_")[0] # 取场景前缀 groups.setdefault(prefix, []).append(img_path) for prefix, imgs in groups.items(): r = random.random() # 整个场景组一起分配,不拆开这段代码先把同一场景前缀的图片归为一组,再按组分配,保证同一个场景的照片不会跨集合。这是我在做过几次训练后总结出来的经验:这种分组划分会让验证集分数稍微“难看”,但更贴近真实部署环境。你宁可训练时看到低一点的 mAP,也别被虚高的指标骗了。
4. 用这套数据跑通训练:从Anaconda环境到第一版权重
4.1 目录结构固定成YOLO习惯,避免“No labels found”
在启动训练之前,先解决目录结构。Ultralytics 默认按图片目录找标签目录,它有一套自己的推断规则。最简单的方式是把数据目录摆成它认识的样子:
datasets/ ├── data.yaml ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/images和labels在同一个父目录下,训练器会自动把images/train中的xxx.jpg对应到labels/train中的xxx.txt。如果把标签放在别的位置,或者文件层级不平齐,启动训练时就会出现一行醒目的警告:
WARNING: no labels found in .../labels/train这行警告几乎总是目录问题,而不是数据本身的问题。看到它,第一反应是去确认 labels 目录是否存在、文件名是否和 images 完全同名,而不是怀疑标签文件写坏了。
4.2 数据配置文件data.yaml:两个类别却容易写错的一行
环境配置是个简单但容易卡住的一步。我常用的做法是在 Anaconda 里新建一个独立环境,避免把 TensorFlow、PyTorch 老版本搅在一起。命令如下:
conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics这里用python=3.10主要是兼容性和稳定性好,新版 Ultralytics 对这个版本支持比较稳。装完库之后,把数据配置文件写出来,内容是 YOLO 训练的“连接器”,它告诉模型你的类别是什么:
path: /absolute/path/to/datasets train: images/train val: images/val test: images/test nc: 2 names: 0: safety_helmet 1: glovenc是类别数量,names的索引顺序必须和 YOLO 标签文件里的第一个数字严格对应。数据标注时如果否把安全帽设为0、手套设为1,这里就必须保持一致。很多训练出来检测结果全部错乱,都是因为顺序没对齐,模型本身一点问题都没有。
4.3 启动训练与日志观察:哪些参数值得赌、哪些是玄学
环境就绪后启动训练,一行命令就能跑:
yolo detect train data=data.yaml model=yolov8m.pt epochs=200 imgsz=640 batch=16 device=0展开说明几个关键参数。model=yolov8m.pt表示从预训练权重开始训练,这里选了中等体量的 m 版本,比 n 更准,比 l 更快。数据量只有 1000 张,用 m 已经是上限,再大就容易过拟合,训练时间也白白浪费。imgsz=640是最常见的默认尺寸,这套数据集的原始分辨率如果高于 1280,用 640 训练等于隐式降采样,速度换精度的权衡,新手可以先保持默认。device=0是使用第一块 GPU,没有 GPU 就改成device=cpu,但训练时间会明显变长,1000 张图建议仍想办法用 GPU。
启动之后,不要盯着进度条发呆,重点观察每轮 Epoch 输出的三行损失值:box_loss、cls_loss、dfl_loss。三条曲线在正常训练里应当整体下降,哪怕有波动,趋势必须向下。如果cls_loss突然跳到nan,说明标签里混进了非法数值,最常见的来源是 YOLO 标注的归一化坐标里出现了大于 1 的数字。
等第一轮训练跑完,看尾部表格里的mAP50(B)和mAP50-95(B)。对安全帽和手套这种大目标检测任务,mAP50 能做到 0.8 以上才算可用。如果只有 0.3 或者更低,优先怀疑数据,而不是调参。参数调整这件事,真正值得动手调的是batch和epochs,其它很多调法在 1000 张的小数据集上,收益约等于玄学。训练的本质是让损失下降,日志里的损失变化比任何花哨的参数都诚实。
5. 踩坑记录:五个让人想删项目的暗坑与补救
5.1 训练启动就报“No labels found”,原因却是标签文件尺寸为 0
现象:数据集路径参数全对,labels 目录非空,训练器却始终打印no labels found。
原因:部分.txt标签文件是空文件,0 字节,训练器在读取时认为它没有标注目标,于是视为不存在。这种情况往往发生在标注环节漏掉了某些图片,但转换脚本仍然为每张图片生成了同名标签。
解决:训练前先写一个快速过滤脚本,把空标签连同对应的图片一起清掉,保证每张训练图至少有一个有效目标。
5.2 训练完成后 mAP 很高,但检测时把所有安全帽都识别成手套
现象:训练日志漂亮,mAP50 达到 0.9,但单独跑测试图片时,安全帽和手套的类别标签互相换位。
原因:类别 ID 顺序不一致。数据集转换时把安全帽标成了类别 1、手套标成了类别 0,而data.yaml里把 0 定义成了安全帽,训练器学会的映射关系与你的预期翻转了。
解决:这个坑最隐蔽,模型并没有错,错的是配置。最稳妥的办法是随机挑三张图,用可视化脚本在图上画出真实框和类别名,和原图对比一眼就能看出映射对不对。
5.3 换了一台电脑重新训练,结果和上次完全不同
现象:同样数据、同样参数,两次训练结果差异明显,上一版训练到 150 轮 mAP50 是 0.85,这次到 150 轮只有 0.78。
原因:划分脚本没有固定随机种子,两次划分出的训练集和验证集不一样。测试集也变了,指标自然没法复现。
解决:在你的划分脚本里固定random.seed(),并把种子值写进训练日志。这个细节一开始不处理,后面做实验对比时全部做废。
5.4 loss 在中段反弹一次后持续上升,最终变成 nan
现象:训练到第 40 轮左右,cls_loss突然从 0.2 跳到 2.0,再往后直接变成nan。
原因:学习率过大叠加数据里有异常标注框,比如某个 YOLO 标签归一化坐标写成了1.85,远远超出图片边界,导致梯度爆炸。
解决:先清洗标签数据,把所有坐标限制在 0~1 范围的标签抽出来检查。然后调低学习率,Ultralytics 里可以指定lr0=0.0001,比默认值低一个数量级。数据干净了,这个现象多半不会再出现。
5.5 labelimg 打完标后训练器总报越界错误,检查坐标却没问题
现象:用 labelimg 手动打标导出 YOLO 格式,打开文件看坐标都是 0 到 1 之间的小数,但训练器仍然提示某些框的宽高为 0。
原因:labelimg 导出时如果误把某个目标框拖成了一个点,宽度或高度会被记录为 0,归一化后仍然是 0,训练器计算损失时对 sqrt 或 log 操作就会异常。
解决:数据清洗时过滤掉w <= 0 or h <= 0的框。这个踩坑概率不高,但只要标了几百张图,必然会遇到一次。
6. 一个立刻能做的验证技巧:批量测试集可视化与置信度门限调整
训练拿到第一版权重后,不要急着部署,先在测试集上做一次全量检查和可视化。最好的验证方式不是只看汇总指标,而是把每个检测结果画出来存成图,人眼扫一遍比看 mAP 数字更能发现问题。
下面这个脚本遍历测试集,对每张图跑一次预测,并把置信度低于设定阈值的框过滤掉,最后输出一张带安全帽和手套颜色区分的可视化图:
from ultralytics import YOLO import cv2 import glob model = YOLO("runs/detect/train/weights/best.pt") threshold = 0.4 # 置信度门限,低于这个值的检测结果直接丢弃 output_dir = "test_visual/" for img_path in glob.glob("datasets/split/test/images/*.jpg"): results = model.predict(img_path, conf=threshold, imgsz=640) boxes = results[0].boxes for box in boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cls_id = int(box.cls[0]) conf = float(box.conf[0]) label = f"{model.names[cls_id]} {conf:.2f}" color = (0, 255, 0) if cls_id == 0 else (0, 165, 255) cv2.rectangle(results[0].orig_img, (x1, y1), (x2, y2), color, 2) cv2.putText(results[0].orig_img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) out_name = img_path.replace("/", "_") cv2.imwrite(output_dir + out_name, results[0].orig_img) print("可视化完成")这里conf=threshold直接传进predict方法,相当于调整推理时的置信度门限,这是部署前最常用到的参数之一。门限设为 0.4 的意思是:凡是模型只有四成把握的判断,全部不显示。门限设得越高,误检越少,但漏检也会变多。你在真实监控画面里如果发现手套经常抓不到,可以把门限降到 0.25;如果发现误检烦人,就往上调到 0.55。小数据集训练出的模型,门限一般落在 0.3 到 0.5 之间比较平衡,以可视化结果为准,不必死磕。
跑完后,把输出目录里的图按“工人正确佩戴”“未戴安全帽”“手套颜色与背景接近”三类各看几张,你会很快发现数据集的盲区在哪里。有些图安全帽颜色鲜亮,检测很稳;有的场景手套是白色、背景也是白色,框就会糊在一起。
我自己的习惯是每次换数据集后,都拿一张正面照、一张侧光照、一张仰拍图做快速体检,同时调整置信度门限直到三张图都能稳住关键框,再谈进一步部署。这步看着不起眼,实际为后续省了大量无谓的调参时间。训练这事,数据和验证比模型结构更值得花时间。希望这篇笔记能帮你把分坑踩在前面,顺利跑出第一版可用的检测权重。
本文还有配套的精品资源,点击获取