ARTICLE DETAIL

资讯详情

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

电力绝缘子缺陷检测:1000张图数据集训练YOLO实战指南

电力绝缘子缺陷检测:1000张图数据集训练YOLO实战指南 简介本资源面向电力巡检与计算机视觉方向的学习者与开发者提供一套真实场景下的绝缘子缺陷检测数据集可直接用于YOLO系列目标检测模型的训练与验证。数据经labelimg精细标注标注框质量较高覆盖多种拍摄场景适合课程设计、科研实验与工程原型验证。压缩包共2000个文件约57.16MB包含1000张图片及对应的xml、txt标签文件并附有html教程、py划分脚本与yaml配置文件voc、coco、yolo三种格式标签分目录存放开箱即用。资源另附YOLO环境搭建与训练案例教程以及训练集、验证集、测试集划分脚本可按需自行切分数据。目前已有853人学习下载能帮助读者快速跑通数据准备到模型训练的完整流程减少标注与格式转换的重复劳动。1. 电力绝缘子缺陷检测1000 张图的数据集到底能不能撑起一个 YOLO 项目输电线路巡检航拍回来的图里绝缘子破损、自爆、掉串这些缺陷肉眼在几百张图里翻一遍能看瞎。很多人第一反应是找个开源数据集直接上 YOLO 训练跑出一个能自动框缺陷的模型。这个标题给的东西正好卡在这个需求上1000 张电力绝缘子图片配 VOC、COCO、YOLO 三种格式标签外加划分脚本和训练教程。听起来是一条龙但真正动手前得先想清楚一件事——1000 张图在目标检测里属于小样本绝缘子缺陷又存在类间不平衡和背景干扰能不能训出可用模型取决于你怎么划分、怎么转格式、怎么设参数。这篇笔记就按我实际做这类项目的顺序把数据集结构、格式转换、划分脚本、训练配置和踩过的坑讲一遍适合刚拿到这批数据准备跑 YOLOv8 或 YOLOv11 的人也适合想评估这个方向值不值得投入的同行。2. 先看清数据集的底子1000 张图、三种标签格式意味着什么2.1 绝缘子缺陷检测的任务定义与类别分布电力绝缘子缺陷检测在目标检测里属于细粒度工业缺陷检测。常见缺陷类型包括绝缘子自爆玻璃绝缘子伞裙破裂、破损、掉串、异物悬挂。和通用 COCO 数据集里猫狗车人不同绝缘子缺陷的类间差异小自爆和正常伞裙在低分辨率下几乎一样模型很容易把正常绝缘子误报成缺陷。1000 张图如果按 8:1:1 划分训练集只有 800 张验证集和测试集各 100 张。这个量级下类别分布比总数更关键。假设自爆样本 300 张、破损 200 张、正常绝缘子 500 张那训练时正常类会主导损失缺陷类召回率上不去。我一般拿到数据集先做一件事统计每个类别的实例数和图片数。不是看文件夹里有多少张图而是解析标签文件数每个类别的框数量。这一步能直接决定后面要不要做重采样或加权重。import os from collections import Counter # 假设 YOLO 格式标签目录每行格式class_id x_center y_center w h label_dir labels/train class_names [insulator, broken, flashover, normal] # 按你的 data.yaml 顺序 counter Counter() for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue with open(os.path.join(label_dir, txt)) as f: for line in f: parts line.strip().split() if len(parts) 5: counter[int(parts[0])] 1 for cid, cnt in sorted(counter.items()): print(f{class_names[cid]}: {cnt} instances)这段代码遍历 YOLO 格式标签统计每个类别的实例总数。参数上注意class_names的顺序必须和data.yaml里的names完全一致否则统计结果会错位。如果发现某个类别实例数不到总数的 5%训练时就要考虑用copy_paste增强或者设置类别权重。1000 张图里如果缺陷实例只有一两百个直接训 YOLOv8n 大概率会出现漏检这时候要么换更小的模型加更强增强要么接受一个偏保守的置信度阈值。2.2 VOC、COCO、YOLO 三种格式的差异与选用场景VOC 格式用 XML 存框坐标是左上角和右下角的绝对像素值适合用 LabelImg 标注和做数据交换。COCO 格式用 JSON 存坐标是 [x, y, width, height] 绝对像素还带iscrowd和area字段适合跑 MMDetection 或做 COCO 预训练权重迁移。YOLO 格式用 TXT 存坐标是归一化的中心点和宽高直接喂给 Ultralytics 的 YOLO 训练脚本。三种格式同时给好处是你不用自己写转换坏处是容易搞混。我见过有人把 COCO 的 JSON 直接改后缀当 YOLO 标签用训练 loss 不降排查半天才发现坐标没归一化。正确做法是训练 YOLO 就用 YOLO 格式做格式转换验证时用 VOC 或 COCO 交叉检查。比如从 VOC 转 YOLO 后随机抽 10 张图用脚本把框画出来和原图对比确认没有偏移或镜像。格式坐标类型单文件组织典型工具适用场景VOC绝对像素 xmin ymin xmax ymax每图一个 XMLLabelImg标注交换、转其他格式COCO绝对像素 x y w h全量一个 JSONCVAT、MMDetection预训练权重迁移、多任务YOLO归一化 cx cy w h每图一个 TXTUltralytics直接训练 YOLOv5/v8/v11选型上如果你只是训一个 YOLO 模型做绝缘子缺陷检测直接用 YOLO 格式最省事。但如果你打算对比不同框架的效果比如用 MMDetection 跑 RTMDet 或 DINO那就需要 COCO 格式。VOC 格式在这批数据里更多是作为中间态方便你用 LabelImg 复查标注质量。2.3 划分脚本怎么用别让随机种子毁掉你的验证集标题里提到划分脚本这是最容易被忽视但影响最大的环节。很多人直接random.shuffle然后按 8:1:1 切结果验证集里全是正常绝缘子缺陷样本一个没有训出来的模型在测试集上缺陷召回率惨不忍睹。正确做法是按类别分层抽样保证每个子集里缺陷和正常的比例接近。import os import random import shutil from collections import defaultdict random.seed(42) # 固定种子保证可复现 img_dir images label_dir labels out_dir dataset_split train_ratio, val_ratio 0.8, 0.1 # 按图片是否包含缺陷分层 def has_defect(label_path): with open(label_path) as f: for line in f: if int(line.split()[0]) ! 0: # 假设 0 是正常绝缘子 return True return False files [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] defect_files [f for f in files if has_defect(os.path.join(label_dir, f.replace(.jpg, .txt).replace(.png, .txt)))] normal_files [f for f in files if f not in defect_files] for group in [defect_files, normal_files]: random.shuffle(group) n_train int(len(group) * train_ratio) n_val int(len(group) * val_ratio) splits { train: group[:n_train], val: group[n_train:n_train n_val], test: group[n_train n_val:] } for split, items in splits.items(): os.makedirs(os.path.join(out_dir, split, images), exist_okTrue) os.makedirs(os.path.join(out_dir, split, labels), exist_okTrue) for item in items: shutil.copy(os.path.join(img_dir, item), os.path.join(out_dir, split, images, item)) lbl item.replace(.jpg, .txt).replace(.png, .txt) shutil.copy(os.path.join(label_dir, lbl), os.path.join(out_dir, split, labels, lbl))这段脚本的核心是has_defect函数和分层逻辑。random.seed(42)保证每次运行划分结果一致方便复现实验。train_ratio和val_ratio按需调整1000 张图建议 8:1:1如果缺陷样本特别少可以改成 7:1.5:1.5让验证集和测试集多分一点缺陷图。运行后检查dataset_split下每个子集的图片数和标签数是否一一对应缺标签的图要单独处理不能直接丢否则训练时 Ultralytics 会报No labels found。3. 从 VOC 和 COCO 转到 YOLO转换脚本与四个边界坑3.1 VOC 转 YOLO 的完整脚本与坐标归一化VOC 的 XML 里size字段给出图片宽高object里bndbox给绝对坐标。转 YOLO 需要做三件事读 XML、算归一化中心点和宽高、按类别映射写 TXT。下面这个脚本处理了图片和标签不在同一目录的情况也处理了difficult字段。import os import xml.etree.ElementTree as ET voc_dir VOC/Annotations img_dir VOC/JPEGImages out_label_dir labels_voc2yolo class_map {insulator: 0, broken: 1, flashover: 2, normal: 3} os.makedirs(out_label_dir, exist_okTrue) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue # 跳过难以识别的目标 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 裁剪到图片边界防止标注越界 xmin, ymin max(0, xmin), max(0, ymin) xmax, ymax min(w, xmax), min(h, ymax) cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: with open(os.path.join(out_label_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))关键点在xmin, ymin max(0, xmin), max(0, ymin)和xmax, ymax min(w, xmax), min(h, ymax)。绝缘子标注时经常有人把框拉到图片外面不裁剪的话归一化后坐标会小于 0 或大于 1YOLO 训练时虽然不报错但实际框会偏移。difficult字段为 1 的目标建议跳过这些通常是遮挡严重或模糊的样本留着会拉低模型精度。class_map必须和data.yaml一致顺序错了类别就全乱。3.2 COCO 转 YOLO 时容易忽略的 iscrowd 和面积过滤COCO 的 JSON 里annotations数组每个元素有bbox、category_id、iscrowd、area。转 YOLO 时iscrowd1的框要跳过因为 YOLO 不支持 crowd 区域。另外area太小的框比如小于 10 像素也要过滤绝缘子缺陷检测里这种小框多半是标注噪声。import json import os coco_json annotations/instances_train.json img_dir train2017 out_label_dir labels_coco2yolo min_area 10 # 最小面积阈值 with open(coco_json) as f: data json.load(f) # 建立 image_id 到文件名的映射 img_id2name {img[id]: img[file_name] for img in data[images]} # category_id 从 1 开始YOLO 从 0 开始 cat_id2yolo {cat[id]: i for i, cat in enumerate(data[categories])} os.makedirs(out_label_dir, exist_okTrue) img2lines {} for ann in data[annotations]: if ann.get(iscrowd, 0) 1: continue if ann[area] min_area: continue img_info next(img for img in data[images] if img[id] ann[image_id]) w, h img_info[width], img_info[height] x, y, bw, bh ann[bbox] cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h yolo_id cat_id2yolo[ann[category_id]] img2lines.setdefault(ann[image_id], []).append(f{yolo_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) for img_id, lines in img2lines.items(): name img_id2name[img_id].replace(.jpg, .txt) with open(os.path.join(out_label_dir, name), w) as f: f.write(\n.join(lines))cat_id2yolo用enumerate把 COCO 的 category_id 映射成从 0 开始的连续整数这一步不能省因为 COCO 的 category_id 可能不连续。min_area设 10 是经验值绝缘子缺陷如果特别小可以降到 5但再低就容易把噪声框进来。转换完随机抽几张图用cv2.rectangle画框验证确认坐标没反。3.3 转换后必须做的三项校验转换脚本跑完不代表数据就能用。我一般做三项校验第一统计转换前后每个类别的实例数差异超过 5% 就要查第二随机抽 20 张图用 OpenCV 把 YOLO 标签画出来和原图对比第三检查有没有空标签文件空文件在训练时会被 Ultralytics 当成负样本如果图片里确实没目标可以留如果是转换丢了就要补。import cv2 import os import random img_dir images/train label_dir labels/train sample_imgs random.sample(os.listdir(img_dir), 20) for img_name in sample_imgs: img cv2.imread(os.path.join(img_dir, img_name)) h, w img.shape[:2] lbl_path os.path.join(label_dir, img_name.replace(.jpg, .txt)) if not os.path.exists(lbl_path): print(fmissing label: {img_name}) continue with open(lbl_path) as f: for line in f: cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(fcheck_{img_name}, img)画出来的框如果明显偏左上或偏右下多半是归一化时用了错误的宽高比如把 COCO 的width和height搞反。这个校验步骤花不了几分钟但能省掉后面几小时的排查。4. 用这批数据训 YOLO配置、参数和收敛判断4.1 data.yaml 与训练命令的最小配置Ultralytics 的 YOLO 训练入口是yolo detect train核心配置文件是data.yaml。1000 张图的数据集data.yaml里train、val、test指向划分后的目录names按类别顺序写。path: ./dataset_split train: train/images val: val/images test: test/images nc: 4 names: 0: insulator 1: broken 2: flashover 3: normal训练命令用 YOLOv8n 或 YOLOv11n 起步因为数据量小大模型容易过拟合。yolo detect train \ datadataset_split/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ projectruns/insulator \ nameexp1epochs150配合patience30如果 30 轮验证集 mAP 不升就早停。imgsz640是 YOLO 默认绝缘子缺陷如果很小可以提到 1280但显存要够。batch16在 8G 显存上跑 YOLOv8n 没问题显存小就降到 8。mosaic1.0和mixup0.1对小数据集很关键能显著提升泛化但如果缺陷样本特别少mixup可能把缺陷特征混没可以降到 0.05 或关掉。4.2 小样本下的学习率与增强参数怎么调1000 张图训练时lr00.01是 SGD 的常见起点但如果用 AdamW建议降到 0.001。判断学习率是否合适看训练日志里的box_loss和cls_loss如果前 10 轮 loss 震荡不降学习率偏大如果 loss 降得极慢学习率偏小。我一般先跑 20 轮看趋势再决定要不要调。增强参数里mosaic对小数据集帮助最大它把 4 张图拼成一张等效增加了样本多样性。但mosaic会让目标变小如果绝缘子缺陷本身就不大mosaic后可能小到模型学不到这时候把mosaic从 1.0 降到 0.5或者用close_mosaic10在最后 10 轮关掉。hsv_h、hsv_s、hsv_v控制颜色抖动航拍图光照变化大可以适当调高hsv_v到 0.5。degrees旋转增强对绝缘子有用因为航拍角度多变但degrees180会把绝缘子转成倒的可能引入噪声一般设 10 到 15。4.3 看什么指标判断模型能不能用训练完看results.csv里的metrics/mAP50和metrics/mAP50-95。绝缘子缺陷检测里mAP50到 0.85 以上算可用mAP50-95到 0.5 以上算不错。但别只看总体 mAP要分看每个类别的metrics/mAP50。如果broken类 mAP 只有 0.3而normal类 0.95说明模型偏向正常类需要检查缺陷样本数量或加类别权重。验证集上的val/box_loss如果持续上升而train/box_loss下降是过拟合信号这时候要么加数据增强要么减模型复杂度要么早停。测试集跑一次yolo detect val看混淆矩阵如果正常绝缘子被大量误报成缺陷调高置信度阈值到 0.5 或 0.6牺牲一点召回换精度。5. 避坑与排查绝缘子数据集训练中最容易翻车的 5 个点5.1 标签类别顺序和 data.yaml 不一致现象训练 loss 正常下降但验证时框的位置对类别全错比如自爆被标成正常。原因YOLO 标签里的class_id是整数data.yaml的names顺序决定这个整数对应哪个类。如果转换脚本里class_map和data.yaml不一致模型学到的就是错位映射。解决转换前先定死data.yaml的names顺序所有转换脚本的class_map按这个顺序写转换后抽一张图确认类别 ID 和名称对应。5.2 图片和标签文件名不匹配现象训练时报No labels found in ...或者某些图被跳过。原因图片是.jpg标签是.txt但文件名大小写不一致或者图片有.JPG和.jpg混用。解决统一转成小写后缀用脚本批量重命名。另外注意 Ultralytics 默认按文件名匹配图片和标签必须在对应目录下同名。5.3 验证集里没有缺陷样本现象训练时val/box_loss很低但测试集上缺陷召回率极低。原因随机划分时缺陷样本全进了训练集验证集只有正常绝缘子模型在验证集上表现好是假象。解决用分层抽样按是否包含缺陷划分保证验证集和测试集里缺陷样本占比和总体一致。划分后统计每个子集的类别实例数差异大就重新划。5.4 图片尺寸不一致导致训练报错现象训练启动时报RuntimeError: shape mismatch或Caught Exception: Image size ...。原因数据集里混了不同分辨率的图YOLO 默认会 resize 到imgsz但如果某张图特别小resize 后目标几乎消失。解决训练前用脚本统计图片宽高把小于 320 像素的图筛出来单独处理或者统一 resize 到 640 再训。Ultralytics 的imgsz参数会做 letterbox但极端长宽比仍可能出问题。5.5 显存不足导致 batch 被迫调小现象batch16时报CUDA out of memory。原因YOLOv8n 在 640 分辨率下 batch16 大约占 4G 显存但如果开了mosaic和mixup显存会涨。解决先降batch到 8再不行降到 4同时开ampTrue混合精度。如果还不行把imgsz从 640 降到 512但绝缘子缺陷小的话不建议降太多。6. 把 1000 张图用出 5000 张的效果离线增强与交叉验证的实操技巧数据量小的时候在线增强不够用我一般会做离线增强扩充训练集。具体做法是对训练集里的缺陷图片做旋转、翻转、亮度调整生成额外样本但验证集和测试集绝对不动。旋转角度选 90、180、270 和 ±15 度翻转用水平和垂直亮度调整 ±30%。每张缺陷图生成 3 到 4 个变体1000 张里如果有 300 张缺陷图能扩到 1200 张左右加上正常图训练集能到 2000 张上下。import cv2 import os import numpy as np src_img_dir dataset_split/train/images src_lbl_dir dataset_split/train/labels aug_img_dir dataset_split/train/images_aug aug_lbl_dir dataset_split/train/labels_aug os.makedirs(aug_img_dir, exist_okTrue) os.makedirs(aug_lbl_dir, exist_okTrue) def augment(img, labels, angle, flip): h, w img.shape[:2] if flip h: img cv2.flip(img, 1) labels [(c, 1 - cx, cy, bw, bh) for c, cx, cy, bw, bh in labels] elif flip v: img cv2.flip(img, 0) labels [(c, cx, 1 - cy, bw, bh) for c, cx, cy, bw, bh in labels] if angle ! 0: M cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0) img cv2.warpAffine(img, M, (w, h)) # 旋转后标签需要重新计算这里简化处理只保留中心点变换 new_labels [] for c, cx, cy, bw, bh in labels: x, y cx * w, cy * h nx M[0, 0] * x M[0, 1] * y M[0, 2] ny M[1, 0] * x M[1, 1] * y M[1, 2] if 0 nx w and 0 ny h: new_labels.append((c, nx / w, ny / h, bw, bh)) labels new_labels return img, labels for img_name in os.listdir(src_img_dir): if not img_name.endswith(.jpg): continue lbl_path os.path.join(src_lbl_dir, img_name.replace(.jpg, .txt)) if not os.path.exists(lbl_path): continue with open(lbl_path) as f: labels [tuple(map(float, line.split())) for line in f if line.strip()] # 只增强包含缺陷的图 if all(int(c) 0 for c, *_ in labels): continue img cv2.imread(os.path.join(src_img_dir, img_name)) for angle in [90, 180, 270]: for flip in [h, v]: aug_img, aug_labels augment(img.copy(), labels.copy(), angle, flip) if not aug_labels: continue base img_name.replace(.jpg, f_a{angle}_{flip}) cv2.imwrite(os.path.join(aug_img_dir, base .jpg), aug_img) with open(os.path.join(aug_lbl_dir, base .txt), w) as f: for c, cx, cy, bw, bh in aug_labels: f.write(f{int(c)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)这段脚本只对包含缺陷的图做增强正常绝缘子不扩避免正常类过度主导。旋转后标签用旋转矩阵重新算中心点宽高保持不变这是简化处理严格来说旋转后宽高会变但小角度下误差可接受。增强完把images_aug和labels_aug合并进训练集重新跑训练。注意增强后的图要和原图一起参与划分不能只增强训练集后再划验证集否则验证集里会有增强样本指标虚高。另一个技巧是交叉验证。1000 张图做 5 折交叉验证每折 800 张训练、200 张验证跑 5 次取平均 mAP。这样能更稳地评估模型性能也能在数据量少时充分利用每张图。Ultralytics 不直接支持 K 折需要自己写循环改data.yaml的train和val路径。我一般跑 3 折因为 5 折训练时间太长3 折已经能看出模型稳不稳定。最后说一个我自己的习惯每次训完模型不管指标多好都会拿测试集里最难的 10 张图单独跑一遍人眼看框。指标是给论文和汇报用的实际能不能用得看那些模糊、遮挡、小目标的图有没有漏。绝缘子缺陷检测里漏检一个自爆可能比误报十个正常更严重所以召回率优先于精度。希望帮到你。本文还有配套的精品资源点击获取
返回列表