安全帽佩戴检测数据集:VOC/COCO/YOLO格式转换与YOLOv8训练实战
2026/8/26 10:59:27 网站建设 项目流程

简介:目标检测是计算机视觉领域的核心任务之一,其工程落地往往始于数据集的规范管理。在实际项目中,不同训练框架对标注格式要求各异:VOC采用XML、COCO使用JSON、YOLO依赖归一化TXT,而格式转换能力直接决定了模型迭代效率。另一方面,数据质量与类别平衡显著影响模型泛化性能,尤其在工业安全场景中,安全帽佩戴检测需要覆盖多角度、多光照条件。YOLOv8作为主流检测框架,结合标准数据集与划分脚本,能快速训练出高精度模型。围绕安全帽检测数据集,详解三种标注格式的转换原理、数据清洗要点及YOLOv8训练全流程,帮助开发者避开工程化中的常见陷阱。 安全帽佩戴检测看起来是个小项目,但真正落地过的人知道,它其实是考验目标检测工程化能力的一个很典型的场景。别的不说,光是把数据集从采集、清洗、标注到训练跑通这一整条链路,就能筛掉一大批只会在Colab上跑官方demo的人。我这次整理了一份实际用过的安全帽检测数据集,1000张标注图片,同时给了VOC、COCO和YOLO三种格式的标签文件,还附带了数据集划分脚本和完整的训练教程。这篇文章就把这个数据集的细节、三种标注格式的转换原理、训练流程以及我踩过的坑一次说清楚。

1. 数据集设计思路与项目整体拆解

1.1 为什么单独强调“三种格式标签”这件事

刚开始做目标检测的人经常会问一个问题:数据集标注格式到底重不重要?我的回答是,格式本身不值钱,但格式转换和统一管理的能力非常值钱。你在网上能找到的公开数据集,比如VOC2007、COCO2017,它们各自都有固定的存储结构。你拿YOLOv8训练,它默认读的是YOLO格式的txt标注;你拿Detectron2训练,它习惯读COCO格式的json;你要是用老牌的SSD或者Faster R-CNN的某些实现,它又要VOC的xml文件。同一个数据集,如果不能在这三种格式之间自由切换,你每换一个框架就要重标一次数据,那个工作量是毁灭性的。

所以我这次做数据集的时候,干脆一次性把三种格式全部生成好,从源头消除“想换个模型结果还要重新标注”的痛点。1000张图片对应的标注文件同步生成,真正做到拿到压缩包就能直接开工。

1.2 安全帽检测的落地场景与数据分布考量

安全帽佩戴检测在工业安全领域的需求一直很稳定。工地出入口的闸机抓拍、塔吊上的高空俯拍、厂区通道的监控摄像头,这些场景都需要实时判断工人有没有正确佩戴安全帽。但不同场景的拍摄角度、光照条件和目标尺寸差别很大,这对数据集的多样性提出了很高的要求。

我做这批数据的时候,刻意控制了几个维度的分布:

  • 场景多样性:包含室内施工、室外工地、夜间低光照、逆光环境、雨天模糊画面等不同条件
  • 目标尺度分布:既有头部占比很大的近景,也有多人密集的远景
  • 姿态覆盖:有正面、侧面、背面,还有低头、仰头、弯腰等动作
  • 遮挡情况:部分图片中头部被手、机械或其他人部分遮挡

这些细节直接影响模型在实际场景中的泛化能力。如果只用那种“人站得整整齐齐、光线充足”的网图训练,部署到真实监控画面中基本会直接崩掉。训练集中的背景如果太干净,模型学到的更多是“环境的背景特征”而不是“安全帽本身”的特征,这个坑很多人踩过。

1.3 项目的整体文件结构

拿到压缩包解压之后,整体目录结构是下面这样的:

safety_helmet_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── voc/ │ ├── coco/ │ └── yolo/ ├── scripts/ │ ├── split_dataset.py │ ├── voc_to_coco.py │ ├── coco_to_yolo.py │ └── voc_to_yolo.py ├── config/ │ └── dataset.yaml ├── train.ipynb └── README.md

