简介电力场景电气设备红外图像变压器检测数据集以VOCYOLO双格式提供4271张红外图像及对应标注面向电力设备检测、目标识别与故障预警方向的研究人员和工程师用于训练多种目标检测模型。压缩包仅1个docx说明文档大小1006KB详细说明数据集格式、类别定义与标注框分布。图片与XML/TXT标注一一对应覆盖ACB、CT、Connection、LA、LBS、MCCB、MOF、PF、PT、VCB、body、core等14个类别共11896个标注框由labelImg绘制矩形框完成。各类别数量差异明显如Connection框数3961个、core框数699个便于开展样本均衡性分析。目前已有100人浏览学习。该数据集只提供准确合理的标注不保证基于其训练模型的精度使用时应结合业务需求评估。对于需要红外图像基准数据与多类别目标框标注的电力设备检测任务这是一份可复用的训练资料。1. 电力红外图像变压器检测数据集4271张、14类、VOCYOLO双格式能拿来干什么红外图像里看变压器第一眼就是灰蒙蒙一片——发热的套管亮一点油枕和散热片只有模糊轮廓。很多做可见光目标检测的人拿到这份4271张、14类别的电力红外数据集第一反应是套用现成模型直接训结果val上指标还行一到真实站所就翻车。原因就藏在数据本身红外图像本质是单通道热辐射图目标对比度和边缘形态跟可见光完全是两个世界而这份数据恰好以VOC和YOLO双格式提供标注让主流检测框架能直接消费。这篇笔记从14类标注体系讲起把格式转换、目录组织、训练配置和验收方法一次说透适合准备做电力设备红外巡检算法选型、需要迁移学习热身、或者正在为标注团队定规范的工程师。2. 先看14类标注和红外图像特性为什么可见光的检测套路在这里不灵2.1 类别体系拆解变压器部件与站内设备各占多少拿到数据集第一步不是急着训练而是先搞清楚这14类到底标了什么。从电力红外巡检的常见标注习惯看14类基本落在两个区间一类是变压器本体上的关键部件比如套管、油枕、散热片、分接开关、本体油箱另一类是站内与变压器关联紧密的设备比如电流互感器、电压互感器、避雷器、绝缘子、断路器、隔离开关、母线最后往往还有一个其他设备兜底类别。组别常见类别检测难点变压器本体区变压器本体、套管、油枕、散热片、分接开关部件之间温度接近边界模糊关联设备区电流互感器、电压互感器、避雷器、绝缘子、断路器、隔离开关、母线小目标多站内遮挡严重兜底类其他设备/背景目标类别内差异大容易误检注意这份数据集的真实类别清单以你拿到的classes.txt或XML里的name字段为准上面的表是按电力红外检测惯例给出的参考框架。第一步建议写一个脚本统计XML里的类别分布而不是直接相信文档描述。import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter xml_dir Path(Annotations) def count_classes(xml_dir: Path) - Counter: counter Counter() for xml_file in sorted(xml_dir.glob(*.xml)): tree ET.parse(xml_file) root tree.getroot() for obj in root.findall(object): name obj.findtext(name) if name: counter[name] 1 return counter if __name__ __main__: cls_counter count_classes(Path(Annotations)) for name, count in cls_counter.most_common(): print(f{name}: {count})这段代码用xml.etree.ElementTree解析XML遍历每个object节点取name字段计数。跑完之后你会得到一张真实的类别频次表。如果某个类只有几十张后面训练时就要重点关照否则模型很容易把这个类学成背景。参数说明里唯一值得改的是xml_dir路径如果你的标注目录不叫Annotations改成实际路径即可。2.2 红外单通道图像的三个硬约束低对比度、弱边缘、小目标红外图像和可见光图像的本质差异是成像机理。可见光靠反射边缘是物体表面的颜色和纹理突变红外靠辐射边缘是温度梯度而电力设备在正常运行状态下温度梯度往往很平缓。这就带来三个直接影响检测器设计的约束。第一个是低对比度。同一台变压器的套管和油枕温差可能只有两三度反映到图像上就是灰度值接近用普通的对比度增强会连背景一起提亮目标反而更不明显。常见做法是限制对比度自适应直方图均衡化CLAHE只在小邻域内做均衡避免全局拉伸。第二个是弱边缘。热像仪分辨率普遍不高常见的640×512已经算好的很多巡检设备还是384×288。目标边缘在低分辨率下呈现为几个像素的过渡带标注框稍微偏一点IoU就掉得厉害。第三个是小目标。套管、避雷器这类细长设备在整幅巡检图里经常只占几十个像素宽属于典型的小目标检测场景。这三件事叠加起来结论很直接拿ImageNet预训练权重直接迁移到红外数据效果通常不如你预期。不是说不能用而是要知道瓶颈在哪。灰度单通道输入会丢掉预训练权重的彩色统计信息伪彩三通道复制则保留了权重结构但引入了域偏移——这是个两难选择后面第5章会展开讲怎么处理。3. 从VOC到YOLOXML与TXT的对应逻辑和一致性校验脚本3.1 两种格式怎么组织标注信息VOC格式的核心是XML文件每个目标对应一个object节点。一个典型的VOC标注长这样annotation filenamestation01_heat_001.jpg/filename size width640/width height512/height depth3/depth /size object namebushing/name bndbox xmin120/xmin ymin80/ymin xmax180/xmax ymax140/ymax /bndbox /object /annotationYOLO格式则是一个txt文件每一行对应一个目标五个数值依次是类别id、归一化中心点x、归一化中心点y、归一化宽、归一化高。对应上面这条标注假设bushing的类别id是0图像宽640高512那行数据就是0 0.234375 0.214844 0.093750 0.117188。换算逻辑是cx (120 180) / 2 / 640w (180 - 120) / 640坐标全部除以图像宽高完成归一化。字段含义VOC (XML)YOLO (TXT)说明图像文件名filename文件名由txt所在目录对应两边要完全一致类别object/name行首整数idid由classes.txt顺序决定目标位置bndbox四个绝对像素坐标归一化的cx, cy, w, h转换时除以宽高图像尺寸size/widthsize/height不需要存读取图片或XML解析格式转换本身不难真正坑人的是两个地方一是XML里的xmax、ymax可能是闭区间有的标注工具从0开始计数有的从1开始转换时要不要减1各框架接受度不一样二是类别id不能按字母表排序必须按照你最终训练用的data.yaml里names列表的顺序。我见过有人用脚本自动排序生成id训练时发现bushing和insulator互换整个模型报废。3.2 用脚本把图片、XML、TXT三方对齐格式转换前先做一致性校验这一步能省下后面几十个小时的排错时间。校验的核心是四件事图片文件是否存在、XML或TXT是否存在、XML里记录的尺寸和实际图片尺寸是否一致、边界框是否落在图像范围内。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image xml_dir Path(Annotations) img_dir Path(JPEGImages) txt_dir Path(labels) issues [] for xml_file in sorted(xml_dir.glob(*.xml)): tree ET.parse(xml_file) root tree.getroot() filename root.findtext(filename) img_path img_dir / filename if not img_path.exists(): issues.append(fmissing image: {filename}) continue with Image.open(img_path) as img: real_w, real_h img.size xml_w int(root.findtext(size/width)) xml_h int(root.findtext(size/height)) if (real_w, real_h) ! (xml_w, xml_h): issues.append(fsize mismatch: {filename} fxml({xml_w},{xml_h}) real({real_w},{real_h})) txt_path txt_dir / (xml_file.stem .txt) if not txt_path.exists(): issues.append(fmissing txt: {txt_path.name}) continue for obj in root.findall(object): box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) if not (0 xmin xmax real_w and 0 ymin ymax real_h): issues.append(fbbox out of range: {filename} f({xmin:.0f},{ymin:.0f},{xmax:.0f},{ymax:.0f})) if issues: print(ffound {len(issues)} issues:) for item in issues[:50]: print( , item) else: print(all checks passed)这段脚本做了三层校验先确认图片存在再比对XML里的宽高和真实图片宽高最后逐框检查边界坐标是否越界。issues列表收集所有异常前50条打印出来。跑完如果输出all checks passed再去做VOC到YOLO的批量转换有异常就按清单逐个处理不要抱着侥幸心理往下走。真实项目里最常见的异常就是越界框特别是目标被图像边缘截断时标注员容易把框拖出画面归一化后坐标变成大于1或者小于0的值训练时loss直接飞掉。4. 把数据集组织成YOLO训练工程目录规范、数据划分与增强参数4.1 目录摆放与data.yaml的写法VOC格式的数据集通常长这样JPEGImages放图片Annotations放XMLImageSets/Main放划分列表。YOLO训练框架需要的是图片和标签分离的目录结构常见做法是把JPEGImages复制为images下的train和val两个子目录labels目录放着对应划分的txt标注文件。整理后的目录结构如下dataset/ ├── images/ │ ├── train/ │ │ ├── station01_heat_001.jpg │ │ └── ... │ └── val/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── station01_heat_001.txt │ │ └── ... │ └── val/ │ └── ... ├── classes.txt └── data.yaml这里有个容易忽略的约定YOLO训练时是按图片文件名自动找对应标签的station01_heat_001.jpg对应的标签必须是station01_heat_001.txt文件名主体完全一致目录不同没关系。如果标签和图片在同一个目录框架也会认但把train和val分开放是更稳的做法避免误加载。data.yaml的写法直接决定训练能否跑起来关键就三行path: /path/to/dataset train: images/train val: images/val names: 0: bushing 1: oil_ conservator 2: radiator # ... 一共14类注意names的索引顺序必须和labels里的txt类别id一一对应。path字段用绝对路径最省心相对路径在换机器跑的时候容易出问题。还有一个细节YOLO框架会自动检测类别数你现在写14类但labels里如果出现id为14的目标训练时那一条会被忽略属于静默错误后面第5章展开讲。4.2 按设备分组划分train/val别让同一样本两边露脸划分数据是看起来简单、实际最能拉开模型差距的一步。很多人随机打乱后按比例切分结果同一个变压器的不同拍摄角度分别进了训练集和验证集验证指标虚高。真实场景里同一台设备的红外图像温度分布高度相关模型相当于见过答案再考试。正确的做法是按设备分组先提取文件名里的设备编号作为分组键把同一设备的所有图片归到一组再以组为单位划分。from pathlib import Path from collections import defaultdict import random random.seed(42) img_dir Path(JPEGImages) def group_key(stem: str) - str: # 假设文件名是 station01_heat_001.jpg取第一个下划线前部分作为设备编号 return stem.split(_)[0] groups defaultdict(list) for img_path in sorted(img_dir.glob(*.jpg)): groups[group_key(img_path.stem)].append(img_path) group_names list(groups.keys()) random.shuffle(group_names) val_ratio 0.2 val_groups set(group_names[:int(len(group_names) * val_ratio)]) train_files, val_files [], [] for g, paths in groups.items(): if g in val_groups: val_files.extend(paths) else: train_files.extend(paths) print(ftrain groups: {len(groups) - len(val_groups)}, fval groups: {len(val_groups)}) print(ftrain images: {len(train_files)}, val images: {len(val_files)})脚本里group_key是核心参数它决定按什么维度分组。如果你的文件名不是设备编号_序号的格式改这个函数就行哪怕提取前几个字符也可以。val_ratio默认取0.2对小数据集建议不要低于0.15否则验证集样本太少指标波动大。分组划分的代价是训练集和验证集的图片分布会有差异但换来的是验证指标更接近真实部署场景——这个取舍值得。4.3 红外增强参数怎么调mosaic要开色彩抖动要关4271张对深度学习来说不算多尤其要覆盖14个类别平均每类只有300张左右不到位的增强会让模型很快过拟合。YOLO系列自带的在线增强参数在红外场景下需要针对性调整。参数推荐值红外场景说明mosaic1.0拼接四张图增加上下文但对小目标可能造成截断hsv_h / hsv_s0.0红外没有颜色信息色调和饱和度扰动无意义hsv_v0.2亮度扰动适量保留模拟不同负荷下的温度差异degrees5旋转角不能太大部件长宽比会把框拉歪fliplr0.5水平翻转安全垂直翻转建议关掉scale0.5缩放范围太大会让部件变得更小不利于检测translate0.1平移增强对巡检图有效模拟拍摄位置偏移这里的核心原则是红外图像的信息通道是亮度和形状不是颜色。所以色彩类增强全部关掉只保留几何和亮度增强。hsv_v的0.2是经验值它模拟的是不同负荷下设备温度整体升高或降低对模型理解相对温差有帮助。还有一个值得开的参数是mosaic它能变相扩大训练样本的上下文多样性但要注意mosaic拼接时小目标可能被切到只剩一半如果训练中val的小目标召回率异常低可以先关掉mosaic试试。5. 训练和部署中的常见坑现象、原因、解决5.1 标注框与图像尺寸不匹配现象训练初期loss正常下降但到中途突然出现NaN或者在验证集上所有目标都预测得偏大。排查时发现部分图片的标注框宽高是图像宽高的好几倍。原因这是典型的越界框问题。热像仪拍摄时经常有设备出画标注员把框拖到画面边缘之外XML里记录的坐标大于图像实际尺寸。转换成YOLO格式时归一化后的值大于1模型学到的框尺度分布就被污染了。解决在第3章的校验脚本基础上把越界框裁剪到图像边界内同时丢弃宽或高小于3像素的极小框。裁剪不是简单截断而是把xmax改成real_w、ymax改成real_h然后重新计算归一化坐标。如果一张图里超过30%的框都越界建议直接删掉这张图别让错误样本参与训练。5.2 类别id错位模型学到的名字对不上现象训练loss正常val的mAP看着不错但打开推理结果一看套管被标成绝缘子油枕被标成散热片。不是个别错误是系统性错位。原因VOC转YOLO时脚本按字母表顺序给类别排了id比如bushing排在insulator前面但训练用的data.yaml里names列表又是按标注时的统计顺序写的两边一错位从第6个类别开始全部对不上。解决用一个固定的classes.txt作为唯一真源VOC转YOLO和data.yaml都从它读取。转格式时先读classes.txt生成name - id映射再遍历XML里的name字段查表查不到的类别直接报错中止。跑完转换后抽查20个txt文件人工核对类别id是不是符合预期这一步不要省。5.3 训练集和验证集重叠指标虚高现象训练时val的mAP高达0.9模型看起来完美换一批真实数据测试直接掉到0.5。更隐蔽的是训练曲线的val loss和train loss几乎同步下降没有明显gap。原因划分时用了随机打乱同一变压器不同角度的图片同时进了训练集和验证集。红外图像的设备特征是高度一致的模型其实记住了设备而不是学会了类别。解决回到第4.2节的分组划分方案按设备编号分组后以组为单位切分。另外验证集里要确保每个类别至少有10个目标如果某个类只在训练集里出现验证时这个类的mAP会显示为0容易误判为模型缺陷。若设备编号无法从文件名提取至少按拍摄日期或批次分组逻辑是一样。5.4 预训练权重在红外数据上不通用现象加载ImageNet预训练权重后冻结backbone只训练检测头结果训练到20个epoch loss就卡住不动val mAP一直在0.2上下挣扎。解冻所有层训练过拟合又来得特别快。原因ImageNet的统计信息来自可见光彩色图像红外图像虽然被存储为三通道伪彩但通道之间的相关性结构和可见光完全不同。冻结backbone相当于强制模型用可见光的纹理特征去理解红外图像等于让一个只见过彩色照片的人去摸黑识别发热设备。解决分两步走。第一步解冻全部层用一个较小的学习率比如0.001训练整个模型5到10个epoch让backbone适应红外域第二步再按常规流程微调。如果显存不够解冻全部层退而求其次只解冻最后两个stage但前面提到的问题还是会打折扣。另一个方向是干脆不用预训练权重从头训练4271张的数据量加上合理增强效果不一定比迁移差尤其当你的红外图像是灰度单通道时。5.5 小目标漏检需要同时调输入尺寸和NMS参数现象套管、避雷器这类细长目标在验证集上的召回率明显低于其他类别检测结果里经常是中等大小的目标框把小目标盖住或者相邻两个小目标被合并成一个框。原因热像仪分辨率低小目标本身只有十几个像素宽加上池化层下采样后特征图上的信息更少。同时NMS的IoU阈值设置不合适两个紧挨着的目标比如同一个设备上的相邻散热片被非极大值抑制误删了。解决三板斧。第一训练时把输入尺寸从默认640提到768或896小目标在特征图上的响应会显著增强代价是显存占用和训练速度。第二如果用的是锚点检测器检查anchors是否覆盖小目标尺度必要时用k-means重新聚类。第三推理时把NMS的IoU阈值从默认0.45适当提高比如调成0.6减少相邻同类目标的互相抑制。这三项按顺序做先提输入尺寸再看锚点最后动NMS不要一上来就改NMS那可能引入更多误检。6. 从训练到验收检查mAP、混淆矩阵和热力图模型才算真正落地训练结束不是终点验收环节直接决定模型能不能进试点。我习惯按三个动作验收每个动作对应一类问题。第一个动作是看类别级别的mAP而不是总mAP。14个类里套管、绝缘子这种外观相似、功能不同的类别最容易混淆总mAP掩盖了它们各自的表现。把每个类别的mAP列出来低于平均线50%的类先回到数据层面看这个类的样本数量和标注一致性。经验阈值是样本量超过200的类别mAP应该大于0.7低于0.5就说明标注或划分有问题。第二个动作是跑一遍混淆矩阵盯着外观相似但对调无意义的类别对。变压器套管和绝缘子在红外图像里都是细长高温体模型分不清是正常的但如果混淆矩阵显示套管和油枕这种形状差异明显的类别也在互相错认那就是标注框位置不准导致的要回头修标注。第三个动作是可视化模型的关注区域。红外图像的目标特征集中在高温区模型如果学到了合理特征热力图应该落在目标温度最高的区域附近。如果热力图散在背景或者整张图都有响应说明模型学到的是场景级别的温度分布模式而不是目标本身这在跨站点部署时会露馅。验证阶段可以跑YOLO自带的评估命令但要在验证集上单独跑不要用训练集凑数yolo detect val modelruns/train/exp/weights/best.pt datadata.yaml batch16 device0batch16是按你的显存调整的显存不够就降到8或4device0指定第一块GPUCPU训练就改成devicecpu但会慢很多。跑完会输出每个类别的mAP和混淆矩阵图把结果保存到runs/val目录里留存。最后收个尾说我自己的习惯每次拿到新数据集我都是先跑第3章的校验脚本再划分数据然后才谈训练参数。跳过校验直接开训十次有八次要返工。数据集本身没有错错的是我们默认它是完美的。这份4271张的红外数据集真正值钱的是那些边界框画得不够好、类别分布不那么均匀的部分——那才是现实场景的真实分布。处理得越仔细模型在真实站所里就越稳。希望这篇笔记帮你在电力红外检测这条路上少踩几个坑。本文还有配套的精品资源点击获取