ARTICLE DETAIL

资讯详情

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

绝缘子检测数据集实战:标注格式、数据体检与YOLO训练避坑

绝缘子检测数据集实战:标注格式、数据体检与YOLO训练避坑 简介电气输电线路绝缘子数据集面向电力系统监控、图像识别与人工智能方向的工程师和研究者覆盖绝缘子识别、损坏检测腐蚀/裂纹/污秽以及计算机视觉模型训练等典型任务可用于提升智能巡检与故障预测效率。zip压缩包内共1947个文件主体为848张jpg绝缘子图像与1096个配套xml标注文件另有doc、md说明文档整体约390.35MB结构清晰便于直接用于目标检测、语义分割和特征提取实验。图像覆盖多种拍摄角度与绝缘子状态既有助于分析环境因素对老化的影响也可结合气象、地理位置数据拓展研究对高校电气专业学生而言是理解输电线路结构与开展工程实训的直观素材。目前已有3891人学习下载适合需要真实样本开展电力AI项目的研究者与开发者。1. 绝缘子检测数据集1000张图为什么可能比万级公开数据集更难用电气输电线路绝缘子数据集解开压缩包大概是1000张左右的无人机与人工巡线视角图片多数是真实杆塔环境下的绝缘子。对于做电力视觉的人来说这份数据比公开的COCO那种“干净”数据集难缠得多目标小、视角多变、背景里有导线、铁塔、树木和天空正常件与破损件又常常贴着出现。拿它跑YOLO系列训练很合适但直接喂进模型大概率翻车——你需要先做数据体检、确认标注格式、再划分训练验证集。这篇按我自己拆项目时的顺序来写新手可以照步骤走老手直接看参数和坑就够了。我会从解压后第一眼看到的目录结构讲起一直讲到切片推理提升小目标召回率全程对应可复现实战步骤。2. 标注格式与数据构成分析解压后别急着写训练脚本我在巡检类数据集上栽过跟头最狠的一次是拿到一个zip后想当然按YOLO格式处理结果里面全是VOC的XML白折腾一下午。从那以后我养成了一个习惯任何压缩包解开之后先看标签后缀再决定后面怎么走。标签格式是整条管道的地基它直接决定你用什么训练框架、要不要写转换脚本、转换时会不会丢坐标。2.1 解压后的第一件事确认目录与标签后缀先用最简单的命令把目录和文件类型铺开看不要打开任何标注文件先扫描后缀就行find . -maxdepth 3 -type d | sort find . -maxdepth 3 -type f \( -name *.jpg -o -name *.png -o -name *.xml -o -name *.txt -o -name *.json \) | head -50第一条命令列出三层以内的目录第二条命令把图片和标注文件的后缀抓出来看前50个。这一步能快速判断压缩包是图片和标签分开存放还是每个样本一个独立文件夹。如果解压目录里含有中文路径在Windows下跑Python脚本很容易遇到编码问题我一般会先把目录重命名成全英文再继续这是最省事的预防手段。常见标注格式和对应工具链可以参考这张表标签后缀格式类型常用处理工具链.xmlPascal VOC转成YOLO TXT后训练或直接给MMDetection.txtYOLO原生格式可直接喂给yolov5/yolov8训练自己的数据集.jsonCOCO格式Detectron2、MMDetection直接消费转YOLO也方便无标注文件只有原始图像需要先做人工标注或半自动标注如果扫描下来一个标注文件都没找到这个包可能只提供了素材图。1000张图全部人工标注不现实我一般会先用一个在公开绝缘子数据上预训练好的模型跑一遍伪标签再人工修正把修正后的标签回灌到目录里。这个流程比从零画框快好几倍。2.2 不同标注格式对应的处理流程确定了格式之后后面的工作流基本就定型了。如果是VOC XML标注里包含size、object、bndbox三段信息YOLO不能直接用必须做坐标归一化转换。如果已经是TXT每行是class_id cx cy w h直接划分目录就能开训。如果是COCO JSON想用YOLO训练就转成TXT想用MMDetection就直接用。绝缘子检测还有一个特殊方向有些巡检图像里的绝缘子串是倾斜的水平框会把一大片背景包进来这时候旋转框比水平框干净得多。如果后续要做旋转目标检测可以走mmrotate那套流程标注格式和DOTA数据集用的RBox体系接近转换逻辑跟VOC转YOLO是一个套路只是多一个角度字段。1000张图的量级做水平框检测已经足够起步旋转框建议等数据量到3000张以上再考虑。3. 数据体检脚本坏图、分辨率分布和类别统计很多人在拿到数据集之后直接开训被低质量的loss曲线折磨几天才发现是数据本身有问题。我的习惯是训练前先跑一遍体检脚本把坏图、分辨率分布、类别分布三项查清楚。体检脚本不是一次性工具以后换任何数据集都能复用。3.1 图片完整性与数量检测解压包里的图片看起来都在不代表每一张都能正常解码。无人机巡检图片在传输和存储过程中偶尔会截断直接把坏图喂给深度学习框架轻则训练中断重则DataLoader反复报错排查起来极其浪费时间。先做完整性检测import glob from PIL import Image, ImageFile # 允许PIL尝试加载截断的图片否则一张坏图会让整个脚本崩溃 ImageFile.LOAD_TRUNCATED_IMAGES True image_paths glob.glob(insulator_dataset/images/*.jpg) print(图片总数:, len(image_paths)) broken [] for p in image_paths: try: with Image.open(p) as im: im.load() # 真正触发解码只open不load可能发现不了坏图 except Exception: broken.append(p) print(损坏图片数量:, len(broken)) for p in broken[:10]: print(p)这段脚本的逻辑很直白用glob拿到所有jpg路径逐个打开并调用im.load()触发解码任何异常都记入损坏列表。关键点是im.load()如果只写Image.open(p)不加载像素数据有些文件头正常但数据损坏的图片会被漏过去。查到损坏图片后直接删掉或者移到单独目录避免后续训练时数据集加载失败。3.2 分辨率分布与宽高比统计绝缘子巡检图片的来路很杂可能是不同型号无人机拍的也可能混入了人工持机拍摄的画面。分辨率不统一会直接影响letterbox补边幅度和训练时的缩放比例。我用一个分桶统计来看整体分布from collections import Counter size_counter Counter() aspect_counter Counter() for p in image_paths: with Image.open(p) as im: w, h im.size # 按160像素取整分桶避免分辨率种类太多看不到趋势 size_counter[(w // 160 * 160, h // 160 * 160)] 1 aspect round(w / h, 1) aspect_counter[aspect] 1 print(分辨率分布按160取整Top 8:) for size, cnt in size_counter.most_common(8): print(size, cnt) print(宽高比分布Top 8:) for aspect, cnt in aspect_counter.most_common(8): print(aspect, cnt)分辨率分桶按160取整是因为原始尺寸可能从640到4000多都有如果逐个打印真实分辨率几十种尺寸堆在一起反而看不出趋势。宽高比分布更重要如果清一色是1.7左右说明全是横拍大图如果出现0.5、0.75这类数值说明混入了竖拍图后面训练时letterbox补边区域会更大模型更容易把补边区域当成背景特征学进去这一点在避坑章节还会展开。3.3 标注类别分布统计类别分布直接决定你能不能训练、训练出来有没有意义。统计之前先判断标注格式XML和TXT各写一个版本。VOC格式统计类别名import glob import xml.etree.ElementTree as ET voc_counter Counter() for xml_path in glob.glob(insulator_dataset/labels/*.xml): tree ET.parse(xml_path) root tree.getroot() for obj in root.iter(object): label obj.findtext(name) voc_counter[label] 1 print(VOC类别分布:, voc_counter)YOLO格式统计类别编号yolo_counter Counter() for txt_path in glob.glob(insulator_dataset/labels/*.txt): with open(txt_path) as f: for line in f: # 每行第一个数字就是类别编号 yolo_counter[int(line.split()[0])] 1 print(YOLO类别编号分布:, yolo_counter)这段统计暴露的问题最多。最常见的情况是只统计出一个类别比如只有insulator或normal说明这个包根本没有缺陷样本无法做正常和破损的分类训练。另一种情况是出现了三个甚至五个类别名比如normal、defect、insulator_broken、dirty混在一起这时候就要决定是合并类别还是删掉不相关的类。统计完成后三个数必须心里有数总图片数、总标注框数、每个类别的框数比例。4. 划分训练集并转成YOLO格式转换脚本与参数说明体检通过之后才进入真正的数据准备阶段。这个阶段要做三件事划分train/val目录、把非YOLO格式转成YOLO格式、写data.yaml。三步做完数据管道才闭合训练脚本才能稳定跑起来。4.1 固定随机种子划分train/val并同步标签划分数据集最容易犯的错是重复采样或者划分时只移动图片忘记同步标签导致训练时大量图片找不到对应标注。我用固定种子加同步复制的方式import os import random import shutil from glob import glob image_paths glob(insulator_dataset/images/*.jpg) random.seed(42) # 固定种子保证每次划分结果完全一致 random.shuffle(image_paths) n_train int(len(image_paths) * 0.8) train_images image_paths[:n_train] val_images image_paths[n_train:] for split_name, files in [(train, train_images), (val, val_images)]: os.makedirs(fprocessed/{split_name}/images, exist_okTrue) os.makedirs(fprocessed/{split_name}/labels, exist_okTrue) for img_path in files: img_name os.path.basename(img_path) shutil.copy(img_path, fprocessed/{split_name}/images/{img_name}) # 标签文件名与图片名保持一致只是后缀不同 base os.path.splitext(img_name)[0] label_path finsulator_dataset/labels/{base}.txt if os.path.exists(label_path): shutil.copy(label_path, fprocessed/{split_name}/labels/{base}.txt)random.seed(42)是这里最关键的一行。没有固定种子每跑一次划分结果都不同你今天训练用的验证集和明天用的不一致测试指标就失去了可比性。80/20的划分比例对1000张的小数据集比较合适如果某个类别样本特别少我建议把划分比例改成90/10并且用分层划分保证两个类别在两个集合里的比例接近而不是简单随机切一刀。4.2 VOC坐标转YOLO归一化坐标如果数据集的标注是XML格式必须转成YOLO的归一化坐标才能喂给yolov8。YOLO格式的核心是把边界框表示成目标中心点的相对坐标加上相对宽高import os import glob import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w float(size.findtext(width)) img_h float(size.findtext(height)) lines [] for obj in root.iter(object): name obj.findtext(name) if name not in class_names: continue cls_id class_names.index(name) box obj.find(bndbox) x_min float(box.findtext(xmin)) y_min float(box.findtext(ymin)) x_max float(box.findtext(xmax)) y_max float(box.findtext(ymax)) # 关键转换公式像素坐标 - 归一化中心点宽高 cx ((x_min x_max) / 2) / img_w cy ((y_min y_max) / 2) / img_h bw (x_max - x_min) / img_w bh (y_max - y_min) / img_h # 防止边界坐标超出图像范围clip到[0,1]区间 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0001), 1.0) bh min(max(bh, 0.0001), 1.0) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) # 类别顺序必须与后面data.yaml里的names完全一致 class_names [normal, defect] os.makedirs(processed/labels_yolo, exist_okTrue) for xml_file in glob.glob(insulator_dataset/labels/*.xml): voc_to_yolo(xml_file, processed/labels_yolo, class_names)转换公式的核心是把xmin/xmax/ymin/ymax四个绝对值变成中心点坐标和宽高再除以图片宽高归一化。class_names列表的顺序就是TXT文件里类别编号的顺序这里写了normal对应0、defect对应1后面data.yaml必须保持一致否则模型会把类别名和编号对应错。最后那段clip处理不能省巡检图片里有些标注框会超出图像边缘几像素不clip会导致训练时损失函数计算异常。4.3 配置data.yaml并启动训练YOLO系列训练自己的数据集时data.yaml是模型和数据之间的桥梁。字段极少但顺序敏感path: processed train: train/images val: val/images names: 0: normal 1: defectpath是相对于data.yaml文件所在目录的路径train和val填的是图片目录的相对路径YOLO会自动在同级目录下找对应的labels目录。names的键必须是从0开始的连续整数值必须与TXT里类别编号的含义一致。写好之后启动训练yolo detect train dataprocessed/data.yaml modelyolov8s.pt epochs100 imgsz640 batch16modelyolov8s.pt表示从官方预训练权重起步1000张图的数据量不适合从头随机初始化迁移学习能明显加快收敛。imgsz640是速度和精度的平衡点如果后面发现小目标漏检严重可以试着调到960或1280代价是显存占用线性上涨。batch16要看显卡显存调整RTX 3090跑yolov8s基本能吃到328G显存建议保守用8。5. 训练与验证的避坑清单小目标、类不平衡和背景干扰这一章写的四个坑全都是我自己在这个场景下踩过的每一个都在训练时出现过非常隐蔽的症状损失曲线看着正常但验证集效果就是上不去。5.1 类别编号对应错位现象训练损失正常下降验证mAP看起来也不差但可视化检测结果时发现明明应该是defect的位置画出来的框标签却是normal。原因TXT文件第一列的编号与data.yaml里names的顺序不一致常见于中途改过class_names列表但没有重新转标签。解决在避坑章节之前写的类别编号分布统计脚本就是用来干这个的先跑一遍确认TXT里出现的编号范围再和data.yaml逐一对上训练完再做一次可视化抽检每张图上把类别名和置信度打出来肉眼扫一遍比看mAP可靠得多。5.2 小目标被缩放吞掉现象验证集mAP在0.8以上但把模型丢到真实大图上推理远处的绝缘子串漏检严重尤其是图片边缘靠近地平线的位置。原因很多巡检图是1920×1080甚至更大而训练时imgsz640会把整图缩到三分之一一个原本只有40像素宽的绝缘子串缩到十几像素特征几乎消失。解决最简单的调整是把imgsz改成960或1280先看显存是否够用如果调大后仍然漏检就得用第六章的切片推理方案把大图切成小块分别推理再合并结果。5.3 类不平衡导致缺陷样本被无视现象defect类的召回率极低甚至整个验证集一个defect都检不出来但normal类的精确率非常高。原因正常绝缘子的样本数量远超破损样本模型学到的最优策略是“把所有目标都判成normal”因为这样整体loss最小。解决先看类别分布统计里的比例如果normal和defect的比例超过4比1我一般会做两步一是对defect类做过采样把比例压到3比1以内二是在训练时对defect类适当提高损失权重。还有一个土办法把defect样本复制几份做轻微的HSV增强后放进训练集对小数据集很有效。5.4 图片分辨率混杂导致letterbox补边异常现象同一批验证图片里1920×1080的图检测效果好1280×720的图明显变差竖拍图最差。原因不同分辨率的图经过letterbox处理后补边位置和幅度不一致模型在高分辨率和横拍图上适应性好在竖拍图上看到的有效内容占比低。解决训练前统一把长边缩放到目标尺寸短边按比例缩放但不补边或者干脆把竖拍图横过来后再入训练集。我在这个项目里的做法是写一个预处理脚本把宽小于高的图片先旋转90度再保存让所有训练样本的宽高比都大于1训练稳定性提升非常明显。6. 提升召回率的一个技巧切片推理合并NMS不少电力巡检场景里一张大图里可能挂了十几串绝缘子每一串在画面里的占比都很小。训练时用了imgsz960或者1280之后推理阶段如果还把整张大图直接丢进模型小目标依然容易被漏掉。切片推理是这个场景下一个非常实用的补救手段本质是“把大图拆成小块分别检测再把结果拼回去”。什么情况下值得上切片推理我一般看两个指标一是图片长边超过1600像素二是目标框平均面积占全图面积的比例小于0.5%。1000张绝缘子巡检图里这两种情况出现的概率很高。切块大小常用640或1024stride必须小于tile_size让相邻块有重叠区域否则目标恰好落在切缝上时会被切掉一半。import cv2 import torch import numpy as np from ultralytics import YOLO from torchvision.ops import nms def slice_infer(model, img_path, tile_size640, stride512, conf0.25): img cv2.imread(img_path) h, w img.shape[:2] all_boxes, all_scores, all_cls [], [], [] for y in range(0, h, stride): if y tile_size h: y h - tile_size for x in range(0, w, stride): if x tile_size w: x w - tile_size tile img[y:y tile_size, x:x tile_size] result model(tile, confconf, verboseFalse)[0] for box in result.boxes: x1, y1, x2, y2 box.xyxy[0].cpu().numpy() score float(box.conf[0]) cls_id int(box.cls[0]) # 关键把tile内的坐标偏移回原图坐标系 all_boxes.append([x1 x, y1 y, x2 x, y2 y]) all_scores.append(score) all_cls.append(cls_id) if not all_boxes: return [] keep nms( torch.tensor(np.array(all_boxes), dtypetorch.float32), torch.tensor(all_scores, dtypetorch.float32), iou_threshold0.5, ).tolist() results [] for i in keep: x1, y1, x2, y2 all_boxes[i] results.append((all_cls[i], all_scores[i], [x1, y1, x2, y2])) return results # 用法 model YOLO(runs/detect/train/weights/best.pt) detections slice_infer(model, big_insulator_image.jpg, tile_size640, stride512)坐标偏移是最容易出错的地方。每个tile的检测框坐标都是相对tile左上角的要拼回原图必须加上tile的起始坐标x和y。末尾的全局NMS必不可少因为重叠区域的同一个目标很可能被相邻两个tile各检一次不合并就会出现同一绝缘子被重复框选。参数选择上目标越小重叠越多stride一般取tile_size的一半到四分之三。从那以后我每次拿到一批新的输电线路图像都会强制走一遍这个流程解压后先看标签后缀跑体检脚本统计类别和分辨率分布再划分目录、转换格式最后才去动训练脚本。这一遍数据确认工作看起来多花半小时实际上省掉的是好几个晚上的重训和调参时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表