images目录存放按train/val/test划分好的原始图片,labels目录下按格式再分三个子目录,scripts目录提供了划分脚本和格式互转脚本。这种组织方式的好处是,你不管用哪个框架,都能在10分钟内把数据路径配好,直接开始训练。

2. VOC、COCO、YOLO三种标签格式的底层逻辑

2.1 VOC格式:标准XML文件结构

VOC格式源自PASCAL VOC挑战赛,它的标注文件是一个XML文件,每个文件对应一张图片。核心结构是这样:

<annotation> <folder>images</folder> <filename>img_0001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>helmet</name> <bndbox> <xmin>312</xmin> <ymin>186</ymin> <xmax>415</xmax> <ymax>302</ymax> </bndbox> </object> <object> <name>head</name> <bndbox> <xmin>450</xmin> <ymin>220</ymin> <xmax>540</xmax> <ymax>340</ymax> </bndbox> </object> </annotation>

注意这里两个重要信息:第一,坐标是像素值,直接对应图片尺寸;第二,尺寸信息写在size节点里,解析时需要读出来做归一化转换。VOC格式最大的优点是人眼可读性好,用文本编辑器打开就能直接看明白标注内容,做数据检查的时候非常方便。

2.2 COCO格式:单一JSON统一管理

COCO格式和VOC有一个本质区别:所有的标注信息都塞进一个JSON文件里。整个文件的顶层结构包含imagesannotationscategories三个核心字段。

{ "images": [ { "id": 1, "file_name": "img_0001.jpg", "width": 1280, "height": 720 } ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [312, 186, 103, 116], "area": 11948, "iscrowd": 0 } ], "categories": [ { "id": 1, "name": "helmet" }, { "id": 2, "name": "head" } ] }

注意COCO格式中bbox是[x, y, width, height]的格式,即左上角坐标加上框的宽高,而VOC里给的是xmin、ymin、xmax、ymax四个边界值。这个差异是最容易在转换中出bug的地方。另外,COCO格式的坐标是float类型,允许小数,而VOC的坐标必须是整数,转换时需要四舍五入。

2.3 YOLO格式:最精简的归一化TXT

YOLO格式的标注是个txt文件,文件名和图片文件名保持一致。每一行代表一个目标,格式为:

class_id x_center y_center width height

其中坐标全部做了归一化,即除以图片宽高,取值范围在0到1之间。一个安全帽+一个未戴安全帽的人头的标注文件长这样:

0 0.405 0.334 0.161 0.276 1 0.477 0.392 0.141 0.292

第一列是类别ID,0代表戴了安全帽,1代表没戴。后面四个值是归一化后的中心点坐标和框的宽高。YOLO格式的好处是极其精简,一个txt文件可以小到几百字节,训练时IO压力小。

2.4 格式转换的核心计算逻辑

从VOC转YOLO是必须掌握的基本操作,核心公式其实只有四个:

# 归一化中心点坐标和宽高的计算公式 x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height

从COCO转YOLO更简单,因为COCO本身已经把框表示成了中心点宽高形式,只需要除以图片宽高归一化即可:

x_center = (bbox[0] + bbox[2] / 2) / width y_center = (bbox[1] + bbox[3] / 2) / height w = bbox[2] / width h = bbox[3] / height

这些转换我写成了现成的脚本放在scripts目录下,你拿到数据后直接用就行。

3. 实操过程:从数据清洗到三种标签生成

3.1 数据采集与初筛的注意事项

数据集的原始图片来源比较杂,有工地现场拍摄的,也有从公开视频中抽取的帧。采集之后最关键的一步是去重,建议用imagededup这类工具,或者直接计算感知哈希值做相似度过滤。不然数据集中大量重复图片,训练出来的模型会对特定画面过拟合,换一个场景效果就拉胯。

去重之后还有两个强制检查项:

  • 分辨率低于640x480的图片直接剔除
  • 画面模糊到人眼都无法判断是否戴了安全帽的图片剔除

1000张图片是最终筛选后的数量。这里给个参考:单类别检测且场景相对固定的情况下,800到1500张可用数据足够跑出一个能用的基线模型。但如果你要做的是多角度多场景的高精度检测,2000张以上会更稳。

