简介目标检测技术正加速落地工业安全场景深度学习模型需要高质量标注数据支撑。YOLO作为主流单阶段检测器依赖规范化标签格式完成训练。在实际工程中VOC与YOLO格式的坐标体系转换是常见痛点而电力行业高空作业安全监控对安全带佩戴检测有迫切需求。本文从目标检测基础原理出发解析VOC绝对像素坐标与YOLO归一化坐标的差异介绍一套已整理完成的电力高空作业安全带检测数据集包含147张图片、4个类别并提供双格式标注与完整训练路径帮助开发者快速上手模型训练高效验证算法效果推动作业安全智能化管理。 做电力杆塔巡检和输电线路检修的朋友应该都有体会高空作业平台上的安全措施尤其是安全带的佩戴情况是老师傅反复叮嘱、安全员反复检查的重点。这两年基于计算机视觉的目标检测技术大量落地到作业现场越来越多团队想用YOLO实现“自动判断作业人员有没有规范佩戴安全带”但真正动手训练时才发现最耗时间的前置工作不是写模型而是准备一份干净、格式规范的数据集。我最近拿到并整理了一份电力行业高空作业安全带检测数据集VOCYOLO双格式147张图片、4个类别压缩包是.7z正好可以直接跑yolov5/yolov8的训练流程。这篇我就把这套数据从解压到训练的完整过程连同踩过的坑一起梳理出来。1. 项目整体设计为什么需要这样一份数据集1.1 电力行业高空作业安全带检测场景电力行业的高空作业场景非常典型输电铁塔登塔作业、电线杆上的抢修、变电站构架上的设备检修都涉及离地数米甚至几十米的高处作业。安全带是这些场景里最基本也是最后一道物理防线但真正到了现场问题往往不是“有没有配发安全带”而是“有没有正确使用”。我见过不少现场监控拍下来的事情工人身上确实挂着安全带但挂钩没有扣在牢固构件上或者作业中途为了方便移位把挂钩暂时解开。这类行为持续时间可能只有几十秒等安全员巡视到的时候早就不在了。所以这几年很多电力公司开始尝试用视频监控来辅助安全管理让摄像头替人盯着作业面。但视频流全天候产生靠人眼在监控室里盯着十几个屏幕别说全天半小时保持高注意力都很难。目标检测模型可以持续分析每一帧画面识别画面里的作业人员、安全带、挂点位置等目标一旦发现异常形态就自动截图告警。这个需求听起来不复杂真正落地时难点就变成了数据模型需要大量带标注的样本不同拍摄角度、不同光照、不同装备形态少一样都容易漏检。在公开数据集很少的情况下比较现实的办法是从历史监控视频、现场手机拍摄素材里截图再人工标注。标注本身不难难的是把图片收集、类别定义、格式处理、目录整理全部标准化。很多团队就卡在这个环节要么标到一半发现类别定义有歧义要么标注格式不统一导致训练脚本报错。所以一份已经整理好、带VOC和YOLO双格式标注的电力安全带检测数据集对个人学习和小团队验证方案来说是很省时间的东西。1.2 目标检测方案选型为什么选YOLO为什么保留VOC格式当前做目标检测可以选的方案不少传统图像处理算法在固定背景、固定角度下也能做但换一个变电站、换一个摄像头角度阈值就要重新调鲁棒性太差。深度学习方案里两阶段检测器比如经典的Faster R-CNN精度不错但推理速度在边缘设备上不够用单阶段检测器里YOLO系列是工程落地最成熟的速度优势明显又有大量预训练权重能直接用部署生态也完整比如已经支持导出ONNX、TensorRT等格式。这个数据集同时提供VOC和YOLO两种标注格式并不是多余。YOLO格式是当前训练脚本直接要用的标签是txt文本路径组织简单复制到yolov5或者yolov8的工程目录下就能训练。VOC格式则是更好的一种存档格式里面的XML文件记录了目标的类别名称、像素坐标、图片尺寸这些信息将来如果想把数据转成COCO格式、标注平台再加工格式或者给其他检测框架用从VOC转过去最方便。两个格式放在同一份压缩包里等于数据多了一层可迁移性不用临时再写转换脚本。2. 数据格式深度拆解VOC与YOLO的标注逻辑2.1 VOC格式核心结构与目录组织用这套数据集之前先要弄清它内部的目录到底长什么样。VOC格式沿用了Pascal VOC比赛的标准目录结构通常包含三个关键目录JPEGImages存放原始图片Annotations存放每张图片对应的XML标注文件ImageSets/Main下则是一些txt文件用来记录哪些图片划分到训练集、哪些划分到验证集。有的版本还会保留一个labels目录那里放的是已经转换好的YOLO格式txt文件。打开任一个XML文件关键的字段并不复杂filename对应图片文件名size记录图片宽度、高度和通道数object表示一个目标标注name是类别名称bndbox里的xmin、ymin、xmax、ymax是目标框在原始像素坐标系下的左上角和右下角坐标。比如某张图片里作业人员站在画面中央那么他的框可能是xmin800、ymin300、xmax1200、ymax900坐标单位是像素直接对应到原图上。这套表示方法非常直观人眼扫一眼XML就能知道标注在哪个区域。需要注意VOC坐标是绝对像素坐标如果原始图片被resize过对应XML里的坐标也必须同步缩放否则就会出现框偏移。我在实际处理中建议先检查一遍所有XML的size字段看是否与JPEGImages里实际图片的宽高一致这种不一致是最常见的标注错位源。2.2 YOLO格式坐标原理YOLO格式的标注文件是和图片同名的txt文件里面每一行代表一个目标物体格式是类别编号、归一化的中心点x坐标、归一化的中心点y坐标、归一化的目标框宽度、归一化的目标框高度。四个数值全部在0到1之间这么做的好处是跟图片分辨率解耦不管训练时输入是640像素还是1280像素标注坐标都不用改。具体换算公式是这样的假如一张图片宽度为W、高度为H目标框左上角是(xmin, ymin)右下角是(xmax, ymax)那么中心点坐标就是x_center(xminxmax)/2y_center(yminymax)/2框宽 xmax-xmin框高ymax-ymin。归一化时只需要把中心点坐标除以W和H框宽高也分别除以W和H。举个例子一张1920x1080的图片里有人体框从(960, 400)到(1440, 900)计算出来中心点x是1200中心点y是650框宽480框高500归一化以后就是0.625、0.6018、0.25、0.463。这里我习惯保留4位小数足够用了。自己写转换脚本时要注意YOLO格式里没有类别名称只有类别编号编号是int类型默认从0开始。一张图片里有多少个目标txt里就有多少行行顺序无所谓但编号必须对应数据集类别定义中的下标。2.3 双格式共存的实际意义VOC和YOLO两种格式各有各的适用场景放一张表对比就清楚对比项VOC格式YOLO格式文件类型XMLtxt坐标形式绝对像素坐标归一化相对坐标是否含类别名含直观只有类别编号适用框架通用、可转换多数格式YOLO系列训练直接使用修改友好度手动改麻烦文本处理脚本简单存储占用较大小得多从实操角度来说标注平台导出VOC更方便人工复核训练前再转换为YOLO格式即可。而这份数据集已经同时提供两种格式你可以直接用labels目录做训练也可以用Annotations目录配合官方代码做格式转换实验顺便熟悉一遍数据扩增链路。我自己在多个项目里都是这么干的双格式能避免后面想换训练框架时重新标数据的尴尬。3. 实操环节从解压到运行YOLO训练3.1 解压7z压缩包与目录检查拿到压缩包后第一步是解压。.7z格式的压缩率通常比zip高尤其对图片这类重复度高的数据效果好所以在数据集分发场景里很常见。我的机器是Ubuntu环境用p7zip解压先安装工具sudo apt update sudo apt install p7zip-full然后解压数据集压缩包7z x 电力行业高空作业安全带检测数据集VOCYOLO格式147张4类别.7z如果是Windows环境安装7-Zip之后直接在文件上右键选择“解压到当前文件夹”就行没必要用命令行。解压完成后先别急着训练应该检查目录是否有问题。我会用tree命令看目录结构没有tree就先用ls -R。关键要检查三件事JPEGImages或images目录下图片数量是不是147张Annotations或labels目录下的标注文件数量是否同样为147个两者的文件名前缀是否一一对应。如果有个别图片没有对应标注那在训练时这一张图会因为没有标签而被自动忽略属于隐藏的数据缺陷。如果还想进一步确认压缩包本身有没有下载损坏可以执行校验命令7z t 电力行业高空作业安全带检测数据集VOCYOLO格式147张4类别.7z这个命令会把压缩包里的文件完整读取一遍并做完整性校验输出没有error说明文件没问题。我习惯收到任何外部数据集先跑一遍这个操作不然解压到一半报CRC错误还得重新下载。3.2 快速可视化检查标签标注数据到底准不准不能光看txt数量和格式最终还是得用眼睛核对。我的做法是写一个简单的可视化脚本把标注框叠加到原图上生成预览图然后肉眼快速翻一遍。下面这段Python脚本基于OpenCV先读取XML文件提取目标框坐标再在原图上画矩形顺便把类别名称写在框上方。import cv2 import xml.etree.ElementTree as ET import os image_dir JPEGImages annotation_dir Annotations output_dir preview os.makedirs(output_dir, exist_okTrue) for xml_file in os.listdir(annotation_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(annotation_dir, xml_file)) root tree.getroot() img_file root.find(filename).text img_path os.path.join(image_dir, img_file) image cv2.imread(img_path) for obj in root.iter(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(float(bbox.find(xmin).text)) ymin int(float(bbox.find(ymin).text)) xmax int(float(bbox.find(xmax).text)) ymax int(float(bbox.find(ymax).text)) cv2.rectangle(image, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(image, name, (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) output_path os.path.join(output_dir, xml_file.replace(.xml, .jpg)) cv2.imwrite(output_path, image) print(fprocessed {xml_file})人眼检查这一步别省147张图虽然不多但真能找到问题。比如有的类别框把人体躯干和头部分开标有的把安全带弹簧钩单独标成一个小框不同标注者之间的习惯差异都会影响最终模型效果。我是按帧随机抽30张左右看一遍如果没有明显异常就认为这批数据质量可用。3.3 组装训练集并启动YOLO训练使用YOLOv8训练前需要把数据集目录整理成推荐结构。YOLOv8在数据加载上比v5更灵活但最稳妥的结构还是下面这种dataset_root/ images/ train/ val/ labels/ train/ val/也就是图片和标签分开两个大目录各自下面再按train、val划分。划分比例上147张图片的量我用的是8:2训练集117张验证集30张。随机划分时更重要的是保证图片对应的txt标注文件跟着图片一起移动防止图片在train里标注却留在了val里。我常用的划分方式是用一句话shell命令cd dataset_root mkdir -p images/train images/val labels/train labels/val # 假设原始目录中有train.txt和val.txt两行分别记录了图片文件名 while read line; do mv images/$line.jpg images/train/; mv labels/$line.txt labels/train/; done train.txt while read line; do mv images/$line.jpg images/val/; mv labels/$line.txt labels/val/; done val.txt然后写一个data.yaml内容大致如下path: /absolute/path/to/dataset_root train: images/train val: images/val names: 0: person 1: helmet 2: safety_belt 3: safety_rope这里类别名称只是示例实际类别定义以压缩包内自带的class文件或XML里的name字段为准。names的顺序必须和labels里的编号对应比如0号一定是第一个类别。接下来启动训练yolo detect train datadata.yaml modelyolov8s.pt epochs100 batch16 imgsz640 patience20可以简单解释一下这几个参数model指定预训练权重使用yolov8s.pt而不是随机初始化是迁移学习的核心epochs100对147张的小数据集来说已经偏多我建议用patience参数做早停当验证集指标连续20个epoch不再提升就自动停止避免白白跑时间imgsz640是默认输入尺寸先别上1280小数据集强行提高分辨率反而容易过拟合。4. 常见问题与排查技巧实录4.1 类别编号必须与data.yaml一致使用YOLO格式训练时最常见的坑是类别编号错位。同一个数据集XML里写着类别name转成txt后只有数字编号。如果你在data.yaml里把names顺序定义错了模型就会把一个类别的目标当成另一个类别去学而且训练过程不会报任何错只会出现诡异的验证集指标和预测结果。比如你把第0类定义成person但labels里实际第0类是helmet那模型学会的就是“helmet就是person”最终输出的检测框会用错误的标签显示。稳妥的做法是训练前跑一遍类别统计。在labels目录下执行for f in labels/train/*.txt; do cat $f; echo; done | awk {print $1} | sort | uniq -c这个命令统计所有标注文本中类别编号的出现次数。正常情况应该是0到3都有分布如果某个编号大量出现或者出现了大于等于4的数字就要回头检查转换脚本和data.yaml的names顺序了。我还习惯把XML里的name字段整体提取一遍检查有没有拼写不统一的情况比如safety_belt和safetybelt这两个看起来差不多在标注里可能是两个不同类别非常坑人。4.2 训练时提示No labels的原因定位YOLO训练报“No labels found”这类错误90%是路径问题剩下10%是标注文件问题。路径问题的典型情况是data.yaml里写的路径不对YOLOv8的train和val字段如果是相对路径会基于path字段计算所以path必须写成绝对路径或者保证从当前工作目录能正确相对定位。另一种情况是图片和标签文件名不匹配。YOLO要求的文件名规则是图片叫IMG_0001.jpg对应的标签就叫IMG_0001.txt。注意如果原始XML文件名和图片名不一致转换脚本按XML名生成txt就可能出现图片有、标签无的情况。我的排查方法很简单在Python里做一次差集import os img_files set(os.path.splitext(f)[0] for f in os.listdir(images/train)) label_files set(os.path.splitext(f)[0] for f in os.listdir(labels/train)) print(images without labels:, img_files - label_files) print(labels without images:, label_files - img_files)哪怕只有一张图没有标注训练时也会因为数据量减少而影响最终效果对于147张的小数据集来说每一张图都很宝贵。4.3 小数据集过拟合与控制策略147张图片训练下来最容易观察到的现象是训练损失一直降验证集的mAP却到一定程度就不再涨甚至往下跌。这就是典型的过拟合模型把训练集里的背景、光线、拍摄角度都记住了一到新的现场画面就失灵。碰到这种情况第一步不是盲目加训练轮次而是做三件事一是检查数据集是否存在偏置比如所有图片都是同一个变电站拍的那模型对颜色和地面纹理的依赖会很严重二是调整增强参数把mosaic、scale、fliplr的强度适当调高一点让模型不能轻易记住原图三是降低模型复杂度4个类别的检测任务用yolov8n或yolov8s就够不要一上来就上yolov8x。还有一个很多人忽略的点就是验证集要能代表真实场景。如果训练集和验证集都来自同一段视频的连续帧那两张图几乎一样mAP再高也不能说明模型泛化能力好。我一般会按拍摄时间或地点分组划分数据集而不是简单随机打乱这个思路在小样本数据里特别重要。4.4 .7z解压损坏预防.7z格式虽然压缩率高但传输过程中也有可能损坏。损坏的表现通常是解压中途报CRC failed或者解压出来的图片用OpenCV读出来是None。遇到这种情况先别急着删压缩包重新下载用上面说过的7z t命令测试一下看是不是个别文件损坏。如果是可以尝试用7z的e命令单独解压那个损坏的文件有时也能救回来一部分数据。更实际的经验是每次拿到数据集的压缩包先不要全量解压可以先解压一小部分比如只解压其中一张图片和一个XML确认内容格式正确再全量解压。另外图片文件本身也可能出现“能解压但无法读取”的情况我习惯批量用OpenCV读一遍所有图片把读不出来的文件单独记录下来。import os import cv2 for f in os.listdir(JPEGImages): img cv2.imread(os.path.join(JPEGImages, f)) if img is None: print(broken image:, f)这种“坏图”如果混进训练集轻则被自动跳过重则导致训练中断必须提前清掉。5. 小样本场景下继续扩展的路径5.1 迁移学习与冻结策略147张图对训练一个模型来说确实少但我依然推荐用这个数据集把完整流程跑通原因在于迁移学习能显著降低对纯数据量的依赖。YOLO的预训练权重在COCO数据集上学到了大量通用特征比如边缘、纹理、物体形状这些基础特征在安全带检测任务里同样有效。训练时模型只需要在预训练权重基础上微调而不是从零开始学什么是“人的轮廓”所以千张级别的数据就能得到一个基本能用的模型。如果担心过拟合可以尝试冻结backbone层只训练检测头。YOLOv8里用freeze参数控制yolo detect train datadata.yaml modelyolov8s.pt epochs80 batch16 imgsz640 freeze10freeze10表示冻结模型前10层。前几层学的是通用边缘和纹理特征冻结它们可以让模型把精力集中在学习安全带这种中高层语义特征上。但这个方法也不是绝对有效如果现场环境和COCO差异太大冻结层次太多反而会限制模型表达能力。我自己的经验是先用不冻结的方案跑一遍看曲线如果明显过拟合再逐步增加冻结层数。5.2 数据增强的现实做法小数据集条件下数据增强是绕不开的话题。YOLOv8自带的增强策略包括mosaic、随机仿射变换、HSV色彩空间扰动、水平翻转等。默认参数已经是比较均衡的配置不建议为了“防止过拟合”把增强强度调得过大因为过度增强会让图片失真到看不清安全带的形态。更合理的做法是控制增强的尺度变化范围让模型在合理范围内看到更多样化的作业姿势。另一个更有效的方式是从真实场景里补充数据。147张样本通常只覆盖几种固定角度和光照条件而现场监控视频中有大量可用的帧。我的办法是先拿这套数据训练一个初版模型用它去自动标注几十段新增监控视频的截图生成带预测框的标注文件再人工修正错标和漏标。这套“半自动标注人工复核”的流程可以快速把样本量从147张扩到1000张以上而且标注质量比纯人工从零标注高很多。5.3 扩展方向数据集的类别定义也可以逐步扩展。目前4个类别主要围绕安全带主体相关目标实际电力作业还会涉及安全帽、绝缘手套、工具包等安全装备。这些类别的检测难度相似后续可以在现有基础上继续标注新类别扩充到8类甚至10类模型就可以完成更全面的作业行为分析。想再往前走一步可以在检测模型后面接一个多目标跟踪器比如ByteTrack或DeepSORT。这样不仅能看到每帧画面里哪些人佩戴了安全带还能跟踪同一个人连续好几秒的作业动作判断他是否中途解开安全带。这个方向本质上就是电力现场的视频结构化把监控画面变成可以检索的结构化数据也是目前安全管控平台比较热门的功能。我个人在实际项目里最深的体会是数据集的价值在精不在多。147张图对于一个生产级模型肯定不够但用来验证标注格式、跑通训练流程、测试部署链路这套流程走一遍大概一天就能把整个技术路线打通。后面真正要扩充数据的时候你已经有了规范的数据组织方式和训练脚本只要持续往里面加标注好的图片就可以了那才是这套小数据集真正发挥作用的地方。本文还有配套的精品资源点击获取