资讯动态

YOLO安全帽手套检测数据集实战:格式转换、划分与训练避坑指南

发布时间:2026/10/1 4:52:06 来源:尧图企业网站定制
简介面向YOLO系列目标检测学习者兼顾安全施工与智能巡检场景这套数据包以真实场景的安全帽佩戴、手套佩戴检测为主线提供一千张高质量图片并预置VOC、COCO、YOLO三种主流格式标签可以直接导入训练框架省去手工格式转换的麻烦。压缩包内共两千个文件其中xml与txt标签文件占绝对多数另有五个html图文教程、三个Python数据集划分脚本以及一个yaml模型配置整体体积仅五十MB左右轻量易用。已有五百四十四人学习下载适合目标检测入门者、工业安全项目开发者用作基准数据或算法验证。下载后可以按教程逐步完成YOLO环境搭建借助划分脚本自由切分训练集、验证集与测试集并输出ImageSets下的索引文件顺利开展YOLOv5、YOLOv8等模型训练与调参实验。1. YOLO安全帽手套检测数据集先分清它到底是什么才能省下踩坑时间做工地或厂区安全检测的工程师拿到这种打包资料时通常已经踩过一轮坑了网上找数据集下载下来只有图片没有标签或者有标签但只有一种格式想用YOLO训练还得自己写转换脚本。而「YOLO安全帽手套检测数据集(含1000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar」这个标题解决的就是这四件事给足样本、给对齐好的多格式标签、给分类划分工具、给训练路径参考。不过我得先说句实在话1000张图对这个任务来说只是「能起步」的量级距离生产环境还差得远。这篇笔记的目标就是让你拿到这个包以后能在一两个小时里完成从解压到跑通训练的全过程同时把常见的坐标系错位、标签串位、过拟合这些坑提前排掉。适合第一次碰目标检测的从业者也适合有基础但想快速搭一套安全帽检测基线的人。2. 拆包后的三种标签VOC、COCO、YOLO坐标系的换算与对齐2.1 三种格式的存储方式与坐标系先找出唯一的「真源」解压rar之后常见目录结构一般是这样的图片放在一个文件夹VOC格式的XML放在AnnotationsCOCO格式的JSON放在一个文件里YOLO格式的TXT放在labels。文件名通常一一对应比如0001.jpg、0001.xml、0001.txtCOCO则是一个大JSON包含所有图的标注。真正动手之前务必先把三种格式的坐标表示搞明白这是整个流程里最容易翻车的地方格式载体坐标表示训练时直接用VOCXMLxmin, ymin, xmax, ymax左上右下绝对像素否需转换COCOJSONbbox [x, y, width, height]左上角加宽高绝对像素否需转换YOLOTXTclass, cx, cy, w, h归一化到[0,1]是YOLO训练时只认TXT标签内容是「类别编号 归一化后的中心点x、中心点y、宽、高」。这个坐标系在转换时最容易错因为中心点坐标是从左上角往右下算一半的宽和高不是直接拿x1加上x2除以二就了事——分母里还有一个归一化系数。我的习惯是拿到包以后先抽三到五张图手动打开一个VOC XML和对应的YOLO TXT对照一下同一目标转换出来的数值是否一致。如果发布者的三种格式是从同一个标注池导出的那基本没问题但如果不一致你得决定以谁为准。大多数情况下我建议以VOC的XML作为「真源」。原因是VOC格式里包含size节点直接记录了原图的宽和高转YOLO时不需要猜COCO的JSON里虽然也有width和height字段但单独文件容易在手动编辑时被改坏。CLIP工具链里很多转换脚本也是从VOC起步的路线最稳妥。2.2 从VOC XML生成YOLO TXT一套可靠的转换脚本以VOC格式为真源转换YOLO标签的核心公式是cx (xmin xmax) / 2 / width cy (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height下面这段代码假定你的VOC XML放在Annotations/图片宽度高度可以从XML的size节点里读取import os import xml.etree.ElementTree as ET classes [helmet, glove] # 必须与后续 data.yaml 里的 names 顺序保持一致 def voc_convert(size, box): dw 1.0 / size[0] dh 1.0 / size[1] cx (box[0] box[2]) / 2.0 cy (box[1] box[3]) / 2.0 w box[2] - box[0] h box[3] - box[1] return cx * dw, cy * dh, w * dw, h * dh def voc2yolo(xml_path, out_path): root ET.parse(xml_path).getroot() size_node root.find(size) img_w int(size_node.find(width).text) img_h int(size_node.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue # 跳过未在 classes 列表里登记的物体 cls_id classes.index(cls_name) box_node obj.find(bndbox) x1 float(box_node.find(xmin).text) y1 float(box_node.find(ymin).text) x2 float(box_node.find(xmax).text) y2 float(box_node.find(ymax).text) cx, cy, w, h voc_convert((img_w, img_h), (x1, y1, x2, y2)) # 归一化后顺手做一个边界钳制防越界 cx max(0.0, min(cx, 1.0)) cy max(0.0, min(cy, 1.0)) w max(0.0, min(w, 1.0)) h max(0.0, min(h, 1.0)) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) # 批量处理 xml_dir Annotations txt_dir labels_yolo os.makedirs(txt_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue stem os.path.splitext(xml_name)[0] voc2yolo(os.path.join(xml_dir, xml_name), os.path.join(txt_dir, stem .txt))逻辑说明voc_convert里用的是(xminxmax)/2这种算中心点的方法而不是先减再除是因为它在数学上等价但少一步变量传递出bug的概率低。classes列表的顺序决定了类别编号这个顺序必须与训练时的data.yaml完全一致否则模型训练出来预测的类别全是对不上的。参数说明.6f是保留六位小数YOLO训练要求不高六位足够cls_id从0开始这一点很多第一次转格式的人会写错从1开始会导致所有类别偏移一位。2.3 转换后边界越过图像范围怎么办clip的几何与取舍如果你转换的框正好在图像边缘算出来的cx或w可能略大于1也可能小于0。YOLO官方的数据加载器遇到这种情况通常不会报错而是直接裁剪但更多情况下你的训练loss会在前几个epoch出现奇怪的抖动。第一个选择是像上面代码一样在归一化数值上做clip保证所有值都在0到1之间。这种处理简单但会改变框的几何比如一个框越界了10像素归一化后clip会把中心点硬拉到边界上框的宽度不变视觉上这个框会比原来小一圈。第二个选择是在像素坐标系先做clip再重新归一化。也就先让x1 max(0, x1)、x2 min(img_w, x2)然后再走转换公式。这样做几何上更准确但代码里多两步。我的建议如果是包里自带的标签一般不会有越界情况直接skip这一层如果你之后拿自己标的数据来训练务必选第二种因为标注软件手滑把框拉出画布是常态。另外如果你是从COCO格式直接转YOLO注意bbox的表示是[x, y, width, height]和VOC的[xmin, ymin, xmax, ymax]是不同的。转换公式里是cx x w/2、cy y h/2不要套用VOC的(x1x2)/2逻辑。3. 划分脚本不是random一下同一张图不能既进训练又进验证3.1 先检查样本边界图层、标注、类别分布划分脚本是数据集里的第一道闸门。很多人图省事从图片文件名里直接取前80%做训练集后20%做验证集这是最危险的做法因为图片文件在磁盘上的排列顺序往往带有采集时间或场景分组信息。如果前80%都是白天拍的后20%全是黄昏拍的训练出来的模型对黄昏场景几乎零泛化。正确的做法是先统计类别分布。安全帽和手套这两类在工地图片里的出现频率差异可能很大安全帽几乎每张都有手套可能只在少数特写镜头里出现。如果你不按类别分层抽样验证集里很可能一只手套都没有训练出来的mAP看着还行实际一测就露馅。具体到这个小数据集我建议先做三件事第一统计图片总数和标签总数是否一一匹配第二统计每一张图里有哪些类别第三粗略看一眼有没有空标签文件——1000张的包混进去几张0KB的txt很正常训练时YOLO会跳过空标签但如果空标签恰好全分到验证集会让验证集的评估样本数变少。3.2 一份同步三类标签的train/val划分脚本下面这个脚本不依赖sklearn用纯Python的Counter做分层抽样按类别组合分桶再按比例抽取避免同类图片全部挤到同一侧import os import random import shutil from collections import defaultdict src_img_dir images src_label_dir labels_yolo # 以yolo txt为划分参考 out_root dataset_split train_img os.path.join(out_root, images, train) val_img os.path.join(out_root, images, val) train_lab os.path.join(out_root, labels, train) val_lab os.path.join(out_root, labels, val) os.makedirs(train_img, exist_okTrue) os.makedirs(val_img, exist_okTrue) os.makedirs(train_lab, exist_okTrue) os.makedirs(val_lab, exist_okTrue) random.seed(42) sample_list [] bucket_map defaultdict(list) for f in os.listdir(src_img_dir): stem os.path.splitext(f)[0] txt_path os.path.join(src_label_dir, stem .txt) if not os.path.exists(txt_path): continue with open(txt_path) as fp: lines [line.strip().split() for line in fp if line.strip()] classes_in_img tuple(sorted(set(line[0] for line in lines))) bucket_map[classes_in_img].append(stem) sample_list.append(stem) val_ratio 0.2 val_stems set() train_stems [] for bucket_key, stems in bucket_map.items(): random.shuffle(stems) cut int(len(stems) * val_ratio) # 每个桶至少留一个样本进训练集 if cut len(stems): cut - 1 val_stems.update(stems[:cut]) train_stems.extend(stems[cut:]) # 同步复制图片和txt标签 for stem in train_stems: img_name stem .jpg # 如果你的图片是png改成 .png lab_name stem .txt if os.path.exists(os.path.join(src_img_dir, img_name)): shutil.copy(os.path.join(src_img_dir, img_name), train_img) shutil.copy(os.path.join(src_label_dir, lab_name), train_lab) for stem in val_stems: img_name stem .jpg lab_name stem .txt if os.path.exists(os.path.join(src_img_dir, img_name)): shutil.copy(os.path.join(src_img_dir, img_name), val_img) shutil.copy(os.path.join(src_label_dir, lab_name), val_lab) print(ftrain: {len(train_stems)}, val: {len(val_stems)})逻辑说明bucket_map以「类别组合」为桶例如某张图同时有安全帽和手套它的桶键是(glove, helmet)只有安全帽的桶键是(helmet,)。每个桶内部随机打乱后按同样的val_ratio抽出验证集保证安全帽和手套在训练集、验证集里都保持接近的比例。参数说明val_ratio 0.2表示验证集占百分之二十1000张图就是约200张进验证集这个比例对小数据集比较合适。random.seed(42)保证每次运行划分结果一致方便复现。如果你的图片格式是png上面代码里两处.jpg都要改否则复制时会跳过所有png图只复制jpg图。这里有一层更细的坑如果数据集里某个类别组合的样本数特别少比如只有3张「只有手套、没有安全帽」的图那么划分后验证集里可能只有一张手套图随机性太强。解决办法是把val_ratio改小到0.1或者干脆把这组全放进训练集另外从别的桶里人工挤出几张补进验证集。3.3 划分完成后必须检查的3项类别比例、空标签、文件名对齐划分完别急着开训练先跑三个快速检查。第一统计train和val各自的类别数量。可以用一条bash命令完成for split in train val; do echo $split cat dataset_split/labels/$split/*.txt | awk {print $1} | sort | uniq -c done正常情况是两个split里类别0和类别1的数量比例接近且都不为零。如果某个类别在验证集数量为0你需要手动把几张对应的图从训练集挪过来。第二检查有没有0字节的空标签find dataset_split/labels -name *.txt -size 0 -print如果找到空文件原因通常是对应图片里确实没有目标或者是标注时漏了。空标签文件本身不致命YOLO训练时会跳过空图但它会减少你的有效训练样本量。我的经验是统计一下数量如果少于5个可以直接删掉对应图片如果多就说明数据质量有问题得回去看原始标注。第三检查图片和标签是否一一对应。常见错误是图片是001.jpg标签却是001.txt看起来没问题但划分脚本里扫描的是图片目录标签文件名的stem可能不同导致复制时漏掉。一个简单校验for d in train val; do img_count$(ls dataset_split/images/$d/*.jpg 2/dev/null | wc -l) lab_count$(ls dataset_split/labels/$d/*.txt 2/dev/null | wc -l) echo $d: $img_count images, $lab_count labels done如果两边数量不一致返回去检查源文件不要带病训练。4. 跑通YOLO训练的最小配置data.yaml、命令和六个关键参数4.1 data.yaml的写法路径、类别顺序不要想当然现在数据已经分成train和val两个目录接下来写YOLO需要的数据配置文件。这里以YOLOv8为例因为它是目前社区里做检测最常用的改进版本既有预训练权重命令行也直观# helmet_glove.yaml path: /home/user/helmet_glove_dataset # 数据集的绝对根目录 train: images/train val: images/val test: images/test # 如果没有test删除这一行 names: 0: helmet 1: glove有一个必须强调的点names里的编号必须从0开始且不能跳号。如果你写成names: 1: helmet 2: gloveYOLO的数据加载器会直接报错或者类别索引错位导致预测结果乱套。另外path建议写绝对路径。相对路径在你的终端工作目录不在数据集根目录时会各种找不到文件这个坑我至少见过三次。4.2 训练命令中的六个关键参数照着调不翻车配置写好后训练命令可以这样起cd /home/user/helmet_glove_dataset yolo detect train \ datahelmet_glove.yaml \ modelyolov8s.pt \ epochs100 \ batch16 \ imgsz640 \ cos_lrTrue \ patience20 \ projectruns \ namehelmet_glove_exp1这里六个参数是重点参数建议值说明modelyolov8s.pt用预训练权重做迁移学习比从零训练收敛快得多epochs1001000张图配100轮足够配合patience提前停batch16显存16GB左右的显卡够用显存不足先减半不要直接改imgszimgsz640安全帽目标不算特别小640是速度和精度的平衡点cos_lrTrue余弦退火学习率小数据集上比固定阶梯下降更稳patience20连续20轮验证损失不降就停防止空跑参数说明yolov8s.pt是最小的可用预训练模型。有人喜欢一上来用yolov8m或yolov8l但在1000张图这种数据量下模型越大过拟合越快。你可以在第一轮跑yolov8s跑完看验证集mAP如果效果不错就不换如果mAP不到0.8再考虑提一级模型。显存和batch的关系batch16在16GB显存上一般能跑640分辨率但如果你的显卡只有8GB把batch降到8而不是把imgsz降到416——缩分辨率会直接影响小目标检测能力安全帽在画面里占比不大缩图像是保显存的下策。4.3 训练中的损失曲线哪些波动不用管、哪些要停训练起来以后YOLO会输出box_loss、cls_loss、dfl_loss三条曲线。很多人第一次跑时盯着train_loss看训练损失从第20轮到第50轮一直在降心里高兴但验证集mAP纹丝不动这是因为训练损失下降可能只是模型在背诵训练集不是泛化。正确看法是val/box_loss和val/cls_loss才是决定什么时候停的指标。当val曲线开始掉头向上而train曲线继续下降这就是过拟合的信号。patience参数就是为此设计的如果20轮内val损失没有创新低训练会提前终止。另外如果你看到loss曲线在某个epoch突然跳高然后下个epoch又恢复这种孤立尖峰不用管经常是batch里混了一张比较复杂的图导致的。但如果loss跳高后一路不回来那就是学习率过热需要调低初始lr或者把cos_lr关闭回归固定lr先排除变量。5. 安全帽手套检测训练避坑五条从失败里摸出来的经验5.1 现象验证集mAP很高实拍却漏检测这是目标检测项目里最经典的情况验证集mAP达到0.85以上但在工地上随便拍一张测试安全帽漏了。原因验证集和训练集来自同一个数据包图片角度、光线、拍摄设备都高度相似模型记住了「这张图是安全帽」的分布而不是「安全帽这个物体」的本质特征。再加上划分脚本如果没做严格分层某几个场景的图可能全落在训练或验证里让验证集指标虚高。解决第一训练时不要关掉mosaic和随机仿射增强。YOLOv8默认开启了mosaic但如果你的YOLO配置里被别人改过务必确认mosaic1.0第二在训练完后挑几张包外的实拍图放到一个单独的文件夹里跑一遍yolo predict用肉眼判断漏检程度而不是只看mAP。第三如果漏检严重去补充真实场景的图片这比调任何参数都有效。5.2 现象标签类别串位安全帽被识别成手套训练和验证loss都很正常但推理时安全性被预测成手套或手套预测成安全帽而且错误方向统一。原因十有八九是类别索引串了。VOC XML里的name可能是中文标签“安全帽”你的classes列表里写的是helmet脚本里因cls_name not in classes把这个框跳过了所有框的索引整体偏移又或者你用了第2.2节的脚本但classes [glove, helmet]写反了顺序导致类别0变成了手套。解决别用肉眼在编辑器里比对直接写一条命令从原始XML里提取真实类别列表grep -rh name Annotations | sort | uniq -c然后把输出的类别名称和data.yaml里names的顺序逐字核对。如果XML里是中文先把XML里的中文映射成英文再进classes列表。这类问题是所有格式转换流程里最容易出的玄学问题排查路径固定一次通。5.3 现象损失先降后崩训练在某个epoch直接NAN训练到第30轮左右loss突然变成nan终端输出一片乱码重新跑一遍结果一样。原因BN崩溃。当batch size较小时BN层统计的均值和方差波动大加上学习率略高梯度过一段训练后爆炸数值溢出。在小数据集上很多人会把batch设成4甚至2来硬跑这是最常见的导火索。解决先把初始学习率从默认的0.01降到0.001再训练一轮。如果还崩把batch从4提到8或从8提到16让BN统计更稳。还有一个办法换用yolov8n.pt预训练模型模型更小梯度更温和。注意NAN问题不是随机出现的同一配置重复跑大概率复现所以别赌运气按参数顺序排查。5.4 现象1000张图训练到一半就过拟合从第40轮开始验证集mAP停止上涨train_loss还在降val_loss掉头向上。原因数据量少 模型容量够大模型开始死记标签。这种情况在安全帽手套这种外观相对固定的目标上很常见尤其是手套背景一旦相似模型会靠背景纹理做判断。解决先做减法换yolov8n或yolov8s不要一上来就用m或l再把epochs从100砍到60patience设成15让训练在下坡之前停住。数据增强方面可以加一点HSV变换和随机翻转但不要上mixup小数据集上mixup容易把安全帽的颜色带偏。如果你发现val_loss过了某个epoch才上升就用回调保存那个epoch前的权重YOLOv8的best.pt本身就是按val mAP保存的这点比较省心。5.5 现象标签与图片在划分时配对错位训练报错找不到某张图的标签或者打开训练日志发现images/train/foo.jpg对应的labels/train/foo.txt不存在。原因划分脚本扫描的是图片目录但标签的命名后缀或存储目录不一样。最常见的有三种一是图片是.jpg脚本里写死成.png二是源pack里YOLO标签的文件夹叫label而脚本里写的是labels三是图片文件名带_结尾而标签文件没有。解决划分前先统一排查文件名。用bash跑一遍for f in images/*.jpg; do stem$(basename $f .jpg) [ -f labels_yolo/$stem.txt ] || echo missing label for $stem done把所有缺标签的文件名列出来先处理再划分。这个问题最隐蔽因为不是所有图都缺只是少数几张训练前面几百轮完全正常直到踩到那张缺失样本才中断。6. 从训练到可交付混淆矩阵判读与预训练迁移的实操技巧6.1 混淆矩阵的对角线为什么「总合不唯一」训练结束YOLOv8会输出混淆矩阵图。很多人发现矩阵对角线上的数值加起来不等于验证集图片数怀疑自己数据有问题。其实YOLO的混淆矩阵是按行归一化的每一行表示该类实际样本被预测成各个类别的比例加起来是100%如果你按列再看一遍列和行的归一化方向不同总和自然不是唯一值。判读技巧是看「非对角线」的格子安全帽被误检成手套的格子如果超过5%说明两类在外观上有重叠需要补拍区分度高的样本如果某一行的background比例偏高说明该类别在验证集里角度太刁钻模型没见过。6.2 用预训练权重分两阶段训练冻结层与渐进解冻1000张图直接全参数微调速度快但容易过拟合。更稳的做法是两阶段训练。第一阶段冻结backbone只训练head部分yolo detect train datahelmet_glove.yaml modelyolov8s.pt freeze10 epochs30 batch16 imgsz640freeze10表示冻结前10层对yolov8s的结构来说大约等于把backbone的浅层和中层冻住让模型先用已有特征提取能力去拟合安全帽和手套的分类头。第二阶段去掉freeze参数用第一阶段的best.pt作为新权重全量微调yolo detect train datahelmet_glove.yaml modelruns/helmet_glove_exp1/weights/best.pt epochs40 batch16 imgsz640这样做的逻辑是先让输出层快速适应两类目标防止随机初始化的head在前几个epoch产生大梯度把预训练特征冲坏第二阶段再慢慢松开全网络让深层特征往安全帽方向偏移。实际跑下来比一次性全量训练mAP高2到3个点而且更稳定。6.3 从安全帽扩展到手套类别时怎样低成本补数据如果你还要继续在这个数据包上扩展新类别建议先把现有1000张图的增强版本做出来水平翻转、随机亮度抖动、随机旋转15度以内混入训练集。增强图比例控制在原图的二分之一以内太多会让模型对纹理过度敏感。要补真实样本的话优先拍不同光照方向的安全帽和手套叠在一起的场景这类组合样本在这个包里的占比往往不足是模型失败的高发区。我的习惯是每次训练前先按第3.3节的方式打印类别分布确认两类样本比例在1:1到2:1之间才开跑否则先做数据平衡。这个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