简介:面向电力线路巡检场景的目标检测数据集,聚焦输电线路螺栓销钉的缺失检测与状态判别。资源涵盖缺陷绝缘子、缺陷销钉、正常绝缘子、正常销钉共4个类别,总计2763个标注框,其中正常绝缘子1070框、正常销钉789框、缺陷销钉656框、缺陷绝缘子248框,所有样本均使用labelImg按矩形框规则完成人工标注,并同时提供Pascal VOC格式xml与YOLO格式txt两种标注,可直接用于主流目标检测框架训练与验证。压缩包共2000个文件,以VOC格式xml标注文件为主体(1999个),并附使用说明txt,整体约143.11MB,目录结构简洁,便于解压后按需读取。目前已有1306人学习下载,读者可基于该数据集进行类别均衡分析、数据增强、模型微调与结构误差对比,也可将其作为输电线路缺陷检测课题的标注基准,尤其适合需要干净标注样本的计算机视觉学习者、电力AI算法工程师及高校研究项目。数据集只提供准确且合理的标注,不附带训练权重或精度保证,可作为电力设备缺陷检测模型训练、验证及算法复现的可靠数据基础。
1. 输电线路螺栓销钉缺失为什么难检测:一套双格式数据集把预处理时间砍掉
输电线路螺栓和销钉缺失,是电力巡检缺陷识别里最难受的一类目标:视野大、目标小,一张4K杆塔照片里螺栓常常只占几十个像素,正常件和缺失件之间可能只差一个阴影。有了2504张、4类别的VOC+YOLO双格式数据集,这类问题的起步成本被大幅压缩——不用自己爬图、裁图、标框,解压之后就可以直接接YOLO系列训练,需要做对比实验时也能读回XML标注。它适合两类人:想系统跑通目标检测全流程的新手,以及正在做电力缺陷方案选型、需要快速拿到baseline的工程师。数据集本身的格式双轨,恰恰省掉了最容易被格式细节卡住的预处理环节。
2. 拆开2504张的7z压缩包:VOC与YOLO双格式的目录结构和标注差异
拿到压缩包先别急着解压训练,花十分钟把目录结构摸清楚,后面能少踩一半的坑。这个数据集同时给了VOC和YOLO两种格式,也就是说同一样本对应了两种标注文件,它们描述的是同一批目标,但存放方式和坐标表达完全不同。VOC格式源于PASCAL VOC竞赛的标注规范,几十年来被各类检测框架兼容;YOLO格式则是为深度学习训练量身定做的归一化表达。理解了两者的差别,你在切换框架时就不会被坐标转换的细节绊住。
2.1 VOC侧:JPEGImages、Annotations、ImageSets 三件套怎么对应
PASCAL VOC是目标检测领域沿用最久的数据集组织方式,这套命名规范在后来的很多数据集里都能看到。它的核心是三个目录:JPEGImages存放全部原始图片,常见是jpg,文件名与标注一一对应;Annotations存放同名xml文件,一个xml对应一张图;ImageSets/Main存放划分文件,train.txt、val.txt里每一行是一个不带后缀的图片文件名。
xml里每个目标用一个<object>块描述,包含类别名和bndbox四点的像素坐标,单位是像素,坐标系以图片左上角为原点。一张典型的XML大概长这样:
<annotation> <filename>IMG_0001.jpg</filename> <size> <width>3840</width> <height>2160</height> <depth>3</depth> </size> <object> <name>bolt_missing</name> <bndbox> <xmin>1024</xmin> <ymin>768</ymin> <xmax>1100</xmax> <ymax>820</ymax> </bndbox> </object> </annotation>这段XML的意思是:图IMG_0001.jpg里有一个目标,类别名为bolt_missing(示例类别名,实际以你解压后的标注为准),框的左上角在(1024, 768),右下角在(1100, 820)。从像素差值可以看出这个目标大约76×52像素,属于典型的小目标。如果一张图有多个螺栓或销钉,就会重复出现多个<object>块,Training时YOLO会逐个读取。
提示:VOC格式没有强制类别ID,类别全靠name字符串。因此在做两种格式互转时,name与数字ID的映射关系必须单独维护,这是后面最容易翻车的地方。
2.2 YOLO侧:images与labels目录、归一化坐标与类别ID
YOLO格式是另一种思路。它不把标注放在XML里,而是每张图对应一个同名txt文件,放在labels目录下。因为YOLO训练时直接把txt读成tensor,所以坐标必须归一化到0到1之间,并且用中心点坐标加宽高表示。
0 0.2766 0.3676 0.0198 0.0241 2 0.6052 0.1833 0.0224 0.0315每行表示一个目标,含义依次是:类别ID、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。这行数据对应的像素框是:中心在(0.2766×3840, 0.3676×2160)≈(1062, 794),宽约0.0198×3840≈76像素。和前面XML里框的位置能对得上,这就是两种格式可以互转的数学基础。
YOLO目录常见的组织方式是images和labels平级,下面再按train/val拆分子目录。有些打包方式是把train和val直接混合放,用独立的文件列表去区分。解压后先确认这套数据集是哪种结构,因为data.yaml里train和val路径要精确指向实际布局,路径写错训练会直接报找不到数据。
2.3 4个类别先看标注再定策略:类别名与数量分布怎么查
标题写的是4类别,但具体是哪4个名字,不要靠猜。进入解压后的目录,先把所有XML里的name统计出来:
grep -ho "<name>[^<]*</name>" VOC/Annotations/*.xml | sort | uniq -c-h是只输出内容不显示文件名,-o是只输出匹配部分,sort对结果排序,uniq -c统计每个类别出现的次数。接着再看YOLO侧的类别ID分布:
cat YOLO/labels/*.txt | awk '{print $1}' | sort | uniq -cawk取每行第一个字段(类别ID),排序后统计数量。这两条命令的结果一拼,就能确认ID与类名的对应关系是否如你所想。比如ID 0对应目标数量最多、ID 3最少,说明类别不均衡;如果某个ID没有对应类名,或某个类名没出现在YOLO文件里,说明格式转换过程存在遗漏。
统计结果会直接决定训练策略:类别分布极不均衡时,mosaic增强和少数类重复采样要重点开;目标普遍小于32×32时,输入分辨率不能锁死在640。我在每一个数据集上落地后都会先跑一遍这两条命令,再决定后面的参数怎么给,而不是直接照抄默认yaml。
3. 用命令行解压并校验数据集:文件数量、图片完整性与类别分布一次查清
7z压缩包的优势是压缩率高,但这个格式在跨系统解压时坑不少。建议先把解压、校验、统计三步固化成一套固定流程,以后拿到任何7z数据集都这么处理。以下命令在Linux或Git Bash环境执行,Windows图形界面在3.1节单独说明。
3.1 Windows与Linux下解压7z的正确姿势
Windows下最省事的做法是装7-Zip,右键压缩包选“解压到当前文件夹”,注意不要把“电力场景输电线路螺栓销钉缺失检测数据集VOC+YOLO格式2504张4类别”这一层目录丢掉。命令行方式在批量处理多个压缩包时更可控:
7z x "电力场景输电线路螺栓销钉缺失检测数据集VOC+YOLO格式2504张4类别.7z" -o"D:\power_dataset"参数x表示解压到指定目录,-o后面紧跟目标路径,路径不要加引号,这是7z命令的一个小坑。Linux下先确认装了p7zip:
sudo apt install p7zip-full 7z x "电力场景输电线路螺栓销钉缺失检测数据集VOC+YOLO格式2504张4类别.7z" -o/home/user/power_datasetLinux解压7z最容易遇到中文名乱码,压缩包顶层目录名是中文,如果系统locale不支持GBK,解压出来就是一串转义符。我一般建议Windows解压后整体拷到Linux训练机;如果必须在Linux解压,先执行7z l查看包内文件名是否正常,乱码就用convmv批量转码,不要手动一个个改名,那会把人逼疯。
3.2 校验文件数与图片完整性:find、wc与PIL检查
解压完第一件事是数文件。标题承诺2504张,那就验证是不是2504张,以及对应的xml和labels是否完整:
find power_dataset -path "*JPEGImages*.jpg" | wc -l find power_dataset -path "*Annotations*.xml" | wc -l find power_dataset -path "*labels*.txt" | wc -l三条命令分别输出图片、XML、YOLO标签的数量。正常情况三者都应等于2504。如果标签数少于图片数,说明部分图片没有标注目标,训练时要用程序自动过滤空标注;如果xml和txt数量不一致,说明两种格式不是同一批样本生成的,这种数据集在互转时会丢目标。
数完文件再验图片能不能正常读取,用Python加Pillow抽查全部图片:
from PIL import Image import glob bad = [] imgs = glob.glob("power_dataset/**/JPEGImages/*.jpg", recursive=True) for p in imgs: try: im = Image.open(p) im.load() except Exception as e: bad.append((p, str(e))) print("total:", len(imgs)) print("broken:", len(bad)) for item in bad[:10]: print(item)这段逻辑不复杂:遍历所有jpg,能打开并加载像素就认为正常,任何异常记入bad列表。跑完后total应该等于2504,broken为0。这一步虽然耗时,但能拦住最恶劣的情况——下载传输中断导致压缩包内文件损坏,训练到一半才报错,排查起来非常痛苦。
3.3 按类别统计标注数量:先知道四个类各有多少再定训练策略
图片完整只是第一步,更关键的是类别分布。用Python做一次全面的统计,既看每个类有多少个目标,也看每张图的平均目标数:
import xml.etree.ElementTree as ET import glob from collections import Counter name_counter = Counter() per_img_counter = Counter() for xml_path in glob.glob("power_dataset/VOC/Annotations/*.xml"): root = ET.parse(xml_path).getroot() names = [obj.find("name").text for obj in root.findall("object")] if names: name_counter.update(names) per_img_counter[len(names)] += 1 else: per_img_counter[0] += 1 print("per-class counts:", name_counter) print("images with k objects:", sorted(per_img_counter.items()))统计结果会暴露几类问题:某个类只有几十个实例,其他类有上千个,说明类别严重不均衡;大量图片的object数少于2,说明很多图是小样本场景,模型能学到的信息有限。知道这些之后,第4章的参数设置才有依据,而不是训练到一半才发现结果偏科。
注意:统计结果要保留下来。训练后对比每个类在验证集上的AP,如果某个类AP明显偏低,回看这里的counts,大概率就是样本量不够或者目标本身就小。
4. 用YOLOv8把数据集跑起来:数据划分、data.yaml与四个必调参数
格式校验通过,下一步就是把数据集喂给YOLOv8训练。这里不讨论改模型结构的事,只讲用现成预训练模型做baseline的标准路径:划分数据、写data.yaml、调参数、开训。这套流程在任何单卡GPU机器上都成立,显存小就换小模型,显存大就换大图。
4.1 把数据集拆成train/val:脚本与随机种子
如果压缩包里没有现成的划分文件,或者你想重新划分,就自己写一个划分脚本。划分原则是:测试集先不动,把2504张按85/15分成训练和验证;尽量按杆塔或线路来源做分层抽样,避免同一基塔的不同角度同时进训练和验证,那会高估模型的泛化能力。
import os, random, shutil random.seed(42) images = sorted(os.listdir("power_dataset/YOLO/images")) random.shuffle(images) split_idx = int(len(images) * 0.85) train_imgs, val_imgs = images[:split_idx], images[split_idx:] for split, imgs in [("train", train_imgs), ("val", val_imgs)]: os.makedirs(f"power_dataset/YOLO/images/{split}", exist_ok=True) os.makedirs(f"power_dataset/YOLO/labels/{split}", exist_ok=True) for img in imgs: stem = os.path.splitext(img)[0] shutil.move(f"power_dataset/YOLO/images/{img}", f"power_dataset/YOLO/images/{split}/{img}") shutil.move(f"power_dataset/YOLO/labels/{stem}.txt", f"power_dataset/YOLO/labels/{split}/{stem}.txt")random.seed(42)保证每次划分结果一致,这是复现实验的前提。shutil.move会把同名图片和标签一起移动,移动前要确认labels目录里存在对应txt,否则脚本会中断。如果你想保留一份完整数据不动,把shutil.move换成shutil.copy即可。
4.2 data.yaml怎么写:路径、类别名与类别ID
YOLOv8使用data.yaml作为数据集配置,用相对或绝对路径指向划分后的目录,并声明类别名。前面统计得到的类别名和ID映射,在这里是唯一可信依据:
path: /home/user/power_dataset/YOLO train: images/train val: images/val names: 0: bolt_normal 1: bolt_missing 2: pin_normal 3: pin_missingpath是根目录,train和val填相对于根目录的子路径。names的顺序不是随便写的,它必须和labels目录txt文件里的第一个字段完全一致。如果数据集原始YOLO标签里ID 0是bolt_missing,而你这里写成了bolt_normal,训练不会报错,但验证集mAP会异常低,且预测时类别全错。这也是为什么第2章强调先统计再写yaml。这里示例类别名来自我对螺栓销钉数据集的常见理解,实际以你的解压结果为准。
4.3 训练指令与关键参数:imgsz、batch、epochs、mosaic怎么给
基础训练命令一行就能跑:
yolo detect train data=data.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=16 device=0yolov8n是参数量最小的版本,先跑通流程,再换s或m。imgsz是训练时resize的输入尺寸,对螺栓这类小目标,640不够用,建议直接给1280,但显存占用会涨到640的四倍左右,8GB卡很可能装不下。batch按显存能塞多少给多少,16在多数情况是安全的起点;遇到CUDA out of memory,就把batch降到8或4,别硬扛。
mosaic增强默认开启,它对小目标检测帮助很大,因为拼接后的图里小目标密度更高。但mosaic会让模型在最后几轮训练时看到的数据分布和真实场景偏差较大,所以YOLOv8默认在后10轮自动关闭。命令行可以显式控制:
yolo detect train data=data.yaml model=yolov8m.pt epochs=200 imgsz=1280 batch=8 device=0 close_mosaic=10close_mosaic=10表示最后10个epoch关闭mosaic增强,让模型在接近真实分布的数据上微调。另外在这个场景下我会增强参数里加上随机旋转和亮度扰动:实际无人机拍摄角度和光照变化很大,而数据集图片多数来自同一批次拍摄,角度和光照分布偏窄,不加扰动的话泛化很容易出问题。直接把augment参数拉高比换模型更划算。
4.4 小目标检测的三个加成:切片思路、SAHI与NWD损失
输电线路螺栓在整幅巡检图里经常小于32×32像素,属于小目标。对这种目标,只靠调大imgsz效果有限,常规增强也解决不了目标太小根本看不清的本质问题。我常用的三个加成手段是:
第一,切片训练。把高分辨率原图切成若干640×640的patch,patch之间留20%重叠,目标被放大了再进模型,检测难度直接下降。第二,推理时用SAHI的思路把大图切块推理再合并结果,训练和推理一致,不容易出现“训练用整图、推理用patch”带来的性能落差。第三,把损失函数换成NWD(Normalized Wasserstein Distance)这类对小目标更友好的度量。NWD的核心思想是用正态分布近似小目标框,再计算两个分布之间的Wasserstein距离,对10像素以下的目标比IoU稳定不少。
这三件事不必同时全上,先跑通基线,再逐个加。切图会改变数据分布和标注坐标,需要配套脚本;NWD需要改模型代码,对新手不友好。我的建议是:先保证imgsz和mosaic正确,再看是否需要SAHI切片推理,最后才动loss函数,顺序反了会连问题出在哪都找不到。
5. 螺栓销钉数据集训练避坑:五条真实翻车记录与排查步骤
这一章来自实践里反复出现过的问题。每条按现象、原因、解决的顺序写,训练前先读一遍,能省下不少调试时间。这些坑不只在电力数据集上遇到,任何VOC+YOLO双格式数据集都会碰到类似问题。
5.1 解压后中文路径导致数据集加载失败
现象:data.yaml写好了,运行训练命令后报Dataset not found或者图片路径全部无效。排查发现命令里的路径是英文,但实际数据集目录却是一串乱码。
原因:压缩包内目录名是中文,Linux下用了默认locale解压,UTF-8和GBK互相转码失败,目录名变成\xe5\x8a\x9b等转义序列,系统识别不了。
解决:不要在Linux下直接解压中文名7z包。先在Windows用7-Zip解压,整体改名成纯英文目录,再拷贝到Linux。如果已经在Linux上解压了,用convmv转换文件名编码:
sudo apt install convmv convmv -f gbk -t utf8 --notest -r power_dataset5.2 类别ID对不上:VOC转YOLO时类别顺序错位
现象:训练loss正常下降,验证mAP也不难看,但可视化预测结果时,两个类别的框完全对调,检测结果张冠李戴。
原因:VOC格式里类别是字符串不携带ID,转YOLO时如果按字母序生成ID,就得到了和原labels文件不同的ID顺序,而训练本身不会报错,导致训练样本的类别标签整体错位。这是格式转换里最阴的坑,因为loss表现一切正常。
解决:转格式前先把两种格式的统计结果放一起对比。YOLO侧ID 0对应的类名必须和XML里出现的name一一对应。最可靠的办法是:先统计XML中的name集合,再统计labels中的ID集合,人工确认映射后再设置data.yaml的names顺序,不要依赖自动字母序。我第一回就是图省事,结果一个类别的框全串了。
5.3 标注框出界导致loss变成NaN
现象:训练到几百轮后loss突然变成nan,之后验证阶段mAP一直是0,怎么调学习率都没用。
原因:部分标注框的xmax或ymax大于图片实际宽高,转换到YOLO归一化坐标后cx、cy或w、h超过1。YOLO训练时框坐标超出预期范围,损失计算里的对数项或IoU计算出现异常。
解决:训练前跑一次边界检查脚本,把所有坐标clip到图片范围内。核心代码是对每个框做以下约束:
xmin = max(0, min(xmin, width - 1)) ymin = max(0, min(ymin, height - 1)) xmax = max(0, min(xmax, width - 1)) ymax = max(0, min(ymax, height - 1))这四行写在任何VOC转YOLO脚本里都不多余,它防的不只是这一份数据集的问题。clip之后再重新统计一遍坐标范围,确认所有值都在0到1之间再开训。
5.4 验证集mAP高但视频里误检多:角度与光照分布问题
现象:离线验证集mAP有0.9,但跑到现场视频上,误检框很多,正常销钉被频繁框成缺失。
原因:训练图片基本来自同一批次拍摄,角度、距离、光照分布集中,模型学到的是“和训练图相似的背景”和“特定角度下的目标形态”,而不是真正的“缺失特征”。迁移到现场不同光照和视角下立刻翻车,验证集mAP一点参考价值都没有。
解决:训练增强里加入随机旋转、HSV扰动、随机水平翻转,让模型在训练时见过更多角度变化。更有效的做法是在现场拍一轮不同角度和光照的图片补充训练集,或者用SAHI方式推理减少背景干扰。别迷信离线mAP,它只能说明模型在数据分布内拟合得好,不能说明现场能用。
5.5 类别不均衡:某个类只有几百个框怎么办
现象:统计时发现其中一个类别只有两三 百个目标,其他类上千,训练后该类别AP明显低于其他类。
原因:缺陷样本本身稀少是行业常态,网络在多数类上训练充分,少数类学不到有效特征。螺栓销钉的缺失样本尤其难收集,因为现场不会专门等你拍。
解决:对少数类做copy-paste增强,把它的目标从原图剪切后随机粘贴到其他图上并同步更新标注;也可以提高mosaic开启概率、设置class weight给少数类更高损失权重。先试copy-paste,实现简单且对mAP的提升最直接。如果数据集里缺失类样本太少,还可以考虑用GAN生成一部分,但那是另一个工程了。
6. 把双格式数据集用到部署:VOC转YOLO脚本、标注可视化与推理路数估算
数据集的价值不只在于训练一次模型。当你需要在不同框架间切换时,双格式的优势就体现出来了。这一章写三个我常用的收尾动作:格式互转、标注可视化、部署吞吐估算。这三个动作做完,这份数据集才算真正被吃透。
6.1 用Python把VOC批量转成YOLO:一个足够用的转换脚本
import glob, os import xml.etree.ElementTree as ET cls_map = {"bolt_normal": 0, "bolt_missing": 1, "pin_normal": 2, "pin_missing": 3} for xml_path in glob.glob("VOC/Annotations/*.xml"): root = ET.parse(xml_path).getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) txt_lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in cls_map: continue b = obj.find("bndbox") xmin = float(b.find("xmin").text) ymin = float(b.find("ymin").text) xmax = float(b.find("xmax").text) ymax = float(b.find("ymax").text) xmin = max(0, min(xmin, w-1)); xmax = max(0, min(xmax, w-1)) ymin = max(0, min(ymin, h-1)); ymax = max(0, min(ymax, h-1)) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h txt_lines.append(f"{cls_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") stem = os.path.splitext(os.path.basename(xml_path))[0] with open(f"labels/{stem}.txt", "w") as f: f.write("\n".join(txt_lines))cls_map就是那个最容易写错的地方,它必须和data.yaml里的names顺序完全一致。clip边界的那三行是防NaN的保险,少写三行,就可能把第5.3节的坑带进你的新项目。输出精度保留6位小数,足够YOLO训练用了。
6.2 标注可视化:把txt画回原图,一眼看出标注质量问题
转换之后务必抽查几张图,把框画回去看。脚本循环读取yolo格式的txt,在图上画出边框并打印类别名。坐标换算时注意从归一化坐标还原成像素坐标:
import cv2, os os.makedirs("check", exist_ok=True) for img_name in os.listdir("images/train"): stem = os.path.splitext(img_name)[0] img = cv2.imread(f"images/train/{img_name}") h, w = img.shape[:2] with open(f"labels/train/{stem}.txt") 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("check/" + img_name, img)这一步能看到三件事:框是否贴住目标、框是否出界、类别ID是否对得上。我每次拿到新数据集都画一遍,尤其当某个类别的mAP诡异偏低时,这步能快速定位是标注问题还是训练问题。
6.3 部署时用TensorRT如何估算路数
训练完的模型最终要跑在推理卡上,常见做法是导成TensorRT引擎,尤其是在T4这类推理卡上做视频流检测。最稳妥的路数估算方法不是查别人的benchmark,而是自己压测:把测试集里几百张图按真实业务的帧率喂给引擎,统计从输入到输出的单路平均延迟,再估算最大路数。
公式是:可用时间除以单路延迟,再乘0.7的安全系数,因为实际视频拉流、预处理、后处理都会抢占资源。比如单路延迟10ms时,理论吞吐约100路,乘0.7后按70路规划,预留余量应付突发的复杂画面。不要在原始数据集上做这个压测,数据分布太接近训练集会偏乐观。我现在拿到任何数据集,都会先解压统计可视化三步走完,再谈训练,这个习惯帮我省下的时间远多于那几次翻车浪费的时间。希望帮到你。
本文还有配套的精品资源,点击获取