简介这份杆塔塔材锈损检测航拍图像数据集面向电力设施智能巡检方向的算法研究者与工程师用于训练和评估铁塔塔材锈损目标检测模型。数据集包含1700余张航拍图像并采用高斯噪声扩充模拟光照不均、传感器缺陷等真实干扰以提升模型鲁棒性标注采用VOC格式记录锈损区域的类别与边界框坐标可直接对接YOLO、Faster R-CNN、Mask R-CNN等主流检测网络。压缩包共2000个文件以1968个jpg图像与1968个xml标注文件为主图像与标签一一对应包体约22.03MB便于快速下载与本地训练。目前已有469人学习下载。借助该数据集读者可完成从数据加载、噪声增强到模型训练与锈损定位的完整流程并针对不同锈损程度和航拍视角开展算法对比与优化为输电线路巡检提供可复用的实验基础。1. 杆塔塔材锈损检测航拍图像1700 张带 VOC 标签的数据集到底能跑出什么输电线路巡检这几年最大的变化是无人机航拍从拍回来给人看变成了拍回来给模型看。杆塔塔材锈损检测就是其中最典型的场景塔材长期暴露在户外镀锌层破损后开始锈蚀早期是点状锈斑后期是成片锈层严重时影响结构强度。人工看图效率低、主观性强于是大家自然想到用目标检测模型自动框出锈损区域。但真正动手时第一个卡住的地方往往不是模型而是数据——公开的杆塔锈损航拍数据集极少自己标注成本又高。这个标题给出的方案是1700 多张航拍图像用高斯噪声做扩充配 VOC 格式标签。它解决的是从零开始攒数据这个最耗时的环节适合做电力巡检算法、想快速验证锈损检测可行性、或者需要一份带标注数据做课程设计/论文实验的人。下面我按自己实际跑这类数据的顺序把选型、转换、训练、踩坑讲清楚。2. 航拍锈损数据为什么值得用高斯噪声扩充先想清楚再动手2.1 航拍图像的三个天然难点杆塔航拍图和常规自然图像差别很大直接套 COCO 预训练模型往往效果打折。第一个难点是目标尺度极端无人机为了安全距离通常离塔材几米到十几米锈损区域在 4000×3000 的原图里可能只占几十个像素缩放到 640 输入后几乎消失。第二个难点是背景干扰强塔材本身是银灰色镀锌件天空、植被、其他金属构件都会和锈色接近模型容易把阴影、泥土、甚至夕阳反光当成锈。第三个难点是锈损形态不统一早期锈是散点中期是条带晚期是块状同一类锈损在像素层面差异巨大。这三点决定了如果数据量不够、场景不够杂模型学到的就是这张图里的锈长这样换一个塔型、换一个光照就翻车。1700 张这个量级说多不多说少也不少——够训一个轻量检测器但不够覆盖所有工况所以扩充是必要的。2.2 高斯噪声扩充解决的是什么问题很多人一听高斯噪声扩充以为是随便加噪凑数其实它的作用很具体航拍图像在阴天、高 ISO、远距离传输时本身就有传感器噪声给干净图像叠加可控的高斯噪声相当于模拟不同成像质量下的锈损外观让模型对噪声鲁棒而不是只认清晰图。它和翻转、裁剪这类几何增强是互补的几何增强改位置和尺度高斯噪声改像素质量。需要说清楚的是高斯噪声扩充不能替代真实场景多样性。它能让模型在低质量图上不掉点但没法凭空造出没见过的塔型。所以正确用法是以 1700 张真实标注为主体高斯噪声作为其中一种增强手段比例控制在合理范围而不是把数据集翻几倍全靠加噪。2.3 扩充前必须先做的两件事动手加噪之前有两步不能省。第一是统计锈损框的尺寸分布确认小目标占比。如果大量框小于 32×32 像素那训练时的输入分辨率、anchor 设置都要相应调整光加噪没用。第二是检查标签质量VOC 标签是 XML容易出现框越界、宽高为 0、类别名拼写不一致等问题这些脏数据加噪后会放大。import os import xml.etree.ElementTree as ET from collections import Counter # 统计 VOC 标签里锈损框的宽高分布判断小目标占比 def stat_boxes(xml_dir): sizes [] cls_counter Counter() for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() cls_counter[name] 1 bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) sizes.append((w, h)) small sum(1 for w, h in sizes if w 32 or h 32) print(类别分布:, cls_counter) print(总框数:, len(sizes), 小目标数:, small, 小目标占比: %.2f%% % (100 * small / max(len(sizes), 1))) stat_boxes(./Annotations)这段代码遍历 VOC 的 Annotations 目录解析每个 object 的类别名和 bbox 宽高最后输出类别分布和小目标占比。参数上xml_dir指向你的标注目录判断小目标的阈值 32 是常用经验值对应 COCO 的 small 定义。跑完如果发现小目标占比超过 40%就要在训练配置里提高输入分辨率或调整 anchor而不是急着加噪。提示先跑统计再决定增强策略比一上来就加噪靠谱得多。标签里的类别名如果有 rust、Rust、xiuSun 混用先统一否则训练时会被当成多个类。3. 从 VOC 到可训练格式转换脚本与四个边界坑3.1 为什么很多人最后还是转成 YOLO 格式VOC 是标注交换的通用格式但真正训练时YOLO 系列因为部署方便、速度快在电力巡检边缘设备上很常见。VOC 的 XML 存的是绝对坐标xmin/ymin/xmax/ymaxYOLO 的 txt 存的是归一化后的class cx cy w h。转换本身不难难的是边界情况处理。下面这份脚本我用了很多次重点在异常处理。import os import xml.etree.ElementTree as ET # VOC - YOLO txt 转换处理越界、零宽高、类别映射 CLASS_MAP {rust: 0} # 按你的实际类别名修改 def voc_to_yolo(xml_dir, out_dir, img_w, img_h): os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() # 优先读 XML 里记录的真实尺寸没有再用传入值 size root.find(size) w_img int(size.find(width).text) if size is not None else img_w h_img int(size.find(height).text) if size is not None else img_h lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue # 未定义类别直接跳过避免脏标签 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 裁剪到图像范围内防止越界框 xmin, xmax max(0, xmin), min(w_img, xmax) ymin, ymax max(0, ymin), min(h_img, ymax) bw, bh xmax - xmin, ymax - ymin if bw 1 or bh 1: continue # 零宽高或退化框丢弃 cx (xmin xmax) / 2 / w_img cy (ymin ymax) / 2 / h_img nw, nh bw / w_img, bh / h_img lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) out_name os.path.splitext(f)[0] .txt with open(os.path.join(out_dir, out_name), w) as fp: fp.write(\n.join(lines)) voc_to_yolo(./Annotations, ./labels, 1920, 1080)逻辑上分四步读 XML、取真实图像尺寸、逐框裁剪并归一化、写 txt。参数说明CLASS_MAP必须和你的标注类别严格对应多类时按需扩展img_w/img_h是兜底值只有当 XML 里没有size节点时才用归一化保留 6 位小数足够再多没意义。四个边界坑分别是框越界裁剪、零宽高丢弃、类别名不在映射表跳过、XML 缺 size 节点用兜底值。这四个不处理训练时轻则 loss 异常重则直接报错。3.2 高斯噪声扩充脚本怎么写才不破坏标签关键点几何增强会改框像素级噪声不改框。高斯噪声只动像素值bbox 坐标不变所以可以在转完 YOLO 格式后单独做标签直接复制。下面脚本对图像加高斯噪声并保存标签同步拷贝。import cv2 import numpy as np import os import shutil # 对图像加高斯噪声标签不变直接复制 def add_gaussian_noise(img_dir, label_dir, out_img_dir, out_label_dir, mean0, sigma15, ratio0.3): os.makedirs(out_img_dir, exist_okTrue) os.makedirs(out_label_dir, exist_okTrue) files [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .png))] for i, f in enumerate(files): img cv2.imread(os.path.join(img_dir, f)) base os.path.splitext(f)[0] # 原图与标签先落盘 cv2.imwrite(os.path.join(out_img_dir, f), img) src_label os.path.join(label_dir, base .txt) if os.path.exists(src_label): shutil.copy(src_label, os.path.join(out_label_dir, base .txt)) # 按比例生成加噪副本 if i % int(1 / ratio) 0: noise np.random.normal(mean, sigma, img.shape).astype(np.float32) noisy np.clip(img.astype(np.float32) noise, 0, 255).astype(np.uint8) cv2.imwrite(os.path.join(out_img_dir, base _gn.jpg), noisy) if os.path.exists(src_label): shutil.copy(src_label, os.path.join(out_label_dir, base _gn.txt)) add_gaussian_noise(./images, ./labels, ./aug_images, ./aug_labels, mean0, sigma15, ratio0.3)参数上sigma是噪声标准差控制强度sigma10 接近轻微传感器噪声sigma25 已经明显可见超过 30 容易把细小锈斑淹没反而害了模型。ratio0.3表示约 30% 的图生成加噪副本这个比例我一般控制在 0.2~0.4太高会让训练集分布偏向噪声图。mean0是零均值高斯符合大多数传感器噪声假设。注意np.clip必须做否则像素溢出会变成噪点块。注意加噪副本的标签是直接复制的前提是没做任何几何变换。如果你后面还要加旋转、缩放必须同步用工具改框别手动复制标签这是最常见的翻车点。3.3 数据集目录怎么组织转换和扩充完成后目录结构建议固定成下面这样方便后续换模型框架时直接复用目录内容说明images/train训练原图 加噪图jpg/png 混合命名带 _gn 区分images/val验证原图只用真实图不加噪labels/train对应 YOLO txt与图像同名labels/val对应 YOLO txt与图像同名Annotations原始 VOC XML保留便于回溯data.yaml类别与路径配置训练入口验证集一定要用未加噪的真实图否则你评估的是模型在噪声上的表现不是真实巡检表现。这一点很多人图省事混在一起最后指标虚高上线就露馅。4. 训练杆塔锈损检测模型的参数怎么设从输入尺寸到 anchor4.1 输入分辨率与 batch 的取舍锈损小目标多输入分辨率直接决定小目标能不能被看到。640 是通用默认但对航拍锈损偏小我一般用 960 或 1024。代价是显存和速度1024 输入比 640 显存占用大约 2.5 倍。如果只有单张 8G 卡1024 下 batch 只能给到 4~8这时用梯度累积补回来。经验是先保证小目标可见再想办法压速度反过来做往往白训。4.2 anchor 与正样本分配YOLO 系列默认 anchor 是基于 COCO 聚类的和锈损框分布不匹配。正确做法是用 3.1 统计出的框尺寸重新聚类 anchor。如果用的是 anchor-free 的版本就重点调正样本分配阈值让小框也能被匹配到正样本。这一步不做模型对小锈斑的召回会明显偏低表现为大块锈检得出点状锈全漏。4.3 一份可复现的训练配置# data.yaml path: ./dataset train: images/train val: images/val nc: 1 names: [rust]# 训练命令以常见 YOLO 框架为例 python train.py \ --data data.yaml \ --imgsz 1024 \ --batch 8 \ --epochs 150 \ --lr0 0.01 \ --lrf 0.01 \ --mosaic 1.0 \ --mixup 0.1 \ --degrees 10 \ --translate 0.1 \ --scale 0.5 \ --fliplr 0.5 \ --name rust_1024参数逐个说imgsz 1024保小目标batch 8是 8G 卡在 1024 下的稳妥值epochs 150对 1700 张量级够收敛太多会过拟合lr0 0.01是初始学习率配合lrf 0.01余弦衰减到 1e-4mosaic 1.0是强增强对小目标友好但会引入拼接伪影如果验证掉点就降到 0.5mixup 0.1轻微混合别开大degrees 10旋转幅度小因为航拍视角相对固定转太多反而不真实scale 0.5缩放增强模拟不同飞行高度。这套配置不是最优解但是一个不容易翻车的起点。提示训练前先用几十张图跑 1 个 epoch确认 loss 正常下降、标签能正确加载再开完整训练。直接上 150 epoch 发现标签路径错了浪费的是几小时。5. 杆塔锈损检测避坑5 个我真实踩过的坑坑一验证集混入加噪图指标虚高。现象是 mAP 很好看实际巡检图一测就掉。原因是加噪图和训练图同分布验证等于开卷考试。解决验证集只用原始真实图加噪图全部放训练集。坑二小锈斑全漏检大锈块正常。现象是召回率低尤其点状锈。原因是输入分辨率太低或 anchor 不匹配。解决提高 imgsz 到 1024重新聚类 anchor检查正样本分配阈值。坑三把阴影、泥土误检成锈。现象是背景区域出现大量假阳性。原因是锈色和背景色接近且训练集负样本不足。解决补充含阴影、泥土、夕阳的负样本图或在损失里加大分类权重让模型学会区分。坑四标签类别名不统一导致多类。现象是训练日志里 nc 对不上或某些框被忽略。原因是 XML 里 rust、Rust、锈 混用。解决转换前统一类别名用 3.1 的脚本做映射未定义类别直接跳过并记录。坑五高斯噪声 sigma 开太大锈斑被淹没。现象是加噪后模型在真实清晰图上反而变差。原因是噪声强度超过真实成像噪声范围模型学了错误的像素分布。解决sigma 控制在 10~20比例 0.2~0.4加噪后抽样目视检查锈斑轮廓要还能辨认。6. 怎么验证这份数据训出的模型真能用一个可落地的评估习惯训练完看 mAP 只是第一步真正决定能不能上线的是分场景评估。我一般会把验证集按光照晴天/阴天、距离近/远、锈损程度点状/条带/块状分组分别算召回和误检。如果整体 mAP 0.7 但点状锈召回只有 0.3那这个模型在早期锈损预警上就是不可用的因为早期恰恰是点状锈。具体做法是给验证集每张图打上场景标签评估脚本按标签分组统计# 按场景分组评估的简化逻辑 groups {sunny: [], cloudy: [], near: [], far: []} # 假设 preds 和 gts 已按图名组织图名里带场景前缀 for name in preds: for g in groups: if name.startswith(g): groups[g].append((preds[name], gts[name])) # 对每组单独算 precision / recall for g, items in groups.items(): print(g, compute_metrics(items))这样跑一遍你会清楚知道模型的能力边界在哪。我的习惯是任何一份锈损数据训出的模型不看到分场景指标就不下能用的结论。1700 张加高斯噪声扩充的数据够你验证技术路线但离覆盖全部真实工况还有距离后续一定要用现场新拍的图持续补数据、做增量训练。数据这件事没有一劳永逸只有一轮轮迭代。希望帮到你。本文还有配套的精品资源点击获取