3.2 标注工具选型与标注规范

我在标注时用的是LabelImg,工具比较老但稳定。和LabelMe、CVAT相比,LabelImg对VOC和YOLO格式的支持很直接,不需要额外配置。

标注规范有三条硬性要求:

  1. 类别只有两类:helmet(佩戴安全帽)和head(未佩戴安全帽的人头)
  2. 标注框必须紧贴目标边缘,允许有不超过2%像素的误差
  3. 对于严重遮挡的目标,遮挡面积超过50%的不标注

这里特别注意第3条,很多人不管三七二十一什么目标都往上框,结果给模型灌了一堆噪声标注。目标检测标注的核心原则是:模型需要学习什么你就标什么,看不清的宁可不标也不要乱标。

我见过最离谱的做法是把整个人都框成一个helmet,相当于告诉模型“人=安全帽”,这种数据集训练出来的模型怎么可能在真实场景里用。

3.3 数据集划分脚本的完整实现

划分脚本我放在了scripts/split_dataset.py,核心思路是用固定随机种子保证每次划分结果一致,方便复现实验。

import os import random import shutil from sklearn.model_selection import train_test_split random.seed(42) image_dir = "images/all" train_dir = "images/train" val_dir = "images/val" test_dir = "images/test" # 按7:2:1划分数据集 images = [f for f in os.listdir(image_dir) if f.endswith(('.jpg', '.png'))] train_files, test_files = train_test_split(images, test_size=0.1, random_state=42) train_files, val_files = train_test_split(train_files, test_size=0.222, random_state=42) # 注意:test_size=0.222是为了让val占原始数据的20%,而不是22.2%

这里有个细节值得展开讲一下。如果你直接对总数据集做一次train_test_split,得到的是训练集和测试集,然后你再对训练集做一次划分得到验证集,那么第二次划分的test_size参数需要按原始数据比例计算。我写0.222是因为0.222乘以0.9约等于0.2,即总数据的20%用作验证集。很多新手直接写0.2,算下来验证集实际只占总数据的18%,偏差不大但不够严谨。

划分完成后,还要对labels目录下三种格式的文件做同样的划分,确保图片和标注文件严格对应。这里我写了一个sync_labels()函数,根据images目录下已有的图片文件列表,把对应名称的标注文件拷贝到对应子目录。

3.4 三种格式生成与验证的自动化处理

数据划分为三个子集之后,接下来的任务是为每个子集生成对应格式的标签。

从VOC转YOLO的核心代码逻辑如下:

import xml.etree.ElementTree as ET def voc_to_yolo(voc_file, output_file): tree = ET.parse(voc_file) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) with open(output_file, 'w') as f: for obj in root.iter('object'): cls_name = obj.find('name').text cls_id = class_mapping[cls_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) # 边界检查和数据清洗 xmin = max(0, min(xmin, width - 1)) xmax = max(0, min(xmax, width - 1)) ymin = max(0, min(ymin, height - 1)) ymax = max(0, min(ymax, height - 1)) if xmax <= xmin or ymax <= ymin: continue # 跳过无效框 x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")

这里有个很容易被忽略但极其关键的细节:边界检查。原图中有些标注框的xmax或ymax可能超出了图片边界,这是因为标注人员在打框时鼠标略微拖出了画布。如果不做截断处理,YOLO训练时会报错或者导致loss异常。

从VOC转COCO需要把所有xml文件的内容汇总到一个JSON中:

def voc_to_coco(voc_files, output_json): images = [] annotations = [] categories = [{"id": 1, "name": "helmet"}, {"id": 2, "name": "head"}] for image_id, voc_file in enumerate(voc_files, start=1): tree = ET.parse(voc_file) root = tree.getroot() filename = root.find('filename').text size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) images.append({ "id": image_id, "file_name": filename, "width": width, "height": height }) for obj in root.iter('object'): cls_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) bbox_width = xmax - xmin bbox_height = ymax - ymin annotations.append({ "id": len(annotations) + 1, "image_id": image_id, "category_id": 1 if cls_name == "helmet" else 2, "bbox": [xmin, ymin, bbox_width, bbox_height], "area": bbox_width * bbox_height, "iscrowd": 0 }) coco_output = { "images": images, "annotations": annotations, "categories": categories } import json with open(output_json, 'w') as f: json.dump(coco_output, f, indent=2)

