螺栓销钉缺失检测:VOC+YOLO双格式数据集解压训练全指南
简介面向电力线路巡检场景的目标检测数据集聚焦输电线路螺栓销钉的缺失检测与状态判别。资源涵盖缺陷绝缘子、缺陷销钉、正常绝缘子、正常销钉共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类别的VOCYOLO双格式数据集这类问题的起步成本被大幅压缩——不用自己爬图、裁图、标框解压之后就可以直接接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 filenameIMG_0001.jpg/filename size width3840/width height2160/height depth3/depth /size object namebolt_missing/name bndbox xmin1024/xmin ymin768/ymin xmax1100/xmax ymax820/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目录、归一化坐标与类别IDYOLO格式是另一种思路。它不把标注放在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右键压缩包选“解压到当前文件夹”注意不要把“电力场景输电线路螺栓销钉缺失检测数据集VOCYOLO格式2504张4类别”这一层目录丢掉。命令行方式在批量处理多个压缩包时更可控7z x 电力场景输电线路螺栓销钉缺失检测数据集VOCYOLO格式2504张4类别.7z -oD:\power_dataset参数x表示解压到指定目录-o后面紧跟目标路径路径不要加引号这是7z命令的一个小坑。Linux下先确认装了p7zipsudo apt install p7zip-full 7z x 电力场景输电线路螺栓销钉缺失检测数据集VOCYOLO格式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, recursiveTrue) 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应该等于2504broken为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(fpower_dataset/YOLO/images/{split}, exist_okTrue) os.makedirs(fpower_dataset/YOLO/labels/{split}, exist_okTrue) for img in imgs: stem os.path.splitext(img)[0] shutil.move(fpower_dataset/YOLO/images/{img}, fpower_dataset/YOLO/images/{split}/{img}) shutil.move(fpower_dataset/YOLO/labels/{stem}.txt, fpower_dataset/YOLO/labels/{split}/{stem}.txt)random.seed(42)保证每次划分结果一致这是复现实验的前提。shutil.move会把同名图片和标签一起移动移动前要确认labels目录里存在对应txt否则脚本会中断。如果你想保留一份完整数据不动把shutil.move换成shutil.copy即可。4.2 data.yaml怎么写路径、类别名与类别IDYOLOv8使用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 datadata.yaml modelyolov8n.pt epochs150 imgsz640 batch16 device0yolov8n是参数量最小的版本先跑通流程再换s或m。imgsz是训练时resize的输入尺寸对螺栓这类小目标640不够用建议直接给1280但显存占用会涨到640的四倍左右8GB卡很可能装不下。batch按显存能塞多少给多少16在多数情况是安全的起点遇到CUDA out of memory就把batch降到8或4别硬扛。mosaic增强默认开启它对小目标检测帮助很大因为拼接后的图里小目标密度更高。但mosaic会让模型在最后几轮训练时看到的数据分布和真实场景偏差较大所以YOLOv8默认在后10轮自动关闭。命令行可以显式控制yolo detect train datadata.yaml modelyolov8m.pt epochs200 imgsz1280 batch8 device0 close_mosaic10close_mosaic10表示最后10个epoch关闭mosaic增强让模型在接近真实分布的数据上微调。另外在这个场景下我会增强参数里加上随机旋转和亮度扰动实际无人机拍摄角度和光照变化很大而数据集图片多数来自同一批次拍摄角度和光照分布偏窄不加扰动的话泛化很容易出问题。直接把augment参数拉高比换模型更划算。4.4 小目标检测的三个加成切片思路、SAHI与NWD损失输电线路螺栓在整幅巡检图里经常小于32×32像素属于小目标。对这种目标只靠调大imgsz效果有限常规增强也解决不了目标太小根本看不清的本质问题。我常用的三个加成手段是第一切片训练。把高分辨率原图切成若干640×640的patchpatch之间留20%重叠目标被放大了再进模型检测难度直接下降。第二推理时用SAHI的思路把大图切块推理再合并结果训练和推理一致不容易出现“训练用整图、推理用patch”带来的性能落差。第三把损失函数换成NWDNormalized Wasserstein Distance这类对小目标更友好的度量。NWD的核心思想是用正态分布近似小目标框再计算两个分布之间的Wasserstein距离对10像素以下的目标比IoU稳定不少。这三件事不必同时全上先跑通基线再逐个加。切图会改变数据分布和标注坐标需要配套脚本NWD需要改模型代码对新手不友好。我的建议是先保证imgsz和mosaic正确再看是否需要SAHI切片推理最后才动loss函数顺序反了会连问题出在哪都找不到。5. 螺栓销钉数据集训练避坑五条真实翻车记录与排查步骤这一章来自实践里反复出现过的问题。每条按现象、原因、解决的顺序写训练前先读一遍能省下不少调试时间。这些坑不只在电力数据集上遇到任何VOCYOLO双格式数据集都会碰到类似问题。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(flabels/{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_okTrue) for img_name in os.listdir(images/train): stem os.path.splitext(img_name)[0] img cv2.imread(fimages/train/{img_name}) h, w img.shape[:2] with open(flabels/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路规划预留余量应付突发的复杂画面。不要在原始数据集上做这个压测数据分布太接近训练集会偏乐观。我现在拿到任何数据集都会先解压统计可视化三步走完再谈训练这个习惯帮我省下的时间远多于那几次翻车浪费的时间。希望帮到你。本文还有配套的精品资源点击获取