基于RFBNet的电力杆塔与输电线异常检测:从航拍数据到边缘部署
简介这份资源面向计算机、自动化等专业的毕业设计、课程设计及项目开发人群提供一套基于RFBNet实现无人机航拍图中电力杆塔与输电线异常检测的完整Python工程。项目将轻量级CNN模型中的RFB模块组装到SSD顶部构建RFB网络探测器可对变电站内电力设备的锈蚀、堆放杂物、绝缘子破损、覆盖异物等异常进行监测属于深度学习目标检测在电力巡检场景中的典型落地案例。压缩包共109个文件约8.35MB以44个py源码文件为核心辅以jpg样例图片、rst说明文档、xml配置、sh脚本及c、cpp、cu等底层NMS与mask实现文件目录结构清晰便于按模块阅读与二次开发。目前已有112人学习下载。源码经过严格测试读者可据此掌握RFBNet的网络搭建、数据组织、训练推理与后处理全流程并在此基础上替换数据集或调整模块快速延展为新的检测课题。1. 从一张航拍图说起RFBNet 为什么适合做电力杆塔与输电线异常检测无人机巡检电力线路单架次能拍回几百到上千张图。杆塔倾斜、绝缘子破损、销钉缺失、导线断股、异物悬挂这些异常如果靠人一张张翻眼睛先报废。更麻烦的是输电线在航拍图里往往只有几个像素宽背景是山林、农田、建筑对比度低常规检测网络很容易把它当成纹理噪声漏掉。RFBNetReceptive Field Block Net解决的正是这个矛盾它借鉴人眼感受野的机制用多分支不同膨胀率的卷积核去模拟不同尺度的感受野让浅层特征也能带上大感受野的语义信息。放到电力巡检场景里这意味着细长的输电线和大块的杆塔结构能在同一套特征里被同时照顾到。Python 实现这套东西配合无人机航拍数据做异常检测是当前工业异常检测算法里落地成本比较低的一条路。这篇笔记面向要做毕业设计、课程设计或实际项目开发的读者把数据、模型、训练、推理、踩坑一条线讲清楚源码和文档层面的组织方式也会给到。2. RFBNet 检测电力异常的底层逻辑与数据准备2.1 感受野分支怎么对应杆塔和输电线RFBNet 的核心模块叫 RFBReceptive Field Block结构上分两条支路一条是普通的 1x1 或 3x3 卷积另一条是多个不同膨胀率的 3x3 卷积并联膨胀率通常取 1、3、5。并联之后做 concat再用 1x1 卷积压回通道数。这样做的效果是同一个输出像素能“看到”不同范围的输入区域。杆塔是刚性结构异常表现为整体倾斜或局部形变需要大感受野来捕捉上下文输电线是细长目标异常表现为断股、悬挂物需要小感受野保留细节。RFB 模块把这两种需求塞进同一个 block浅层特征图经过它之后既保留了边缘信息又带上了区域语义。这是 RFBNet 比普通 SSD 在细长目标上表现更好的直接原因。实际写代码时RFB 模块的膨胀率不是随便设的。输入分辨率 512x512 时膨胀率 1、3、5 对应的有效感受野大约覆盖 3x3、7x7、11x11 像素区域。如果无人机飞行高度导致输电线在图中只有 2 到 3 个像素宽膨胀率 5 的分支反而会引入过多背景噪声。我一般会先统计数据集中输电线的最小宽度再决定膨胀率组合。2.2 航拍数据集的采集与标注规范无人机航拍电力巡检数据没有现成的公开大规模数据集常见做法是自己采集或找实验室已有数据。采集时注意三点飞行高度控制在 30 到 80 米保证杆塔完整入镜且输电线可分辨光照尽量选上午 10 点前或下午 4 点后避免正午顶光导致导线反光过曝每基杆塔至少拍 3 个角度正侧、斜侧、俯视各一张。标注用 LabelImg 或 CVAT类别建议先分四类杆塔正常、杆塔异常、输电线正常、输电线异常。异常再细分倾斜、破损、异物、断股。标注框要贴紧目标边缘输电线标注时如果一条线跨越多个杆塔建议按杆塔区间分段标注否则框会拉得很长训练时正负样本比例失衡。数据格式统一成 VOC 的 XML后面转 YOLO 或 COCO 都方便。目录结构按下面组织dataset/ ├── annotations/ # XML 标注文件 ├── images/ # 原始航拍图 ├── ImageSets/ │ └── Main/ │ ├── train.txt # 训练集文件名列表 │ ├── val.txt # 验证集 │ └── test.txt # 测试集 └── labels/ # 转换后的 YOLO 格式标签划分比例按 7:2:1 走如果总图少于 500 张验证集可以并进训练集只留测试集做最终评估。注意同一个杆塔的多角度照片必须分到同一个集合否则验证集里出现训练时见过的杆塔指标会虚高。2.3 从 VOC 到训练张量转换脚本与增强策略RFBNet 原版基于 SSD 框架输入格式是 COCO 或 VOC。这里给一个 VOC 转 YOLO 再统一读入的脚本方便后续用 PyTorch 的 Dataset 类加载import os import xml.etree.ElementTree as ET import cv2 import numpy as np # 类别映射0 为背景1 开始为实际类别 CLASSES [tower_normal, tower_abnormal, line_normal, line_abnormal] class_to_id {name: i 1 for i, name in enumerate(CLASSES)} def voc_to_yolo(xml_path, img_path, output_label_path): tree ET.parse(xml_path) root tree.getroot() img cv2.imread(img_path) h, w img.shape[:2] lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_to_id: continue cls_id class_to_id[name] 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) # 归一化中心点和宽高 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(output_label_path, w) as f: f.write(\n.join(lines))这段代码的逻辑很直接读 XML取每个 object 的类别和边界框按图像宽高归一化。参数上注意class_to_id从 1 开始0 留给背景这是 SSD 系列的标准做法。如果类别名和标注文件里的不一致脚本会静默跳过训练时表现为某些类别永远检测不到排查时先检查这里。数据增强用 albumentations 库重点开三个随机裁剪crop 到 0.8 到 1.0 倍面积、水平翻转、亮度对比度扰动。不要用垂直翻转航拍图里杆塔倒过来不符合物理规律模型会学到错误特征。Mosaic 增强在输电线场景要慎用四张图拼一起容易把导线接成不存在的长线产生假阳性。3. 用 PyTorch 搭 RFBNet骨干、RFB 模块与检测头3.1 RFB 模块的代码实现与膨胀率选择RFB 模块有两种版本RFB 和 RFB-s。RFB 用在浅层膨胀率 1、3、5RFB-s 用在深层膨胀率 1、3但分支更多。下面是一个可直接用的 RFB 实现import torch import torch.nn as nn class BasicConv(nn.Module): def __init__(self, in_planes, out_planes, kernel_size, stride1, padding0, dilation1): super(BasicConv, self).__init__() self.conv nn.Conv2d(in_planes, out_planes, kernel_sizekernel_size, stridestride, paddingpadding, dilationdilation, biasFalse) self.bn nn.BatchNorm2d(out_planes) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return self.relu(self.bn(self.conv(x))) class RFB(nn.Module): def __init__(self, in_planes, out_planes): super(RFB, self).__init__() # 主干分支1x1 压缩 self.branch0 nn.Sequential( BasicConv(in_planes, out_planes, 1) ) # 膨胀率 1 的分支 self.branch1 nn.Sequential( BasicConv(in_planes, out_planes, 1), BasicConv(out_planes, out_planes, 3, padding1, dilation1) ) # 膨胀率 3 的分支 self.branch2 nn.Sequential( BasicConv(in_planes, out_planes, 1), BasicConv(out_planes, out_planes, 3, padding3, dilation3) ) # 膨胀率 5 的分支 self.branch3 nn.Sequential( BasicConv(in_planes, out_planes, 1), BasicConv(out_planes, out_planes, 3, padding5, dilation5) ) self.conv_cat BasicConv(4 * out_planes, out_planes, 1) self.residual BasicConv(in_planes, out_planes, 1) def forward(self, x): x0 self.branch0(x) x1 self.branch1(x) x2 self.branch2(x) x3 self.branch3(x) out torch.cat((x0, x1, x2, x3), dim1) out self.conv_cat(out) out out self.residual(x) # 残差连接缓解梯度消失 return out参数说明in_planes是输入通道数out_planes是每个分支的输出通道数最终 concat 后是 4 倍再用 1x1 压回out_planes。残差连接不是原论文的强制要求但在电力数据上加上之后小目标召回率有提升。膨胀率如果改成 1、2、4感受野更密集适合输电线像素宽度小于 3 的情况代价是计算量略增。3.2 骨干网络选型VGG16 还是 MobileNetRFBNet 原版用 VGG16 做骨干在 512 输入下参数量约 30M推理一张图在 1080Ti 上约 25ms。如果部署到无人机机载设备或边缘盒子VGG16 太重换成 MobileNetV2 做骨干参数量降到 4M 左右推理速度提升到 8ms但 mAP 会掉 3 到 5 个点。我的建议是毕业设计和课程设计用 VGG16指标好看代码改动少实际项目如果算力受限用 MobileNetV2但要把 RFB 模块的 out_planes 从 256 降到 128否则特征图通道数太多边缘设备内存扛不住。骨干网络只取到 conv4_3 和 conv7后面的层用 RFB 和额外卷积层替代这是 SSD 系列的标准做法。3.3 检测头与先验框设置检测头接在 6 个特征层上尺寸分别是 38x38、19x19、10x10、5x5、3x3、1x1输入 512 时。每个特征层对应一组先验框宽高比取 1、2、3、1/2、1/3。输电线是细长目标宽高比要额外加 1/5 和 5否则先验框和真实框的 IoU 太低正样本匹配不上。先验框的尺度按 SSD 公式算s_k s_min (s_max - s_min) * (k - 1) / (m - 1)s_min 取 0.1s_max 取 0.9。电力场景里杆塔通常占图像 1/4 到 1/2输电线占 1/10 以下所以小尺度先验框要加密。具体做法是在 38x38 和 19x19 两层各加一组尺度 0.05 和 0.08 的框。正负样本匹配用 IoU 阈值 0.5低于 0.3 算负样本中间忽略。输电线标注框往往很扁IoU 计算时如果按常规方式正样本会很少。可以在匹配阶段对输电线类别单独把阈值降到 0.35这个改动在代码里就是一行判断但召回率能提 8 个点左右。4. 训练、调参与推理部署的完整链路4.1 损失函数与训练脚本关键参数损失函数用 SSD 的 MultiBoxLoss分类用交叉熵定位用 smooth L1。电力数据里正常样本远多于异常样本分类损失要加类别权重异常类权重设为正常类的 3 倍。下面是一个训练循环的核心片段import torch.optim as optim from torch.utils.data import DataLoader # 超参数 lr 1e-3 momentum 0.9 weight_decay 5e-4 batch_size 8 epochs 200 model RFBNet(num_classes5).cuda() optimizer optim.SGD(model.parameters(), lrlr, momentummomentum, weight_decayweight_decay) scheduler optim.lr_scheduler.MultiStepLR(optimizer, milestones[120, 160], gamma0.1) criterion MultiBoxLoss(num_classes5, overlap_thresh0.5, neg_pos_ratio3) train_loader DataLoader(PowerDataset(train), batch_sizebatch_size, shuffleTrue, num_workers4) for epoch in range(epochs): model.train() for images, targets in train_loader: images images.cuda() targets [t.cuda() for t in targets] optimizer.zero_grad() out model(images) loss_l, loss_c criterion(out, targets) loss loss_l loss_c loss.backward() optimizer.step() scheduler.step() if epoch % 10 0: print(fEpoch {epoch}, loss_l {loss_l.item():.4f}, loss_c {loss_c.item():.4f})参数说明neg_pos_ratio3表示负样本最多是正样本的 3 倍电力场景可以调到 2因为异常样本本身少负样本太多会压制正样本梯度。学习率 1e-3 是 VGG16 骨干的常用值如果换 MobileNet降到 5e-4。milestones 设在 120 和 160总 epoch 200这是经验值数据量小于 1000 张时总 epoch 可以降到 150。4.2 推理与后处理NMS 阈值和置信度过滤推理时模型输出每个先验框的类别置信度和偏移量解码后做 NMS。电力场景的 NMS 阈值不能统一设 0.5杆塔之间可能挨得近阈值太高会误删输电线细长重叠少阈值可以低一点。我一般分类别设杆塔 0.45输电线 0.3。置信度过滤阈值按类别调杆塔异常 0.6输电线异常 0.5。输电线本身像素少模型置信度普遍偏低阈值设 0.6 会漏掉一半。推理脚本里加一个按类别读取阈值的小逻辑CONF_THRESH {tower_normal: 0.6, tower_abnormal: 0.6, line_normal: 0.5, line_abnormal: 0.5} NMS_THRESH {tower_normal: 0.45, tower_abnormal: 0.45, line_normal: 0.3, line_abnormal: 0.3}后处理完把结果画到原图上保存时用cv2.imwrite注意中文路径会写失败统一用英文路径或先cv2.imencode再写文件。4.3 模型导出与边缘设备部署训练完的模型导出成 ONNX再用 TensorRT 或 OpenVINO 加速。导出时注意把 NMS 也写进计算图否则部署端还要用 Python 做后处理速度上不去。ONNX 导出命令python export_onnx.py --weights weights/rfbn et_power.pth --input-size 512 --output rfbn et_power.onnx --opset 11导出后检查输入输出节点名用 Netron 看一眼。如果部署到 Jetson 系列TensorRT 的 fp16 模式能把推理压到 5ms 以内但精度掉 1 个点左右看项目能不能接受。边缘设备内存小于 2G 时把输入分辨率从 512 降到 320先验框尺度同步缩放否则检测框会整体偏移。5. 避坑与排查电力航拍检测里最容易翻车的 5 个点5.1 输电线标注框太扁导致正样本匹配不上现象训练 loss 一直不降输电线类别 mAP 接近 0。原因输电线标注框宽高比超过 10:1和默认先验框的 IoU 低于 0.3匹配阶段全被当成负样本。解决在先验框设置里加宽高比 1/5 和 1/7 的框同时把输电线类别的匹配阈值从 0.5 降到 0.35。改完之后重新生成先验框不要只改匹配代码否则框本身还是对不上。5.2 数据增强把导线接成假长线现象模型在验证集上把两根不相干的输电线检测成一条或者把背景里的田埂当成导线。原因Mosaic 或随机裁剪增强时两张图的导线边缘被拼到一起模型学到了拼接特征。解决电力场景关掉 Mosaic随机裁剪的比例控制在 0.9 到 1.0不要裁太狠。如果一定要用拼接增强拼接后人工检查一遍把产生假长线的样本剔除。5.3 学习率太大导致杆塔框震荡现象训练前期 loss 下降中期突然反弹杆塔检测框在图像上左右跳。原因VGG16 骨干加 RFB 模块后参数量大1e-3 的学习率在 batch_size 小于 8 时不稳定。解决把学习率降到 5e-4或者用 warmup前 5 个 epoch 从 1e-4 线性升到 1e-3。另外检查 batch_size小于 4 时 BatchNorm 统计量不准也会引起震荡尽量凑到 8。5.4 推理时中文路径写图失败现象推理脚本跑完没报错但输出目录里没有图。原因cv2.imwrite对中文路径支持不好静默失败。解决用cv2.imencode加tofile写文件或者把输出路径改成英文。这个坑在 Windows 上尤其常见Linux 下如果 locale 没设对也会出现。5.5 边缘设备上 fp16 导出后检测框偏移现象ONNX 模型在服务器上正常导出 TensorRT fp16 后框整体往左上偏。原因fp16 精度不够先验框解码时的指数运算误差被放大。解决把解码部分留在 CPU 上用 fp32 算或者导出时把先验框的尺度参数放大 100 倍再在部署端除回去。更稳妥的做法是先用 fp32 跑通确认精度后再试 fp16掉点超过 2 个点就放弃。6. 进阶技巧用热异常检测思路补强可见光模型的漏检可见光模型在阴天或逆光时对输电线断股几乎无能为力因为断股处和背景灰度接近。如果无人机同时挂了红外相机可以引入热异常检测的思路把红外图配准到可见光图上断股处因为接触电阻增大温度比正常导线高 3 到 8 摄氏度在红外图里是明显的亮斑。具体做法是训练一个双流 RFBNet可见光分支和红外分支各自过骨干在 RFB 模块之后做特征融合融合方式用逐元素相加再 1x1 卷积。红外分支的输入是单通道复制成三通道喂给同样的骨干。训练时两个分支的损失加权求和红外分支权重 0.4可见光 0.6。配准用 SIFT 加单应性矩阵误差控制在 2 个像素以内否则融合特征会错位。验证这套方案是否值得做看一个指标在逆光测试集上单可见光模型的输电线异常召回率如果低于 60%双流模型能提到 75% 以上就值得投入。如果红外和可见光配准误差大于 5 个像素融合反而会拉低指标这时候不如各跑各的后处理阶段做框融合。我自己做这类项目最大的教训是不要一上来就堆模块。先把单类杆塔检测跑通mAP 到 0.8 以上再加输电线类别再加异常细分最后才考虑多模态。每加一个东西回测一遍之前的数据确认没有把已经跑通的类别带崩。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取