每次生成完标签之后,我强烈建议做一个简单的校验,检查所有标注坐标值是否在合法范围内,确保标注框数量大于零,并确认图片数量和标注文件数量一致。我的数据集在发布前已经做过了这些校验,但你如果修改了数据,需要重新执行验证。

4. YOLOv8训练全流程实战

4.1 环境准备与依赖安装

训练推荐使用YOLOv8,它对新手更友好,文档完善,训练流程标准。你只要有一个NVIDIA显卡(6G以上显存就能跑),没有显卡用CPU也能训练,只是速度慢很多。

# 创建虚拟环境(建议Python 3.8以上) conda create -n yolo python=3.9 conda activate yolo # 安装PyTorch(根据自己的CUDA版本选择命令) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装YOLOv8 pip install ultralytics

用CPU训练的要注意:batch size只能设成1到2,且训练时间会是GPU的几十倍。1000张图片、50个epoch,CPU可能要跑几个小时,GPU只要十分钟。这还是没有优化的情况。

4.2 数据集配置文件编写

YOLOv8训练时需要一个YAML配置文件,指定数据集的路径和类别信息。我已经在config目录下准备好了dataset.yaml

path: /path/to/safety_helmet_dataset train: images/train val: images/val test: images/test names: 0: helmet 1: head

这里有几个容易踩的坑:

  • path字段必须是数据集的绝对路径,不能写相对路径,否则YOLOv8在训练时会找不到数据
  • trainval字段填的是相对于path的路径
  • 类别名称必须和标注文件中的类别ID严格对应

4.3 模型训练参数与启动命令

数据准备好了之后,直接跑下面的命令开始训练:

yolo detect train \ model=yolov8s.pt \ data=config/dataset.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ project=runs \ name=helmet_detection

参数选择是有讲究的,我逐个说一下:

  • model=yolov8s.pt:使用s版本作为预训练权重,兼顾速度和精度。如果你追求更高的精度,可以换yolov8m.pt或yolov8l.pt,但显存占用会同步增加
  • epochs=100:1000张图片的数据量,100个epoch完全足够模型收敛。数据量太少反而要担心过拟合,可以在训练过程中观察val loss曲线
  • imgsz=640:训练分辨率,YOLOv8默认就是640。如果你觉得数据中有很多小目标,可以把分辨率提高到768或1024,但显存占用会明显增加
  • batch=16:这个值要根据显存调整。8G显存建议batch=8,16G以上可以开到16到32
  • patience=20:连续20个epoch验证集指标没有提升就提前终止训练,这个机制能节省大量时间

训练过程中要时刻关注两个关键指标:mAP50mAP50-95。mAP50是IoU为0.5时的平均精度,mAP50-95是对不同IoU阈值(从0.5到0.95,步长0.05)计算的平均值。mAP50-95对定位精度的要求更严格,是衡量模型综合能力的重要指标。

4.4 训练结果分析与模型导出

训练完成后,runs/detect/helmet_detection/weights/目录下会生成两个文件:best.ptlast.pt。best.pt是在验证集上表现最好的模型,last.pt是最后一次epoch的权重。做推理部署的时候一定用best.pt,别用last.pt。

模型训练完之后的评估结果,可以用下面的命令查看:

yolo detect val \ model=runs/detect/helmet_detection/weights/best.pt \ data=config/dataset.yaml

如果一切顺利,mAP50在95%以上,mAP50-95在80%左右,这个模型已经相当能打了。拿到后的模型可以导出成ONNX格式,方便部署到边缘设备:

yolo export model=runs/detect/helmet_detection/weights/best.pt format=onnx opset=12

4.5 推理测试与可视化验证

