简介:为电力智能巡检场景设计的变电站设备缺陷图像数据集,提供8307张现场拍摄的jpg图片,每张均配齐Pascal VOC标准xml标注文件和YOLOv5/v8兼容txt标签文件,无缺失或空标注。数据覆盖17类典型故障,包括避雷器破损、变压器渗油、开关柜异物、隔离开关锈蚀、互感器破损、接地线松动、绝缘子污闪、电缆沟盖板移位、警示牌脱落、安全围栏损坏、未悬挂标示牌、小动物入侵、箱门闭合异常、蓄电池漏液、工作区域未设围挡、作业人员未戴安全帽、作业平台防护失效等高频问题,边界框坐标完整准确,类别名称按行业常用缩写命名,可直接用于YOLO系列、Faster R-CNN、SSD等主流检测框架的训练与验证。压缩包共4个文件、体积仅3KB,内含txt使用说明、Python辅助脚本、inscode配置模板及gitignore文件,说明文档写明文件组织方式、类别映射表及常见问题处理建议,方便后续二次开发和排错。目前已有260人学习/下载,适合电力运维人员、算法工程师和高校研究者进行缺陷检测模型训练、迁移学习及样本扩充。
1. 变电站缺陷图像数据集:8307张标注图为什么值得下
做变电站巡检视觉项目的人,最头疼的往往不是模型选型,而是手里没有一张带精确标注的缺陷图。公开数据集里变电站场景本身就少,标注格式还不统一,有的只有框没有类别,有的类别太粗,训练出来的模型到了现场翻车率极高。这份变电站设备缺陷图像数据集,包含8307张真实巡检拍摄的JPEG原图和对应的Pascal VOC XML与YOLO txt双格式标注文件,覆盖17类典型故障类型,基本把瓷瓶、金具、引流线、绝缘子这些核心部件的常见缺陷都收进去了。它解决的核心问题,就是让你省掉从零标注的数周时间,直接把精力放到模型训练和调优上。适合正在做电力设备缺陷检测、变电站智能巡检、安全管控视觉系统的开发者、算法工程师和高校课题组成员,无论你打算用YOLO系列还是Faster R-CNN、SSD,这套双格式数据都能直接进训练管线,不用在格式转换上再折腾一轮。
2. 数据集构成与标注规范:17类缺陷到底覆盖了什么
2.1 数据总量与文件结构
这份数据集的核心参数是8307张图像,全部来自变电站户外设备巡检场景,拍摄角度以平视和俯视为主,包含晴天、阴天、逆光、黄昏等不同光照条件。图像原始分辨率集中在一千万像素上下,也就是约3648×2736或类似量级的大图,这样的分辨率对检测小目标非常关键,比如绝缘子自爆碎片、螺栓松动这类像素占比很小的缺陷,低分辨率数据根本学不到有效特征。
解压之后你会看到三个核心目录。
├── VOC2007/ │ ├── JPEGImages/ # 8307张原图,jpg格式 │ ├── Annotations/ # 每个xml对应一张jpg,Pascal VOC格式 │ └── ImageSets/ │ └── Main/ # train.txt, val.txt, trainval.txt 划分文件 ├── YOLO/ │ ├── images/ # 全部原图,与VOC2007/JPEGImages一致 │ └── labels/ # 每个txt对应一张jpg,归一化坐标 └── classes.txt # 17个类别名称,按索引从0开始VOC2007目录下是标准的Pascal VOC组织方式,JPEGImages存放全部图片,Annotations存放同名XML文件,ImageSets/Main里有官方划分好的训练验证集文件。YOLO目录则是为Darknet/YOLOv5/YOLOv8准备的直接可用的images和labels结构。两个目录下的图片完全一致,区别只在于标注文件格式。classes.txt是第一优先级要看的文件,它决定了类别索引的对应关系,YOLO格式txt里第一个数字就是classes.txt的行索引。
2.2 17类缺陷的类型分布与辨识要点
这17类故障类型涵盖了变电站户外设备最常见的视觉可辨识缺陷,类别名称全部是英文,对应实际运维术语。按照缺陷发生部位可以分成四组:一是绝缘子类,包括绝缘子自爆、绝缘子缺失、绝缘子闪络痕迹、绝缘子裂纹;二是金具与连接类,包括均压环歪斜、均压环缺失、防震锤脱落、防震锤歪斜、并联间隙异常;三是导线与引流线类,包括引流线断股、引流线散股、导线异物悬挂;四是基础与附属设施类,包括螺栓松动、锈蚀、鸟巢、设备渗漏油、围栏破损。
不同的缺陷在标注时框选策略不同,这一点直接影响你后续训练时的先验知识。比如绝缘子自爆,标注框一般紧贴爆裂点而非整个绝缘子串,因为缺陷本身就是局部碎裂;引流线散股则要框住散开的线股整体区域,而不是单根断丝;鸟巢属于大目标,但巢穴周边的枝条往往和背景中的杆塔横担粘连,标注框会适当外扩以保证包含完整轮廓。理解这些标注倾向,能帮你在推理时设置更合理的NMS阈值和置信度阈值。
2.3 类别不平衡分布与先验判断
8307张图分布到17个类别,不可能是平均的。常见的数据集设计规律是,高频缺陷如绝缘子自爆、锈蚀、异物悬挂,每类可能有800-1200张;中频如均压环歪斜、防震锤脱落,每类可能有300-600张;低频如并联间隙异常、设备渗漏油,每类可能只有100-200张甚至更少。这个分布特征决定了你训练时不能盲目用默认的类别权重。如果直接喂给YOLO训练,模型会对高频类别过拟合,低频类别几乎学不到。我一般会先跑一个类别统计脚本,生成分布直方图,再决定是采用focal loss、给低频类别加采样权重,还是用复制粘贴增强。
import os from collections import Counter labels_dir = "YOLO/labels" class_counter = Counter() for file in os.listdir(labels_dir): if not file.endswith(".txt"): continue with open(os.path.join(labels_dir, file), "r") as f: for line in f: cls_id = int(line.strip().split()[0]) class_counter[cls_id] += 1 with open("classes.txt", "r") as f: class_names = [line.strip() for line in f.readlines()] for cls_id in sorted(class_counter.keys()): print(f"{class_names[cls_id]}: {class_counter[cls_id]} instances")这段脚本读入YOLO格式标签,统计每个类别的目标实例总数,而不是图片张数。判断类别不平衡时应该以实例数为准,因为一张图中可能有多个同类别缺陷框。输出结果后,如果发现最低类别实例数不足最高类别的五分之一,就需要启动针对性的数据增强策略。不建议一开始就用mixup或mosaic暴力增广,先试最简单的水平翻转和轻微色彩抖动,观察低频类别mAP是否明显上升。
3. VOC与YOLO双格式解析:坐标体系转换与类别对齐
3.1 两种标注格式的本质差异
VOC格式是XML文件,每个目标框记录的是绝对像素坐标,即xmin、ymin、xmax、ymax四个整数,坐标原点是图像左上角,x轴向右、y轴向下。YOLO格式是纯文本文件,每行记录一个目标,五个空格分隔的数字依次是类别索引、归一化中心点x坐标、归一化中心点y坐标、归一化框宽、归一化框高。归一化的含义是除以图像的宽和高,所以所有数值都在0到1之间。
这两种格式之间不是简单的字段替换,关键区别在于两点。第一是坐标表达形式不同,VOC记录边界框的左上角和右下角,YOLO记录中心点和宽高,转换时需要做一次换算。第二是类别索引基准不同,VOC中的类名是字符串,写在XML的 标签里,YOLO里是整数索引,对应classes.txt的行号。如果classes.txt的顺序和XML中的name不一致,训练时会出现严重的错位问题。这个数据集里两者的对齐是验证过的,但你自己动手转换其他数据集时必须把对齐脚本写对。
3.2 XML与TXT的转换换算逻辑
假设你手里只有VOC标注,想转成YOLO格式,核心换算公式如下。
import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_width, img_height, class_map): tree = ET.parse(xml_path) root = tree.getroot() yolo_lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: continue cls_id = class_map[name] bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) center_x = (xmin + xmax) / 2.0 / img_width center_y = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height yolo_lines.append(f"{cls_id} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}") return yolo_lines class_map = {} with open("classes.txt", "r") as f: for idx, line in enumerate(f.readlines()): class_map[line.strip()] = idx这段代码里的class_map从classes.txt构建,保证类别索引和Darknet系模型的要求一致。center_x的计算方式是先求边框左右坐标均值,再除以图像宽度,得到的归一化中心点。width等于右减左除以宽度,注意不能直接拿xmax减xmin再除以宽度,因为xmin和xmax本身就是像素单位,换算没有问题,但必须除以原图宽度,不能除以缩放后的尺寸。我在实际项目中见过有人把输入网络的resize尺寸拿来当除数,导致框的位置偏移,训练了十几个epoch才发现mAP始终上不去。
3.3 反向转换与数据验证
反向从YOLO转VOC同样有实际用途,比如你训练完模型,想把预测结果转成VOC格式给标注工具或评测脚本用。核心逻辑是用归一化中心点和宽高反推绝对坐标,注意边界裁剪。
def yolo_to_voc(yolo_line, img_width, img_height, class_list): parts = yolo_line.strip().split() cls_id = int(parts[0]) center_x = float(parts[1]) * img_width center_y = float(parts[2]) * img_height box_width = float(parts[3]) * img_width box_height = float(parts[4]) * img_height xmin = int(max(0, center_x - box_width / 2)) ymin = int(max(0, center_y - box_height / 2)) xmax = int(min(img_width, center_x + box_width / 2)) ymax = int(min(img_height, center_y + box_height / 2)) return { "name": class_list[cls_id], "xmin": xmin, "ymin": ymin, "xmax": xmax, "ymax": ymax }反向转换最容易出问题的点是坐标越界。YOLO格式的归一化值可能会因为标注时手误出现1.00001之类的越界数值,乘以图像宽高后会超出范围,所以要做clip裁剪,确保xmin不小于0、xmax不大于图像宽度。如果你发现转换后的框大面积偏移,优先检查是不是classes.txt顺序调换过。验证转换正确性最直接的办法是可视化:把标注框画在原图上,随机抽50张,肉眼扫一遍即可。我每次转换完都会写一个draw_boxes脚本,用OpenCV画框,如果框内目标边缘基本吻合,说明转换无误。
4. 用这份数据集训练YOLO模型:从划分到参数配置
4.1 训练集验证集划分策略
数据集的VOC2007/ImageSets/Main里已经给了官方划分好的train.txt和val.txt,这个划分是我见过比较合理的,大约按8:2比例切分。但你需要确认的是,这份划分是否充分打乱了类别分布。更好的做法是自己重新划分,做分层抽样,确保每个类别在训练集和验证集里的比例大致相同。
import os import random from collections import defaultdict labels_dir = "YOLO/labels" train_ratio = 0.8 random.seed(42) class_to_images = defaultdict(list) for file in os.listdir(labels_dir): if not file.endswith(".txt"): continue image_id = os.path.splitext(file)[0] with open(os.path.join(labels_dir, file), "r") as f: classes = set() for line in f: classes.add(int(line.strip().split()[0])) for cls_id in classes: class_to_images[cls_id].append(image_id) train_files = [] val_files = [] for cls_id, img_list in class_to_images.items(): random.shuffle(img_list) split_idx = int(len(img_list) * train_ratio) train_files.extend(img_list[:split_idx]) val_files.extend(img_list[split_idx:]) train_files = list(set(train_files)) val_files = list(set(val_files)) with open("train_custom.txt", "w") as f: for img_id in train_files: f.write(f"data/images/{img_id}.jpg\n") with open("val_custom.txt", "w") as f: for img_id in val_files: f.write(f"data/images/{img_id}.jpg\n") print(f"Train: {len(train_files)}, Val: {len(val_files)}")这段脚本把每个类别对应的图片分别收集,在每个类别内部按比例抽样,最后合并去重。注意这里用的是集合去重,因为一张图可能同时含多个类别的缺陷,它可能被多个类别列表引用,直接extend会产生大量重复。筛选时建议用交集判断而不是多标签包含判断,否则训练集里会过度充斥多目标复杂图,验证集反而只剩简单样本,mAP虚高。
4.2 类别配置与data.yaml文件
使用YOLOv5或YOLOv8训练时,需要写一个data.yaml文件描述数据集路径和类别名称。这里有一个常见坑:类别名称必须和训练脚本里的索引严格对应,索引从0开始,一行一个类名,不能有空格行。
train: /path/to/dataset/train_custom.txt val: /path/to/dataset/val_custom.txt nc: 17 names: 0: insulator_break 1: insulator_missing 2: insulator_flash 3: insulator_crack 4: grading_ring_tilt 5: grading_ring_missing 6: damper_fall_off 7: damper_tilt 8: parallel_gap_abnormal 9: drainage_line_break 10: drainage_line_scatter 11: wire_foreign_object 12: bolt_loose 13: rust 14: bird_nest 15: equipment_oil_leak 16: fence_damagetrain和val字段推荐写txt文件路径而非目录路径,因为当你用ImageSets里的划分文件时,txt里写的是图片绝对路径或相对路径。如果你直接用YOLO目录下的images和labels结构,也可以把train和val直接指向父目录,Ultralytics会自动读取。但建议用txt方式,因为灵活性更高,方便你调整划分比例做实验。nc参数必须等于classes.txt中的行数,names列表的顺序也必须与classes.txt一致,这两处不一致是新手最容易犯的错误。
4.3 训练超参数设置与硬件适配
针对这个数据集的特性,我给出一个经过多轮实验的基准参数组合。
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 1280 \ --batch 8 \ --epochs 100 \ --cache \ --device 0img参数设成1280而不是默认的640,这个决策直接关系到小目标检测效果。变电站设备巡检图里有大量小目标缺陷,比如绝缘子自爆裂纹可能只有几十个像素,如果缩放到640,这些目标可能只有十几个像素,模型根本学不到纹理特征。但如果你的显存只有8G,1280的输入尺寸配batch 8已经接近极限,可以降到img 960配batch 16试试。cache参数把图像加载到内存里,8307张一千六百万像素的图大约占12G内存,如果你的机器内存不够就不要开。
epochs我建议从100起步,观察loss曲线和val mAP曲线是否在最后20个epoch仍然明显上升,如果还在上升,继续追加到150。一般来说,这种工业场景单类小样本数据,100个epoch的拟合程度对YOLOv5s来说已经够用,继续训练提升有限,容易在低频类别上过拟合。如果发现训练后期val loss开始反弹,说明开始过拟合,需要提前终止或调高weight_decay。
4.4 类别权重与损失函数调优
类别不平衡是本数据集最需要重视的问题。我实测的经验是,直接训练后,高频类别如rust、insulator_break的mAP可以到85%以上,但低频类别如parallel_gap_abnormal、fence_damage可能只有50%。解决思路有两层。
第一层是给损失函数加类别权重。YOLOv5和YOLOv8的loss_component里有一个class_weights参数,你可以把每个类别的权重设为总实例数除以该类实例数,再做归一化,让低频类别获得更大的梯度贡献。但注意权重不要设得过于极端,否则低频类别会压制高频类别,整体mAP反而下降。
第二层是数据层面的增广。对低频类别采用过采样或贴图合成策略,把低频类别的目标从原图中裁剪出来,通过随机旋转、缩放、亮度调整后粘贴到背景图上。这种方案比单纯改权重更稳,因为模型能真正看到多样化的样本,而不是在梯度上硬掰。具体做法时,我会先从YOLO标签里筛选出低频类别的图片,用OpenCV按标注框裁剪,保存成小图库;然后在训练前用脚本随机往其他图片上粘贴,粘贴时要注意避开原图已有目标的区域,否则覆盖遮挡会导致标注错乱。
5. 避坑指南:标注格式、训练评估与推理阶段的高频坑
5.1 标注框错位半个身位,mAP却很高
现象:训练完模型,验证集mAP能达到0.85,看起来不错。但拉到现场测试,发现检测框总是偏向目标左上方半个身体的位置,尤其是绝缘子自爆这种小目标,框的中心偏移明显。
原因:VOC格式的XML里bndbox如果标注时人工打点就存在系统性偏移,训练集里大量框的左上角偏紧、右下角偏松,模型学到的框分布就跟着偏移。但mAP是在同一套有偏标注上评估的,模型预测偏移和标注偏移方向一致,所以mAP不降反升,这叫标注偏差的自洽陷阱。
解决:抽20张图人工检查标注质量,重点是边界框是否紧贴目标边缘。发现问题后,重新标注或对全部标注做一致性修正。修正方式可以用简单的规则平移:统计所有框中心点相对目标语义中心的角度偏移,算平均偏移向量,然后对所有框做反向平移补偿。我在一个模拟项目X里就是这样修掉的,修正后mAP从0.85降到0.82,但人眼检测效果明显变准了。
5.2 训练到一半loss变成NaN
现象:YOLOv5训练到第30个epoch,loss突然变成NaN,之后一直卡住,print出来的box_loss和cls_loss全是nan。
原因:最可能是学习率过高或batch里混入了异常标注。这个数据集的XML里可能有极端的框尺寸,比如宽度为0或高度为0,这种标注会被转换成YOLO格式里的width=0,训练时计算IoU遇到除零变成NaN。另一种情况是某张图的标注框坐标越界,超出图像边界后归一化值大于1,输出层计算损失时梯度爆炸。
解决:训练前写一个校验脚本,检查YOLO标签里是否有width或height小于等于0的行,以及center_x + width/2是否大于1。发现异常标签直接删除该目标或整张图。如果标签没问题,就降低初始学习率,从0.01降到0.001,同时开启warmup。我一般会在第一个epoch后打印一下loss值,如果已经出现inf,说明数据有问题而不是学习率问题。
5.3 低频类别训练后完全没有检出率
现象:验证集上鸟巢检测得很好,但渗漏油几乎一个都检不出来,漏检率达到100%。
原因:类别数量的极端不平衡。假设渗漏油只有60个实例,而鸟巢有1200个实例,默认训练配置下,模型几乎把所有注意力放在鸟巢上。而且渗漏油目标本身纹理复杂,油渍边缘模糊,标注框内部特征分布混乱,模型很难学到稳定特征。
解决:先做数据增广,对渗漏油类别做十倍的复制粘贴扩展。如果扩展后还是没有改善,考虑做类别聚类,把渗漏油和锈蚀合并成一个"表面异常"的大类,因为两者在视觉上都是表面变色和污损,边界本身就模糊。合并后检测任务从17类变成16类,渗漏油的样本量显著提升,整体准确率反而更高。
5.4 VOC转YOLO后类别索引全部错位
现象:某开发者下载了一份VOC数据集,自己写了转换脚本,训练出的模型把绝缘子自爆检成鸟巢,所有类别整体平移了三位。
原因:转换脚本里的class_map直接用了VOC XML里name字符串和自建的class_list索引做匹配,但class_list的顺序和目标检测框架内置的COCO类别顺序混用了。比如XML里的name是"insulator_break",class_list里排在第0位,但转换成txt时却写入了一个从其他地方加载的类别表索引3。
解决:强制以classes.txt为准。转换前先读classes.txt,用里面的行号作为索引基准,再用这个索引去对应XML里的name。每次转换完做一次反向校验:随机挑一张图,读它的XML和TXT,打印对比框中目标的name和索引是否一致。这个校验脚本花十分钟写,能在训练前拦住99%的错位问题。
5.5 验证集mAP高但现场实测漏检严重
现象:验证集mAP 0.88,但把模型部署到变电站现场实拍视频上,漏掉大量缺陷,特别是暗光条件下的绝缘子裂纹。
原因:数据分布漂移。数据集的图像是白天正常光照下拍摄的,而现场视频可能包含夜间红外补光、傍晚逆光、雨天反光等成像条件,这些在训练集里占比很有限。更关键的是,现场摄像头安装角度是俯视,与数据集里的平视巡检视角不同,目标的表观特征差异很大。
解决:现场实测后发现漏检集中,优先做面向目标域的微调。采集现场视频片段,抽帧后用这个预训练模型做自动标注,人工修正后加入训练集,再微调20个epoch。如果数据量不允许采集,先把推理端的对比度增强预处理打开,或者在推理时使用test-time augmentation,把原图和水平翻转图各推理一次合并结果,能缓解一部分光照差异问题。
6. 进阶玩法:用mAP混淆矩阵定位模型短板,定向补强
训练完模型只是第一步,真正决定模型能不能上线的是评估结果的深度分析。很多人只看一个总体mAP,但变电站缺陷检测这个场景,不同类别的难度差异极大,一个笼统的数字掩盖了太多问题。我拿到这份数据集训练完YOLOv5s之后,做了一套标准化的评估流程,每次训练完都跑一遍,定向补强弱项。
第一步是生成混淆矩阵。用验证集跑推理,保存每张图的预测框和真实框,计算IoU匹配,IoU大于0.5算命中。然后用sklearn的confusion_matrix统计每个真实类别的命中率,重点关注对角线之外的高混淆对。我看到的具体情况是,防震锤脱落和防震锤歪斜之间的混淆非常严重,因为两者形态接近,脱落状态下锤体可能还挂在线上,只是角度变了。这种混淆靠增加训练集里两者的对比样本能缓解,但更有效的做法是在后处理里加入时序判断,连续多帧检测到同一位置存在疑似脱落或歪斜时再触发告警,单帧检测结果不做最终判断。
第二步是分析每个类别的precision-recall曲线。YOLO系框架会输出PR曲线图,但注意PR曲线的横轴是召回率,纵轴是精确率,不同类别的曲线形态差异极大。锈蚀和异物的PR曲线起点高但下降快,说明高置信度下检测准确,但置信度一降低错误框大量涌入。渗漏油的PR曲线则是整体偏低,说明模型从未对该类别产生过高的置信度,单纯调阈值救不回来,必须从数据和模型层面解决。这一步能指导你决定每个类别独立设置置信度阈值,而不是全模型共享一个阈值。
第三步是难例挖掘。用当前模型跑一遍全部训练集,找出置信度排名靠前的误检框和漏检的标注框,导出成一个小图集。然后针对这些难例做三次复制粘贴增强,并微调20个epoch。这个方法在模拟项目X上效果非常明显,最难的两个类别mAP分别提升了9%和12%。难例挖掘不能超过三轮,超过后会过拟合难例的噪声特征,反而削弱泛化能力。
关于置信度阈值,一个实用的调整经验是:高发性且后果严重的缺陷如绝缘子自爆、引流线断股,置信度阈值调低到0.15,宁错杀不放过;低频但形态混淆严重的类别如防震锤脱落与歪斜,阈值调到0.35,只在极确定时才输出。参数配置写在推理脚本里,每个类别单独一行,不要指望一个全局阈值解决所有问题。从那以后我每次做完一个数据集训练,都强制走完生成混淆矩阵、分析PR曲线、难例挖掘这三步,才敢把模型提给现场测试。这套数据集的合理价值也正在于此——它能让你在真实场景需求驱动下,把每一步都踩到实处。希望帮到你。
本文还有配套的精品资源,点击获取