YOLOv5安全帽检测数据集制作:VOC与YOLO格式转换及训练实战

📅 2026/8/26 12:03:45
YOLOv5安全帽检测数据集制作:VOC与YOLO格式转换及训练实战
简介目标检测是计算机视觉的核心任务之一在工业安全领域有着广泛应用。安全帽检测作为典型场景对保障施工人员安全具有重要意义。模型性能的上限往往取决于训练数据的质量而公开数据集与真实场景之间存在标注粒度、光照条件、目标尺度等差异直接使用难以满足落地需求。因此构建一套贴合现场场景的数据集成为工程师的基本功。本文从数据采集与清洗、标注规范、VOC与YOLO格式的转换原理出发介绍YOLOv5训练过程中的关键参数调整与排坑技巧。通过合理的类别定义、坐标转换脚本和训练策略即使只有几百张图片也能训练出可靠的安全帽检测模型为工地监控、厂区巡检等场景提供高效智能的视觉解决方案。 说起YOLOV5安全帽检测数据集很多人第一反应是“网上不是有现成的吗直接下载不就行了”。但真正在工地监控、厂区巡检这类场景里跑过一遍就会发现公开数据集和现场数据之间总隔着一道鸿沟标注粒度对不上、场景光照差异大、小目标扎堆、视频抽帧出来的画面模糊得没法看。自己动手做一套带yolo格式和voc格式的安全帽检测数据集再把基于YOLOv5的检测模型训练起来这个流程几乎是每个做工业视觉落地的工程师都绕不过去的基本功。这篇文章就把整个项目从头到尾拆开讲从数据采集、图片标注规范、VOC和YOLO两种格式的转换脚本到YOLOv5训练参数调整和实际排坑。目标很直接让你看完就能照着搭一套自己的安全帽检测数据集并且能把模型训到能用的水平。1. 项目需求分析与整体设计思路1.1 安全帽检测为什么值得自己动手做一次安全帽检测在目标检测里属于“场景单一、类别简单、但是坑特别多”的典型任务。说它场景单一是因为绝大多数监控摄像头都是固定角度俯拍背景无非是工地、厂房、井下通道这些地方说它类别简单是因为核心目标就两类或者三类戴了安全帽的头部、没戴安全帽的头部有时候再加一个完整人体用于人员统计。但真正做起来你会发现这个任务比想象中刁钻。安全帽在画面里经常只有几十个像素人和人之间还会互相遮挡帽子的颜色和背景里的水泥地、安全护栏很容易混淆再加上雨天反光、夜间补光、戴着帽檐遮挡额头……这些问题加在一起直接决定了一个模型是真正能上线还是只在测试集上好看。正因为场景足够聚焦安全帽检测非常适合作为第一次完整走通“数据集构建 - 标注 - 格式转换 - 模型训练 - 部署验证”这条链路的选择。它不会像通用目标检测那样类别多到标注到崩溃又能把数据质量、标注一致性问题暴露得很彻底。很多团队甚至用这个项目作为新同学接触目标检测的入门训练。1.2 自制数据集与公开数据集的取舍网上的安全帽相关数据集并不少SHWD、Global Safety Helmet Dataset、各类开源安全帽检测集合加起来带标注图片量级从几千张到几万张都有。但对落地项目来说直接拿来用通常会遇到几个实际问题。第一是标注粒度不一致。有的数据集只标helmet和head有的把person也标了还有的把安全帽再细分成反光背心、安全背带等更多类别。标准不统一模型学出来的语义边界就是模糊的到了你的场景里反而成了干扰。第二是场景分布偏差。公开数据集大量来自网络抓图和国外工地你真正要部署的现场可能是国内某个钢结构厂房摄像机安装高度、俯仰角度、光照条件都不同。第三是数据泄露隐患公开数据集训练出来模型的实际泛化能力需要严格验证不能拿过来就说能商用。所以我一直建议公开数据集可以当预训练和补充数据用但核心训练集一定要从自己的目标场景里采集并重新标注。自己做数据集的成本说白了就是时间和耐心换来的是对每一张图片、每一个框都心里有数模型出问题的时候你能直接定位到是哪一类样本不够而不是对着一个黑盒数据集干瞪眼。1.3 格式选型为什么要同时准备yolo格式和voc格式VOC格式和YOLO格式是目标检测领域最常用的两种标注存储方式。VOC格式本质上是一个XML文件把图片名称、尺寸、目标类别和真实坐标xmin, ymin, xmax, ymax全部写在标签里可读性极强人眼一看就懂。YOLO格式则是每个图片对应一个txt文件每行记录类别id和归一化后的中心点坐标、宽高格式紧凑模型训练时读取效率很高。做项目的时候我建议两种格式都保留。原因很简单VOC格式是“通用交换格式”后续想切到Faster R-CNN、SSD、mmdetection或者其他框架VOC标注可以直接用YOLO格式则是YOLO系列训练时的输入格式YOLOv5、YOLOv8、YOLOv11这些版本全部都吃txt格式。如果你只留了YOLO格式某天想换个非YOLO系模型试试就得重新标注或者写脚本反推只留VOC格式训练YOLO的时候每次都要临时转。最省事的做法是标注阶段统一用VOC格式保存训练前用脚本批量转成YOLO格式两边文件都存放在数据集目录里。这样既保留了可读性又兼顾了训练效率而且转换脚本写一次以后所有项目都能复用。2. 数据采集与预处理实战2.1 图片素材从哪里来安全帽检测数据集的图片来源我按性价比从高到低排一下。排第一的是现场视频抽帧。工地、厂区、仓库的监控录像通常24小时都在录从里面按一定间隔抽帧比如每5秒抽一帧就能获得大量连续场景的图片。这种方法最大优势是场景完全贴合真实部署环境但注意抽帧时不要连续抽否则相邻帧几乎一模一样造成数据冗余训练时不会带来额外信息。排第二的是手机或相机实地拍摄。带着设备去现场围绕不同角度、不同距离拍一批照片能覆盖监控摄像头拍不到的死角和近距离细节。这个办法在项目初期尤其好用几百张高质量的现场照片就能撑起第一版模型。排第三才是公开数据集和网络图片。公开数据集适合补充极端情况比如夜间施工、雨天、特殊颜色的安全帽这些在单一现场不一定拍得到。网络图片则需要严格过滤检查分辨率、水印、场景是否和你的目标接近。另外还有一个经常被忽略的来源把已经部署但效果不好的历史视频拿出来重新分析。旧系统抓拍到的漏检片段、误检片段每一帧都是金子因为它们本身就代表着模型容易犯错的区域补进去效果立竿见影。2.2 清洗与初筛别让脏数据浪费训练时间很多人拿到图片就直接开始标注这是个大坑。训练集的质量上限决定模型的效果上限而这个上限在数据清洗阶段就已经基本确定了。我习惯按这几条标准筛图。模糊图片直接删。手持拍摄、视频快速移动导致的运动模糊人眼能看出来但模型学不到有效特征保留它们只会让训练loss震荡。判断标准很简单把图片放大到100%如果安全帽边缘轮廓看不清删。少于一定像素的目标不标或者删。比如安全帽区域的像素宽度小于20px这种目标即使标注了YOLOv5在640分辨率下也很难学到有效特征反而会干扰正负样本的平衡。当然如果你的场景里小目标特别多那应该通过提高训练分辨率来解决而不是硬标一堆模糊小框。重复或近似重复的帧要多样化挑选。视频抽帧产生的图片往往连续几帧几乎一样这类重复数据会让模型对某些特定姿态过拟合。抽样的时候打乱时间点同一段视频里不同时间、不同人物位置的帧才有价值。遮挡太严重的目标要评估。安全帽被脚手架挡住一半或者人背对镜头完全看不到头这类样本在真实场景里确实存在但初版数据集不建议大量纳入。先把清晰、完整的目标学好再来处理难例。2.3 数据增强小数据集也能撑起训练目标检测领域有个现实问题很多项目现场能采集到的高质量图片就是几百张到一两千张距离训练一个鲁棒模型还差得远。这时候数据增强就是成本最低的扩军方案。YOLOv5本身内置了很强大的在线增强策略Mosaic、随机仿射变换、HSV色域扰动、水平翻转这些默认就会启用。Mosaic把4张图拼成1张相当于让模型在每张训练图上同时看到4个不同场景对小目标检测尤其友好也间接缓解了小数据集的问题。除了在线增强离线增强我建议只做两种水平翻转和轻微的亮度对比度调整。翻转是因为安全帽检测场景中左右对称性很强亮度调整是为了模拟白天不同时段的阳光变化。离线增强不要做得太狠我之前见过有人把图片旋转45度、加高斯噪声、随机遮挡全上结果模型在真实场景反而变差了。因为增强过猛引入的分布跟真实场景脱离模型学到的是“花式假样本”而不是目标本身的不变性。3. 图片标注全流程详解3.1 标注工具选择与基础操作做VOC格式标注我推荐直接用LabelImg虽然这个工具很多年没更新了但在“画框标注”这个功能上它依然是最干净利落的。你不需要安装一堆依赖也不用配数据库打开软件指定图片目录和预定义类别就能开始工作。刚上手的人记住几个快捷键就够了w是开始画框a和d切换上一张下一张图片ctrls保存del删除当前选中的框。标注的操作节奏基本是打开一张图按w在目标左上角按下鼠标拖到右下角松开松开后输入类别然后ctrls保存切下一张。一个熟练的标注人员一天标300到500个目标是很正常的。LabelImg默认保存的就是VOC格式的XML文件。需要注意软件界面里有一个PascalVOC和YOLO格式的切换按钮这个按钮默认在PascalVOC那一侧它只是改变保存格式不代表什么高级功能。我建议标注期间始终用PascalVOC模式YOLO格式后面用脚本统一转。注意项目路径和图片文件名不要出现中文和特殊字符。LabelImg对中文路径支持很差YOLO训练时OpenCV读取中文路径也经常报错。我一般统一规范成类似IMG_0001.jpg这样的命名目录用英文省掉一堆玄学问题。3.2 标注规范安全帽任务最容易出错的环节标注是一个看似没有技术含量、实际决定成败的环节。同一个目标两个人标出来的框可能差很多这种不一致性会直接变成训练噪声。所以批量标注前一定要先定清楚标注规范最好让标注团队先用几十张图试标统一标准后再正式开工。类别定义是第一个要拍板的事。我建议最少分为两类helmet戴安全帽的头部区域和head未戴安全帽的头部区域。加上person变成三类也可以适合需要人员统计的场景。但注意一旦加类别标注量和模型复杂度都会上升初版项目建议先用两类跑通。边界框的定义要统一。我推荐贴住目标可见区域而不是试图画出目标的“真实轮廓”。安全帽被遮挡时只标可见部分两个安全帽紧紧挨着时各自标各自的不要为了省事合并成一个框。头部略有侧转时框要包含整个头部包括下巴到头顶的完整范围不能只框帽子本身。还有一条容易被忽略的规范同一个目标在连续多帧里出现时框的大小要尽量稳定。一开始标得大后来标得小标签噪声就会叠加到边界框回归的loss里。3.3 VOC标注文件的完整结构VOC格式的核心是一个XML文件它的标准结构是这样的annotation folderimages/folder filenameIMG_0001.jpg/filename path/datasets/helmet/images/IMG_0001.jpg/path source databaseSafety Helmet Dataset/database /source size width1920/width height1080/height depth3/depth /size segmented0/segmented object namehelmet/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin512/xmin ymin310/ymin xmax680/xmax ymax470/ymax /bndbox /object /annotation这里最关键的字段是size里的width和height以及每个object下面的bndbox。bndbox里的四个数值都是像素坐标左上角为原点x轴向右y轴向下。truncated表示目标是否被边界截断difficult表示目标是否难以辨认这两个字段在最简单的数据集里可以全部填0但保留它们能让文件结构更完整。一张图里有几个目标就有几个object节点。如果一个目标都没有XML里就只有size没有任何object这种“空标注”图片对训练没有好处建议单独处理掉。3.4 VOC转YOLO格式脚本实现与验证VOC格式的坐标是像素坐标YOLO格式要求的是归一化后的中心点坐标和宽高所以转换的本质就是一次坐标变换。转换公式很简单x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height box_width (xmax - xmin) / width box_height (ymax - ymin) / height下面是完整的转换脚本基于Python标准库实现不需要额外安装任何依赖import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: print(fwarning: {filename} contains unknown class {name}) continue class_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 防止边界坐标越界后产生负数 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h # 丢弃宽高为0的异常框 if box_w 0 or box_h 0: continue lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_name os.path.splitext(filename)[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) # 类别顺序必须和后续data.yaml中names的顺序完全一致 class_names [helmet, head] xml_root annotations/voc txt_root labels/yolo os.makedirs(txt_root, exist_okTrue) for xml_file in os.listdir(xml_root): if xml_file.endswith(.xml): voc_to_yoto(os.path.join(xml_root, xml_file), txt_root, class_names)脚本跑完后重点不是看生成了多少文件而是随机挑几个txt文件验证坐标是否合理。验证方法很简单读一个txt把里面的归一化坐标乘以图片宽高画回图片上肉眼对比原XML的框是否重合。第一次写转换脚本的人很容易在x_center和y_center的顺序上翻车YOLO格式标准是class x_center y_center width height千万别写成xmin ymin xmax ymax。3.5 训练集目录组织与data.yaml配置完成标注和格式转换后数据集目录建议按下面这种方式组织datasets/helmet ├── helmet.yaml ├── images │ ├── train │ ├── val │ └── test └── labels ├── train ├── val └── testimages和labels下的子目录一一对应images/train里的IMG_0001.jpg对应labels/train里的IMG_0001.txt。训练集和验证集的比例我一般用8比2或者9比1。划分的时候注意按视频来源分层采样同一个视频的帧尽量都扔进训练集或都扔进验证集避免数据泄露导致验证指标虚高。data.yaml是YOLOv5读取数据集的入口配置文件内容很简单train: ./datasets/helmet/images/train val: ./datasets/helmet/images/val nc: 2 names: [helmet, head]这里最容易犯的错误是names顺序和转换脚本里的class_names不一致。比如转换脚本里helmet是0、head是1但yaml里names不小心写成了[head, helmet]模型训练时就会把helemt当成head来学mAP指标会完全失真。这种错误用代码检查很难看出来但一推理就露馅。4. YOLOv5训练与效果优化4.1 环境准备与一个能跑通的训练命令YOLOv5的训练环境配置不算复杂PyTorch的安装网上教程很多这里只强调版本兼容性。建议直接用官方仓库推荐的一套Python 3.8以上PyTorch 1.8以上CUDA按显卡驱动对应安装。代码克隆下来后执行pip install -r requirements.txt即可这一步会装好opencv、tqdm、pandas等依赖。环境就绪后一个最基础的训练命令长这样python train.py --img 640 --batch 16 --epochs 100 \ --data ./datasets/helmet/helmet.yaml \ --weights yolov5s.pt --cache这个命令的意思是用yolov5s作为预训练权重输入分辨率640×640每个批次16张图训练100轮数据集配置用刚才写好的helmet.yaml。--cache参数会把图片预先加载到内存里加速训练但如果你内存只有16G而数据集很大建议去掉这个参数不然可能内存溢出。第一次跑通后不要急着调参先观察loss有没有稳定下降如果出现NaN或者剧烈震荡优先检查数据格式和类别配置而不是怀疑模型结构。4.2 关键超参数的调优思路YOLOv5的超参数主要集中在data/hyps/hyp.scratch-low.yaml这个文件里训练时也可以直接用--hyp指定。对安全帽检测这个任务有几个参数值得重点关注。第一个是lr0初始学习率默认0.01。小数据集配合这个学习率经常出现前期震荡可以降到0.005甚至0.001。我自己的经验是安全帽数据集通常在几百到几千张图片参数量本来就小学习率保守一点反而收敛更快。第二个是batch批大小。在不爆显存的前提下batch越大训练越稳定。8G显存跑yolov5sbatch设16通常没问题如果显存只有6Gbatch设8或者4同时适当降低--img到480也能获得可用模型。第三个是anchor的自动调整。YOLOv5训练时默认会基于训练集标签重新聚类锚框--noautoanchor参数可以关闭这个功能。安全帽目标的宽高比相对固定大部分接近1比1或1比1.2自动聚类出的锚框一般会比较合理不用手动干预。另外训练轮数不是越多越好。用--save-period 10每10轮保存一次权重配合验证集指标观察一旦连续20轮mAP没有提升就可以停了再训下去大概率是过拟合。4.3 评估指标怎么读mAP不是唯一YOLOv5训练结束后会在runs/train/exp目录下生成一堆结果图表包括results.png、confusion_matrix.png、val_batch0_pred.jpg等。大家最关心的是mAP0.5和mAP0.5:0.95但只看这两个数字远远不够。mAP0.5表示IoU阈值0.5时的平均精度安全帽检测这类小目标较多的场景mAP0.5达到0.9以上算及格0.95以上算优秀。mAP0.5:0.95则更严格它把IoU从0.5到0.95以0.05为步长平均计算更能反映边界框的定位精度。真正要仔细看的是confusion_matrix.png。它会告诉你helmet和head这两个类别之间有没有相互混淆。如果大量helmet被误判成head说明标注时两类边界不清晰或者训练数据中这两个类别的外观差异不够明显。还有一种常见情况是背景被误检成helmet这时候可以通过增加负样本图片——也就是完全没有目标的纯背景图片——来压制误报。4.4 推理部署与现场效果验证训练完成后用下面命令对一张新图片做推理python detect.py --weights runs/train/exp/weights/best.pt \ --source test.jpg --conf 0.5--conf是置信度阈值实际部署时这个值很关键。安全帽检测属于“漏检比误报更严重”的场景你希望没戴帽子的头一定要被抓住这时候阈值可以适当调低到0.3甚至0.25代价是误报会增加。反过来如果项目要求减少告警打扰阈值提到0.6以上。现场效果验证不要用测试集图片要拿一段部署环境里新录制的视频来测。重点观察三个场景人员密集、安全帽颜色与背景接近、人员快速移动。这几个场景能扛住模型上线基本就有底了。5. 常见问题与排查技巧实录5.1 数据集层面的典型问题标注数据常见的问题我整理成了一张速查表基本覆盖了从标注到训练第一天会踩的大多数坑。现象常见原因处理方式训练时提示找不到图片或标签数据集路径配置错误、文件夹层级不对对照data.yaml检查train/val路径用绝对路径最稳妥训练loss一直很大且不降标注框坐标异常例如超出图片边界转换脚本加坐标裁剪删除宽高为0的框模型把背景检测成安全帽训练集中缺少纯背景负样本从原视频中抽一批无目标图片放入训练集某个类别完全检不出来该类样本数量过少或类别索引错位统计样本分布核对转换脚本和yaml的类别顺序验证集mAP很高线上效果差数据划分不合理训练验证来自同一视频段按视频来源分层划分保证场景不重叠有一条我特别想强调类别索引错位是新手最容易踩但最难一眼看出来的问题。现象就是训练的时候loss正常下降mAP也很漂亮一放到真实环境里模型把所有戴帽子的都报成未戴帽子。原因往往是标注脚本里的class_names和data.yaml里的names顺序不一致。排查方法是随便找一张验证集图片打印预测结果的类别名称对照原始的标注看一眼三分钟就能定位问题。5.2 训练过程层面的典型问题训练过程中的问题往往和数据无关而是参数和环境的锅。最吓人的现象是loss出现NaN。通常是学习率过大或者梯度爆炸先降低lr0到0.001重试如果还不行检查标注文件里是否存在全零的txt文件。YOLO格式如果一行是0 0 0 0 0模型就会把中心点学习到图片左上角极易引发数值异常。写个脚本把所有txt文件读一遍过滤掉所有坐标值之和为0的行问题基本能解决。Loss不下降但也没有NaN一般分两种。如果训练loss和验证loss都高说明模型容量不够换更大的预训练模型比如yolov5m如果训练loss低、验证loss高那就是过拟合了增加数据量、加强数据增强或者减小模型容量都可以缓解。显存溢出报错CUDA out of memory时不要急着换显卡先降低batch到8或4再把--img从640降到512。安全帽检测对分辨率的敏感度其实可控小模型训练在低分辨率下完全能接受。5.3 一个标注边界的经验性建议最后分享一个我在实际项目中踩得很深的经验关于标注边界。做安全帽检测时helmet和head这两个类别在画框的时候很容易产生边界模糊一个安全帽戴在头上如果盒子紧贴帽檐框那帽檐以下露出的头发、耳朵算不算head我的建议是只要这个区域包含了未戴安全帽的头部特征就单独标一个head如果头部大部分被帽子覆盖只需要标helmet不要额外标head。但这里有个度的问题。如果一张画面里一个人没戴帽子他的头部区域就只标head不标helmet。如果用两类数据训练模型很容易把“戴了帽子的头部区域”当成正样本把“没戴帽子的头部区域”当成负样本这个逻辑本身是自洽的。但标注时如果标准反复横跳有时候把帽檐下的脸也框进helmet有时候又把帽子下沿的一段脸划到head模型学到的东西就会非常混乱。我现在的做法是helmet的框从帽顶到帽檐下沿紧密贴合帽子的可见轮廓head的框从下巴到头顶完整包含整个头部。如果一个头几乎全都露出来就只标head如果一个头被帽子完全遮住就只标helmet如果帽子和面部各占一半两个都标。标准看起来很死板但实际标注效率反而更高模型效果也更稳。这类细节不在任何官方文档里但数据集的最终质量往往就是在这些细小的判断标准里拉开差距的。我自己做完这个项目后最大的体会是与其花大量时间去调学习率、调anchor不如先把标注规范和格式验证做好。数据对了YOLOv5随手训一版都不会差数据乱了再好的模型结构也救不回来。本文还有配套的精品资源点击获取