ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

防震锤检测数据集:2721张双格式标注图与YOLO训练实战

防震锤检测数据集:2721张双格式标注图与YOLO训练实战 简介电力场景下的输电线防震锤检测数据集面向电力巡检视觉识别、无人机巡检图像处理及目标检测算法开发者提供包含DamperSpiral螺旋防震锤和DamperStockbridge斯托克布里奇防震锤两类目标的真实线路图像共2721张标注框总数8061个其中DamperStockbridge类框数为6980、DamperSpiral类框数为1081类别分布差异明显图像覆盖不同杆塔类型、拍摄角度和光照条件可有效支撑防震锤缺失或型号识别模型的训练与评估。压缩包内文件总数2000个以VOC格式xml标注文件为主同时包含YOLO格式txt标注文件与使用说明整体体积约207.94MB已有264人学习浏览。数据由labelImg按矩形框标注坐标规范兼容YOLO、Faster R-CNN等主流目标检测框架。该数据集特别适合作为电力场景目标检测算法的训练集或评测基准也可用于迁移学习与数据增强研究帮助开发者快速验证检测方案。1. 电力场景输电线防震锤检测数据集2721 张双格式标注图到底能干什么防震锤检测在输电线路巡检里属于那种看着简单、做起来全是坑的目标检测任务目标小、在导线两侧对称排列、背景里全是铁塔和绝缘子的干扰通用模型直接跑经常漏检。这套数据集给出 2721 张 jpg 和一一对应的 VOC xml、YOLO txt标注了 DamperStockbridge防振锤和 DamperSpiral螺旋防振器两个类别总框数 8061全程用 labelImg 画矩形框。它解决的问题很直接巡检图像里防震锤本体和附属螺旋线怎么被稳定检测出来从而为后续的缺失判定、缺陷分析提供前提。适合正在做电力设备缺陷识别、想用真实工业场景数据练手 YOLO 系列模型的从业者拿到手不用重新标注划分目录改好配置就能进训练。2. 双格式文件拆解VOC 的 xml 与 YOLO 的 txt 怎么一一对应2.1 三个 2721 是怎么对齐的双格式数据集最容易让人迷惑的不是算法是文件。这套数据 jpg、xml、txt 各 2721 个文件名主名完全一致firc_damper_1828.jpg、firc_damper_1828.xml、firc_damper_1828.txt 是同一张图的一组数据firc_damper_2532 同理。整个包是扁平结构所有文件混在同一层目录没有 images/labels 分目录压缩包里的「使用前必读.txt」也建议先打开看一眼里面一般写了标注工具、类别约定和格式说明。从文件形态能反推制作流程先用 labelImg 打开图片画矩形框默认保存为 VOC 格式的 xml全部标完之后再写脚本把 xml 批量转成 YOLO 训练用的 txt。所以 xml 是标注源头txt 是派生产物。这个关系决定了后续所有脚本的写法必须以文件主名stem为 key 去关联三套文件而不是拿扩展名过滤后按列表顺序 zip。很多人拿到双格式数据直接 glob 一堆 txt 和 jpg 配对结果把不同图像的标注配到同一组训练出来的模型框和内容对不上这就是第一层坑。2.2 VOC 的 xmlobject 块里存着类别名和绝对坐标随便打开一个 xml比如 firc_damper_1828.xml结构是标准 Pascal VOCannotation folderimages/folder filenamefirc_damper_1828.jpg/filename size width1920/width height1080/height depth3/depth /size object nameDamperStockbridge/name bndbox xmin412/xmin ymin301/ymin xmax685/xmax ymax438/ymax /bndbox /object /annotation上面这段是拿典型结构举例具体数字以你解压后实际 xml 里的 size 和 bndbox 为准。annotation 根节点下filename 对应 jpg 文件名size 里是原图宽高和通道数每个 object 块是一个标注目标name 是类别名bndbox 里四个数字是矩形框在原图上的绝对像素坐标xmin、ymin 是左上角xmax、ymax 是右下角。xml 这种文本格式的好处是可读、不怕误操作即使完全不懂标注工具打开文件看到 name 和 bndbox 也能人工核对。这套数据集里只有两种 nameDamperStockbridge 是常见的防振锤两端的锤头加中间钢绞线DamperSpiral 是缠绕在导线上的螺旋防振器。两者在巡检大图里体积都不大是典型的小目标检测场景前景和导线、间隔棒在灰度上高度接近这也是为什么通用模型在这种数据上容易翻车。2.3 YOLO 的 txt五个数字和归一化坐标换算txt 每一行是一个目标固定五个数字class x_center y_center width height。前两个是中心点后两个是宽高全部是相对原图的归一化比值范围 0 到 1。从 xml 到 txt 的换算公式固定x_center ((xmin xmax) / 2) / width y_center ((ymin ymax) / 2) / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height拿 2.2 节那个框举例假设原图 1920×1080xml 里 xmin412、ymin301、xmax685、ymax438算出来中心点约 (0.2857, 0.3421)宽高约 (0.1422, 0.1269)对应 txt 行就是1 0.2857 0.3421 0.1422 0.1269第一位 1 是类别序号按摘要里的类别列表顺序0 对应 DamperSpiral1 对应 DamperStockbridge。这个序号不是约定俗成它和训练时 data.yaml 的 names 列表顺序严格绑定任何一端改了另一端就必须同步改。另外注意摘要里专门写了不包含分割路径的 txt 文件意思是这份数据的 txt 里只有矩形框五个数字没有实例分割需要的多边形坐标点所以只能喂检测任务。想拿去做 yolo 实例分割或者 mmrotate 那种旋转框训练都要自己补标注再转格式水平框 txt 直接接不进去。2.4 双格式之间最常见的三个误区第一个误区是认为类别序号按字母序排。YOLO txt 里只有 0 和 1 两个数字它不携带类别名序号完全取决于转换脚本里 classes 列表的书写顺序和字母序没有任何关系。第二个误区是认为 xml 和 txt 的框数必须严格相等。转换脚本通常会对过小框做过滤或者对越界坐标做 clamp所以某些 txt 行数比 xml 少是正常现象反而说明转换脚本做了清洗不用慌。第三个误区是认为 VOC xml 能直接被 ultralytics YOLO 读取训练。官方接口虽然提供了一些转换能力但实际工程里绝大多数人还是先把 xml 转成 txt 再训少让框架多做一步就少一层出错的可能。3. 接进 YOLO 训练目录组织、train/val 划分与参数设置3.1 先建标准目录别在原目录上硬训拿到扁平结构的 2721 张图第一步不是调模型是把目录整理成 YOLO 期望的样子。ultralytics 系列训练接口读数据的默认逻辑是data.yaml 指定 train/val 的图像路径labels 路径自动做目录名替换images/train 对应 labels/train。所以目录结构统一整理成firc_damper/ ├── data.yaml ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/labels 下的 txt 要和 images 下的 jpg 一一对应不能多也不能少。多一个孤立 txt 训练不会报错但 val 指标会失真因为模型在一张不存在的图上被评估少一个 txt 时这张图会被静默跳过标注数不明不白地少了一截。这两种情况都不会在日志里显式警告属于最阴的坑。3.2 划分脚本三套文件必须同步走划分数据集时最典型的操作失误是只搬 jpg然后发现 labels 目录结构全乱。正确做法是按文件主名移动三件套图片进 imagesxml 和 txt 进 labels同一主名的三套文件不能拆散。给一个我常用的脚本import os import random import shutil src rD:\firc_damper train_ratio 0.7 random.seed(42) stems [] for f in os.listdir(src): if f.lower().endswith((.jpg, .jpeg, .png)): stems.append(os.path.splitext(f)[0]) random.shuffle(stems) split int(len(stems) * train_ratio) pairs [(train, stems[:split]), (val, stems[split:])] for split_name, split_stems in pairs: for folder in (images, labels): os.makedirs(os.path.join(src, folder, split_name), exist_okTrue) for stem in split_stems: for ext in (.jpg, .jpeg, .png, .xml, .txt): old os.path.join(src, stem ext) if not os.path.exists(old): continue new_dir os.path.join(src, images if ext in (.jpg, .jpeg, .png) else labels, split_name) shutil.move(old, os.path.join(new_dir, stem ext)) print(train:, len(pairs[0][1]), val:, len(pairs[1][1]))逻辑说明先扫所有图片文件拿主名列表按 7:3 随机分到 train 和 val然后对每个主名逐个移动 jpg/xml/txt。移动时按扩展名决定目标目录保证三套文件永远跟同一个主名走。参数说明train_ratio0.7 对 2721 张的数据量够用大约 1905 张训练、816 张验证random.seed(42) 把随机种子固定下来保证之后换模型、换参数对比时有相同的划分基准这也是我早期没固定 seed 反复对比不出结论的血泪经验。数据集本身没有官方划分文件所以自己定 seed 没问题但定了就别再动。3.3 data.yaml 与训练命令names 顺序绝对不能改data.yaml 是数据和模型之间的接口文件字段很少但每一个都致命path: D:/firc_damper train: images/train val: images/val names: 0: DamperSpiral 1: DamperStockbridgepath 建议写正斜杠Windows 下反斜杠在 YAML 解析时容易踩转义坑。names 里 0 和 1 的顺序必须和 txt 第一个数字严格一致调整顺序等于把所有标签整体错位模型训练不报错但结果全串这个问题在第 5 章专门展开。训练命令用 YOLO 系列的标准写法yolo detect train \ dataD:/firc_damper/data.yaml \ modelyolov8s.pt \ imgsz1280 \ epochs100 \ batch8 \ mosaic0.0参数说明imgsz1280 是本场景最值得调整的参数防震锤在原图里往往只有几十个像素640 输入下小目标直接被下采样抹掉提到 1280 能明显改善小框召回。显存不够就降到 960再不够回 640但要做好漏检率上升的心理准备。mosaic0.0 我建议先关掉mosaic 把四张图缩到一张输入里小目标变得更小对电力巡检场景不友好。batch8 在 1280 输入下大约需要 12G 显存3060Ti 以上基本能跑OOM 就减半。第一次训练跑起来后别急着看精度先看 results.png 里的 mAP50 曲线是否随 epoch 上升。正常情况 30 轮内 mAP50 会有明显抬头如果几十轮贴着 0 不动基本是标签路径没配对回查目录结构。4. 类别不平衡与框数差异1081 对 6980要不要做处理4.1 先算清楚这 1081 和 6980 意味着什么总框数 8061两个类别分布悬殊类别框数占比平均每张图框数DamperStockbridge698086.6%约 2.57DamperSpiral108113.4%约 0.40这里说的框数不是图片数一张图可能同时包含两个类别也可能只有 Stockbridge。比例接近 1:6.5也就是说损失函数里 Stockbridge 的梯度贡献远大于 Spiral模型会倾向把不确定的目标判成多数类DamperSpiral 的召回被牺牲。还有一层要确认这个不平衡是真实场景分布还是标注偏差。建议随机抽 50 张包含 DamperSpiral 的 xml人工核对框是否真的都在螺旋防振器上有没有把间隔棒、均压环这类外观相近的设备误标进来。两个类别在巡检图上本来就容易串标注员手滑的概率不低。数据说明里写着「只提供准确且合理标注不对模型精度作保证」意思是质量可以信但分布和调参得自己负责。4.2 三个处理方向与我的选择第一个方向是不处理直接训。如果业务场景只关心 Stockbridge 防振锤有没有脱落Spiral 只是顺带学习那默认配置就够最后只看 Stockbridge 的单类 AP 就行。第二个方向是调整采样。给 DamperSpiral 做整图级的 oversampling让它在每个 epoch 里出现次数接近多数类。注意是复制整张图而不是只复制框只复制框会破坏背景分布模型学到的是贴片而不是目标。在 ultralytics 里实现整图过采样可以把少数类样本单独放一个目录训练前用脚本按倍数复制或者直接拼一个重复后的数据集路径。第三个方向是调 loss 权重。ultralytics YOLO 默认没有暴露 per-class loss weight 参数想按框数反比加权得改源码。很多项目宣传的 class weight 实际只在分类 loss 上乘系数对框回归没有作用效果有限。我一般先从第一个方向跑 baseline如果 Spiral 的召回确实不够再上整图 oversampling。直接改 loss 成本最高放最后。4.3 评估视角别只盯着 mAP这个数据集报告指标时建议把 mAP0.5、mAP0.5:0.95 和 per-class AP 分开看。mAP 是类别平均会被多数的 Stockbridge 拉高。假设 Stockbridge AP 0.85、Spiral AP 0.45平均下来 0.65 看着还行实际上有 55% 的螺旋防振器没检测到。电力巡检场景有个特殊性漏检比误检代价大。防振锤脱落或螺旋防振器滑移巡检人员靠照片发现问题模型漏了这一段这一基塔就可能被略过。所以评估时我更关注 Spiral 的 recall 而不是整体 mAP。我通常会在 val 上单独统计 Spiral 的 TP、FP、FN算完 recall 再决定要不要动采样策略。后处理里把 score_threshold 从 0.25 往下降是提高 recall 最直接但不优雅的手段会带进来一批误检得自己平衡。5. 避坑指南VOC 转 YOLO 与训练中最容易翻车的 5 个踩坑点5.1 坐标越界loss 变 NaN 或 AP 恒为 0现象训练前期 loss 正常某轮突然变 NaN 后重启或者训练全程不报错但 val 的 AP 恒等于 0。原因xml 里 bndbox 的 xmax 或 ymax 超出原图宽高范围转换脚本没做 clamp归一化后出现大于 1 的坐标或负宽高。YOLO 在 loss 计算里对宽高做 log遇到非正数直接炸。解决转换时对 xmax、ymax clamp 到图像范围内并过滤宽或高小于 2 像素的框。如果你拿到的是已转好的 txt训练前跑一遍第 6 章的校验脚本一样能提前发现。clamp 的代码很短xmin max(0, int(xmin)) ymin max(0, int(ymin)) xmax min(w - 1, int(xmax)) ymax min(h - 1, int(ymax))5.2 三套文件不同步训练静默吃数据现象训练日志里 image 数量正常但某个 epoch 的 targets 数明显偏少或者 val 指标忽高忽低。原因扁平目录里有人手动删过部分 xmljpg 还留在原目录或者解压时文件名被截断xml 后缀后面多了一截。YOLO 读到图片但找不到对应 txt 时会跳过不报任何警告。解决以图片主名为唯一 key比对 xml 和 txt 是否存在这步做进校验脚本里。更重要的是养成习惯永远不要在原始数据目录里手动删文件删了就删了没有后悔药统一走脚本处理。5.3 类别序号错位训练不报错预测全串类现象loss 正常下降val AP 也正常但把模型导出 onnx 部署后画框DamperSpiral 的框被标成 DamperStockbridge。原因VOC 转 YOLO 时 classes 列表写的是 [DamperSpiral,DamperStockbridge]而 data.yaml 或者导出 onnx 时 names 写成了 [DamperStockbridge,DamperSpiral]。txt 里只存序号序号和类别名的映射全靠配置任何一端顺序变了结果就全串。解决训练前随机抽 5 张 val 图把 txt 坐标按当前 data.yaml 的 names 画框人眼过一遍。这一步比看任何指标都直接我每次换数据集都不跳。5.4 图像编码异常dataloader 卡死或读图 None现象训练中途某个 worker 一直占用或者 loss 不再下降报 ValueError: cannot reshape array。原因部分 jpg 实际是 PNG 改后缀或者文件在拷贝拷贝中断裂。OpenCV 的 imread 读到损坏文件返回 None模型拿 None 前向直接炸。解决训练前把全目录图片统一过一遍 cv2.imread损坏文件移到 bad 目录。这一步 30 分钟能跑完 2721 张但能省下后面排查半天训练中断的精力。5.5 小目标漏检imgsz640 是个陷阱现象val 上 DamperSpiral 的 AP 比 Stockbridge 低一大截漏检的基本是远距离小框。原因防震锤在 1920 宽的原图里可能只有 30×30 像素缩到 640 输入后剩 10 像素左右特征图上只剩一两个点模型根本学不到完整形状。这不是数据集的问题是输入分辨率的问题。解决首选 imgsz1280显存不允许就做 tiling把大图切成块按块检测再合并坐标回原图。还有一种玄学做法是关 mosaic、增强 hsv 让模型更稳但对这种数据帮助有限根子还是在分辨率上。6. 训练前先跑一遍全量校验把坏标签在 30 分钟内揪出来6.1 校验脚本主名对齐 坐标边界下面这个脚本是我拿到任何双格式数据集都会先跑的核心就两件事三套文件是否齐全、坐标是否越界。import os import cv2 import xml.etree.ElementTree as ET imgs_dir rD:\firc_damper\images\train labels_dir rD:\firc_damper\labels\train classes [DamperSpiral, DamperStockbridge] bad 0 for name in sorted(os.listdir(imgs_dir)): stem os.path.splitext(name)[0] img cv2.imread(os.path.join(imgs_dir, name)) if img is None: print(坏图:, name); bad 1; continue xml_path os.path.join(labels_dir, stem .xml) txt_path os.path.join(labels_dir, stem .txt) if not os.path.exists(xml_path): print(缺 xml:, stem); bad 1; continue tree ET.parse(xml_path) for obj in tree.iter(object): if obj.findtext(name) not in classes: print(未知类别:, stem, obj.findtext(name)); bad 1 if not os.path.exists(txt_path): print(缺 txt:, stem); bad 1; continue h, w img.shape[:2] for line in open(txt_path, encodingutf-8): parts line.strip().split() if len(parts) ! 5: print(txt 列数不对:, stem, line.strip()); bad 1; continue _, xc, yc, bw, bh parts xc, yc, bw, bh map(float, (xc, yc, bw, bh)) if not (0 xc 1 and 0 yc 1 and 0 bw 1 and 0 bh 1): print(坐标越界:, stem, line.strip()); bad 1 print(校验完成异常数:, bad)逻辑说明以图片目录为基准逐个主名检查 xml 和 txt 是否存在再分别校验 xml 里的类别名和 txt 里的坐标范围。任何一项异常都打印并累计。参数说明imgs_dir、labels_dir 换成你划分后的实际路径classes 顺序要和 data.yaml 的 names 一致否则会把正常数据误报成未知类别。6.2 可视化抽检画框回图人眼过一遍脚本校验只能查出硬错误类别串标、框偏了半个身位这类问题得靠人眼。随机抽 10 张 val 图把 txt 里每个框按类别画上去存成 jpg打开看一遍。看的时候重点确认两件事框是不是压在目标本体上以及 DamperSpiral 和 DamperStockbridge 有没有互相串。这一步花 10 分钟但能避免训练三天后才发现标签系统性错误。6.3 训练后看什么训练结束后别只收 mAP 一个数。打开 val 的 per-class 结果表分别记下两个类别的 AP 和 recall。如果 Spiral 的 recall 明显低于 Stockbridge按第 4 章的思路处理类别不平衡。另外把模型导出 onnx在没参与训练的几张野外巡线图上跑一遍推理确认输出框和类别名没有错位再谈部署到巡检设备上。从那以后我每次拿到标注数据集无论对方说质量多高都会先花 30 分钟强制跑一遍上面的校验和可视化再碰训练命令。这套防震锤数据标注整体靠谱但双格式数据集经过多轮转换和拷贝出问题的概率从来都不低。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进