简介针对输电线路巡检中螺栓销钉缺失检测模型训练的数据需求这套电力场景目标检测数据集覆盖缺陷绝缘子、缺陷销钉、正常绝缘子、正常销钉四类典型目标共2763个标注框并同时提供Pascal VOC与YOLO两种标注格式可直接接入YOLO系列、Faster R-CNN等常见检测框架的训练与评估免去自行转换格式的额外工作。压缩包共2000个文件以XML标注文件为主并附使用说明TXT文件整体约143.11MB体积适中、按需解压即可使用。标注由LabelImg手工绘制矩形框完成类别划分明确适合电力视觉入门学习、算法复现、模型精度对比与数据增强等场景。数据集只提供准确且合理的人工标注不附带模型权重便于使用者自行完成训练与评估。目前已有1306人学习下载可作为输电线路缺陷检测任务的数据补充、基准评测与算法验证参考。1. 输电线路螺栓销钉缺失检测数据集VOCYOLO 双格式先搞清楚它到底给你省了什么事做电力巡检的都知道输电线路上的螺栓和销钉是最容易出问题的小目标体积小、背景乱、缺失特征又不明显人工复查一整天也找不到几张有效缺陷图。这份数据集把问题收敛得非常具体2504张实拍图4个类别同时给出VOC和YOLO两种格式标注外面套一层7z压缩包解压出来就是标准的目标检测训练目录。它解决的问题不是“有没有图”而是“拿什么标注数据直接喂给YOLO”。适合刚入行做电力缺陷检测的算法工程师也适合想验证小目标检测思路的老手。但我的建议很直接拿到压缩包别急着拖进训练脚本先花十分钟检查目录和标注对应关系这十会分钟能帮你省掉后面至少半天排查训练异常的功夫。2. 先拆开 7z解压命令、目录结构和图片标注对齐检查从网盘或内网下载这种.7z数据集第一件事不是解压到桌面看图片而是先列一下压缩包内容。这一步能发现很多隐藏问题路径是否带中文、是否有多层嵌套目录、标注文件和图片是否成对出现。常见做法是 Windows 上用 Bandizip 或 7-ZipLinux 上直接用 p7zip 命令行。2.1 Windows 和 Linux 下解压 7z 的常用方式Windows 下我没有太多要讲的装完 7-Zip 后右键解压就行。但注意一点右键菜单里的“解压到当前文件夹”和“解压到 xxx\”效果不同前者会把压缩包内所有文件直接倒出来容易跟原有文件混在一起后者会创建一个独立目录我一般选后者保持目录干净。Linux 服务器上更常用命令行先看压缩包里有什么7z l 电力场景输电线路螺栓销钉缺失检测数据集VOCYOLO格式2504张4类别.7z7z l只列出文件清单不真正解压。输出里会显示每个文件的完整路径、大小和压缩率。我会重点看文件路径前缀是否一致如果压缩包里的文件散落在一堆不同层级的目录里解压后要花不少时间整理。确认没问题后执行解压7z x 电力场景输电线路螺栓销钉缺失检测数据集VOCYOLO格式2504张4类别.7z -o./power_data-o是指定输出目录注意-o和目录之间不能有空格。7z x会保留压缩包内的完整目录结构而7z e会把所有文件平铺到同一层文件名重复时还会互相覆盖。对于带标注的数据集我强烈建议用x而不是e。如果系统提示7z: command not found先装 p7zipsudo apt update sudo apt install p7zip-full -y有些精简镜像只有p7zip没有p7zip-full后者才支持 7z 格式别装错了。2.2 解压后先做一次图片和标注的对齐检查解压完成之后不要直接打开 data.yaml 开始训练。我先跟你说一个真实情况很多开源数据集标注都缺文件有些图没标签有些标签对不上图训练时程序不一定报错但评测结果会莫名其妙地差。常见目录结构一般是这样power_data/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── data.yaml └── classes.txt有些数据集会把 VOC 的 XML 放在Annotations/把图片放在JPEGImages/。你这份既然写了 VOCYOLO 双格式很可能解压后同时存在两个目录。重点检查的只有一个YOLO 的 txt 标签是否和图片一一对应。我用一个简单脚本做对齐检查import os from glob import glob image_dir power_data/images label_dir power_data/labels images glob(os.path.join(image_dir, *.jpg)) glob(os.path.join(image_dir, *.png)) labels glob(os.path.join(label_dir, *.txt)) image_names {os.path.splitext(os.path.basename(p))[0] for p in images} label_names {os.path.splitext(os.path.basename(p))[0] for p in labels} print(图片数量:, len(image_names)) print(标签数量:, len(label_names)) print(缺标签的图片:, image_names - label_names) print(缺图片的标签:, label_names - image_names)这段脚本把图片文件名和标签文件名分别取出来做差集。如果第一行差集里出现文件名就是有图没标签第二行差集里有东西就是有标签没图。两类文件都可能导致训练时数据加载异常或者 dataloader 报错后崩掉。我用这个脚本检查过不少“号称整理好”的数据集几乎每次都能捞出问题等到训练完看 mAP 才发现就太迟了。如果图片数量是 2504但标签只有 2400 多个优先看缺的是哪几张用肉眼对比文件名找规律。常见原因是标注时某些图片模糊或太小被丢弃但图片文件还留在原目录里。处理方法有两种要么删掉这些无效图片要么重新补标。补标成本高删图更实际只要删除的量不影响类别平衡就行。3. 解析 VOC 的 XML 结构和 YOLO 标签写一个自己的转换脚本这份数据集同时给 VOC 和 YOLO 格式等于给你留了后路用 MMDetection 或 Detectron2 就吃 XML用 YOLO 系列就吃 txt。但实际训练中你仍然需要理解两种格式的差异因为经常要改类别、合并数据集、或者把别人给的 Pascal VOC 数据转成 YOLO。3.1 先看 XML 里到底写了什么VOC 格式最早来自 Pascal VOC 比赛核心是每个图片对应一个 XML 文件。打开一个标注文件后结构是这样的annotation filenameIMG_1024.jpg/filename size width1920/width height1080/height depth3/depth /size object namebolt/name bndbox xmin740/xmin ymin512/ymin xmax808/xmax ymax580/ymax /bndbox /object /annotationfilename是原始图片名size是图像宽高object里每出现一次就是一个目标框bndbox是左下角和右下角坐标单位是像素。YOLO 的 txt 文件比这简洁得多每行对应一个目标0 0.402 0.505 0.035 0.063第一个数字是类别编号后面四个数分别是归一化后的中心点 x、中心点 y、框宽 w、框高 h。归一化就是把像素坐标除以图片宽度和高度所以不管原图是 1080P 还是 4K标注值都落在 0 到 1 之间。3.2 用 Python 写一个 VOC 转 YOLO 的脚本数据集里已经给了 YOLO 标签但你仍然会碰到需要自己转换的场景最简单的一种是你要把这份数据和另一个 VOC 格式数据集合并那个数据集只有 XML没有 txt。我一般会写一个零依赖的小脚本用标准库就能跑import os import glob import xml.etree.ElementTree as ET # 类别映射表名字必须和 data.yaml 里写的一致 class_mapping { bolt: 0, pin: 1, bolt_missing: 2, pin_missing: 3, } def convert_voc_to_yolo(xml_dir, out_dir): if not os.path.exists(out_dir): os.makedirs(out_dir) for xml_path in glob.glob(os.path.join(xml_dir, *.xml)): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text image_name os.path.splitext(filename)[0] out_txt os.path.join(out_dir, image_name .txt) size root.find(size) width int(size.find(width).text) height int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_mapping: print(f[skip] {filename} 里出现未映射类: {name}) continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界防御防止标注越界 xmin max(0, xmin) ymin max(0, ymin) xmax min(width, xmax) ymax min(height, ymax) w xmax - xmin h ymax - ymin if w 0 or h 0: print(f[skip] {filename} 里存在非法框: {name}) continue # YOLO 格式中心点 宽高全部归一化 cx (xmin xmax) / 2.0 / width cy (ymin ymax) / 2.0 / height bw w / width bh h / height lines.append(f{class_mapping[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: convert_voc_to_yolo(VOC/Annotations, YOLO/labels)这段代码核心逻辑很简单解析 XML拿size做分母把绝对坐标换算成相对坐标。注意我在写坐标时做了max(0, ...)和min(width, ...)的裁剪这是从标注工具带过来的血泪经验。很多开源数据集里会出现 target 框超出图片边界的负值或大于 width/height 的坐标不裁剪会导致 YOLO 训练时 loss 异常甚至直接出 NaN。class_mapping是我常用的四类示例具体类别名一定要对着你解压出来的data.yaml改。如果 XML 里出现class_mapping里没有的类名脚本会打印[skip]不会崩。这个行为是故意的目的是让你发现漏标或标注不一致的情况。你跑完脚本后统计一下 skipped 数量如果超过几十个文件建议先回头处理数据而不是继续训练。3.3 转换脚本的几个关键参数别改错第一个参数是xml_dir必须指向存放所有 XML 的根目录不能传子目录。第二个参数是out_dir脚本会自己创建文件夹。还有class_mapping的顺序YOLO 类别编号是 0 开始的编号一旦错位训练出来的模型把所有类别全识别成第一个类别而且指标看着还很正常非常难排查。另外这个脚本没有处理遮挡和难例。VOC 标注里经常有difficult1/difficult字段默认忽略它。如果你的数据集有这类标记并且想过滤难样本在循环里加一句判断difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue这是把“难样本”当训练噪声排除掉的做法。但注意如果测试集里仍然包含难样本训练时不看它验证时又拿它算指标mAP 会被拉低。这个度要根据实际场景调整我的习惯是先不过滤看榜再决定。4. 训 YOLO 前避坑类别表、数据划分和标签清洗的 5 个实际案例数据格式转完之后很多人急着开训结果在训练脚本里连续翻车。下面这几条是我在类似数据集上真实踩过的坑每条按现象、原因、解决三步说清楚。4.1 换台机器训练 loss 曲线剧烈震荡中文路径和反斜杠惹的祸现象同一份数据集在 Windows 上训练很正常放到 Linux 服务器上 loss 不走或者曲线跟心电图一样来回跳。原因解压路径里带了中文目录名或者代码里用了字符串拼接把图片路径写死成E:\datasets\...。Windows 的 OpenCV 能容忍部分中文路径Linux 上一旦传入中文路径imread 会静默返回空矩阵训练程序却不一定立刻退出而是不断重复采样同一张坏图。解决把所有数据集放到英文路径下训练脚本不做路径拼接统一用os.path.join。我的习惯是解压后立刻改成mv 电力场景输电线路螺栓销钉缺失检测数据集VOCYOLO格式2504张4类别.7z power_data压缩包中文名只在归档阶段有意义进入训练阶段越早改成纯英文越省事。另外代码里如果出现path.replace(\\, /)这种写法趁早删掉直接在根目录处理干净。4.2 训练能跑但 mAP 全是 0classes.txt 和 data.yaml 的类别顺序不一致现象loss 下降也能检测出框但每个类别的 AP 全是 0输出结果张冠李戴。原因数据集的 txt 标签里写的是训练前定好的编号但data.yaml里的names列表顺序跟classes.txt不一样。YOLO 不检查语义只认编号。比如class_mapping里把螺栓设成 0配置文件里第一个名字却是销钉模型学到的特征没有错名字对应错了。解决把这个四类别数据集打开先读classes.txt再核对data.yamlnames: 0: bolt 1: pin 2: bolt_missing 3: pin_missing如果顺序不一致以classes.txt为准改data.yaml。还有一个肉眼看不出来的问题txt 标签里类别编号虽然是 0 到 3但某张图里出现了编号 4这会让 dataloader 在计算 loss 时越界。用一个小脚本扫一遍标签内容awk {print $1} power_data/labels/train/*.txt | sort | uniq -c如果输出出现了不是 0 到 3 的数字说明有脏标签找出对应文件重新标注或者删掉该帧。4.3 验证集损失一直降不下去空标签文件占了一多半现象训练过程很顺但每当跑验证时验证 loss 总比训练 loss 高一截而且不降。原因图片和标签数量对上了但大量 txt 文件本身就是空的0 字节。很多标注工具在导出时会为没有目标的图片生成空文件训练集里混入空标签还能接受验证集里如果空样本太多计算 loss 时无目标分支占比过高模型会觉得“什么都不检测”是相对安全的结果真实缺陷全被漏掉。解决统计空文件比例find power_data/labels -name *.txt -empty | wc -l如果空文件占比超过 5%就要评估这些图片是不是真的没有缺陷。在电力场景里正常塔位本来就不该有缺陷空标签是合理的但验证集不能全是空标签。我会把正常图片和缺陷图片分开验证时按缺陷样本至少占一半的方式重新切分。把这个比例写进数据切分脚本里以后每次都能自动规避这个问题。4.4 同一张图被拆到训练集和验证集数据集划分时按文件名做哈希泄漏现象训练 mAP 刷到 0.98验证 mAP 只有 0.6且重复跑几次指标波动很大。原因原始数据里很多螺栓和销钉是在同一张塔架图上截出来的小图文件名前缀相同。如果你用random_split按图片文件全体随机划分同一塔架的不同裁剪会被分到训练集和验证集两边模型其实已经“见过”验证目标了。解决用文件名前缀做分组划分保证同一个塔架的所有图只进一边。一个粗糙但有效的实现import random from glob import glob images glob(power_data/images/*.jpg) random.shuffle(images) groups {} for img in images: prefix img.split(/)[-1].split(_)[0] groups.setdefault(prefix, []).append(img) val_prefix set(random.sample(groups.keys(), int(len(groups) * 0.2))) train_files [img for prefix, imgs in groups.items() for img in imgs if prefix not in val_prefix] val_files [img for prefix, imgs in groups.items() for img in imgs if prefix in val_prefix]这里prefix img.split(/)[-1].split(_)[0]是按命名规律取的如果你的文件名没有这种规律就换成正则提取塔架编号。这个脚本的代价是可能造成类别分布偏移但比起泄漏我宁愿让验证集保持“陌生塔架”的状态那才是真实部署时面对的场景。4.5 销钉太小640 分辨率下几乎看不清直接整图训练容易漏检现象训练了 100 轮验证集 mAP 50 还能看但到了实际巡检视频里远距离的销钉缺失根本抓不到。原因光电吊舱拍出来一张图可能几千像素宽销钉只占几十个像素直接缩放到 640 喂给 YOLO小目标尺寸只有 3 到 5 像素模型很难学到有效特征。VOC 转 YOLO 后框的宽高已经变成相对于整图的归一化比例看起来数值很小这是正常的但训练策略必须调整。解决不要只依赖缩放先统计标注框的像素面积分布import os from glob import glob for txt in glob(power_data/labels/train/*.txt): with open(txt) as f: for line in f: parts line.strip().split() w float(parts[3]) h float(parts[4]) print(f{w * 1920:.lf} {h * 1080:.lf})这里我用 1920x1080 做参考因为原图大概率是这个分辨率。如果大部分框都小于 40 像素说明小目标居多。常见做法是先把原图切成 640 或 960 的 patch再做检测尤其适合输电线这种长条型图像。切图时要注意目标不能刚好被切到边界外我一般允许两 patch 之间有 5% 重叠并在生成标注时过滤掉被边界截断一半以上的框。5. 用这份数据集跑通 YOLOdata.yaml 和验证参数的落地技巧5.1 data.yaml 按这份模板改path: ./power_data train: images/train val: images/val nc: 4 names: 0: bolt 1: pin 2: bolt_missing 3: pin_missingnames不是一个摆设它会直接写进训练后的模型文件里。推理阶段你拿到类别名才是最重要的所以这里一定不能顺手写错顺序。5.2 先跑 10 个 epoch 验证流程别一上来就是 300 轮我的习惯是用小学习率快速跑一轮流程python train.py --data data.yaml --epochs 10 --batch 16 --imgsz 640这 10 轮只确认一件事数据加载不报错、loss 能下降、验证集有输出。跑完看一眼results.png如果 AP 曲线全是平的回去检查上一章的标签清洗不要继续加大 epoch。5.3 验证时刻意挑几张“脏图”看效果训练完成后不要只看 mAP 数字。我会从原图里挑背景最乱、目标最多的两张单独画预测框。如果这类图都能准说明模型学到了真实特征如果只是 mAP 高但检测框总往横担的边缘靠大概率还是对纹理过拟合。从那以后我每次拿到新的数据集都强制先走一遍解压、对齐、查空标签、查类别顺序这四步再进训练。这套流程帮我挡住了大多数翻车现场。希望帮到你。本文还有配套的精品资源点击获取