
简介这份文档面向电力设备检测方向的研究人员与工程师系统整理了一套电力场景电气设备红外图像变压器检测数据集说明可用于目标检测模型的数据准备与实验设计。文档对应资源包仅含1个docx文件大小约1006KB篇幅集中介绍数据集的构成、标注格式与类别统计。数据集共收录4271张红外图像每张图像配套VOC格式xml与YOLO格式txt标注覆盖ACB、CT、Connection、LA、LBS、MCCB、PT、VCB、body、core等14个类别合计11896个矩形标注框采用labelImg完成标注便于直接迁移到YOLO系列或Pascal VOC流程。文中还给出各类别框数分布与使用边界并说明不对训练所得模型或权重精度作保证。已有101人学习适合需要快速了解数据规模、类别长尾与标注规范进而评估其是否适用于变电站设备缺陷检测与异常状态识别任务的读者。1. 红外图里的变压器检测4271 张、14 类数据先看清再动手拿到这份电力场景电气设备红外图像数据集第一反应别急着喂给检测器。它名字里带“变压器检测”但 14 个类别标的是开关柜内的设备与部件ACB、CT、PT、VCB、MCCB、LA、LBS、MOF、PF、body、core以及三个拼写相近的连接器类。4271 张 jpg、4271 个 xml、4271 个 txt共 11896 个矩形框VOC 与 YOLO 双格式并存用 labelImg 画的框。适合做电力设备红外目标检测的基线训练、类别长尾实验和现场巡检算法的预研不适合直接拿来做“变压器本体缺陷诊断”因为标注体系里没有缺陷类型只有设备位置。先看清类别账再谈训练能省掉很多返工。2. 标注体系拆解三个 connection 类与 11896 个框的分布账2.1 三个 connection 类拼写差异背后是标注批次问题这份数据最需要先处理的是类别名。Connection有 3961 个框connection有 1952 个框Conection有 471 个框。前两个只差大小写第三个少了一个n中文语义都指向“连接器”。labelImg 的类别列表是人工维护的不同批次标注时手输或复制粘贴很容易出现这种串号。血泪经验是如果直接把 14 类原样塞进 YOLO模型会把三个类当成三个不同目标学推理时同一个连接器可能在不同置信度下跳类评估曲线看着有 14 类实际有两类始终学不稳。处理方式取决于业务定义。如果现场只关心“连接器”这个大类就合并成一个Connection总框数变成 396119524716384占总框数的 53.67%。合并后类别数降到 12 类长尾更集中但连接器类会占据一半以上标注其他类容易被压制。如果业务上要区分“插接式连接器”和“螺栓连接点”就不能盲目合并得先抽 100 张图看这三类框是否落在不同部件上。我的建议是保守起步先保留 14 类做一次统计和可视化确认Conection不是另一种部件后再合并成 12 类重训。不要一上来就合并合并错了没有后悔药。先跑一个只读统计脚本把 xml 里的类别名和框数点清楚。不要用 txt 统计因为 txt 只有 class id看不出拼写问题。from pathlib import Path import xml.etree.ElementTree as ET from collections import Counter root Path(data/ir_power) xml_dir root / Annotations name_counter Counter() img_with_cls Counter() bad_xml [] for xml_path in sorted(xml_dir.glob(*.xml)): try: tree ET.parse(xml_path) except ET.ParseError as e: bad_xml.append((xml_path.name, str(e))) continue names_in_img set() for obj in tree.getroot().findall(object): name (obj.findtext(name) or ).strip() if not name: continue name_counter[name] 1 names_in_img.add(name) for n in names_in_img: img_with_cls[n] 1 print(类别框数) for k, v in name_counter.most_common(): print(f{k:12s} {v:5d} 出现在 {img_with_cls[k]:4d} 张图中) print(坏 xml, bad_xml[:5], 总数, len(bad_xml))这段脚本只读 xml不改文件。name_counter统计每个类别名出现多少次img_with_cls统一种类别出现在多少张图里后者能看出类别覆盖面。比如PF只有 56 个框如果只分布在 20 张图里验证集切分时很容易一张都分不到。bad_xml用来提前发现截断文件避免转换到一半报错。实际跑完如果发现Conection和Connection的图片高度重叠基本可以判定是同一类拆分如果各自集中在不同子目录就要谨慎。2.2 框数分布决定训练策略3961 比 56 的类别失衡把原始 14 类的框数摊开看长尾非常明显。下面这张表按原始类别统计占比以 11896 为分母。MOF、PF、core 在原数据里没有给中文释义先保留缩写训练前找业务方确认不要自己猜。类别名中文/说明框数占比训练关注点ACB空气断路器940.79%极少数类单独看 APCT电流互感器7906.64%中等样本注意小目标Conection连接器拼写变体4713.96%先确认是否合并Connection连接器396133.30%主导类防压制LA避雷器2672.24%小样本增强慎用LBS负荷开关5484.61%中等样本MCCB塑壳断路器160013.45%主力学稳MOF原标注未给中文2732.29%先确认语义PF原标注未给中文560.47%最少类别只看 mAPPT电压互感器6615.56%中等样本VCB真空断路器1441.21%小样本body柜身3803.19%大目标尺度差异大connection连接器小写变体195216.41%与 Connection 高度疑似重复core原标注未给中文6995.87%先确认语义总框数 11896。连接器三个变体加起来 6384 个框占 53.67%如果把Conection也并进去连接器类在训练集里会形成绝对主导。反过来PF只有 56 个框ACB94 个VCB144 个这三个类在随机切分下很可能验证集只有几张图。训练时如果只看整体 mAP50连接器类把分数拉高小类全军覆没也看不出来。我的做法是训练前先按类别分层统计图片数验证集保证每个类至少有 10 张图出现不够的类从训练集里按图片而不是按框补避免同一张图重复采样导致过拟合。类别失衡还会影响 NMS 后的类别竞争。连接器框多模型在柜内密集区域容易把 CT、PT 的局部也预测成连接器。解决不是简单加类别权重而是先做合并去重再用 per-class AP 和混淆矩阵定位。权重的作用是让损失更关注小类但如果标签本身有三个重复类权重会放大噪声。2.3 文件组织与命名一致性检查数据集给的是 jpg 图片、VOC xml 和 YOLO txt三者数量都是 4271理论上按同名主键一一对应。实际拿到手的包经常出现a.jpg配a.xml但 txt 叫a.txt的情况也可能有图片存在而 xml 缺失。先跑一致性检查不要等到训练时报“标签找不到”。from pathlib import Path root Path(data/ir_power) img_dir root / JPEGImages xml_dir root / Annotations txt_dir root / labels imgs {p.stem for p in img_dir.glob(*.jpg)} xmls {p.stem for p in xml_dir.glob(*.xml)} txts {p.stem for p in txt_dir.glob(*.txt)} print(图片数, len(imgs), xml数, len(xmls), txt数, len(txts)) print(有图无xml, sorted(imgs - xmls)[:10], 总数, len(imgs - xmls)) print(有xml无图, sorted(xmls - imgs)[:10], 总数, len(xmls - imgs)) print(有图无txt, sorted(imgs - txts)[:10], 总数, len(imgs - txts)) print(有txt无图, sorted(txts - imgs)[:10], 总数, len(txts - imgs))stem是去掉扩展名的主键三个集合做差就能定位缺失。实际处理时有图无 xml 的样本不要直接删先看是不是负样本如果业务允许负样本可以保留图片但不进标注转换训练时作为背景图。有 xml 无图的样本必须删否则转换脚本读不到尺寸。有图无 txt 的样本如果决定以 xml 为准重建 YOLO 标签可以忽略原 txt如果决定沿用原 txt就要补齐。我的习惯是以 xml 为唯一真源重建 txt因为 xml 保留了类别名能发现拼写问题txt 只有数字 id错了很难追溯。3. VOC 到 YOLO 的落地坐标归一化、分组切分与 data.yaml 对齐3.1 从 XML 到 YOLO坐标归一化与越界裁剪VOC 的框是xmin, ymin, xmax, ymax绝对像素坐标YOLO 要的是class_id x_center y_center width height全部除以图像宽高归一化到 0~1。转换看着简单翻车点集中在三处图像尺寸用 xml 里的size还是实际 jpg 尺寸框超出图像边界要不要裁类别名到 id 的映射顺序是否和data.yaml一致。我的做法是以实际 jpg 尺寸为准因为 xml 的size是标注时写入的图片被重存或裁剪后可能不一致。框先裁剪到[0, W-1]和[0, H-1]再算中心点和宽高宽高小于 1 像素的框直接丢弃。from pathlib import Path from PIL import Image, ImageOps import xml.etree.ElementTree as ET # 固定类别顺序必须与 data.yaml 的 names 完全一致 CLASSES [ ACB, CT, Connection, LA, LBS, MCCB, MOF, PF, PT, VCB, body, core, ] # 如果决定合并三个连接器变体用这个映射保守方案先设 ALIAS {} ALIAS {Conection: Connection, connection: Connection} CLS_ID {name: i for i, name in enumerate(CLASSES)} def convert_one(xml_path: Path, img_path: Path, out_txt: Path): with Image.open(img_path) as im: im ImageOps.exif_transpose(im) # 处理手机/相机 EXIF 旋转 W, H im.size root ET.parse(xml_path).getroot() lines [] for obj in root.findall(object): name (obj.findtext(name) or ).strip() name ALIAS.get(name, name) if name not in CLS_ID: continue # 未登记类别先跳过并记录不要静默丢 b obj.find(bndbox) if b is None: continue xmin max(0.0, min(W - 1.0, float(b.findtext(xmin)))) ymin max(0.0, min(H - 1.0, float(b.findtext(ymin)))) xmax max(0.0, min(W - 1.0, float(b.findtext(xmax)))) ymax max(0.0, min(H - 1.0, float(b.findtext(ymax)))) if xmax - xmin 1.0 or ymax - ymin 1.0: continue xc (xmin xmax) / 2.0 / W yc (ymin ymax) / 2.0 / H bw (xmax - xmin) / W bh (ymax - ymin) / H lines.append(f{CLS_ID[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_txt.write_text(\n.join(lines), encodingutf-8)CLASSES是唯一 id 来源顺序一旦定下就不要改。ALIAS是可选合并开关保守方案留空跑完统计再决定。ImageOps.exif_transpose会按 EXIF 方向把图转正返回的size才是标注时人眼看到的宽高如果原图有 EXIF 旋转而训练时用 OpenCV 读图不会自动转就会导致框整体错位。这里的处理是转换阶段按转正后的尺寸算坐标但保存的 jpg 并没有物理旋转所以更稳的做法是在转换前把 jpg 也按 EXIF 转正后重存保证训练读图和标注坐标系一致。xmin和xmax都夹到W-1避免右边界框算出大于 1 的宽。小于 1 像素的框丢弃通常来自标注误点。脚本没有写“未登记类别”的日志实际跑的时候建议加一个Counter把跳过的类别名打出来防止新类别被静默吞掉。3.2 训练/验证切分按设备分组别让同一台变压器同时出现在两边随机按图片切分是红外数据集最容易踩的坑。同一台柜子可能拍了几十张角度、曝光略有差异随机切分后训练集和验证集里出现同一设备的近重复帧验证 mAP 会虚高现场换一座变电站就掉点。正确做法是先做近重复分组再按组切分。文件名有规律时按前缀分组最省事文件名是纯序号时用感知哈希把视觉上接近的图聚成组。下面这段用imagehash的 dhash 做粗聚类阈值 6 是经验值红外图纹理少阈值调太大会把不同设备合并调太小又切不开近重复帧。from pathlib import Path from PIL import Image import imagehash import random img_dir Path(data/ir_power/JPEGImages) images sorted(img_dir.glob(*.jpg)) random.seed(42) groups [] # 每个组是一个图片列表 for p in images: h imagehash.dhash(Image.open(p).convert(L)) placed False for g in groups: if h - g[hash] 6: g[items].append(p) placed True break if not placed: groups.append({hash: h, items: [p]}) random.shuffle(groups) n_val max(1, int(len(groups) * 0.2)) val_groups, train_groups groups[:n_val], groups[n_val:] val [p for g in val_groups for p in g[items]] train [p for g in train_groups for p in g[items]] print(组数, len(groups), 训练图, len(train), 验证图, len(val))dhash对亮度变化不敏感适合红外图。h - g[hash]是汉明距离小于等于 6 认为视觉接近。分组后再按 8:2 切保证同一设备的近重复帧只落在一边。实际项目里如果文件名带设备编号比如stationA_cabinet03_001.jpg直接取stationA_cabinet03作为组键更可靠感知哈希只作为兜底。切分完把文件名列表存成train.txt和val.txt后面复制图片时按列表走不要重新随机。3.3 data.yaml 与 dataloader 对接names 顺序必须和 txt 的 class id 一致YOLO 训练读取的是images/train、images/val和对应的labels/train、labels/val。目录结构建议统一成下面这样图片和标签分开放主键同名。data.yaml里的names顺序就是 txt 里 class id 的顺序错一位整个训练都在学错类。path: /data/ir_power train: images/train val: images/val names: 0: ACB 1: CT 2: Connection 3: LA 4: LBS 5: MCCB 6: MOF 7: PF 8: PT 9: VCB 10: body 11: core如果保留 14 类names要按原始 14 个名字展开并把Conection、connection单独给 id。合并方案则只留 12 类ALIAS在转换时已经把三个变体映射到Connection。两种方案不能混用转换脚本用 12 类映射yaml 却写 14 类训练时不会报错但类别会整体错位这是最隐蔽的翻车。训练命令以常见检测框架为例字段名按所用框架调整yolo detect train \ data/data/ir_power/data.yaml \ modelyour_detector.pt \ imgsz960 \ epochs100 \ batch8 \ workers4 \ seed42 \ projectruns/ir_power \ namebaseline_12clsimgsz960是为了小目标后文展开。batch8是 8GB 显存下的保守值显存够可以上 16。workers4避免数据加载成为瓶颈。seed42固定随机种子方便复现。name里带类别数防止不同实验目录混在一起。训练前先跑一次val或小样本过拟合确认标签能被读出来再开长训练。4. 训练参数取舍960 输入、长尾采样与红外增强的边界4.1 输入分辨率与增强红外图别照搬 RGB 那套红外图像和可见光最大的差别是通道信息单一、纹理弱、对比度靠温度差撑。直接把 RGB 检测那套 HSV 色调增强、通道互换、马赛克拼图搬过来轻则学不到有效特征重则把设备边缘拼断。我的默认配置是输入 960关闭 HSV 色调和饱和度扰动保留少量亮度扰动关闭马赛克和 MixUp保留随机缩放和平移。body这种柜身大目标在 960 下仍然完整PF、ACB这种小目标也不至于被下采样成几个像素。如果显存只够 640优先保证小类图片的切图训练而不是硬拉全图。# 训练配置片段字段名按所用框架调整 imgsz: 960 hsv_h: 0.0 # 红外没有色调概念关掉 hsv_s: 0.0 # 饱和度扰动同样关掉 hsv_v: 0.15 # 只留少量亮度扰动模拟曝光差异 degrees: 0.0 # 柜内设备有安装方向不做大角度旋转 translate: 0.1 scale: 0.25 fliplr: 0.0 # 左右翻转会破坏柜内布局语义先关 mosaic: 0.0 # 小目标被拼图缩得更小先关 mixup: 0.0hsv_h和hsv_s置零是因为红外图转成三通道后色调和饱和度是伪彩色映射出来的扰动它们等于改变温度到颜色的映射关系模型会学到不存在的线索。hsv_v保留 0.15 是模拟不同曝光。degrees置零是因为开关柜内设备有明确安装方向旋转 90 度会造出不存在的场景。fliplr关掉也是同理左右翻转后断路器操作面板位置反了模型可能记住错误布局。mosaic关闭的代价是背景多样性下降但换来了小目标不被缩小。如果数据量不够导致过拟合再考虑低概率 mosaic比如 0.2不要一上来就 1.0。对比度增强可以用 CLAHE 在数据加载阶段做但要注意别把噪声也放大。常见做法是离线生成一份 CLAHE 增强副本只对训练集做验证集保持原图这样评估分布和现场一致。在线增强更灵活但调试成本高。我一般先跑原图基线看 per-class AP 再决定要不要加 CLAHE。4.2 长尾类别采样与损失函数别只看总 mAP连接器类占一半以上框PF只有 56 个框直接用默认损失模型会把概率 mass 全压到连接器上。类别权重不是越猛越好按框数反比1/count会给PF极大权重几十个样本反复学很快过拟合。更稳的是平方根倒数把权重差距压平一些。下面这段按框数算权重训练时传给损失函数或采样器。import numpy as np # 按第 2 章统计的 12 类合并后框数顺序与 CLASSES 一致 counts np.array([ 94, 790, 6384, 267, 548, 1600, 273, 56, 661, 144, 380, 699, ], dtypenp.float64) w 1.0 / np.sqrt(counts) w w / w.mean() for i, v in enumerate(w): print(i, round(float(v), 3))1/sqrt(count)让PF的权重约为连接器的 10 倍而不是 100 倍。权重归一化到均值 1避免整体损失尺度变化太大。实际训练时如果框架不支持类别权重可以用重采样每个 epoch 按权重从图片池抽图但同一张图重复出现会加剧过拟合采样率别超过 2 倍。另一个办法是 focal loss降低易分样本的损失贡献让模型关注小类难样本。focal loss 的gamma从 1.5 起步不要直接上 3红外图噪声大gamma 太高会把难样本当噪声压掉。验证阶段不要只报一个总 mAP。把PF、ACB、VCB三个小类的 AP 单独打出来再和连接器类对比。如果总 mAP 涨了但小类 AP 掉了说明模型在牺牲小类换主导类这种模型上线后现场漏检的正是少数设备。4.3 评估指标与阈值选择per-class AP 与按类阈值评估时用较低的置信度阈值把 AP 算全常见做法是conf0.001、iou0.6让 PR 曲线完整。部署阈值另算不能拿评估阈值直接上线。连接器类样本多置信度 0.25 可能就够PF样本少模型输出分数整体偏低阈值 0.25 会漏掉一半需要按类调低到 0.1 左右再人工复核。下面这张表是起步建议实际值要用验证集 PR 曲线定不要照抄。类别组代表类别框数特征验证重点初始 conf 建议主导类Connection6384合并后防压制其他类0.25主力学MCCB、CT、PT600~1600框回归稳定性0.25小样本PF、ACB、VCB56~144召回率、漏检0.10~0.15大目标body380边界完整性0.30待确认MOF、core273、699语义是否正确先人工抽检评估命令用低阈值算 APyolo detect val \ modelruns/ir_power/baseline_12cls/weights/best.pt \ data/data/ir_power/data.yaml \ imgsz960 \ conf0.001 \ iou0.6 \ plotsTrueconf0.001是为了让 PR 曲线覆盖低分样本plotsTrue会输出混淆矩阵和 PR 曲线。看混淆矩阵时重点看两件事连接器是否把 CT、PT 大量误吸body是否和core混淆。如果连接器行非对角线上全是其他类说明类别定义或标注边界有问题调阈值救不回来。5. 避坑排查标注错位、类别串号和评估虚高的 5 个现场5.1 现象验证集 mAP 很高换一批图就大面积漏检现象很典型验证集 mAP50 到 0.9现场抽 50 张新图连接器和 CT 漏检一半。原因通常不是模型不行是切分泄漏。同一台柜子连续拍了多帧随机切分后训练集和验证集共享近重复帧模型记住了背景而不是设备。解决分三步先用 3.2 的 dhash 分组把汉明距离小于等于 6 的图聚成组再按组切 8:2最后人工抽 20 张验证集图确认没有和训练集同角度、同曝光的孪生图。分组键能用品编号就用品编号不要图省事用纯随机。5.2 现象训练 loss 正常下降但推理时所有连接器都跳成同一个类现象是混淆矩阵里Conection、Connection、connection三行互相串或者模型只输出Connection。原因是三个类本质是同一部件标注批次不同导致类别名分裂而data.yaml的 names 顺序又和 txt 里的 id 对不上。解决以 xml 为真源重建 txt固定CLASSES列表合并方案用ALIAS把三个变体映射到一个类data.yaml同步改成 12 类。重建后抽查 100 个 txt确认 class id 落在 0~11 且与 names 对应。不要保留原 txt 再手工改 yaml两边顺序一旦错位训练不会报错但全盘学错。5.3 现象框整体偏移、缩放或者只剩一半现象是可视化时框跑到设备外面或者框只盖住设备上半截。原因常见两个xml 里的size和实际 jpg 尺寸不一致图片带 EXIF 旋转标注是在转正后的图上画的训练读图却没转。解决转换阶段用ImageOps.exif_transpose读实际尺寸并把 jpg 物理转正后重存保证训练读图和标注坐标系一致同时校验 xml 的 width/height 与 PIL 读出的尺寸差异超过 2 像素就标记出来人工确认。不要直接信任 xml 的 size尤其是图片被二次导出过的包。5.4 现象PF、ACB 的 AP 一直是 0训练日志里这几个类没有梯度现象是小类 AP 为 0但数据集中确实有这些框。原因通常是小目标在 640 输入下被下采样到几个像素加上 mosaic 把图缩小特征直接消失或者类别权重没生效梯度被主导类淹没。解决输入提到 960关闭 mosaic 和 mixup先跑 20 个 epoch 看小类是否有正样本匹配检查 txt 里小类 id 是否真的存在别只信统计脚本损失端用平方根倒数权重或 focal loss别用 1/count 硬压。如果显存不够 960切图训练比硬拉全图更有效切图时保证框不被切断。5.5 现象同一份配置两次训练结果差很多评估复现不了现象是同样的 yaml、同样的数据两次训练 mAP 差 3~5 个点混淆矩阵也不一样。原因是随机种子没固定、数据加载顺序变了、标签缓存没清。解决训练命令固定seed关闭或固定workers的随机性每次换数据后删掉旧的labels.cache再训练验证时用同一份权重和同一组conf/iou。如果框架默认开 AMP混合精度也会带来小幅波动对比实验时先关 AMP 跑基线。复现性不是玄学是这些开关有没有钉死。6. 进阶验证混淆矩阵、可视化抽检与一次小样本回归训练完不要只看一个 mAP 数字。我的验收顺序是先看混淆矩阵再看 per-class PR 曲线最后抽 50 张图人工画框。混淆矩阵重点看连接器与 CT、PT 的交叉以及body和core是否互相吸PR 曲线看小类曲线是否贴着坐标轴贴轴说明阈值稍高就全漏。抽检时每个类别至少覆盖 3 张图小类优先框画在图上直接看有没有偏移、漏标、重复框。下面这段是抽检可视化脚本按类别从验证集里抽图画真值和预测。from pathlib import Path from PIL import Image, ImageDraw import random # 伪代码preds 为 {图片名: [(cls_id, xc, yc, w, h, conf), ...]} # gt 为 {图片名: [(cls_id, xc, yc, w, h), ...]} def draw_one(img_path, gt_boxes, pred_boxes, names, out_path): im Image.open(img_path).convert(RGB) W, H im.size dr ImageDraw.Draw(im) for cls_id, xc, yc, w, h in gt_boxes: x1, y1 (xc - w / 2) * W, (yc - h / 2) * H x2, y2 (xc w / 2) * W, (yc h / 2) * H dr.rectangle([x1, y1, x2, y2], outlinelime, width3) dr.text((x1 3, y1 3), names[cls_id], filllime) for cls_id, xc, yc, w, h, conf in pred_boxes: x1, y1 (xc - w / 2) * W, (yc - h / 2) * H x2, y2 (xc w / 2) * W, (yc h / 2) * H dr.rectangle([x1, y1, x2, y2], outlinered, width2) im.save(out_path)绿色画真值红色画预测同一张图上直接看偏移和漏检。names用data.yaml的顺序不要另起一套。抽检发现框整体偏移回到 5.3 查 EXIF 和尺寸发现小类全是漏检回到 4.3 调低该类阈值再看不要急着重训。验收表可以按下面几项过一遍任何一项不通过都先停别把问题带进部署。验收项通过标准不通过时先查类别 id 一致性抽 100 个 txtid 与 names 全对应转换脚本的 CLASSES 顺序框可视化50 张抽检无整体偏移、无半框EXIF、xml size、jpg 实际尺寸小类召回PF/ACB/VCB 在低阈值下召回可接受输入分辨率、mosaic、类别权重切分泄漏验证集无训练集近重复帧dhash 分组阈值、文件名分组键复现性两次同配置 mAP 差异小于 1 个点seed、cache、AMP 开关从那以后我每次拿到新数据集都先把三个 connection 类的统计和可视化跑一遍再决定合并还是保留然后才开训练。这个习惯救过好几次——有一次合并后忘了同步data.yaml训练 loss 看着正常混淆矩阵一画全串了。希望帮到你。本文还有配套的精品资源点击获取