ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

变电站红外图像数据集VOC转YOLO全流程:PT/CT检测训练避坑指南

变电站红外图像数据集VOC转YOLO全流程:PT/CT检测训练避坑指南 简介面向变电站设备状态监测的红外图像数据集包含889张现场拍摄的电压电流互感器红外图像供电力运维人员、计算机视觉研究者及深度学习入门者训练与评估模型用于监测互感器运行状态、预警过热故障。资源整体共1778个文件包括889张JPG原始红外图像和889个XML标注文件压缩后仅24.72MB标注采用VOC格式每张图像都配有像素级语义分割结果电流互感器TC共标注516个电压互感器TP共标注650个类别区分明确可直接用于目标检测、实例分割等任务。利用红外图像中的温度分布可开展设备热分析、老化预测也可基于这些标注训练YOLO、Faster R-CNN等目标检测算法或通过CNN实现互感器分类与异常检测。目前已有2083人学习下载适合作为电力场景深度学习实验、毕业设计或智能运维算法验证的标准数据集。1. 变电站红外图像数据集889张VOC标注能省下两天标注时间但坑也不少做变电站设备目标检测的人多半有过这种经历可见光数据集一抓一大把红外图像却总在“带标注”这一步卡住。变电站红外巡检图看似平淡——灰蒙蒙的画面里设备轮廓模糊温度分布代替了纹理细节最麻烦的是电压互感器PT和电流互感器CT在红外下长得几乎一样标错一次类别模型就跟着学歪。这份变电站红外图像数据集一共889张带VOC格式的XML标签覆盖常见的PT与CT设备正好解决“有图没标注”的尴尬。适合两类人刚接触红外目标检测、想拿真实数据练手的新手以及手头有巡检需求、想快速验证算法或小规模训练的工程师。不过说实话我第一次用它跑YOLO时也踩了不少坑这篇文章就把数据链路、格式转换和训练排错一次说清楚。2. VOC标签先拆明白XML结构、边界框坐标与PT/CT标注规则2.1 VOC标注不是“画个框”那么简朴XML字段逐个拆解VOC格式全称是PASCAL VOC核心是每张图像对应一个同名的XML文件目标信息全部写在XML里。打开一个标注文件结构大致长这样annotation folderJPEGImages/folder filenameinfrared_0001.jpg/filename size width640/width height512/height depth3/depth /size object namecurrent_transformer/name difficult0/difficult bndbox xmin120/xmin ymin89/ymin xmax286/xmax ymax241/ymax /bndbox /object /annotation这里最要紧的字段是size和bndbox。size里记录的是原始图像的宽、高、通道数后面做归一化转换时要靠它做分母bndbox给的是左上角坐标xmin, ymin和右下角坐标xmax, ymax也就是大家常说的 xyxy 格式。这和COCO格式的 [x, y, width, height] 不一样和YOLO的归一化中心点格式也不一样转换时稍不注意就会把 xyxy 当成 xywh 直接用结果框全部偏移这是最常见的翻车点之一。VOC标注字段还包含truncated目标是否被截断和difficult目标是否难以识别等可选字段。在电力设备场景里图像边缘的设备经常被截断truncated字段就是干这个用的。处理的时候建议保留difficult标记训练时可以把difficult1的样本过滤掉否则验证集的 mAP 会被这些“争议框”拖低。下面这张表是处理时最常碰到的字段字段含义处理建议filename图像文件名转换输出文件名时依赖它size/width, height原始图像尺寸归一化分母必须以它为准object/name目标类别名转换成类别ID前先统计类别集合bndbox左上角右下角坐标注意是xyxy不是xywhdifficult难以识别标记difficult1的样本建议训练时过滤truncated目标截断标记边缘被截断的框可保留但需关注类别名是个容易忽略的坑。不同批次标注时有人把电流互感器写成current_transformer有人简写成CT还有人统一标成transformer或voltage_transformer。转换前一定要先跑一遍统计脚本把所有name值打印出来统一映射表否则类别ID对不上训练时损失函数直接爆掉。2.2 PT和CT怎么区分红外图里的同源设备标注难点电压互感器PT和电流互感器CT在可见光下还算好认但在红外图像里两者轮廓高度相似都是挂在构架上的瓷套或方形箱体温度分布一覆盖细节几乎全没了。标注的时候需要依赖安装位置、器身比例和接线方式这些间接特征来辅助判断。下面这个表是我在拆这份数据时总结的区分经验区分维度电压互感器PT电流互感器CT安装位置母线侧、线路侧常与避雷器相邻断路器两侧间隔内成组出现器身形状体积偏大油箱与瓷套一体瓷套层数更密径向更窄红外特征温升集中在顶部接线端子一次绕组发热整体温度分布较均匀标注易错点容易和避雷器混淆容易和PT混淆、容易被杆塔遮挡这份数据集的难点不在画框而在类别判断。红外图像分辨率本身不高设备发热区域和背景对比度又低标注时容易出现两种问题一是外接矩形框得过大一个框把旁边的绝缘子或支柱也包进去二是设备紧挨着两个框高度重叠导致训练时正样本重复。我一般会写个简单脚本遍历XML统计所有框的IoU把IoU超过0.6的框单独导出来人工复查import xml.etree.ElementTree as ET import os def box_iou(a, b): x1 max(a[0], b[0]); y1 max(a[1], b[1]) x2 min(a[2], b[2]); y2 min(a[3], b[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area_a (a[2] - a[0]) * (a[3] - a[1]) area_b (b[2] - b[0]) * (b[3] - b[1]) return inter / (area_a area_b - inter 1e-6) xml_dir Annotations for xml_file in os.listdir(xml_dir): tree ET.parse(os.path.join(xml_dir, xml_file)) boxes [] for obj in tree.getroot().iter(object): bb obj.find(bndbox) boxes.append([float(bb.findtext(xmin)), float(bb.findtext(ymin)), float(bb.findtext(xmax)), float(bb.findtext(ymax))]) for i in range(len(boxes)): for j in range(i 1, len(boxes)): if box_iou(boxes[i], boxes[j]) 0.6: print(f{xml_file}: IoU{box_iou(boxes[i], boxes[j]):.2f})这段代码的逻辑是对每个XML文件内的所有框两两计算交并比超过0.6就打印出来。参数0.6是我根据红外设备重叠情况定的经验值如果目标是密集排列的绝缘子串可以放宽到0.7如果框的是大型变压器本体0.4就值得怀疑了。复查时重点看两个高度重叠的框是不是属于不同的设备——如果是同设备被标了两遍直接删掉一个。2.3 889张图的划分逻辑为什么建议按拍摄批次切分把数据集划分成训练集和验证集看起来用个random_split就完事但在变电站红外场景里随机划分有隐藏问题。红外巡检图往往是同一批设备连续拍摄几十帧相邻帧之间背景几乎一样、设备角度只有微小变化。如果这些相似帧同时进了训练集和验证集验证指标会虚高模型看起来 mAP 有0.85换到真正的新场景立刻掉到0.6。我处理这类巡检数据集的习惯是按拍摄批次划分。先看文件名或目录结构把同一批次、同一机位拍摄的图像归为一组然后按组分训练/验证而不是按单张图分。比如这份数据有889张如果共约20个拍摄批次按8:2划分就是16个批次进训练、4个批次进验证。这样验证集里的设备和训练集设备角度、温度分布都有差异评估结果才接近真实部署。还有一种更严格的做法是按变电站站点划分训练集用A站的图验证集用B站的图能直接检验模型的跨站泛化能力但需要数据集本身覆盖多个站点——如果只有单站点数据按批次划分已经够用。3. 红外图像的数据链路灰度特性、直方图均衡与训练清单生成3.1 红外图像为什么看着“发白”灰度分布和可见光的本质差异把红外图像和可见光图像放进同一个训练管线是新手最容易犯的错误之一。可见光图像有三个通道纹理、颜色、边缘信息丰富而红外热像仪接收的是物体表面的辐射强度灰度值反映的是温度高低。变电站设备的红外图有几个明显特征背景天空和构架温度低灰度值集中在暗部设备本体温度高灰度值集中在一个相对窄的亮部区间整个画面的直方图呈双峰或尖峰分布中间过渡带很窄。这意味着直接套用 ImageNet 预训练模型时模型的浅层卷积核是为自然图像设计的对红外灰度图的响应会很奇怪。我一般做两件事一是把灰度图复制成三通道让模型输入保持标准形状二是做对比度增强把设备的温度区间拉伸开。直方图均衡是最常用的手段但对红外图来说全局直方图均衡容易把背景噪点一并放大所以优先用CLAHE对比度受限的自适应直方图均衡它按小块做均衡能在增强设备区域对比度的同时抑制背景噪声放大。3.2 预处理三件套CLAHE增强、归一化和通道转换下面这段代码是适合红外设备检测的预处理做法import cv2 def load_infrared_rgb(path, clahe_clip2.0, tile_size8): img cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f无法读取图像: {path}) clahe cv2.createCLAHE(clipLimitclahe_clip, tileGridSize(tile_size, tile_size)) img clahe.apply(img) img_rgb cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) img_norm img_rgb.astype(float32) / 255.0 return img_norm img load_infrared_rgb(JPEGImages/infrared_0001.jpg)说明一下参数clahe_clip2.0是对比度限制阈值值越大增强越激进红外设备图上如果设备占画面比例小可以提高到3.0但如果背景温度分布复杂太大会把背景的温差也拉出来训练时引入大量无效纹理tile_size8是分块大小8×8是常用值图像分辨率高时可以用16。最后一步归一化到0到1是因为后续训练框架内部还会做一次ImageNet标准化提前归一化可以避免数值溢出。注意这里读图时用了IMREAD_GRAYSCALE如果原图本身就是单通道红外图这个读法是正确的如果部分图像是伪彩色图灰度化会丢失温度分层信息这类图建议保留原始伪彩色通道直接作为三通道输入。3.3 生成训练清单从图像名列表到train.txt/val.txt很多检测框架不直接吃XML而是先要一份“图像路径清单”。生成清单这一步看似简单但路径写法直接影响后面的训练。下面是我常用的脚本import os import random from sklearn.model_selection import train_test_split random.seed(42) image_dir JPEGImages images sorted([f for f in os.listdir(image_dir) if f.endswith(.jpg)]) train_files, val_files train_test_split(images, test_size0.2, random_state42, shuffleTrue) with open(train.txt, w) as f: for name in train_files: f.write(fJPEGImages/{name}\n) with open(val.txt, w) as f: for name in val_files: f.write(fJPEGImages/{name}\n) print(f训练集 {len(train_files)} 张验证集 {len(val_files)} 张)这段脚本的逻辑是把所有jpg文件名取出来按8:2划分并写入两个txt。两个关键点一是路径写法这里写的是相对路径JPEGImages/infrared_0001.jpgYOLOv5/YOLOv8的Data YAML里如果配置了path字段txt里用不带扩展名的文件名即可但如果直接给绝对路径迁移到别的机器就要全部重写所以建议用相对路径二是随机种子固定为42保证每次划分结果一致否则换个机器划分结果不同复现实验时指标对不上。划分完之后记得统计一下训练集和验证集的类别分布确认两个集合里PT和CT都有足够样本出现某一类全在训练集、验证集一条都没有的情况评估结果会失真。4. VOC转YOLO格式转换脚本与四个边界坑4.1 转换脚本把XML里的xyxy映射成归一化的中心点格式把VOC转成YOLO是绕不开的一步因为YOLO系列训练时读的是每张图对应的txt文件每行格式是类别ID 中心点x 中心点y 框宽 框高所有坐标都归一化到0到1之间。下面是我在多个红外数据集上验证过的转换脚本import xml.etree.ElementTree as ET import os CLASS_MAP { voltage_transformer: 0, current_transformer: 1, } def convert_voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename) size root.find(size) img_w int(size.findtext(width)) img_h int(size.findtext(height)) lines [] for obj in root.iter(object): name obj.findtext(name) if name not in CLASS_MAP: print(f跳过未知类别 {name} in {filename}) continue cls_id CLASS_MAP[name] bbox obj.find(bndbox) xmin float(bbox.findtext(xmin)) ymin float(bbox.findtext(ymin)) xmax float(bbox.findtext(xmax)) ymax float(bbox.findtext(ymax)) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h x_center max(0.0, min(1.0, x_center)) y_center max(0.0, min(1.0, y_center)) box_w max(0.0, min(1.0, box_w)) box_h max(0.0, min(1.0, box_h)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_name os.path.splitext(filename)[0] .txt out_path os.path.join(out_dir, out_name) with open(out_path, w) as f: f.write(\n.join(lines) \n) xml_dir Annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir)脚本的核心逻辑分三步从XML读宽高和边界框坐标、用中心点公式做归一化、限制数值范围后写入txt。CLASS_MAP是类别映射表键用XML里的name值是对应的类别ID必须和模型的类别顺序保持一致。:.6f保留6位小数足够再多的精度在640分辨率下已经没有实际意义。最后的裁剪逻辑max(0.0, min(1.0, ...))是防止个别框略微超出图像边界导致训练报错但注意这只能处理轻微越界如果xmax比图像宽度大很多说明XML本身有问题要回头查原图。4.2 边界坑一归一化分母到底用哪组宽高这是转换时最容易翻车的地方。有人图省事用cv2.imread读图后取shape得到宽高但红外图经常被预处理脚本重新保存过实际保存的尺寸可能和XML里记录的尺寸不一致。比如原图是640×512某次批量处理时转成了512×512但XML里的size没更新归一化后坐标就全偏了。正确做法是只用XML里的size字段做分母因为它描述的是标注时参考的图像尺寸。转换完成后抽三到五张图把生成的txt坐标反算回整数坐标画在原图上肉眼检查一遍确认框的位置没有整体偏移。我每次转完格式都会跑一次这个校验花不了两分钟能省掉后面训练出问题时的排查时间。4.3 边界坑二类别名的通配处理红外设备数据集的类别命名经常不统一。有的标注员把电压互感器写成PT有的写成voltage_transformer还有的细分出pt_primary之类的层级。如果脚本里硬编码映射表遇到没见过的名称会直接跳过或报错导致少标一堆目标。转换前先写一行命令统计所有类别名grep -h name Annotations/*.xml | sort | uniq -c | sort -rn看输出的名称清单再决定是做归一化映射把PT、pt、voltage_transformer统一映射到同一个ID还是做合并归类。如果这份数据集的标注者把所有设备统一标成了transformer或equipment那就按单个类别处理训练时类别数设为1。这个决定要在转换前做转换后再改类别ID就是灾难。4.4 边界坑三空标注文件与缺失XML红外巡检图里很多帧没有目标设备或者目标太小被标注员忽略这类图像在XML里没有object节点。转换脚本写出空txt文件大部分训练框架能接受空标注但个别数据加载器会在读取时抛Empty label file异常导致训练中断。我一般会在转换脚本里统计空文件单独生成一份清单然后做两个选择要么把它从训练清单里剔除要么保留并确保数据加载器能跳过空文件。剔除更省心但要注意别把验证集里用于测漏检的负样本也剔光了否则模型学不会“没有设备时不该输出框”。4.5 边界坑四train.txt和val.txt的路径写法决定成败路径这个问题在不同框架里表现不一样。Darknet版的YOLOv3要求txt里写图片的绝对路径或相对路径训练时直接打开这个路径读图Ultralytics YOLOv8则不同它在Data YAML里指定了train和val指向txt文件txt里写的是图片相对路径同时它会自动把路径中的images替换成labels去寻找对应的标注txt。如果图片在JPEGImages/目录而标注在labels/目录且两者不在同一个父目录下YOLOv8就会找不到标注文件。最简单的规避办法是保持统一目录结构images/放图labels/放txttrain.txt里写images/infrared_0001.jpgYOLOv8会自动解析到labels/infrared_0001.txt。如果你用的是老版Darknet结构那就在txt里写完整路径并确保labels和JPEGImages同级。5. 训练参数与常见问题排查过拟合、漏检和标注噪声5.1 小数据集训练参数基线889张图像在这个领域算偏小规模的数据集直接套用COCO的训练配置会过拟合。我测试过的参数基线如下参数建议值说明模型YOLOv8n 或 YOLOv8s小模型在小数据上更稳imgsz640红外设备框占比不大640够用epochs300早停机制patience设为50batch16显存不够降到8optimizerAdamWlr00.001, weight_decay0.0005mosaic0.0 或 0.5设备重叠严重时建议关闭预处理增强轻微HSV、随机平移、翻转不要开大角度旋转类别数1 或 2由XML里的name决定早停patience50防止小数据集上过拟合红外设备图像的另一个特殊性是设备框通常比较紧凑、形状变化不大开大角度的旋转增强反而会制造大量“设备倒挂”的无效样本。我一般只开水平翻转和小于15度的旋转mosaic在设备重叠严重时建议关闭因为马赛克拼接会让断裂的设备框更难学。5.2 现象loss降了但mAP不动甚至训练集mAP就异常高排查思路这种情况先怀疑数据泄漏。前面提到按批次划分如果随机划分时同一拍摄序列的相似帧同时进了训练和验证验证集的“好成绩”其实是记忆而非泛化。具体表现为训练loss正常下降验证mAP看着也不低但换到另一个变电站的红外图立刻崩掉。解决方法是重新按批次划分数据并检查train.txt、val.txt里有没有重复的图像路径输出重复行sort train.txt val.txt | uniq -d另外还要看类别是否平衡。如果889张里CT有700个标注框而PT只有150个模型会偏向学CT的特征PT的召回率起不来。这种情况可以给PT类别加更高的cls损失权重或者用简单的过采样——把PT样本复制一份放进训练集但注意不要复制到验证集。5.3 现象CT漏检集中在图像边缘框总是对不齐红外巡检图里设备经常位于画面边缘或被裁掉一半这时模型的漏检率明显偏高。原因有两个层面一是训练数据里边缘设备框的样本占比本来就低二是很多增强策略在随机裁剪时会丢掉边缘目标。解决方法是做“边缘过采样”对包含边缘框的图像做平移增强让设备往画面中心靠拢再训练。做法是对这类图像随机平移10%到20%的像素然后重新生成标注框坐标。也可以打开YOLOv8的close_mosaic10让最后10轮训练禁用马赛克增强帮助模型在真实分布上微调收敛。5.4 现象验证集计算mAP时同一设备被算了两次“错框”这个问题根源在标注阶段。如果某个设备在XML里被标了两遍比如两个标注框IoU极高验证时一个框匹配了预测框另一个框没匹配上就会被算成漏检或误检拖低mAP。前面2.2节的IoU统计脚本就是干这个的发现重复框后保留置信度较高的那个标注框或者按标注重叠策略合并两个框。这个操作要在数据划分之前做否则清理后的结果会让训练集和验证集分布不一致。从那以后我每拿到一批新数据第一步永远是跑重叠统计而不是直接进训练流程。5.5 现象模型不收敛loss曲线锯齿状跳动训练集验证集差别巨大排查方向是图像通道问题。红外原图是单通道灰度但很多框架的预训练权重要求三通道输入。如果加载图像时代码里用了cv2.imread(path)默认读成三通道而另一步预处理又把它当成单通道做处理通道数不一致会导致模型前向传播报错或loss异常。统一的做法是在数据加载层里强制做灰度转三通道像我前面代码那样cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)。还要检查图像像素值范围红外图如果本身是16位PNG保存读出来像素值最高到65535直接送进模型会数值爆炸必须转到8位范围再做归一化。这类问题通常表现为开头loss在正常范围训练几十轮后突然出现NaN十有八九是某个样本像素值异常定位到具体是哪个文件后单独修复。5.6 训练后必做的一步用验证集输出PR曲线和置信度分布很多人训练完只看一个最终mAP数这在红外小数据集上远远不够。因为样本量小mAP的置信区间很宽同一份数据换不同初始种子可能差出3到5个点。我建议训练完后导出验证集的PR曲线和每一类的AP值同时看置信度分布如果大量预测框的置信度集中在0.9以上说明模型过于自信、在陌生场景可能脆如果集中在0.3附近说明训练不充分。另外用val模式跑一遍测试集保存推理结果的图像把漏检和误检案例抽出来看比任何指标都能说明问题。对896张这种规模的数据人工检查每一张验证集的推理结果完全可行这步别省。6. 把889张用到极致难例挖掘、预标注循环与推理验证6.1 难例挖掘用第一版模型找出最难的样本889张数据集训练出的模型第一版往往在常见角度、清晰温度分布的设备上表现不错但在遮挡、极端角度、多设备密集排列的场景上漏检严重。把这些漏检样本找出来补充进训练集的方法叫难例挖掘hard example mining。具体做法是训练完第一版模型后对全部训练集做一次推理把置信度低于0.3的检测结果对应的图像和标注框单独导出。这些样本通常具备特征模糊、目标过小、背景复杂等特点人工筛选一遍后把这些图像在训练清单里的权重加大。对小数据集这比盲目收集更多数据来得实在。6.2 预标注循环用模型生成XML人工只做修正另一个提效手段是用训练好的模型做预标注。推理模型输出的是归一化的txt坐标需要把它反算成VOC格式的XML然后导入LabelImg进行人工修正。这个流程在重标注一批相似场景的新图像时效率翻倍——模型先框出大部分设备标注员只需要调整边界和修正类别而不是从零画框。反算脚本的核心就是把txt里的中心点、宽高还原成左上角、右下角整数坐标再写入XML结构。需要注意预标注的框通常比人工标注略松或略紧人工修正时重点检查设备边缘有没有被框截断。6.3 验证模型推理效果一个简单的单图推理脚本from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) img cv2.imread(val_images/infrared_0100.jpg) results model.predict(img, conf0.25, iou0.5, verboseTrue) for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 box.xyxy[0].tolist() print(f类别{cls_id} 置信度{conf:.3f} 坐标({x1:.1f},{y1:.1f},{x2:.1f},{y2:.1f}))这段代码的逻辑是用训练好的best.pt对单张红外图做推理输出每个目标框的类别、置信度和坐标。conf0.25是置信度阈值红外设备图如果目标特征弱、误检多可以调到0.35iou0.5是NMS的IoU阈值设备重叠严重时降到0.45能减少相邻框被合并的概率。推理时最好逐张比对原图检查输出框是否对准设备本体。从那以后我每次换数据集都强制走一遍“直方图检查、标注重叠统计、格式转换校验、单图推理目检”的流程这四步做完才敢提交训练结果。这套流程也希望能帮到你少走我踩过的弯路。本文还有配套的精品资源点击获取
返回列表