ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

输电线路金具检测YOLO数据集:VOC/COCO/YOLO三格式与训练教程

输电线路金具检测YOLO数据集:VOC/COCO/YOLO三格式与训练教程 简介这是一份面向电力巡检与目标检测方向的YOLO输电线路电力金具检测数据集适合从事输电线路缺陷识别、金具检测算法训练的学生与工程师使用。数据均来自真实场景场景丰富经labelimg精细标注标注框质量较高并同步提供voc、coco和yolo三种格式标签可直接接入YOLO系列模型训练。资源包共2000个文件以1985个xml标注文件为主另含少量txt说明、html教程与py脚本压缩包约819.31MB。随包附赠YOLO环境搭建、训练案例教程以及训练集、验证集、测试集划分脚本可按需自行划分数据。目前已有210人学习下载能帮助读者省去数据采集与格式转换环节快速搭建训练流程并复现检测效果。1. 一万张输电线路金具图为什么值得你花一个周末跑通输电线路巡检从人工爬塔转向无人机挂载相机之后真正的瓶颈早就不是飞手而是后端那一堆密密麻麻的金具图像怎么自动识别。绝缘子、防震锤、悬垂线夹、耐张线夹、均压环、间隔棒这些零件在航拍图里尺寸小、背景杂、遮挡多拿通用 COCO 预训练权重直接推理召回率经常惨不忍睹。我见过太多团队卡在第一步没有贴合场景的标注数据模型再新也是空转。这个标题里的东西本质就是一份面向输电线路电力金具检测的 YOLO 数据集含一万张图片同时给出 VOC、COCO、YOLO 三种格式标签外加划分脚本和训练教程。它解决的是「从零标注到能开训」这段最耗人力的路。适合两类人一类是想快速验证金具检测可行性的算法同学一类是手里有巡检图但不知道怎么整理成训练集的电力行业工程师。下面我按自己踩过的顺序把这份数据从解压到跑出第一个权重讲清楚。2. 三种标签格式到底差在哪VOC、COCO、YOLO 的选型与互转2.1 先搞明白每种格式在训练链路里的位置VOC 格式的核心是每张图对应一个 XML 文件里面用object节点记录类别名和xmin/ymin/xmax/ymax四个绝对像素坐标。它的好处是可读性强用文本编辑器打开就能核对框对不对缺点是文件数量翻倍一万张图就是一万个 XML磁盘 inode 压力不小。COCO 格式把所有标注塞进一个 JSON字段分images、annotations、categories三块bbox是[x, y, width, height]的绝对坐标。它适合做多任务、做评测因为 pycocotools 那套 mAP 计算是行业默认。YOLO 格式最轻每张图一个 txt每行class_id cx cy w h后四个全是归一化到 0 到 1 的相对值。Ultralytics 系的训练器只认这个。选型上我的建议很直接如果你用 YOLOv5 到 YOLOv11 这条线训练时只喂 YOLO 格式VOC 和 COCO 留着做数据核对和跨框架迁移。别在训练脚本里同时挂三种解析器那是给自己埋雷。2.2 用脚本把 VOC 转成 YOLO四个边界坑要盯死转换逻辑不复杂但细节翻车率极高。下面这段是我常用的转换脚本骨架处理一万张图大概两分钟。import xml.etree.ElementTree as ET import os from PIL import Image # 类别映射顺序必须和训练时 data.yaml 里的 names 完全一致 CLASSES [insulator, damper, suspension_clamp, tension_clamp, grading_ring, spacer] def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 从 XML 里读原始宽高不要用 PIL 再开一次图省 IO size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue # 未登记类别直接跳过避免 class_id 越界 cls_id CLASSES.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 裁剪到图像边界航拍图里框出界是常态 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) if xmax xmin or ymax ymin: continue # 退化框丢弃 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines))逻辑说明先读 XML 里的size拿宽高这是归一化的分母千万别用 PIL 重新读图一万张图会多花几十秒 IO。类别映射表CLASSES的顺序就是最终class_id训练配置里的names必须一字不差否则模型学出来的类别全是错位的。坐标裁剪那两行是血泪经验航拍图里标注员手抖把框拖出图像边界很常见不裁的话归一化后会出现大于 1 的值YOLO 训练时直接报normalized coordinates out of range。参数说明xmin/ymin/xmax/ymax是绝对像素转相对值前先除以w和h。cx cy是框中心bw bh是宽高这四个值都必须在 0 到 1 之间。如果你的数据集里出现bw或bh等于 0说明标注框宽高为 0这种脏数据要在转换阶段就丢掉别留到训练时才发现 loss 变 NaN。2.3 COCO 转 YOLO 时最容易忽略的 id 连续性COCO 的categories里id不一定从 0 开始有的标注工具从 1 开始编。转 YOLO 时如果你直接拿category_id当class_id训练时类别数对不上轻则报索引错误重则静默错位。正确做法是建一个category_id到0..N-1的映射字典按categories列表顺序重新编号。另外 COCO 的bbox是[x, y, w, h]转 YOLO 时中心点要算x w/2别直接拿x当中心这个错误我见过不止一个团队犯训出来的框整体偏移半个身位。3. 划分脚本怎么写训练集、验证集、测试集的比例与分层3.1 别用随机划分金具类别不均衡会坑死你一万张图里绝缘子的数量可能是防震锤的五六倍。如果直接random.shuffle按 8:1:1 切验证集里很可能某个稀有类别一张都没有训到一半发现mAP对这类永远是 0你还以为是模型不行。正确做法是按类别做分层抽样保证每个类别在三个子集里的占比接近全局占比。下面这个脚本用sklearn的train_test_split配合stratify参数但前提是你得先给每张图打一个「主类别」标签。import os import shutil import numpy as np from sklearn.model_selection import train_test_split IMG_DIR images LBL_DIR labels OUT_DIR dataset def get_main_class(label_path): # 一张图可能有多个框取面积最大的那个框的类别作为主类别 best_cls, best_area -1, 0 with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id int(parts[0]) area float(parts[3]) * float(parts[4]) if area best_area: best_area, best_cls area, cls_id return best_cls names [os.path.splitext(f)[0] for f in os.listdir(IMG_DIR) if f.endswith(.jpg)] labels [get_main_class(os.path.join(LBL_DIR, n .txt)) for n in names] # 先切出 10% 测试集再从剩下的切 11% 做验证集最终约 8:1:1 train_val, test, _, _ train_test_split(names, labels, test_size0.1, stratifylabels, random_state42) train, val train_test_split(train_val, test_size0.11, stratify[labels[names.index(n)] for n in train_val], random_state42) for split, items in [(train, train), (val, val), (test, test)]: for sub in [images, labels]: os.makedirs(os.path.join(OUT_DIR, split, sub), exist_okTrue) for n in items: shutil.copy(os.path.join(IMG_DIR, n .jpg), os.path.join(OUT_DIR, split, images, n .jpg)) shutil.copy(os.path.join(LBL_DIR, n .txt), os.path.join(OUT_DIR, split, labels, n .txt))逻辑说明get_main_class取面积最大的框作为这张图的主类别这是分层抽样的依据。stratify参数保证切分后每个子集的类别分布和原始一致。random_state42固定随机种子保证你和我跑出来的划分完全一样方便复现。两次切分先切测试集再切验证集比例算下来接近 8:1:1。参数说明test_size0.1是测试集占比0.11是在剩余 90% 里再切 11%最终验证集约占全量 10%。如果你的稀有类别样本少于 20 张stratify会报错这时候要么合并类别要么对稀有类做过采样别硬切。3.2 划分完必须做一次完整性校验切完之后别急着开训先跑一遍校验每张图有没有对应的 label 文件每个 label 文件里的class_id有没有超出names长度坐标有没有越界。我一般写个十行的小脚本扫一遍比训到一半报错再回头查省事得多。校验通过后把dataset目录结构固定成images/train、labels/train这种标准布局Ultralytics 系直接认。4. 用这份数据跑通第一个 YOLO 训练配置、命令与显存控制4.1 data.yaml 里五个字段一个都不能错Ultralytics 系的训练入口是一个 yaml 配置文件字段少但每个都关键。path: /home/user/dataset train: images/train val: images/val test: images/test nc: 6 names: [insulator, damper, suspension_clamp, tension_clamp, grading_ring, spacer]path是数据集根目录train/val/test是相对路径别写绝对路径换机器就崩。nc是类别数必须和names长度一致。names的顺序必须和转换脚本里的CLASSES完全一致差一个位置所有类别标签全错位。这个坑我踩过训了六个小时发现绝缘子被识别成防震锤回头查就是 names 顺序写反了。4.2 从命令行启动训练先小后大第一次跑别直接上大模型先用yolov8n这种 nano 版本验证链路通不通。yolo detect train \ data/home/user/dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ workers8 \ projectruns/golden \ nameexp1imgsz640是输入分辨率金具目标普遍偏小如果显存够可以提到 1024小目标召回会明显改善。batch16是批大小8G 显存跑 640 分辨率大概能到 1612G 能到 32。workers8是数据加载线程数设成 CPU 核数的 0.75 倍左右比较稳设太高反而抢主进程资源。device0指定第一块 GPU多卡用0,1。训练启动后盯三个东西box_loss是不是稳定下降cls_loss有没有震荡mAP50在验证集上是不是逐步爬升。如果box_loss前三轮就变 NaN八成是标签里有非法值回去跑第 3 章的校验脚本。如果mAP50一直卡在 0.1 以下先确认names顺序和类别数再确认图片和标签是不是一一对应。4.3 显存不够时的三个降级手段显存爆了别急着换卡按顺序试这三招第一把imgsz从 640 降到 512显存占用大概降 35%小目标召回会掉一点但能跑起来第二batch减半配合accumulate参数做梯度累积比如batch8 accumulate2等效于batch16第三换更小的模型yolov8n换yolov8s是反向操作显存不够时应该往更小换n 是最小的。这三招用完还爆那就是数据分辨率本身太高先把图统一缩到长边 1280 再训。5. 避坑与排查金具检测训练里最常见的五类翻车5.1 现象训练 loss 正常下降但验证集 mAP 始终为 0原因data.yaml里的names顺序和标签文件里的class_id对不上模型学到的类别和验证时计算的类别完全错位。解决把names列表和转换脚本里的CLASSES逐字比对顺序、拼写、下划线都要一致。改完重新生成标签别只改 yaml。5.2 现象某类金具的召回率极低其他类正常原因这类样本在训练集里数量太少或者标注框普遍偏小。解决先统计每个类别的框数量和平均面积数量少于 500 的类别做针对性过采样面积小于 32x32 像素的框在imgsz640下几乎学不到把输入分辨率提到 1024 再试。5.3 现象训练到一半突然报 CUDA out of memory原因workers设太高数据加载进程和主进程抢显存或者某张图的尺寸异常大batch 里混进一张 4K 图直接把显存打满。解决workers降到 4训练前用脚本把所有图的长边统一缩到 1280 以内异常尺寸的图单独处理。5.4 现象模型在测试集上框的位置整体偏移原因VOC 或 COCO 转 YOLO 时中心点算错常见的是把x当中心而不是x w/2。解决随机抽十张图把 YOLO 标签反算回绝对坐标画到原图上肉眼核对偏移一眼就能看出来。5.5 现象训练速度极慢GPU 利用率长期低于 30%原因数据加载是瓶颈workers太少或者磁盘 IO 太慢。解决把数据集放到 SSD 上workers提到 CPU 核数的 0.75 倍cacheTrue可以把整个数据集缓存到内存一万张 640 分辨率的图大概占 8G 内存内存够就开。6. 把一万张图用出两万张的效果小目标增强与难例回流数据量固定的时候提升金具检测精度的杠杆主要在增强策略和难例利用上。我一般会做两件事。第一针对小目标做 mosaic 和 copy-paste 增强。mosaic 把四张图拼成一张等效于让模型在一个 batch 里看到更多小目标上下文copy-paste 把稀有类别的金具框抠出来贴到其他图上能快速扩充稀有类样本。Ultralytics 系默认开了 mosaic但close_mosaic参数默认是最后 10 轮关闭如果你的小目标特别多可以把这个值调大让 mosaic 多开一会儿。第二难例回流。第一轮训完之后用模型在验证集上推理把漏检和误检的图挑出来人工复核标注加回训练集再训第二轮。这个循环做两到三次mAP 通常能涨 3 到 5 个点。下面这段是挑难例的脚本骨架。from ultralytics import YOLO import os model YOLO(runs/golden/exp1/weights/best.pt) val_dir dataset/val/images hard_dir hard_examples os.makedirs(hard_dir, exist_okTrue) results model.predict(sourceval_dir, conf0.25, iou0.5, save_txtTrue, streamTrue) for r in results: # 如果预测框数量为 0说明这张图漏检严重直接回流 if len(r.boxes) 0: src r.path dst os.path.join(hard_dir, os.path.basename(src)) os.link(src, dst) # 硬链接省磁盘别用 copy逻辑说明conf0.25是置信度阈值低于这个值的框不算预测。len(r.boxes) 0说明模型在这张图上什么都没检出来要么是漏检要么是这张图本来就没有目标。回流之前必须人工过一遍把本来就没目标的图剔掉否则会把噪声喂回训练集。os.link建硬链接不占额外磁盘空间一万张图也就几秒。参数说明iou0.5是 NMS 的 IoU 阈值金具之间重叠不多0.5 够用。streamTrue是流式推理避免一次性把所有结果堆在内存里。回流两轮之后如果 mAP 不再涨说明数据已经榨干了该考虑换更大的模型或者补采新场景的图。我自己的习惯是每训完一轮就把best.pt和对应的data.yaml、划分脚本一起打包存档标注版本用 git 管起来。金具检测这活儿模型结构换来换去收益有限真正拉开差距的是数据清洗和难例回流做得多细。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进