ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLO安全帽检测数据集全解析:10000张图片与三种标注格式实战

YOLO安全帽检测数据集全解析:10000张图片与三种标注格式实战 简介在工业安全与智能监控领域目标检测技术正发挥着越来越重要的作用其中安全帽佩戴识别是工地和工厂场景中最典型的落地应用之一。然而构建一个高质量的目标检测数据集并非易事涉及数据标注、格式转换和模型训练等多个环节。VOC、COCO、YOLO作为三种主流的数据标注格式各自有着不同的组织逻辑与适用场景理解它们之间的转换原理是高效使用数据集的前提。本文围绕一套包含10000张图片的安全帽检测数据集详细介绍了从数据集设计、格式转换到训练参数调优的完整流程并结合实际训练经验给出了小目标检测优化、故障排查以及部署扩展的实用建议为工业安全领域的开发者提供了一套可复用的工程实践参考。 做安全帽检测这个事说起来不算复杂但真正动手做数据集和训练的时候坑是一个接一个。最近整理了一份YOLO安全帽佩戴目标检测数据集包含10000张图片并且一次性给全了VOC、COCO和YOLO三种格式的标签外加划分脚本和训练教程。这篇文章就把这套东西从头到尾拆开讲清楚从数据集结构、格式转换原理到训练脚本的参数选择再到我实际跑训练时踩过的那些坑全部记录下来给后面做工业安全、工地监控、施工场景检测的朋友一个完整参考。1. 数据集的整体设计与格式选型思路安全帽佩戴检测属于典型的目标检测落地场景应用价值很直接工地出入口、塔吊摄像头、车间通道只要部署一个实时检测模型就能自动抓拍未佩戴安全帽的人员减少人工盯屏幕的负担。在模型选型上YOLO系列一直是这个场景的首选。原因也很简单检测速度快能满足摄像头视频流的实时性要求生态成熟从训练到部署的链路非常完整而且针对小目标的检测能力在持续增强。我做这套数据集的时候核心目标是让拿到手的人能直接用不用再花大量时间做格式转换和脚本编写所以打包时直接内置了三种主流标注格式。1.1 为什么同时提供VOC、COCO和YOLO三种格式当前主流目标检测框架的数据接口基本就这三类理解它们的区别是使用数据集的第一步。VOC格式本质是一套基于XML的文件组织方式核心是每个XML文件对应一张图片用标签名、坐标框、难易标志等元素描述图中目标位置。COCO格式则是把所有图片和标注统一存进一个JSON文件用数组结构组织更适合大规模数据集管理。YOLO格式最简洁每张图片对应一个TXT文件每行记录类别ID、归一化后的中心点坐标和宽高。我做数据标注时通常先用LabelImg标注生成VOC格式再通过脚本转换出COCO和YOLO格式。之所以保留三种是因为你在用的时候可能碰到这种情况用Detectron2或者MMDetection做对比实验时WiderPerson这类公共数据集本身是COCO格式你的自定义数据集如果是VOC格式就需要额外转换对齐而如果直接训练YOLO系列用YOLO格式最省事少一道转换环节。三份标签全部给齐意味着无论你从哪个框架入手都能直接开工。1.2 10000张图片的规模定位与场景覆盖数据集规模不是盲目堆出来的。对于安全帽检测这种单类目标检测任务10000张图片属于中等偏上的量级关键在于覆盖度。我整理这套数据时重点考虑了以下几个维度拍摄环境包括白天、傍晚、夜间、逆光、阴影等不同光照条件。人物姿态正面、侧面、背面、弯腰、半蹲、行走中等不同姿态。安全帽状态正确佩戴、未佩戴、手持安全帽、帽子戴歪等。镜头距离近景特写、中距离、远景小目标。遮挡情况部分遮挡、多人密集场景、与脚手架或杂物重叠等。单类目标检测最容易出现过拟合的情况是戴帽子的样本远多于未戴帽子的样本模型会倾向于把所有“人”都预测成戴帽。所以我在整理时特别控制了正负样本比例确保未佩戴安全帽的样本占比在35%到40%之间实际训练出来的模型对“漏戴”的召回率才会比较理想。1.3 划分脚本存在的意义很多新手拿到数据集后直接拿全部数据去训练然后发现验证结果好得出奇一到现场就翻车。原因就是没有做数据划分或者划分时不注意随机性。数据划分的目标是把样本集拆分成训练集、验证集和测试集训练集用来更新模型参数验证集用来调整超参数和判断是否过拟合测试集只在最终评估时使用一次模拟模型从未见过的新数据。这份数据集里附带的划分脚本支持按比例划分和按数量划分两种方式同时会检查所有图片和对应标签是否成对存在避免训练到一半因为某张图缺少标签而崩溃。后面在第三节我会把脚本的逻辑和用法完整讲一遍。2. 核心标签格式解析与转换细节既然给了三种格式的标签就有必要把每种格式的文件组织逻辑、关键字段含义和使用时容易踩的坑讲清楚。这部分放在最前面讲是因为我发现很多朋友在网上搜了代码来转换格式但转换出来的标签在训练时报错归根结底是没理解格式本身的约束。2.1 VOC格式XML里的关键字段VOC格式的核心文件是XML结构大致如下annotation folderimages/folder filename000001.jpg/filename size width1920/width height1080/height depth3/depth /size object namehelmet/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin342/xmin ymin512/ymin xmax668/xmax ymax783/ymax /bndbox /object /annotation使用VOC格式时最需要注意的是filename字段和实际文件名必须完全一致包括后缀。有些标注工具生成XML时会把图片路径写成绝对路径换电脑后路径变了训练脚本读取不到图片。我的习惯是拿到数据集后统一把路径相关的字段改成相对路径保证数据集在任意目录都可以移动使用。另一个容易忽视的是difficult字段。如果某个目标严重遮挡或极小标成1表示困难样本在训练时默认会被忽略。但如果你希望模型对这些困难样本也具备检测能力训练时就要显式设置不忽略困难样本。2.2 COCO格式JSON的目录结构与类别管理COCO格式把整个数据集的标注信息集中在一个JSON文件中核心结构如下{ images: [ { id: 1, file_name: 000001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [342, 512, 326, 271], area: 88346, iscrowd: 0 } ], categories: [ { id: 1, name: helmet } ] }COCO标注中bbox存储的是[x, y, width, height]即左上角坐标和宽高这与VOC的[xmin, ymin, xmax, ymax]格式不一样转换时必须做减法。area字段虽然不参与所有模型的训练计算但很多评估脚本会依赖它来计算小目标、中目标、大目标的分布比例所以不能随便填。如果使用脚本批量生成建议统一用width * height计算。iscrowd字段也值得注意。当为1时表示该区域是一群密集目标检测时通常不把该区域作为正样本。安全帽检测场景中如果一群人密集出现每个目标都独立标注会更利于模型学习。所以我的建议是尽量把iscrowd置0并把密集人群中的每个目标单独框出来。2.3 YOLO格式归一化坐标与类别ID的坑YOLO格式的TXT文件每行代表一个目标格式为class_id x_center y_center width height其中坐标值都是相对于图片宽高的归一化比例取值0到1。从VOC坐标转换到YOLO坐标的公式很简单x_center ((xmin xmax) / 2) / image_width y_center ((ymin ymax) / 2) / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height实际转换时最常犯的错误是小数精度不够。有些转换脚本只保留4位小数短边的坐标误差会被放大导致检测框轻微偏移。我在脚本里直接保留6位小数确保训练时读取的坐标精度足够。另一个容易出错的是类别ID的对应关系。VOC标签里的是类别名称而YOLO格式里必须是数字ID如果按字母顺序排序生成ID而数据集里的类别顺序调整了就会导致标签张冠李戴。我整理的数据集里两个类别ID 0是未佩戴安全帽表示头实际上更准确地说应该是personID 1是佩戴安全帽helmet这样定义的逻辑是模型只需要学习人的头部位置并区分是否戴了帽子。2.4 三种格式转换的脚本思路如果你拿到的是其他公开数据集想统一转换成需要的格式我建议按这个思路写转换脚本解析源格式把每个目标的坐标信息读入内存统一存成字典结构键为图片名值为目标框列表。读取每张图片的宽高为后续归一化或反归一化做准备。按目标格式输出分别生成XML、JSON或TXT文件。生成完成后做一轮校验检查所有坐标是否越界、类别ID是否存在、文件是否完整。这步校验非常关键。我见过不少转换完的数据集图片数量和标签数量对不上或者训练时突然报“index out of range”的错误排查一下发现是某些XML文件里的坐标值超过了图片尺寸。所以我在划分脚本里内置了校验逻辑发现异常直接打印文件名方便定位问题数据。3. 实操过程数据划分、训练准备与模型训练这一节是整个数据集最核心的使用流程。我按照实际操作的顺序从目录结构开始到划分脚本的使用再到训练参数的选择完整走一遍流程。3.1 推荐的项目目录结构拿到数据集后我建议按照下面的目录结构组织项目safety_helmet_detection/ ├── dataset/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── annotations/ │ │ ├── train.json │ │ ├── val.json │ │ └── test.json │ └── voc_xmls/ │ ├── train/ │ ├── val/ │ └── test/ ├── scripts/ │ ├── split_dataset.py │ └── visualize_labels.py ├── configs/ │ └── helmet.yaml └── runs/图片和标签分开存放方便不同框架读取时指定不同的路径。annotations目录存放COCO格式的JSON文件voc_xmls目录存放VOC格式的XML文件。这样即使后期需要新增类别或者重新划分也不至于全部推倒重来。3.2 数据划分脚本的工作流程划分脚本的核心逻辑不复杂但我在设计时为了减少使用者的操作成本做了几个细节优化。首先是支持两种划分方式按比例划分和按数量划分。按比例划分适合平时做实验通常按照8:1:1或者7:2:1切分训练集、验证集、测试集。按数量划分适合后期增加数据时保持原有数据集规模例如已经训练了一个模型现在增加了1000张新图片只想把训练集扩到8000张验证集和测试集保持不变。其次是保证图片和标签的严格配对。脚本遍历图片目录时每读取一张图片就去对应的标签目录查找同名标签文件如果缺失就跳过并记录。这一步能过滤掉标注遗漏的数据。最后是随机种子的设置。脚本里固定随机种子保证每次运行生成的划分结果一致。这样做的意义在于复现实验否则每次重新划分训练集都变了模型效果就没有可比性。具体代码逻辑如下import os import random import shutil random.seed(42) image_dir dataset/images/all label_dir dataset/labels/all train_ratio 0.8 val_ratio 0.1 images [f for f in os.listdir(image_dir) if f.endswith(.jpg)] random.shuffle(images) train_count int(len(images) * train_ratio) val_count int(len(images) * val_ratio) train_files images[:train_count] val_files images[train_count:train_count val_count] test_files images[train_count val_count:] for split, files in [(train, train_files), (val, val_files), (test, test_files)]: os.makedirs(fdataset/images/{split}, exist_okTrue) os.makedirs(fdataset/labels/{split}, exist_okTrue) for f in files: src_img os.path.join(image_dir, f) dst_img os.path.join(fdataset/images/{split}, f) shutil.copy(src_img, dst_img) label_file f.replace(.jpg, .txt) src_lbl os.path.join(label_dir, label_file) if os.path.exists(src_lbl): dst_lbl os.path.join(fdataset/labels/{split}, label_file) shutil.copy(src_lbl, dst_lbl) else: print(fMissing label: {label_file})这段代码里有几个点值得注意。一是划分前先把所有图片打乱否则前80%的图片可能都是同一个批次拍摄的场景相似度太高模型在验证集上表现好但在真实场景中泛化能力差。二是通过固定的随机种子保证结果可复现更换机器后运行也不会出现训练集漂移。三是对缺失标签做显式检查宁可跳过也不要让坏数据混进训练集。3.3 数据集的YAML配置文件使用YOLOv5、YOLOv8或YOLOv11训练时需要一个YAML文件描述数据集路径和类别信息。官方推荐的写法是把路径直接写成绝对路径但项目换电脑后绝对路径会失效。我的做法是用相对路径把YAML文件放在项目根目录下路径相对于运行训练命令的工作目录。path: ./dataset train: images/train val: images/val test: images/test names: 0: person 1: helmet如果你的训练脚本在项目的子目录中运行建议把path字段改成相对于该子目录的路径或者直接使用绝对路径。names这个字段对应的类别ID顺序必须和标签TXT文件里的ID完全一致否则模型输出的检测框类别就是错的。3.4 YOLO训练参数选择与完整训练流程很多人训练YOLO时喜欢直接用默认参数这在小数据集上问题不大但在10000张图片的数据集上参数选择会影响最终精度尤其是安全帽检测这种需要识别远距离小目标的场景。我推荐在YOLOv8上使用这样一组参数启动训练yolo train \ modelyolov8s.pt \ dataconfigs/helmet.yaml \ imgsz1280 \ batch16 \ epochs100 \ patience15 \ optimizerAdamW \ lr00.0008 \ lrf0.01 \ weight_decay0.0005 \ device0 \ projectruns \ namehelmet_exp每个参数我都说明一下理由。imgsz设置为1280是因为安全帽在监控画面中常常属于中小目标默认的640虽然训练速度快但小目标特征在多次下采样后可能丢失。输入分辨率提升到1280后小目标的检测精度提升非常明显代价是GPU显存占用上升。如果你的显卡是12GB显存batch设为8更稳妥。batch设置为16这个值需要根据显存动态调整。如果训练中途爆显存优先减小batch而不是减小imgsz因为对于小目标检测来说分辨率比batch更重要。epochs设置为100配合patience15早停策略即连续15个epoch验证集指标没有提升就自动停止。10000张图片的规模一般50到70个epoch就能收敛设置100只是为了防止部分复杂场景下收敛速度慢。早停的触发条件我一般用验证集mAP50作为监控指标这个指标对安全帽检测这种单类任务来说最直观。optimizer选择AdamW而不是SGD这是我在多次实验中的经验。从零训练一个检测器SGD往往更稳但使用预训练权重微调时AdamW收敛更快对小目标的适应性也更好。如果你从零开始训练可以换成SGD初始学习率设置为0.01。lr0设置为0.0008。这个值对应的是AdamW下的初始学习率过大会导致loss震荡过小则收敛极慢。如果你使用的是YOLOv5官方的建议值是0.01针对SGD第一次训练建议直接在训练日志里观察loss曲线的下降趋势再调整。device0表示使用第一张显卡。如果没有GPU环境只能使用CPU训练那就需要把batch降到4甚至2imgsz降到640训练时间会成倍增加。不过10000张图片的数据量在CPU上训练时间成本会非常高强烈建议至少有云GPU环境。训练完成后模型会自动保存训练过程中最优权重和最后一个epoch的权重路径在runs/helmet_exp/weights/下面。测试阶段使用best.pt即可。3.5 标注文件的自动可视化校验在开始训练之前强烈建议先做一次标签可视化校验。这一步的目的一是检查标签坐标和图片内容是否对齐二是直观判断划分后的数据是否存在问题。import cv2 import os image_path dataset/images/train/000001.jpg label_path dataset/labels/train/000001.txt img cv2.imread(image_path) h, w img.shape[:2] colors [(0, 255, 0), (0, 0, 255)] # 类别0: 绿色, 类别1: 红色 with open(label_path, r) as f: for line in f: parts line.strip().split() class_id int(parts[0]) x_center float(parts[1]) * w y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h x1 int(x_center - box_w / 2) y1 int(y_center - box_h / 2) x2 int(x_center box_w / 2) y2 int(y_center box_h / 2) cv2.rectangle(img, (x1, y1), (x2, y2), colors[class_id], 2) cv2.putText(img, str(class_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[class_id], 2) cv2.imwrite(check_000001.jpg, img)检查时重点看三点框是否贴合目标边缘类别ID是否正确绿色是person红色是helmet是否存在明显漏标或错标。一套10000张的数据集不可能每张都看但随机抽30到50张做检查是值得的否则训练出来的模型会继承标注错误。4. 训练过程中的常见问题与排查技巧实录实操过程中我几乎每次都会遇到一些重复出现的问题这里挑出几个高频的整理成速查表方便你自己排查。4.1 高频问题速查表问题现象可能原因解决方案训练时报错 “AssertionError: Label shape (1, 5) is invalid”YOLO标签中的坐标值不在0到1范围内检查是否有标签未归一化重新运行转换脚本训练完成后mAP50很高但实际检测效果差数据集划分时没有随机打乱训练集和验证集场景高度相似重新划分数据确认随机种子生效检测结果中所有目标都被识别为戴帽未戴帽样本比例过低模型学到的是偏置分布扩充未戴帽样本或增加类别权重图像数量与标签数量不一致标注过程中漏标或删除了部分图片运行校验脚本找出缺失标签的图片并处理训练时GPU显存不足 (CUDA out of memory)batch或imgsz过大先降低batch若仍不足再降低imgsz模型在1米距离检测准确在5米外完全失效训练集中近距离样本过多缺少远距离小目标样本增加远景图片数量或提高训练输入分辨率背景复杂时误检率偏高把安全帽形状的物体误判为帽负样本不足模型缺乏对“非安全帽物体”的判别力增加不包含目标的纯背景图片作为负样本4.2 标签坐标越界问题标签坐标越界是YOLO训练中非常典型的报错通常表现为训练集中的某个TXT文件里出现了大于1或小于0的坐标值。产生原因一般是标注框超出了图片边界转换脚本又没有做裁剪。解决思路是写一个清洗脚本对所有标签做范围限制def clamp_label(line, img_w, img_h): parts line.strip().split() class_id int(parts[0]) x_c float(parts[1]) y_c float(parts[2]) box_w float(parts[3]) box_h float(parts[4]) x_c max(0.0, min(1.0, x_c)) y_c max(0.0, min(1.0, y_c)) box_w max(0.0, min(1.0, box_w)) box_h max(0.0, min(1.0, box_h)) return f{class_id} {x_c:.6f} {y_c:.6f} {box_w:.6f} {box_h:.6f}这个处理虽然能避免训练直接崩溃但更好的做法还是从源头解决在转换阶段发现越界框时就根据图片边界裁切标注框而不是简单地把中心点拉回图片内。4.3 安全帽检测小目标优化我观察过很多工地监控画面摄像头通常安装在出入口上方或者塔吊高处人员距离摄像头平均在10米以上。这个距离下一个成人头部在1080P画面中的尺寸可能只有30x30像素。针对这类小目标常规训练策略需要做一些调整。除了前面提到把输入分辨率提高到1280外还可以在YOLO训练中专门增强数据预处理策略。YOLOv8的增强参数中scale控制随机缩放的幅度默认值是0.5对于小目标场景可以适当降低到0.3避免大量小目标被缩得更小。同时mosaic增强对小目标检测非常有效它把4张图拼成一张相当于模型在训练中见到的上下文信息更丰富。但要注意mosaic增强在训练后期的最后10个epoch应该关闭否则模型会持续看到拼接痕迹影响真实场景的检测表现。如果追求极致精度可以采用SAHISlicing Aided Hyper Inference这类切片推理方案将大图切成多个有重叠的小图分别推理后再合并结果。实测在1080P画面下对5米外的小目标SAHI可以把检测精度提升15到20个百分点代价是推理时间增加。我自己在全景监控部署中就是先用YOLO快速检测命中置信度低于阈值时再用SAHI做二次确认兼顾速度和精度。4.4 训练loss曲线的读法训练时很多人只看mAP其实loss曲线更能提前暴露问题。YOLO的训练loss包含三部分box_loss、cls_loss和dfl_loss。安全帽检测是单类任务cls_loss会快速下降如果训练到一半cls_loss还在高位徘徊说明图片中的目标特征没有学习到位。box_loss持续不降则说明回归分支收敛不佳考虑调低初始学习率或增大训练轮数。但loss不是越低越好还要看验证集loss和训练集loss的差距。如果训练集loss持续下降而验证集loss反而上升就出现了过拟合。这时优先增加数据增强的强度或者增加验证集样本量而不是简单地降低epoch数。4.5 和公开数据集混用的一个细节如果你想把这份数据和公开数据集比如SHWD、GDUT-HWD等安全帽数据集混合训练注意类别ID的映射问题。不同数据集的类别定义可能不同有的把“person”和“helmet”分开有的直接定义“head”和“helmet”。混合前必须统一类别ID字典否则同一个模型里ID 0在不同数据集里代表的内容不一致训练时模型会学出混乱的特征。我的做法是统一映射成“person”和“helmet”转完后再运行一次可视化校验脚本抽检混合后的标签是否正确。5. 数据集的扩展思路与部署方向数据集本身是一个基础资源但只用一次训练就结束那太可惜了。基于这套数据还可以向多个方向扩展。5.1 用半监督方式扩充困难样本训练完第一版模型后把模型的预测结果保存下来筛选出置信度低、但人工确认是正确的检测框作为伪标签加入下一轮训练。这个过程在半监督学习中叫自训练。实际使用中我用这个方式从工地上积累的原始视频中自动挖掘了近5000张困难样本第二轮训练的mAP50从89.2%提升到了93.7%。关键点是伪标签的置信度阈值必须设置得比较高我习惯设到0.9以上宁可少加也不加错。5.2 部署侧的优化方向训练好的模型要部署到真实工地摄像头中通常有两种路径。一种是使用TensorRT推理。把best.pt导出为ONNX再转换为TensorRT的engine格式在Jetson Orin或者边缘盒子上的推理延迟可以压到10毫秒左右。导出时注意指定--opset12否则某些算子可能不支持。另一种是直接用YOLO官方的部署方案在Jetson Nano或树莓派上跑轻量模型。这种情况下我建议把模型蒸馏成YOLOv8n虽然mAP50会掉2到3个点但推理帧率可以提高2倍以上在实时监控中更实用。5.3 场景泛化的思考安全帽检测的数据集场景非常多工地、煤矿、油气田、电力检修都有不同的光照、背景和安全帽样式。这套数据集的图片我尽量覆盖多种场景但如果你有特定的部署环境强烈建议采集现场数据后微调模型。我遇到过这种情况用通用数据集训练的模型在建筑工地表现良好但换到某化工厂后由于安全帽颜色变成了蓝色和背景中蓝色管道颜色相近导致漏检率大幅上升。后来补充了该厂区1000张现场图片微调30个epoch后漏检率才降下来。所以数据集永远不是越大越好而是越贴近部署场景越好。这套10000张的数据集适合作为基础后续结合现场数据持续迭代才能达到理想的工业级检测效果。使用这套数据集时我最后再分享一个实操经验训练前花半小时做完标签可视化检查训练中花十分钟读完loss曲线这两步能节省后面至少一整天的问题排查时间。AI模型的训练没有太多玄学数据的干净程度往往比网络的复杂程度更能决定最终效果。本文还有配套的精品资源点击获取
返回列表