简介:这份变电站及输电线路异物检测图像数据集面向电力巡检、计算机视觉方向的研究者与算法工程师,用于训练和评估目标检测模型,解决电力设施异物识别与定位问题。资源包共336个文件,由168张jpg图像与168个xml标注文件一一对应,压缩包约18.29MB,VOC格式标签完整记录异物类别与边界框坐标,可直接用于YOLO、Faster R-CNN等框架的数据转换与训练。目前已有3451人学习下载,适合入门目标检测或开展电力场景实验的读者。数据覆盖变电站与输电线路两类场景,异物目标明确,便于划分训练集与验证集,配合缩放、归一化、翻转旋转等预处理与增强手段,可较快搭建检测流程并评估模型在未见数据上的表现。对于希望将检测模型集成到无人机巡检或远程监控系统的开发者,这份数据可作为验证算法精度与实时性的基础素材。
1. 变电站及输电线路异物检测:168 张 VOC 图像能撑起什么
如果你正在做变电站或输电线路的智能巡检,大概率会遇到一个很现实的问题:公开的电力场景数据集要么太大跑不动,要么标注格式不匹配,要么类别定义跟自己的业务对不上。这个标题指向的是一份 168 张图像、VOC 标签的异物检测数据集,规模不大,但恰好卡在“能快速验证想法”和“能跑通完整训练流程”之间的甜点区。它解决的不是刷榜问题,而是让你在半天内把“数据加载→格式转换→模型训练→指标评估”这条链路跑通,先确认技术路线可行,再决定要不要投入标注更多数据。适合两类人:一是刚接触电力巡检检测任务、想找个真实场景练手的算法工程师;二是手里有巡检图像但不知道怎么组织成训练集的现场技术人员。168 张不算多,但 VOC 格式的标注信息完整,类别明确指向异物,这意味着你可以用它做迁移学习的起点,而不是从零标注。
2. 拆开这份 VOC 数据集:目录结构、类别定义与标注粒度
2.1 VOC 格式在电力异物检测里的实际含义
VOC 格式本质上是一套约定:每张图像对应一个 XML 文件,XML 里记录图像尺寸、目标类别和边界框坐标。在电力异物检测场景里,这个“异物”通常指悬挂在导线上的风筝、塑料袋、鸟巢,或者变电站设备区内的漂浮物、施工遗留物。168 张图像意味着标注框数量可能在 300 到 600 之间,具体取决于每张图里异物实例的多少。你需要先确认的是:这份数据集的类别是单一“foreign_object”还是细分了“kite”“plastic”“nest”等子类。如果是单类,训练时 num_classes 设为 2(背景+异物);如果是多类,就要按实际类别数调整。VOC 的 XML 结构里,<object>标签下的<name>就是类别名,<bndbox>里的 xmin/ymin/xmax/ymax 是左上角和右下角坐标,注意这些坐标是绝对像素值,不是归一化值,转换时需要除以图像宽高。
2.2 用脚本统计类别分布和框尺寸
拿到数据集后不要急着训练,先跑一遍统计脚本,搞清楚类别是否均衡、框的尺度分布是否集中。这一步能帮你判断要不要做数据增强,以及 anchor 尺寸怎么设。
import os import xml.etree.ElementTree as ET from collections import Counter # 指向 VOC 格式的 Annotations 目录 ann_dir = "dataset/Annotations" cls_counter = Counter() widths, heights = [], [] for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() for obj in root.findall("object"): cls_name = obj.find("name").text cls_counter[cls_name] += 1 bbox = obj.find("bndbox") w = float(bbox.find("xmax").text) - float(bbox.find("xmin").text) h = float(bbox.find("ymax").text) - float(bbox.find("ymin").text) widths.append(w) heights.append(h) print("类别分布:", dict(cls_counter)) print("框宽 min/mean/max:", min(widths), sum(widths)/len(widths), max(widths)) print("框高 min/mean/max:", min(heights), sum(heights)/len(heights), max(heights))这段代码遍历所有 XML,统计每个类别的实例数,同时收集边界框的宽高。逻辑很直接:ET.parse解析 XML,findall("object")拿到所有目标,find("name")取类别名。参数方面,ann_dir要改成你实际的 Annotations 路径。跑完之后看输出:如果某个类别只有个位数实例,训练时几乎必然被背景淹没,需要考虑过采样或合并类别;如果框的宽高均值在 50 像素以下,说明异物目标偏小,YOLO 的输入尺寸至少设到 640,否则下采样后特征图上的目标可能只剩几个像素。
2.3 图像尺寸与标注质量的快速抽检
VOC 数据集常见的一个坑是图像尺寸不一致,有的 1920×1080,有的 1280×720,甚至混入了灰度图。训练框架通常会在 dataloader 里统一 resize,但如果原始尺寸差异太大,resize 后的形变会影响小目标检测。抽检方法是随机选 10 张图,用 PIL 读一下尺寸和通道数,同时把 XML 里的框画到图上,肉眼确认标注有没有明显偏移。这一步花不了五分钟,但能避免训练到一半发现标签错位的翻车情况。
from PIL import Image, ImageDraw import random img_dir = "dataset/JPEGImages" ann_dir = "dataset/Annotations" samples = random.sample(os.listdir(img_dir), 10) for img_name in samples: img = Image.open(os.path.join(img_dir, img_name)) print(img_name, img.size, img.mode) xml_path = os.path.join(ann_dir, img_name.replace(".jpg", ".xml")) if not os.path.exists(xml_path): print(" 缺失标注:", xml_path) continue tree = ET.parse(xml_path) draw = ImageDraw.Draw(img) for obj in tree.getroot().findall("object"): bbox = obj.find("bndbox") coords = [float(bbox.find(k).text) for k in ["xmin", "ymin", "xmax", "ymax"]] draw.rectangle(coords, outline="red", width=3) img.save(f"check_{img_name}")这里用ImageDraw.rectangle把每个框画出来,保存成 check_ 前缀的文件。重点看两件事:框是否紧贴异物边缘,以及有没有漏标。如果发现某张图里明显有异物但 XML 里没有对应 object,说明标注不完整,训练时这张图会教模型“这种东西不是目标”,直接拉低召回。参数上,random.sample的 10 可以按需调整,但不要少于 5 张,否则抽检意义不大。
3. 从 VOC 到 YOLO 格式:转换脚本与四个边界坑
3.1 为什么训练前必须做格式转换
现在主流的检测框架里,YOLO 系列用 txt 格式,每行是class_id x_center y_center width height,全部归一化到 0~1。VOC 的 XML 是绝对坐标,且类别是字符串。直接拿 XML 去喂 YOLO 的 dataloader 不是不行,但需要自己写解析逻辑,而且很多开源训练脚本默认只认 txt。转换的核心就三步:读 XML 拿宽高和框坐标,把绝对坐标转成归一化中心点格式,把类别名映射成从 0 开始的整数 id。听起来简单,但实际转换时容易在四个地方翻车。
3.2 转换脚本:类别映射、坐标归一化与文件对齐
import os import xml.etree.ElementTree as ET # 类别名到 id 的映射,按你的实际类别修改 classes = ["foreign_object"] # 如果是多类,按字母序或自定义顺序排列 class_to_id = {name: i for i, name in enumerate(classes)} ann_dir = "dataset/Annotations" img_dir = "dataset/JPEGImages" out_dir = "dataset/labels" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() size = root.find("size") img_w = float(size.find("width").text) img_h = float(size.find("height").text) lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text.strip() if cls_name not in class_to_id: print(f"跳过未知类别 {cls_name} in {xml_file}") continue 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) # 归一化中心点和宽高 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 # 边界裁剪,防止坐标越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{class_to_id[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") txt_name = xml_file.replace(".xml", ".txt") with open(os.path.join(out_dir, txt_name), "w") as f: f.write("\n".join(lines))逻辑说明:先读 XML 的 size 节点拿图像宽高,然后对每个 object 取类别名和框坐标。归一化时用中心点加宽高的格式,这是 YOLO 的标准输入。min(max(...))那几行是边界裁剪,防止标注时手抖把 xmax 写到图像外面导致归一化后大于 1。参数上,classes列表必须和你的实际类别完全一致,顺序决定了 class_id,训练和推理时要保持一致。out_dir是输出 txt 的目录,通常放在labels文件夹下,和images平行。
3.3 四个边界坑:文件名不对齐、类别空格、坐标越界、空标注
第一个坑是文件名不对齐。VOC 的 JPEGImages 里是001.jpg,Annotations 里是001.xml,转换后 labels 里是001.txt,这没问题。但如果图像是.png或.JPG大写扩展名,替换.xml时就会漏掉。解决方法是先统一扩展名,或者在脚本里用os.path.splitext取主干名再拼.txt。第二个坑是类别名带空格。XML 里写的是<name>foreign object</name>,你映射表里写的是foreign_object,直接比较必然失败。转换前先strip()再去掉内部空格,或者统一用下划线。第三个坑是坐标越界,上面脚本已经用裁剪处理了,但更稳妥的做法是转换后抽查几个 txt,看有没有负数或大于 1 的值。第四个坑是空标注。有些图像里没有异物,XML 里没有 object 节点,转换后会生成一个空 txt。这本身没问题,YOLO 会把空 txt 当作纯背景图,但如果空 txt 太多,正负样本失衡,训练时 loss 会偏向背景。统计一下空 txt 的比例,超过 30% 就要考虑剔除部分背景图。
4. 用 YOLOv8 跑通训练:配置文件、参数与显存控制
4.1 数据集 YAML 的写法与路径陷阱
YOLOv8 训练需要一个 YAML 文件描述数据集路径和类别。这个文件看着简单,但路径写错是新手最常见的翻车点。path是数据集根目录,train和val是相对 path 的子目录,里面放 images,labels 目录要和 images 平行且同名替换。比如path: ./dataset,train: images/train,那么实际图像路径是./dataset/images/train,标签路径是./dataset/labels/train。注意 YOLOv8 不会自动把 images 替换成 labels,它要求 labels 目录和 images 目录结构完全镜像。
# dataset.yaml path: ./dataset train: images/train val: images/val nc: 1 names: 0: foreign_objectnc是类别数,names是 id 到类别名的映射。如果你的数据集没有预先划分 train/val,需要自己按 8:2 拆分。168 张图,train 大概 134 张,val 34 张。拆分时注意同一场景的连续帧不要同时出现在 train 和 val 里,否则验证指标会虚高。
4.2 训练命令与关键参数解释
yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=8 \ lr0=0.01 \ patience=20 \ device=0 \ project=runs/train \ name=foreign_object_exp逐项说明:model=yolov8n.pt用 nano 版本,参数量小,168 张图足够微调,用大模型反而容易过拟合。imgsz=640是输入尺寸,前面统计如果框偏小就保持 640,不要降到 320。batch=8是显存和梯度的折中,8G 显存跑 640 尺寸大概能到 16,但小数据集用大 batch 容易震荡,8 比较稳。lr0=0.01是初始学习率,YOLOv8 默认 0.01,如果 loss 前期下降太慢可以调到 0.02,但不要超过 0.05。patience=20是早停耐心值,20 个 epoch 验证指标不升就停,防止过拟合。device=0指定第一块 GPU,CPU 训练会慢到无法接受。
4.3 显存不够时的三个降级方案
168 张图本身不大,但 640 输入尺寸下,YOLOv8n 的显存占用大概在 4G 到 6G。如果只有 4G 显存,有三个选择:一是把imgsz降到 512,但小目标召回会掉;二是把batch降到 4,梯度累积用nbs=16补偿;三是用yolov8n.pt的 FP16 推理,训练时加amp=True,YOLOv8 默认开启 AMP,不用手动设。如果还是 OOM,检查是不是 dataloader 的workers设太大了,默认 8 在 Windows 上容易出问题,改成 4 或 2。
5. 避坑与排查:小数据集训练异物检测的五个血泪教训
5.1 验证集 mAP 高但实际推理漏检严重
现象:训练日志里 mAP@0.5 到了 0.85,但拿现场新拍的图去推理,异物要么框歪要么直接漏掉。原因通常是验证集和训练集来自同一批图像,场景、光照、角度高度相似,模型记住了背景纹理而不是异物特征。解决方法是重新划分验证集,确保验证集的拍摄场景和训练集有差异,比如训练集用晴天图,验证集混入阴天或逆光图。如果数据量实在不够,至少把验证集里的图像做一次随机裁剪或亮度扰动,逼模型学形状而不是颜色。
5.2 训练 loss 震荡不收敛
现象:前 10 个 epoch loss 从 2.0 降到 0.8,然后开始上下跳,验证 mAP 不升反降。原因有两个:一是学习率太大,168 张图的小数据集用 0.01 的 lr0 在后期容易跳过最优解;二是 batch 太小导致梯度噪声大。解决方法是加余弦退火调度,YOLOv8 默认带cos_lr,但需要手动开cos_lr=True,或者把lr0降到 0.005,同时把batch提到 16(如果显存允许)。另一个容易被忽略的点是数据增强太猛,Mosaic 和 MixUp 在小数据集上会把异物目标切碎,导致模型学不到完整形状,可以先把mosaic=0关掉试一轮。
5.3 类别不平衡导致小类召回为零
现象:如果数据集里“鸟巢”只有 5 个实例,“塑料袋”有 200 个,训练后鸟巢的召回几乎为零。原因就是前面统计脚本暴露的问题,小类被大类淹没。解决方法是过采样小类图像,或者用copy_paste增强把小类目标复制到其他图上。YOLOv8 支持copy_paste=0.3,但只对分割任务生效,检测任务需要自己写增强逻辑。更简单的做法是在 loss 里给小类加权,但 YOLOv8 不直接暴露类别权重参数,需要改源码里的BCEWithLogitsLoss的pos_weight。
5.4 推理时框重叠严重
现象:一张图里同一个异物被检出多个框,NMS 后仍然有重叠。原因是异物形状不规则,模型在多个位置都给出了高置信度。解决方法是调低conf阈值的同时调高iou阈值。默认conf=0.25,iou=0.7,可以改成conf=0.4,iou=0.5,让 NMS 更激进地合并重叠框。如果还是不行,说明标注时框画得太松,模型学到的边界不明确,需要回去修标注。
5.5 图像预处理不一致导致推理偏移
现象:训练时用了 YOLOv8 默认的 letterbox 填充,推理时自己用 OpenCV 直接 resize,结果框的位置整体偏移。原因是 letterbox 会保持宽高比并在边缘填充灰条,而直接 resize 会拉伸图像。解决方法是推理时也用 letterbox,或者训练时把rect=False关掉,让所有图像统一 resize 到正方形。YOLOv8 的predict接口默认带 letterbox,但如果你自己写前处理,一定要对齐。
6. 把 168 张图用出 1680 张的效果:增强策略与验证技巧
小数据集的出路不在模型多大,而在增强多巧。我一般会先用albumentations做一轮离线增强,把训练集扩到 3 倍,重点加三类变换:随机旋转 ±15 度,模拟无人机不同航向角;随机亮度对比度扰动,模拟阴天和逆光;随机遮挡,模拟导线交叉或设备遮挡。注意旋转后要同步更新框坐标,albumentations 的BboxParams可以自动处理。离线增强的好处是增强后的图可以人工抽检,避免 Mosaic 那种把目标切碎的情况。在线增强用 YOLOv8 自带的degrees=15、hsv_v=0.4、erasing=0.2就够了,但mosaic建议在最后 20 个 epoch 关掉,让模型在真实分布上收尾。
验证阶段不要只看 mAP,小数据集上 mAP 波动很大。我习惯额外看两个指标:一是固定conf=0.3下的召回率,这个直接对应现场漏检率;二是画 PR 曲线,看曲线在哪个置信度区间下降最快。如果 PR 曲线在 0.5 附近断崖,说明模型对某些难样本没信心,把这些样本挑出来单独看,往往是标注问题。最后,168 张图训出来的模型不要直接上现场,先拿 20 张完全没参与训练的现场图做盲测,人工数漏检和误检,漏检超过 2 个就回去补数据。这个习惯帮我省过好几次返工,希望帮到你。
本文还有配套的精品资源,点击获取