训练完之后,一定要找几张训练集之外的图片做推理测试。不要用训练集里的图片测,那样没有任何说服力。用你自己手机拍的、或者从监控视频里截取的画面测试才能反映真实效果。

yolo predict \ model=runs/detect/helmet_detection/weights/best.pt \ source=/path/to/test_images \ conf=0.5 \ save=True

注意conf=0.5这个参数,它表示只有置信度大于0.5的检测框会被保留。如果你的场景中安全帽比较小,或者画面比较模糊,可以适当降低置信度阈值到0.25,但也要做好误检增多的准备。在检测结果中,helmet类别的框是绿色的,head类别(未戴头盔)的框是红色的,方便直接观察判断。

5. 常见问题与排查技巧实录

5.1 训练时loss一直不下降或者直接爆掉

这个问题比较常见,八成出在数据上。首先检查标注文件里有没有越界的坐标值,用我前面提到的边界截断逻辑处理一下。其次看类别ID是否连续,YOLO要求类别ID必须是从0开始的连续整数,如果有类别ID是3但总共只有2类,训练就会直接报错或者随机失败。

还有个容易被忽视的问题:图片通道数。YOLOv8默认输入三通道RGB图片,如果你混入了单通道灰度图或带透明通道的PNG图,训练时可能会报维度不匹配的错误。至少把图片统一转成RGB格式再拿来训练。

5.2 模型训练完后精度很高但推理速度很慢

推理速度主要取决于模型大小和输入分辨率。yolov8s在GPU上能做到每帧几十毫秒,但如果部署到没有GPU的嵌入式设备里,延迟就会大幅度增加。想提速有两条路:

  1. 用更小的模型结构:yolov8n比yolov8s的参数量减少约50%,精度会有损失但是速度提升明显
  2. 降低推理分辨率:把推理时的imgsz从640降低到416或320,速度提升显著但小目标检测精度会下降

对于安全帽这种目标尺寸相对较大的场景,把推理分辨率降到416对精度影响通常可控,是边缘端部署的一个极好的优化方向。

5.3 远距离小目标检测效果差怎么办

工地监控摄像头的画面里,人往往只占整幅画面的很小一部分,安全帽更是小得可怜。遇到这种情况,升级策略有四个方向:

  • 把训练和推理的分辨率提高到768或者1024,这是最简单的多消耗点显存解决问题
  • 在数据集中加入更多包含小目标的图片,比如在标注时把画面中的远处工人也认真标注出来
  • 使用YOLOv8的P2输出层,在yaml文件中设置model=yolov8s-p2.yaml,让模型保留更高分辨率的特征图
  • 使用SAHI切片推理库,把大图先切成若干小块分别检测再合并结果

第四个方案在超大分辨率的监控画面上效果很好,缺点是推理时间会成倍增加,部署时要做好权衡。

5.4 标注文件与图片错位导致训练结果混乱

这个问题最隐蔽也最危险。我见过有人训练了完整的模型,推理时发现模型把戴了安全帽的人识别成没戴,查了两天才发现是训练集里面某张图片的名称跟标注文件名称对不上,导致模型在错误的图像上学习标签,相当于模型学习了一个错误的映射关系。

规避方法也很简单,训练前写一段校验脚本,循环遍历一遍所有图片,检查对应名称的标注文件是否存在。如果存在,再打开标注文件检查坐标范围是否在图片尺寸内。这一步检查虽然简单,但能帮你避免一整天的无效训练。

6. 数据集的后续扩展与迭代方向

数据集不能一次做完就完事,实际部署后收集到的bad case才是迭代最有价值的数据。我在训练和部署这个安全帽检测模型的过程中的体会是:模型精度不足时先别急着换模型结构,优先检查数据和标注质量;训练跑完看指标之前,先随机抽100张验证集图片,把预测结果可视化出来人眼扫一遍,远远比只看mAP数字更能发现问题。

如果你手上也有工地现场的真实监控数据,建议按照这个数据集的标注规范去补充标注,每次新增200到300张高质量图片就重新训练一轮,模型的场景适应能力会持续提升。这个数据集和配套的脚本可以当作你安全帽项目的起点,别把它当成终点。

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

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

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

立即咨询