ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

RFBNET与空洞卷积:无人机巡检输电线路异常检测实战

RFBNET与空洞卷积:无人机巡检输电线路异常检测实战 简介面向无人机电力巡检与设备运维场景这套基于RFBNET的电力杆塔和输电线异常检测Python项目提供了可直接运行的完整源码与配套文档适合毕业设计、课程设计及项目开发。项目采用轻量级CNN框架将RFB模块嵌入SSD网络顶部构成RFB检测器能够对航拍图像中的目标进行定位与分类识别锈蚀、堆放杂物、绝缘子破损、覆盖异物等典型异常。资源包共109个文件、约8.35MB涵盖44个Python源码、20张测试图片、14个RST说明文档以及C/CUDA扩展、Shell脚本、XML配置等代码按数据预处理、模型定义、训练评估等模块组织便于参考复现。另附演示视频与运行脚本已有112人学习使用源码经过严格测试可放心在其基础上延伸应用尤其适合具备一定深度学习基础的开发者作为课程设计或工程项目的起点。1. 为什么用RFBNET做无人机航拍输电线路异常检测无人机巡检一个架次就能带回几千张航拍图杆塔和输电线在画面里往往只占几十个像素背景是田地、山体、云影。直接用通用目标检测模型要么把绝缘子漏掉要么把阴影当作输电线。RFBNET的核心思路是在骨干网络的不同层级上挂一组尺寸不同的空洞卷积让检测头同时拥有小、中、大三种感受野对小目标的召回率比同体量的SSD高一截同时推理速度仍能支撑离线批量处理。它的源码结构不复杂训练也不依赖超大规模数据集用几百张标注图就能训出一个可演示的模型正好撑起毕业设计或课程设计的完整工作量。下面从RFB模块原理讲起逐步落到可运行的Python代码、训练参数和可交付文档。2. RFBNET原理与检测头结构从RFB模块到特征融合RFBNET是在SSD基础上改进的目标检测网络。它保留了SSD的多尺度特征图检测策略但把原本简单的3×3卷积换成了RFBReceptive Field Block模块。RFB模块通过多分支空洞卷积来模拟人类视觉的感受野变化让模型对电线这类细长小目标有更强的响应。这一章先讲RFB模块的PyTorch实现再说明锚框、损失函数和输出解码四个部分连起来就是检测头的完整数据流。2.1 Receptive Field Block空洞卷积如何扩大感受野普通卷积扩大感受野只能靠堆层数或加大卷积核前者增加网络深度后者显著增加参数量。RFB的做法是并行使用不同尺寸的卷积核并在大卷积核分支里插入空洞卷积。空洞卷积在不增加权重数量的前提下把卷积核的采样点拉开等效感受野变大。对于无人机航拍图中宽度只有几个像素的输电线这种多感受野融合可以同时看到线本身和线周围的绝缘子、杆塔横担上下文信息更完整。下面是RFB模块的一个常用实现结构上分为三个分支每个分支先做1×1降维再做对应尺寸的卷积和空洞卷积最后把特征相加import torch import torch.nn as nn class BasicRFB(nn.Module): def __init__(self, in_channels, out_channels, stride1, scale0.1): super(BasicRFB, self).__init__() self.reduce nn.Conv2d(in_channels, out_channels, 1) self.branch1 nn.Conv2d(out_channels, out_channels, 1) self.branch2 nn.Conv2d(out_channels, out_channels, 3, stridestride, padding1) self.branch3 nn.Conv2d(out_channels, out_channels, 5, stridestride, padding2) self.dilation1 nn.Conv2d(out_channels, out_channels, 3, padding1, dilation1) self.dilation2 nn.Conv2d(out_channels, out_channels, 3, padding3, dilation3) self.dilation3 nn.Conv2d(out_channels, out_channels, 3, padding5, dilation5) self.conv_out nn.Conv2d(out_channels * 3, out_channels, 1) self.scale scale def forward(self, x): x self.reduce(x) b1 self.branch1(x) b2 self.branch2(x) b3 self.branch3(x) d1 self.dilation1(b1) d2 self.dilation2(b2) d3 self.dilation3(b3) out torch.cat([d1, d2, d3], dim1) out self.conv_out(out) return x self.scale * out这段代码里的三个分支分别处理感受野为1、3、5的原始区域再通过空洞率为1、3、5的卷积进一步拉伸采样点。参数scale通常取0.1作用是让RFB模块先充当残差补充避免初始化时破坏主干网络已有的特征分布。实际训练中把输出通道控制在128到256之间特征图尺寸为38×38时显存占用和精度比较平衡通道数过大对小数据集反而容易过拟合。2.2 RFBNET的锚框设计与先验框参数RFBNET的检测头使用锚框机制每个特征图位置生成若干个先验框。SSD的默认锚框把宽高比集中在1、2、3但输电线是典型的细长目标宽高比经常到1:8甚至1:12。如果锚框宽高比不够回归分支需要做很大的形状偏移训练初期loss下降很慢。因此做电力场景时一般会根据数据集重新统计目标框的长宽分布再决定锚框参数。下表是一组针对无人机航拍电力杆塔、输电线的锚框配置特征图来自主干网络的不同层级特征图输入尺寸步长锚框缩放比锚框宽高比主要负责目标P338×38300×30080.05, 0.10, 0.150.3, 0.5, 1.0输电线、绝缘子P419×19300×300160.15, 0.25, 0.350.5, 1.0, 2.0杆塔横担、绝缘子串P510×10300×300320.35, 0.50, 0.651.0, 2.0, 3.0杆塔整体、大型异响生成锚框时中心点坐标是特征图像素坐标乘以步长宽高是缩放比乘以输入尺寸再乘以宽高比。要注意缩放比必须覆盖航拍图中目标的真实像素占比。拿一张4000×3000的原图举例如果塔身占整图的6%缩放到300×300后大约是18像素那么0.06这个缩放比就需要出现在锚框列表里否则模型根本看不到这个尺度。2.3 损失函数与难样本挖掘RFBNET的损失分为定位损失和分类损失。定位分支输出的是锚框中心点、宽高的偏移量使用Smooth L1损失分类分支输出每个锚框对每个类别的置信度使用交叉熵损失。总损失是两部分加权相加权重一般为1:1。在电力杆塔数据集里背景锚框数量远大于目标锚框如果不做处理模型会学成“什么都检测不到”也能把loss压得很低。常用的处理方式是硬负样本挖掘每轮迭代按分类loss从高到低排序挑出一定比例的负样本参与反向传播让背景和前景的比例保持在3:1左右。下面这段伪代码展示了训练循环中的loss组织方式for images, targets in dataloader: pred model(images) conf_loss, loc_loss build_loss(pred, targets, num_classes3) # 难样本挖掘只看分类损失挑出排名靠前的负样本 neg_loss conf_loss[targets[is_neg]] _, neg_idx torch.topk(neg_loss, kmin(512, len(neg_loss))) hard_neg_mask torch.zeros_like(conf_loss, dtypetorch.bool) hard_neg_mask[targets[is_neg]][neg_idx] True pos_mask targets[is_pos] selected pos_mask | hard_neg_mask final_loss loc_loss.sum() conf_loss[selected].mean() final_loss.backward()这里is_pos和is_neg来自锚框与真实框的匹配结果IoU大于0.5判定为正样本小于0.3判定为负样本介于两者之间的锚框不参与分类损失。参数512表示最多回传512个困难负样本的梯度这个数值可以根据数据集大小调整。小数据集建议降到256防止难样本对噪声标注过度敏感。2.4 检测头输出解析模型输出的是一个形状为[batch, num_anchors, 4 num_classes]的特征张量。每个锚框占据一行前4个值是中心点和宽高的偏移量后面的值是对每个类别的置信度。推理时要把偏移量解码回真实坐标解码公式与训练时使用的编码方式严格对应否则检测框会全部漂移。def decode_boxes(pred, anchors, img_w, img_h): cx pred[..., 0] * anchors[..., 2] anchors[..., 0] cy pred[..., 1] * anchors[..., 3] anchors[..., 1] w torch.exp(pred[..., 2]) * anchors[..., 2] h torch.exp(pred[..., 3]) * anchors[..., 3] xmin (cx - w / 2) * img_w ymin (cy - h / 2) * img_h xmax (cx w / 2) * img_w ymax (cy h / 2) * img_h return torch.stack([xmin, ymin, xmax, ymax], dim-1)解码后的坐标单位是像素。由于训练时图像会先缩放到300×300锚框的宽高也在这个尺度下设计所以解码后要乘以原图的宽高比例才能得到与原图对应的框。这里直接用anchors[..., 2]表示锚框宽度实际源码里通常存放的是相对于特征图的宽高需要先乘以步长。多尺度特征图各自对应一个解码分支代码里写成一个函数时要注意传入的anchors必须来自同一层特征图。3. 用Python搭建RFBNET训练环境与数据准备模型结构能跑通只是第一步真正让RFBNET学会识别电力杆塔和输电线需要把Python环境、数据集、增强策略和训练参数串起来。这一章从零开始搭建训练流程所有命令都在Linux或Windows的conda终端下可执行。3.1 创建Python虚拟环境与安装依赖用conda创建独立环境避免和系统Python环境互相干扰。RFBNET的常见稳定组合是Python 3.8加PyTorch 1.9这两个版本匹配度最好网络层定义和后续转ONNX都不会出兼容性问题。conda create -n rfbenv python3.8 -y conda activate rfbenv pip install torch1.9.0cu111 torchvision0.10.0cu111 -f https://download.pytorch.org/whl/torch_stable.html pip install opencv-python pyyaml tqdm tensorboard albumentations安装完成后用python -c import torch; print(torch.__version__)确认CUDA可用。如果显卡驱动支持CUDA 11.3以上也可以直接安装更新的PyTorch 2.x版本但代码里nn.Module的接口基本不变只有个别函数名需要兼容。依赖版本不要盲目升级RFBNET源码里如果用了torchvision.ops.nmsPyTorch 1.9以上都包含低于这个版本会报错。3.2 无人机航拍数据集的VOC格式整理课程设计和毕业设计通常不会提供现成数据集需要自己从公开航拍素材或实拍视频中截帧标注。标注工具推荐labelImg它支持Pascal VOC格式的XML输出操作成本最低。采集图片时尽量覆盖不同高度、不同天气和不同光照无人机俯拍角度下杆塔和输电线容易重叠这种样本要保留不能因为难标就删掉。数据集目录按照VOC结构组织data/ ├── JPEGImages/ │ ├── frame_0001.jpg │ └── frame_0002.jpg ├── Annotations/ │ ├── frame_0001.xml │ └── frame_0002.xml └── ImageSets/ └── Main/ ├── train.txt └── val.txtXML里最核心的是object节点每个目标类别的定义需要和训练代码里的类别列表完全一致。习惯上用三个类别tower表示杆塔wire表示输电线defect表示绝缘子破损、异物悬挂等异常。如果数据中某类样本太少可以先合并成anomaly一个类再在检测后处理阶段做规则细分。annotation filenameframe_0001.jpg/filename size width4000/width height3000/height /size object namewire/name bndbox xmin1862/xmin ymin220/ymin xmax2010/xmax ymax260/ymax /bndbox /object /annotation标注时有一个容易被忽略的问题输电线贴着杆塔边缘穿过时标注框容易被裁断。框的边界最好完整包含输电线的可见部分不要把阴影和背景标进去。标注完用脚本统计每个类别的框数量和面积分布如果wire类的框面积普遍小于图像面积的1%说明小目标样本占比高需要在下个环节重点做针对性增强。3.3 面向小目标的增强策略裁剪、拼接与马赛克直接resize会把细长的输电线缩成几个像素性能损失很大。无人机航拍图的增强策略和通用目标检测不一样不能只靠随机翻转和颜色抖动要优先保证目标尺度不丢失。常见的做法是大图裁剪把原图上的一个区域裁成300×300的patch让输电线在patch中的相对尺度接近训练目标尺度。import albumentations as A transform A.Compose([ A.RandomSizedBBoxSafeCrop(height300, width300, erosion_rate0.2), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ], bbox_paramsA.BboxParams(formatpascal_voc, label_fields[labels]))RandomSizedBBoxSafeCrop会随机选一个包含完整目标框的区域进行裁剪避免把目标切掉一半。erosion_rate0.2表示裁剪边界可以向目标框内缩进20%增加目标位置的多样性。如果显存允许还可以叠加Mosaic增强把四张图拼接成一张让模型在一个batch里看到更多小目标。这里要注意Mosaic增强会让目标框跨越拼接边界锚框匹配逻辑需要能处理缩放后的空框否则训练会报错。3.4 训练参数表与启动命令训练参数的选择直接决定模型能不能收敛。下面这组参数是无人机电力场景下调试过的基准值单卡RTX 3060上跑300轮约需要两小时。参数名推荐值说明input_size300×300输入分辨率越高小目标越清晰但越慢batch_size16batch过大容易显存溢出8到32之间尝试initial_lr0.001使用warmup后逐步恢复milestones[150, 250]在第150轮和第250轮lr乘以0.1num_classes3tower、wire、defectmax_epochs300小数据集300轮左右过拟合风险可控neg_pos_ratio3.0正负样本数量比难样本挖掘参数启动训练的命令以常见的源码目录结构为例python tools/train.py \ --dataset voc \ --data-path data/ \ --backbone vgg16 \ --input-size 300 \ --batch-size 16 \ --lr 0.001 \ --epochs 300 \ --num-workers 4 \ --cuda命令里的--backbone vgg16是RFBNET论文里的标准配置也可以用MobileNetV2替换以提升推理速度。注意--num-workers不要超过CPU物理核数否则数据加载会抢占GPU训练资源导致每个epoch时间反而变长。注意这里的300×300输入尺寸和2.2节的锚框缩放比是配套的。如果为了提升小目标精度把输入改成512×512锚框宽高和中心化解码公式里的缩放因子也要按比例计算否则会出现所有检测框偏移到图像边缘的现象。3.5 训练日志与loss曲线解读训练时终端会打印每个epoch的loc_loss、conf_loss和total_loss。正常收敛时conf_loss应该在前50轮快速下降之后进入平台期loc_loss下降速度较慢属于正常现象。如果total_loss出现周期性反弹先检查学习率是不是用了阶梯式衰减以及是否启用了warmup。如果从头到尾loss都在0.4附近抖动很可能锚框缩放比没有覆盖目标尺寸需要回到2.2节的表格重新配置。训练结束后用验证集计算mAPpython tools/evaluate.py \ --dataset voc \ --data-path data/ \ --weights weights/rfbenet_best.pth \ --iou-threshold 0.5mAP低于0.5时不要急着调模型结构优先检查标注框有没有偏移、类别有没有混标、输电线遮挡严重的样本是否被删掉。很多情况下模型表现差不是网络问题而是数据问题。4. 异常检测推理加载权重对航拍图做目标检测训练完成后的RFBNET权重文件就是一个可用的检测器。把权重加载到Python脚本中对新的航拍图做前向推理、解码、NMS后处理再把检测结果转换成异常告警这是整个项目最接近“落地”的部分。这一章的代码可以直接抄进自己的detect.py。4.1 模型加载与图像预处理推理时模型的输入和训练时保持一致都要先缩放到训练指定的input_size。加载模型的时候不要直接torch.load整个pth而是先初始化网络结构再导入state_dict这样即使Python环境变了也不会暴露源码路径问题。import torch import cv2 import numpy as np def load_model(weights_path, num_classes3): from models.detector import RFBNet model RFBNet(num_classesnum_classes) state_dict torch.load(weights_path, map_locationcuda) model.load_state_dict(state_dict) model.eval() return model.cuda() def preprocess(image_path, input_size300): img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (input_size, input_size)) img img.astype(np.float32) / 255.0 img torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0) return img.cuda()load_model里的RFBNet类要放在models/detector.py中类名和训练时的完全一致。预处理时注意cv2.imread读出来是BGR顺序虽然很多代码不转也能跑但RGB顺序会微调模型响应用标准ImageNet均值和方差做归一化时最好统一通道顺序。4.2 后处理NMS与阈值调节模型输出可能包含大量重叠的检测框需要通过置信度阈值和NMS筛选。RFBNET的推理流程是先解码偏移量再按置信度过滤最后做非极大值抑制def postprocess(pred, anchors, conf_thresh0.3, nms_thresh0.45, img_w300, img_h300): boxes decode_boxes(pred[..., :4], anchors, img_w, img_h) scores pred[..., 4:] class_ids scores.argmax(dim-1) max_scores scores.max(dim-1).values keep max_scores conf_thresh boxes, scores, class_ids boxes[keep], max_scores[keep], class_ids[keep] keep_idx torchvision.ops.nms(boxes, scores, iou_thresholdnms_thresh) return boxes[keep_idx], scores[keep_idx], class_ids[keep_idx]conf_thresh和nms_thresh对结果的影响可以用一个表格概括参数场景conf_threshnms_thresh效果漏检多下调到0.20.5召回率提升误报增多误检多调到0.40.4精确率提升漏检风险增加杆塔和输电线重叠0.30.3减少两个类别互相抑制的情况在航拍图中输电线经常和杆塔框重叠如果把nms_thresh设得太大输电线框会被杆塔框抑制掉。一般会按类别分别做NMS先处理tower再处理wire避免跨类别抑制。4.3 异常判定检测框如何变成故障告警异常检测可以落在两个层面一个是模型直接检测defect类别另一个是根据tower和wire的几何关系推导异常。第二种方法更适合数据不足的项目因为正常样本容易标注而异常样本稀缺。def check_anomaly(detections, img_w300): towers [d for d in detections if d[class_id] 0] wires [d for d in detections if d[class_id] 1] defects [] for t in towers: t_box t[box] has_wire False for w in wires: if box_iou(t_box, w[box]) 0.05: has_wire True break if not has_wire: defects.append({type: missing_wire, box: t_box}) return defects这个规则的逻辑是杆塔区域附近应该有输电线穿过如果检测不到任何wire框就判定为“缺失或断线”。具体项目中还会加上线长连续性判断把同一条线路上的wire框按端点距离串联如果两个框的端点距离超过阈值说明线可能有断裂。规则简单但非常实用毕业设计答辩时可以解释成“知识驱动的后处理”和模型检测形成互补。4.4 推理阶段的常见问题与排查推理时最常见的是检测结果全是空框。第一步检查输入图像的尺寸是否和训练一致第二步打印pred张量的数值范围。如果所有置信度都等于0说明模型加载了未训练的权重或者model.eval()被漏掉导致dropout层仍然生效。显存不足时可以把输入尺寸从300降到256同时把batch size设为1。但要注意decode_boxes函数里的锚框缩放比是按输入尺寸生成的更换输入尺寸后必须同步缩放锚框参数。还有一种情况是航拍图分辨率太高超过3000×4000直接resize会丢失小目标。一般会先用滑窗把大图切成600×600的块每个块独立推理最后把所有块的检测结果合并回原图坐标这样能在不牺牲精度的前提下处理原分辨率。5. 把RFBNET源码改造成毕业设计可交付的工程很多课程设计的代码能跑通但不好好整理也拿不到高分。毕业设计答辩时评审看到的不只是模型效果更是代码组织、文档完整度和可复现性。这一章把源码整理和文档写作的关键步骤列出来按这个顺序改造自己的项目交付质量会明显上一个台阶。5.1 工程目录分层与代码规范不要把 所有Python文件堆在根目录下。一般会按config、models、utils、tools、docs五层来组织源码训练和推理入口统一放在tools里模型定义放在models里数据预处理、增强、可视化这种可复用函数放进utils。rfbenet-powerline/ ├── config/ │ └── rfbenet.yaml ├── models/ │ ├── __init__.py │ ├── rfb.py │ └── detector.py ├── utils/ │ ├── dataset.py │ ├── augmentation.py │ ├── decode.py │ └── metrics.py ├── tools/ │ ├── train.py │ ├── evaluate.py │ └── detect.py ├── data/ ├── weights/ └── docs/ ├── 需求分析.md ├── 总体设计.md └── 测试报告.mdconfig/rfbenet.yaml里放所有超参数训练脚本启动时用yaml.safe_load读取。这样做的好处是答辩现场调整参数时不需要改代码只需改配置文件演示起来更专业。代码注释不要求每行都有但类名、函数名、关键参数含义必须写清楚尤其是锚框生成和loss计算这种分支多的部分。5.2 从README到课程设计文档文档怎么写“源码文档”的交付物里文档不是把注释复制一遍而是按设计流程组织。下面的表格可以当作课程设计文档的骨架章节常见内容关联代码需求分析无人机巡检场景、异常类型定义、性能预期README.md总体设计RFBNET结构图、检测流程、模块划分models/、config/详细设计数据增强策略、锚框生成、loss计算utils/、tools/train.py测试结果训练曲线、mAP、检测样例截图weights/、docs/测试报告.md环境部署Python版本、依赖清单、运行命令requirements.txt写README.md时开头第一段要说明项目解决什么问题然后放一张检测效果图接着是目录结构和快速开始命令。不要只写“本代码实现RFBNET”要写清楚“实现了基于RFBNET的无人机航拍电力杆塔、输电线异常检测能识别三类目标输出带标记的结果图”。需求分析里把“异常检测”具体化是检测绝缘子破损、输电线断股、还是异物悬挂。定义得越细后面的设计越容易写。总体设计配一张数据流图技术路线确定为“RFBNET检测目标框 规则判断异常”文档逻辑就完整了。详细设计要写锚框参数为什么这样设置可以把2.2节的表格原样放进去增加可信度。5.3 用TensorBoard和PR曲线验证效果训练时的可视化结果对答辩非常有用。训练脚本里加入TensorBoard的SummaryWriter记录每个epoch的loss、lr和mAPfrom torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/rfbenet_exp1) for epoch in range(epochs): train_loss train_one_epoch(...) writer.add_scalar(loss/train, train_loss, epoch) writer.add_scalar(lr, current_lr, epoch) if epoch % 10 0: mAP evaluate(...) writer.add_scalar(metrics/mAP0.5, mAP, epoch)启动命令是tensorboard --logdir runs浏览器打开后会看到loss下降曲线和mAP上升曲线。除了曲线再保存几张检测结果图把正常样本和异常样本对照排列能直观展示模型在杆塔、输电线、缺陷三类目标上的表现。PR曲线可以调用sklearn.metrics.average_precision_score生成但要注意先按类别分别计算再求均值这才是mAP的真正含义。5.4 答辩验收自测清单最后说一个交付前会走一遍的自测清单这些项检查完项目基本就能达到答辩要求检查项验证方法目标值环境可复现在干净环境执行pip install -r requirements.txt后训练无报错推理速度对单张4000×3000大图连续推理20次取平均单张小于500ms检测精度运行tools/evaluate.py输出mAPmAP≥0.70文档完整度按README里的命令从数据准备到检测完整走一遍每一步有输出代码规范检查models/和utils/中函数是否有注释和类型标注无裸奔函数答辩前用固定随机种子重训一次把训练命令和输出日志一并放进附录评审老师能直接复现比口头解释“我调过参”更有说服力。本文还有配套的精品资源点击获取
返回列表