
简介配网输电线路巡检中航拍视角下的缺陷目标检测长期面临小目标密集、背景复杂、标注样本稀缺等痛点。该数据集面向电力巡检算法工程师与计算机视觉学习者提供1787张高清晰度航拍原图围绕“不规范捆绑”“接线盒外壳缺失”“张力夹外壳缺失”3类典型配网缺陷进行矩形框标注共7987个真实标注框且未做数据增强适合用于YOLO系列模型微调、VOC格式训练及检测精度对照实验。压缩包内共2000个文件包含1787张jpg图片、1787份XML标注文件与1787份TXT标注文件目录结构按JPEGImages、Annotations、labels三类划分便于主流检测框架直接读取资源整体约181MB。目前已有65人学习下载适合需要规范配网缺陷标注数据来验证网络结构、调参或撰写实验报告的研究者快速获取使用。1. 航拍配网缺陷检测难在哪1787张的规模意味着什么配网缺陷检测这几年成了无人机巡检的刚需。绝缘子破损、鸟巢、异物悬挂这些东西靠人眼盯图库效率太低一线班组真正需要的是一个能自动圈出问题区域的模型。这套目标检测航拍配网缺陷检测数据集共1787张图、3个类别同时给了YOLO和VOC两套格式恰好是这类小场景数据集的典型样本。先说结论1787张在深度学习里算小数据集但配网缺陷检测恰恰是「数据少、目标小、背景杂」的典型场景。航拍视角下绝缘子一串可能只有二三十像素宽鸟巢被电线杆塔的纹理淹没这类问题用通用目标检测的套路硬套往往在验证集上翻车。这个数据集的直接价值在于它把「无人机拍到什么算缺陷、缺陷长什么样」这件事固化成了可训练的标注且同时提供两种格式省掉了最常见的格式转换环节适合用来做预研验证和算法选型对比。适合谁用刚接触目标检测、想跑通一套配网缺陷检测流程的工程师以及需要小样本基线数据的算法团队。2. 数据集结构先摸清3类目标和两种标注格式的关系2.1 三类缺陷目标怎么定义类别字段与边界配网缺陷检测的可检测对象虽然在不同项目里叫法略有出入但核心类别基本一致绝缘子破损/自爆、鸟巢、异物悬挂或防震锤缺失。这套数据集固定为3类意味着标注人员在框选时已经把「缺陷」收敛到了这三类上。实际使用时先看类别编号和名称的对应关系——我一般习惯先把classes.txt或VOC格式里的name字段列出来确认自己心中的类别顺序和标注文件里的顺序一致否则训练出来的模型类别标签会错位这是最隐蔽的坑。关于标注边界缺陷检测里最容易出争议的是「这个绝缘子到底破没破」。实操中会靠图像分辨率来兜底——如果目标在图中占比过小比如小于16x16像素即使人眼能看出来是缺陷模型也很难学强行标注反而引入噪声。这个数据集的1787张图大概率是在无人机巡检视频里抽帧得到的抽帧间隔决定相似度相似度太高的帧进入训练集会放大过拟合风险后文我单独说怎么处理。2.2 zip解压后的文件组织images、Annotations、labels拿到目标检测航拍配网缺陷检测数据集1787张3类YOLOVOC格式.zip解压后常见结构是dataset/ ├── images/ # 全部图像一般以 jpg 为主 │ ├── 0001.jpg │ └── ... ├── Annotations/ # VOC格式标注XML文件与图像同名 │ ├── 0001.xml │ └── ... ├── labels/ # YOLO格式标注txt文件与图像同名 │ ├── 0001.txt │ └── ... └── classes.txt # 类别清单一行一个类别名VOC格式的XML里关键信息是filename、size宽高深和object块里的name、bndbox的xmin/ymin/xmax/ymax。YOLO格式则是每行一个目标class_id x_center y_center width height前四个数值都是相对图像尺寸归一化到0~1之间的浮点数。如果解压后发现 labels 目录部分txt为空文件说明对应图像没有标注目标——正常现象但训练时ultralytics会把它当成背景图参与负样本学习。2.3 VOC与YOLO格式的关键差异坐标系和归一化两种格式最大的区别不在文件后缀而在坐标表达。VOC存的是像素绝对坐标整数左上角右下角YOLO存的是归一化中心点坐标加宽高浮点。转换时最容易出问题的就是「归一化越界」和「绝对坐标取整误差」。另外YOLO格式严格要求类别ID从0开始连续编号如果你的数据集类别编号是1、2、3直接拿来训练会报错或把第一类当背景这一步我在下一章写转换脚本时一并处理。提示拿到数据集先随机抽3张图人工比对一下XML和txt里框的是不是同一个目标。这一步花不了5分钟但能挡住后面十几个小时的无效训练。3. 把VOC标注转成YOLO格式转换脚本与四个边界坑3.1 一个稳妥的VOC转YOLO脚本虽然这套数据集同时给了两种格式但日常收集的数据经常只有VOC格式且YOLO版本升级后对标注格式的要求有微调。我的建议是不要依赖别人给的 labels自己用脚本从XML重新生成一遍至少能校验一遍标注的合法性。下面是经过多次跑通的转换脚本。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_file, class_names, img_width, img_height): tree ET.parse(xml_file) root tree.getroot() lines [] for obj in root.iter(object): name obj.findtext(name).strip() if name not in class_names: continue # 跳过不在类别清单里的目标 class_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 边界裁剪防止坐标越界导致后续训练崩溃 xmin max(0, min(xmin, img_width - 1)) xmax max(0, min(xmax, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) ymax max(0, min(ymax, img_height - 1)) # 过滤掉宽高小于1像素的无效框 if xmax - xmin 1 or ymax - ymin 1: continue x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # 归一化后可能因四舍五入出现1.0000000需要兜底处理 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) width min(max(width, 0.0), 1.0) height min(max(height, 0.0), 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return lines def convert_all(xml_dir, img_dir, out_dir, class_names): Path(out_dir).mkdir(parentsTrue, exist_okTrue) for xml_path in Path(xml_dir).glob(*.xml): img_path Path(img_dir) / (xml_path.stem .jpg) if not img_path.exists(): print(f[WARN] 找不到图像: {img_path}跳过 {xml_path.name}) continue # 直接读图像宽高避免XML里的size字段与实际图像不一致 from PIL import Image with Image.open(img_path) as im: img_w, img_h im.size lines voc_to_yolo(str(xml_path), class_names, img_w, img_h) if lines: with open(Path(out_dir) / (xml_path.stem .txt), w) as f: f.write(\n.join(lines) \n) if __name__ __main__: class_names [insulator_defect, bird_nest, foreign_object] convert_all(Annotations, images, labels, class_names)这段脚本的核心逻辑解析XML拿到像素坐标、用PIL直接读取图像宽高、归一化坐标、写出txt。其中几个参数值得说清楚。class_names的顺序就是最终YOLO标签的ID映射一定要和数据集自带的classes.txt保持一致否则类别错乱。img_width和img_height用Image.open实时读取而不是信XML里的size字段是为了防图像被resize过但XML没同步更新的情况。3.2 四个边界坑越界框、空标注、格式错位、类别遗漏坑一坐标越界。标注软件手滑把xmax标得比图像宽度还大是常见情况YOLO训练时会出现assertion failed或loss变成NaN。脚本里我先裁剪到[0, img_width-1]区间再归一化保证输出永远合法。坑二空txt文件。一张图没有任何目标时YOLO允许空标签文件但有些工具链会报错。我建议转换后统计一下空文件数量如果空文件占比超过10%要考虑标注是否漏标严重别急着训练。坑三类别编号从1开始。有的标注工具习惯把第一类的ID记为1而不是0转换后必须检查第一行开头的数字是不是0否则模型会多学一个背景ID。我习惯在转换后跑一条命令快速统计类别分布cat labels/*.txt | awk {print $1} | sort | uniq -c坑四类别遗漏。XML里出现了class_names里没有的类别名脚本里直接continue跳过。这个行为会静默丢目标所以转换后要对比一下XML里的类别集合和classes.txt是否一致不一致时要查是标注错了还是类别定义漏了。3.3 转换后的标注校验可视化是后悔药转换脚本跑完不是终点我强烈建议抽查可视化。可以用OpenCV在图上画框把YOLO格式的归一化坐标换算回像素坐标再画出来人工看一眼有没有框错位、框太小、框丢失。import cv2 import numpy as np def draw_yolo_boxes(img_path, txt_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(check_ os.path.basename(img_path), img) if __name__ __main__: class_names [insulator_defect, bird_nest, foreign_object] draw_yolo_boxes(images/0001.jpg, labels/0001.txt, class_names)三个可视化重点一是看框是否紧贴目标配网缺陷里绝缘子破损往往只占绝缘子串的一小段框得太大模型会学到背景二是看类别标签是否和框内内容相符尤其异物悬挂和鸟巢容易搞混三是看小目标框是否被绘制出来如果某个只有10x10像素的目标频繁出现但没框大概率标注漏了。4. 用转换后的数据训练YOLOv8级模型最小命令与三个必调参数4.1 数据目录划分与YAML配置训练前先建好标准目录结构Ultralytics 的YOLO框架要求数据集的图像和标签按 train/val 分目录组织且标签目录和图像目录平行。我现在习惯先跑一个划分脚本按 8:1:1 切分训练/验证/测试集测试集留到最后评估模型真实泛化能力。# 在项目根目录下执行 mkdir -p dataset/images/train dataset/images/val dataset/images/test mkdir -p dataset/labels/train dataset/labels/val dataset/labels/test # 用脚本按比例划分这里以90%训练、10%验证为例测试集另留 python - EOF import os, random from pathlib import Path random.seed(42) image_files sorted(Path(images).glob(*.jpg)) random.shuffle(image_files) val_count int(len(image_files) * 0.1) val_files set(image_files[:val_count]) for img in image_files: if img.name in val_files: os.rename(str(img), fimages/val/{img.name}) os.rename(flabels/{img.stem}.txt, flabels/val/{img.stem}.txt) else: os.rename(str(img), fimages/train/{img.name}) os.rename(flabels/{img.stem}.txt, flabels/train/{img.stem}.txt) EOF划分时用随机种子是为了结果可复现配网巡检数据里同一设备不同角度的重复帧很常见随机划分比按时间划分更稳但如果你知道图像是按航线连续采集的最好按设备或航线分组划分避免同一座杆塔的相似帧同时进训练集和验证集导致验证指标虚高。这是配网这类场景最容易踩的坑我在第5章细说。然后写数据配置YAML# dataset.yaml path: dataset # 数据集根目录相对于当前工作目录 train: images/train val: images/val test: images/test nc: 3 names: 0: insulator_defect 1: bird_nest 2: foreign_objectpath字段在Ultralytics新版本里可以直接填相对路径但我建议填绝对路径省得在不同目录下运行命令时踩相对路径解析不一致的坑。nc和names必须与转换脚本里的class_names完全一致顺序不一致是灾难级的错位。4.2 训练最小命令先验证跑通再谈精度yolo detect train \ modelyolov8n.pt \ datadataset.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns \ namedefect_exp1这条命令里我选了yolov8n而不是更大的yolov8s或yolov8m原因有三。一1787张属于小数据集大模型起手就过拟合小模型至少能跑完二配网缺陷检测最终要部署到无人机机载边缘设备上轻量模型更贴近实际三n模型训练一轮只要几十秒方便快速迭代调参。如果你手头有预训练权重就用预训练权重起步别从头训练——否则小数据集很难收敛loss下降慢且容易卡在局部最优。训练过程中盯两个指标看趋势。一是train/box_loss是不是稳定下降如果前10轮不降反升大概率学习率有问题或标签里有非法值。二是val/box_loss和mAP50验证loss先降后升是过拟合的典型信号这时要动用下一节的早停机制。4.3 三个必调参数epochs、imgsz、patience参数一epochs。小数据集上我一般设100原因很简单训练集只有1600张左右每个epoch更新次数少100轮刚好让模型充分消化但不要无脑加到300配网缺陷数据集里背景相似度高epochs过大后验证集的mAP会明显回退。Ello可观察results.csv里的val_mAP50若最后20轮没有上升趋势说明已到平台期。参数二imgsz。航拍小目标检测里这是最关键的参数。默认640对普通目标没问题但绝缘子破损可能在原图里只占30像素宽缩放成640后只剩十几像素模型根本学不到纹理特征。做法是先在640跑通流程再把imgsz960或1280训一个对比组。代价是训练耗时约增长为原来的2-3倍但小数据集上计算开销可接受换取的是小目标召回率提升。参数三patience早停耐心值。默认100表示100轮验证集指标不涨就停配网数据集我建议改成30。小数据集本来就容易过拟合早停太晚等于白训后面几十轮。命令里加patience30即可同时配合saveTrue保留最佳权重。提示第一次训练务必用最小配置yolov8n、imgsz640、epochs20跑通整条链路确认没有报错、指标画得出来再回头调这三个参数。我见过太多人一上来就上1280分辨率加300轮训练12小时后发现标签文件有问题白烧几度电。4.4 训练结果解读weights文件和results.csv训练完成后在runs/defect_exp1/下能看到weights/best.pt、weights/last.pt、results.csv和一系列曲线图。best.pt是验证集指标最好的权重后续推理就用它last.pt是最后一轮权重通常不如best。results.csv里含每一轮的train/box_loss、val/box_loss、metrics/mAP50(B)、metrics/mAP50-95(B)等指标列我习惯用pandas拉出来看最后10行的趋势。判断模型有没有训练好有个简单标准mAP50在0.7以上算合格0.8以上算优秀但配网缺陷检测里mAP50高不一定代表实用——因为验证集里背景单一的帧太多真正涉及复杂背景的缺陷帧可能被稀释。所以第6章会讲用混淆矩阵和实际视频帧验证这里先记住mAP50是基线参考不是最终结论。5. 小数据集训练避坑与排查从loss翻车到类别幻觉5.1 现象训练loss变成NaNweight直接作废小数据集训练最常见的翻车现场是 train/box_loss 在某一步突然变成 nan之后所有指标全废。原因通常集中在三个方向。第一标签文件里有非法值比如某个txt里出现了0 5.000000 5.000000 0.000000 0.000000这种宽高为0的目标除零导致loss nan。第二学习率过高yolov8默认学习率对 n 模型一般是0.01但小数据集上偶尔会出现梯度爆炸。第三图像文件损坏某张jpg实际是空文件或已被截断读取时像素值异常。解决办法按顺序排查先统计标签文件里宽高为0的行用脚本过滤掉再把学习率降到0.005或0.001重试最后逐个图像文件检查完整性用PIL的Image.verify()批量跑一遍损坏的直接删除或重新导出。from PIL import Image from pathlib import Path corrupted [] for img_path in Path(images).glob(*.jpg): try: with Image.open(img_path) as im: im.verify() except Exception: corrupted.append(img_path) print(损坏图像数:, len(corrupted)) for p in corrupted: print(p)5.2 现象训练集mAP很高验证集mAP掉一半这是1787张数据集上过拟合的典型特征train/box_loss 降到0.01val/box_loss 却停在0.06以上mAP50训练到0.95但验证只有0.6。造成这个现象的根本原因是训练帧相似度过高——无人机巡检视频按顺序抽帧时同一座杆塔拍了二三十张几乎一样的图随机划分后这些相似帧同时进入训练集和验证集模型看似泛化实际记住了背景。解决手段有两个层面。数据层面划分时按设备ID或拍摄时间分组保证同一设备的所有帧只进训练集或只进验证集验证集才能反映真实泛化。算法层面数据增强开到最大hsv_h0.02, hsv_s0.8, degrees10, translate0.1, scale0.5, fliplr0.5这些增强参数对小数据集非常管用相当于把训练样本量放大几倍。同时把 epochs 收短到80配合patience30早停。5.3 现象小目标漏检严重mAP50 看着不错但实际框不到缺陷航拍图像里绝缘子破损目标往往只有几十像素训练时模型倾向于学到「绝缘子串」这个大目标破损部分被当成背景。此时单独看 mAP50 会被大目标拉高掩盖小目标几乎全丢的事实。解决办法是把 imgsz 提到1280并检查有没有开启多尺度训练。YOLOv8 的scale0.9默认会对输入随机缩放但小目标在缩小时会更难学可以改成scale0.5减小随机缩放幅度。另一个思路是把数据集里的缺陷区域裁剪出来单独训练一个二次检测模型先检测绝缘子串再检测破损两级检测在配网业务里是常见且可靠的落地方式。5.4 现象鸟巢误检率极高杆塔纹理被当成目标鸟巢由树枝堆叠而成在低分辨率下和杆塔的钢结构纹理非常像模型容易把杆塔的支架连接处误报成鸟巢。根因是鸟巢的类内差异大——有的窝很大有的只是几根树枝——而训练样本里鸟巢的数量往往远少于绝缘子缺陷类不平衡导致误检。解决思路按顺序试。第一检查类别分布若鸟巢样本不足总体的20%考虑只做二分类绝缘子缺陷/非缺陷把鸟巢和异物并成「其他缺陷」简化问题。第二对鸟巢类做复制粘贴增强把标注的鸟巢小图随机贴到含杆塔但没有鸟巢的图像上让模型学到「有树枝堆叠才算」而不是「杆塔纹理就算」。第三推理时提高置信度阈值到0.4同时用conf和iou联合过滤牺牲部分召回换误检率下降。5.5 现象验证指标不涨反而下降越训越差配网数据集上常见的一种情况是前20轮mAP缓涨第30轮开始逐步下跌像是模型在「遗忘」已学到的特征。原因多数是学习率策略不匹配或验证集分布和训练集差异过大。yolov8默认用余弦退火调度初始学习率0.01对小数据集偏激进。此时在 config 里补上lr00.005, lrf0.01再训一轮同时检查验证集中是否有极端恶劣的图比如大面积过曝或对焦模糊若集中在某几类考虑从验证集剔除或补充更多正常样本。提示排查时先把所有可疑因素一次性都列出来再逐项排除切忌同时改三个超参。我会把每次实验的配置和指标手写进一个markdown表里对比时一目了然。这一步的「血泪经验」是改一个变量、录一条结果、再动下一个小数据集实验成本低足够你把每个参数试明白。6. 验证与收敛技巧用混淆矩阵和视频帧卡住模型底线训练结束后别急着看 mAP 交差配网缺陷检测的验收要看模型在你实际拍摄的视频帧上的表现。把训练得到best.pt拉出来对一段没参与训练的巡检视频跑推理逐帧输出检测结果和置信度重点统计两类错误漏检该框没框和误检框了不该框的。漏检高的类别回到第5章第3节的方向调误检高的类别调置信度阈值或做类别合并。from ultralytics import YOLO model YOLO(runs/defect_exp1/weights/best.pt) results model.predict( sourceinspection_video.mp4, conf0.35, iou0.5, imgsz1280, saveTrue, vid_stride10, line_width2 )这里vid_stride10表示每10帧抽1帧做推理巡检视频冗余帧多没必要逐帧检测conf0.35是按第5章误检排查后折中的阈值实际阈值用网格搜索在验证集上扫一遍选误检率低于某业务容忍线的最大阈值。逐帧推理结果里如果漏检集中在目标过小的帧优先裁切放大而不是盲目提高分辨率。如果误检集中在杆塔区域考虑给模型加一个背景类别把杆塔整体框成背景类样本让模型学会区分「杆塔的一部分」和「缺陷」。最后再强调一个习惯验证集指标只是底线模型要能扛住现场不同光线、不同角度、不同设备的图才算真正可以用。每训一版模型就用当天拍的一段视频从头到尾看一遍预测结果拿「肉眼感觉」和「指标体系」一起衡量两边对得上再发给现场班组试跑。按这个流程走完1787张的配网缺陷数据集能做出来的模型水平是有边界的——它足以支撑原型验证和算法选型但要做到现场可用还要靠采集更多缺陷样本、按第5节的方法反复清洗标注以及必要时引入两级检测架构。希望这些步骤和踩坑记录能帮你在自己的数据集上少走一段冤枉路。本文还有配套的精品资源点击获取