YOLOv5安全帽检测数据集制作与格式转换实操指南

发布时间:2026/8/26 21:43:11
YOLOv5安全帽检测数据集制作与格式转换实操指南 简介目标检测模型的性能高度依赖训练数据的质量而数据集的构建与格式转换是工程落地中的第一道门槛。在YOLO系列模型中标注文件采用归一化坐标的txt格式而PASCAL VOC则使用XML描述目标位置两者转换需严格遵循坐标换算逻辑。掌握VOC格式与YOLO格式的互转方法、标注工具选型以及数据划分技巧能显著提升模型训练效率与检测精度。本文面向安全帽检测这一工业场景从数据集获取、标注规范、格式转换到YOLOv5训练前的数据校验与增强策略提供一套可直接落地的完整操作指南帮助开发者避开文件缺失、坐标越界、类别错位等常见坑点快速构建可用的安全帽检测模型。 做安全帽检测项目快两年了问得最多的不是模型结构而是数据集。最近又有人问我YOLOV5安全帽检测数据集用yolo格式还是voc格式图片标注用什么工具能不能直接下载现成的这些问题背后其实是一个共同诉求——想用最快速度把安全帽检测跑通但被数据集和格式卡住了。这篇文章我就把从数据集获取、图片标注、格式转换到最终用来训练YOLOv5的完整流程按我自己的实操经验讲一遍不铺理论只讲能落地的做法适合正准备训练安全帽检测模型的开发者和学生参考。1. 安全帽数据集的三种获取渠道下载、合并、自建标注1.1 直接下载现成数据集先搞清楚里面到底有什么网上流传较广的安全帽检测数据集常见就有 SHWDSafety Helmet Wearing Dataset、AI Studio 上的一些安全帽数据集、Roboflow Universe 上的公开项目、Kaggle 上的工地安全数据集等。这些数据集下载下来之后第一步不是急着解压开训练而是先做一次摸排。我自己通常用一条命令看看结构大概什么样——find . -maxdepth 2 -type d | sort然后统计图片数量和标注数量是不是对得上。很多时候你会发现图片数量不等于标注文件数量或者有些图片没有任何标注文件这种情况在下载型数据集里非常普遍后面我会专门讲到怎么处理。另外还要留意数据集的许可证。有些公开数据集标注的是 Apache License 2.0、GPL、CC BY 4.0 这类许可下载时我一般会看一眼 README 或者发布页的说明。如果是自己做项目和毕设这些问题不大但如果要商用部署就得提前确认许可边界别等到产品上线了才发现数据集本身不能用于商业场景。1.2 多个数据集合并时的类别口径统一实际项目中单靠一个现成数据集往往不够用尤其是工地场景差异大有的数据集偏室内厂房有的偏露天施工。我常用的做法是找两三个来源合并但合并前必须先做一件事把类别定义统一。有的数据集把“戴了安全帽的头部”叫 helmet有的叫 head_with_helmet还有的叫 with_helmet未戴帽头部同样存在 head、without_helmet、no_helmet 这些写法。如果不统一训练出来的模型类别会乱套。我通常会强制归并成两类类别 0helmet表示戴安全帽的头部类别 1head表示未戴安全帽的头部有些数据集还把安全帽单独从头部框出来也就是一顶帽子一个框和一个“头帽子”的框含义完全不同这个在合并时需要特别注意。我的经验是宁可使用统一的“头部包围盒”标准也不要混用不同标注语义否则训练时 loss 会莫名其妙抖动。1.3 自建标注数据视频抽帧是最快的补数据方式如果现有数据集在真实场景下表现不好最有效的补数据方式是自己拍视频或者让现场人员用手机拍几段不同角度、不同时段的视频然后抽帧标注。用 ffmpeg 抽帧我用的命令很简单ffmpeg -i video.mp4 -vf fps2 -q:v 2 frame_%04d.jpg这里 fps2 表示每秒抽 2 帧。实际使用时建议先按 1 到 3 帧每秒抽一部分看看效果抽得太密会让相邻帧高度相似相当于重复样本抽得太稀又会漏掉关键瞬间。安全帽检测的场景里人员走动、头部转动带来的尺度变化很典型我自己一般每秒抽 2 帧再人工剔除模糊帧这样能兼顾数据量和多样性。自建标注的另一个隐藏好处是你能完全控制场景分布。比如现场主要是红色安全帽那就要刻意多拍一些蓝色、白色、黄色的样本不然模型很容易对颜色产生偏置。2. yolo格式与voc格式同一份坐标的两种表达2.1 voc格式是一个xml小卡片PASCAL VOC 格式是把每张图片的标注信息写进一个 XML 文件。这个 XML 里记录了图片文件名、图片宽高、每个目标的类别名和包围盒坐标。典型结构长这样annotation filename000001.jpg/filename size width1920/width height1080/height depth3/depth /size object namehelmet/name bndbox xmin100/xmin ymin200/ymin xmax300/xmax ymax400/ymax /bndbox /object /annotationxmin、ymin 是左上角坐标xmax、ymax 是右下角坐标单位都是像素值。这套格式的可读性非常好人眼直接能看懂框在图片的哪个位置所以很多标注工具默认导出格式就是 VOC。2.2 yolo格式是几行归一化数字YOLO 系列使用的标注格式就简洁得多一个 txt 文件里每一行代表一个目标格式是类别id x_center y_center width height其中 x_center、y_center、width、height 都是归一化值也就是除以图片宽高后的比例取值范围在 0 到 1 之间。拿上面那个 XML 举例如果图片宽 1920、高 1080目标框左上角 (100, 200)、右下角 (300, 400)换算过程是先算像素中心的坐标x_center (100 300) / 2 200y_center (200 400) / 2 300width 300 - 100 200height 400 - 200 200然后再除以图片宽高0 200/1920 300/1080 200/1920 200/1080算出来就是0 0.104167 0.277778 0.104167 0.185185格式化写入文本时我一般保留 6 位小数精度完全够用。YOLOv5 在训练时读取的就是这种 txt 文件配套的图片和标签文件放在对应目录下靠相同的主文件名关联。注意YOLO 格式的坐标是相对于整张图归一化后的比例值不是像素值。有些朋友手动转格式时直接把像素坐标写进去结果训练时 loss 直接爆炸就是这个原因。2.3 两种格式互相转换的完整脚本我自己在整理数据时经常要来回转格式所以写了一个一直留着用的脚本。VOC 转 YOLO 的核心逻辑如下import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, classes): tree ET.parse(xml_file) root tree.getroot() 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 classes: continue cls_id classes.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) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines # 使用示例 classes [helmet, head] with open(000001.txt, w) as f: lines voc_to_yolo(000001.xml, classes) f.write(\n.join(lines))反过来YOLO 转 VOC 也不难就是把归一化坐标乘回图片宽高。这里有一个容易踩的坑有些数据集的 txt 里只写了四列没写类别 id转 VOC 时类别编号会全部错位。所以在转换前先确认每类 id 对应的类别名是什么别想当然。3. 标注工具的选型与完整标注流程3.1 labelImg、labelme、Roboflow怎么选工具选型这事群里经常有人问。我的看法很直接要导出 VOC 或 YOLO 格式首选 labelImg。虽然是老工具但界面简洁快捷键顺手框完直接保存成 xml 或 txt不用写转换脚本。labelme 默认导出 json 格式适合做语义分割和多边形标注。目标检测场景用它倒也能做但还要写一步 json 转 yolo 的转换有点绕。Roboflow 是网页版工具支持在线标注、自动预标注、数据集版本管理、一键导出多种格式。团队协作或者大批量标注时效率很高但数据上传到云端要考虑隐私和合规问题。自己小规模整理数据我更多用本地工具。如果你同时处理几千张图我建议先用一个预训练模型做自动预标注再用工具人工修正。这个流程能省掉 60% 以上的标注时间后面细说。3.2 我的日常标注步骤第一步先把所有图片按固定尺寸缩放或者直接保持原图导入标注工具我一般保持原图避免小目标在缩放后看不清。安全帽在画面里经常只有几十个像素大小缩放后容易漏标。第二步定义类别。我用的标注类别就两个helmet 和 head。最怕的是标注过程中自己把同类目标拆成不同类别比如把红色安全帽标成 helmet_red、白色标成 helmet_white除非业务上有明确需求否则千万不要这么干会增加模型分类难度。第三步逐张画框。这里有个操作习惯先用粗框快速把所有目标都框出来再统一微调边界比一张图反复放大缩小效率高。画安全帽框的时候框的边界尽量贴近头部的真实轮廓不要把帽子边缘外的大片背景包进来也不要只框帽子不带下巴这两类不一致的框会把模型训歪。第四步标注完成后导出数据立刻做一次可视化检查。所谓可视化检查就是把标注框画回原图肉眼过一遍。代码很简单import cv2 img cv2.imread(000001.jpg) with open(000001.txt) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) xc, yc, w, h map(float, parts[1:]) x1 int((xc - w / 2) * img.shape[1]) y1 int((yc - h / 2) * img.shape[0]) x2 int((xc w / 2) * img.shape[1]) y2 int((yc h / 2) * img.shape[0]) color (0, 255, 0) if cls_id 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(check_000001.jpg, img)把检查图按顺序翻一遍比看标注数据本身更能发现问题。很多标注错误比如框偏移、类别标反、漏标严重遮挡目标都能在这一步暴露出来。3.3 标注质量的验收标准我在团队里带标注任务时一般会定一个简单的验收指标重新抽检 10% 的图片人工重新标注一次计算两组标注的 IoU要求 IoU 0.7 的框占比不低于 90%。这样既能保证整体标注质量又不至于要求每张图都完美毕竟密集场景下完全一致的框几乎不可能。提示安全帽检测的难点之一是小目标密集排列标注时相邻头部之间容易互相遮挡。我自己的规则是只要目标可见面积超过 50%就给它标一个框不要求完整可见。这个规则要提前定好避免不同标注人员标准不一致。4. 从零组织可训练数据集目录结构、数据划分与增强4.1 YOLOv5 期望的目录结构长什么样YOLOv5 训练时默认从 dataset.yaml 指定的路径读取数据标准目录结构是这样dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/images 和 labels 下的文件名一一对应000001.jpg 对应 000001.txt。训练集和验证集分开存放。如果你拿到的数据是 VOC 格式需要先整体转成 YOLO 格式再按上面结构放好。对应的 dataset.yaml 文件内容大致是path: ./dataset train: images/train val: images/val test: images/test nc: 2 names: [helmet, head]这里要注意path 指向的路径最好是数据集根目录的绝对路径或者相对 YOLOv5 主目录的相对路径别配错否则训练会一直报图片加载不出来的错。我自己遇到过好几次 root 路径配错一查全是路径问题。4.2 数据划分别让同一段视频的连续帧同时出现在训练集和验证集划分训练集、验证集时最容易被忽略的问题是数据泄露。如果数据来源是视频抽帧相邻几帧内容高度相似随机划分时很容易出现同一段画面的近邻帧一半在训练集、一半在验证集导致验证 mAP 虚高一到真实场景就露馅。我的做法是先按视频片段分组同一段视频的帧只能整体划到同一个集合里。具体做法是抽帧时文件名带视频编号比如 video01_frame_0001.jpg然后按前缀分组再按组划分。如果用的是现成数据集没有视频编号信息可以先用图像感知哈希去重把相似度极高的图片挑出来只保留一张再做划分。划分时我习惯按 8:1:1 或者 9:1 切训练集和验证集测试集如果有就单独留一份。安全帽检测这种二分类任务几百张验证图已经足够看出模型效果不需要留太多。4.3 数据增强mosaic、copy-paste 与真实场景的取舍YOLOv5 自带的 mosaic 增强已经很强了它会把 4 张图拼接成一张相当于在训练时同步制造了不同尺度、不同背景的组合样本对小目标检测特别有帮助。实际操作时我一般保持默认开启 mosaic并且不改 mosaic 概率参数除非发现训练集和验证集差异过大。除了内置增强我还会对安全帽数据做定向补充。一个很有效的手段是 copy-paste把从头像里抠出来的安全帽区域随机粘贴到工地背景图片的合理位置然后自动生成对应的 box 标注。这相当于用程序批量制造新样本能把稀有角度、稀有颜色的帽子样本数量提上来。实现时要注意粘贴区域的背景尽量贴合比如不要把人头贴到天上否则模型学到的上下文特征就乱了。还有一个关键点是亮度、对比度、色温的扰动。工地场景早晚光线差异很大大太阳下的顺光、反光、逆光都会让安全帽的外观发生剧烈变化。给训练集随机叠加一些亮度调整import cv2 import numpy as np def adjust_brightness(img, factor): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv[:, :, 2] np.clip(hsv[:, :, 2] * factor, 0, 255) return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)factor 随机取 0.6 到 1.4 之间基本能覆盖白天到傍晚的光线变化。这个增强我在实测中带来的提升比想象中大因为很多现场摄像头的曝光和手机相机完全不同模型如果没见过暗光样本部署到现场就会频繁漏检。5. 训练前的数据校验与踩坑实录5.1 图片和标签文件名对不上训练一半卡住我在一次整理数据时把图片从手机里导出后使用了某种批量重命名工具结果图片文件名和标注文件名格式对不上了。YOLOv5 训练时虽然不会直接报错但会出现大量图片没有对应标签的情况最终表现是训练 loss 下降异常。后来我写了一条快速校验命令把图片目录和标签目录的主文件名都提取出来做差集for f in images/train/*.jpg; do base$(basename $f .jpg); [ -f labels/train/$base.txt ] || echo missing label: $f; done输出为空的文件就是缺标注的图。这类问题在下载数据集里很常见尤其是网上流传的压缩包被二次打包后文件名容易被改掉所以训练前跑一遍这个检查非常必要。5.2 坐标越界和空标注文件的处理YOLO 格式的坐标理论上应该是 0 到 1 之间的比例值但有些标注工具导出时会把贴近图片边缘的框写成负数或者大于 1 的值。训练时这种越界坐标会带来 NaN loss 或在数据增强时出现奇怪的框。我的校验策略很简单凡是坐标越界的框先看超出多少如果只是几个像素级别的小幅度越界直接 clamp 到 0 和 1 之间如果越界严重说明标注错误删掉该框。同时还有一个特别隐蔽的问题空标注文件。有些图片确实没有任何目标但标注文件存在且内容是 0 字节。YOLOv5 对这类图片的处理是跳过还是报错取决于版本。为了避免不确定行为我会把空文件统一列出来决定是删除图片还是补充标注。5.3 类别编号错乱导致训练结果完全不可用我踩过最大的坑是有一次把多个来源的数据合并后没有统一类别 id导致同一个 helmet 类别在不同文件里一会儿是 0一会儿是 1。训练过程本身不报错验证集的 mAP 看起来也还行但实际推理时模型把戴帽和不戴帽完全搞反了。这个问题的排查比较费劲因为 loss 曲线看不出异常。我后来靠可视化抽查才定位到问题随机抽了 50 张验证图把标注框画上去发现同一类目标的颜色都不一致。从那之后我每次合并数据都会先运行一个全局统计把每个类别 id 在每个文件中的出现频率打出来检查是否有 id 冲突。import os from collections import Counter label_dir labels/train counter Counter() for name in os.listdir(label_dir): with open(os.path.join(label_dir, name)) as f: for line in f: cls_id line.split()[0] counter[cls_id] 1 print(counter)如果某个类别 id 只出现在极少数文件里基本可以确认是合并时导致的错位需要重新映射。6. 安全帽场景训练优化小目标、拥挤与类别失衡6.1 小目标检测输入尺寸和anchor的调整思路工地摄像头安装位置普遍较高画面里的头部在很多场景下都是小目标。YOLOv5 默认输入尺寸是 640x640对于大部分自然场景够用但安全帽头部如果只有二三十个像素640 下特征已经非常微弱。我的做法是训练时把 --img 参数提升到 1280代价是训练速度明显下降显存占用翻倍。如果显卡显存有限可以退一步用 960再配合 mosaic 增强也能显著改善小目标召回。推理端如果性能吃紧还可以训练时用大分辨率、推理时用中等分辨率配合 TTATest Time Augmentation在重要场景下提升精度。Anchor 设置方面YOLOv5 会自动从你的数据集中聚类计算最佳 anchor通常在训练开始时会看到 autoanchor 的提示。如果你的数据集里目标普遍偏小可以考虑在训练命令里不修改 anchor 相关参数让 YOLOv5 自动调整。6.2 密集人群下的漏检调置信度阈值比调模型更立竿见影安全帽检测最常见场景是工人集中作业人群密集时互相遮挡模型的置信度会被拉低。一个常用的推理技巧是降低置信度阈值YOLOv5 的默认 conf-thres 是 0.25我在密集场景会调到 0.1 到 0.15同时配合 NMS 的 iou-thres 调整默认 0.45可以略微提高到 0.5让重叠框更容易被合并。注意降低置信度阈值会带来更多误检所以实际部署时我会结合业务规则做二次过滤比如连续多帧检测结果取平均、检测框大小异常的直接丢弃等。6.3 戴帽与不戴帽样本数量差距大训练权重怎么调很多安全帽数据集中“戴帽”样本远多于“不戴帽”样本因为现场大多数工人是规范佩戴的。但如果业务上最关心的是“未戴帽”的报警类别不平衡就会导致不戴帽的召回率偏低。处理方式我一般不直接改损失函数权重而是先用数据手段把不戴帽样本数量拉上来。实在找不到更多样本时再考虑调整 YOLOv5 里的 cls 损失系数。当然也可以使用更复杂的损失函数但在工程落地中数据层面的平衡往往性价比最高。如果你用 YOLOv5 的 --hyp 参数调整超参数文件可以把 cls 这一项的 weight 适当调大比如从默认 0.5 调整到 0.7 左右同时观察不戴帽类别的召回率变化。调整时要小步走一次只改一个参数否则很难定位是哪个改动生效。6.4 一条完整的YOLOv5训练命令参考最后给一条我自己常用的训练命令基于 YOLOv5 v6 或 v7 官方仓库代码准备齐全后直接跑python train.py --img 1280 --batch 16 --epochs 100 --data dataset.yaml --weights yolov5s.pt --cache --hyp data/hyps/hyp.scratch-low.yaml--weights 用 yolov5s.pt 还是 yolov5m.pt 取决于算力和精度需求我自己一般先用 s 跑通全流程确认数据没问题后再用 m 追求精度。--cache 可以把图片缓存进内存大幅缩短训练时间但需要足够的物理内存建议 16G 以上再开。训练结束之后评估时别只盯着 mAP还要看每个类别的精确率和召回率。安全帽场景里“漏检”比“误检”严重得多所以我会优先保证召回率付出的代价是可以接受的少量误报。我在实际项目中把数据质量和训练流程理顺之后模型在自建测试集上的表现稳定了很多其中一个关键体会是数据集本身的干净程度往往比模型结构调整带来更大的收益。磨刀不误砍柴工第一次做安全帽检测的同学建议先把数据关把好再谈训练调参。等这一套流程跑通了后面的迭代都会顺畅很多。本文还有配套的精品资源点击获取