做隧道围岩缺陷识别的同行应该都有同感:拿通用目标检测模型去跑掌子面、衬砌表面的图像,COCO上精度漂漂亮亮,一上现场就原形毕露。裂缝细成一条线,节理在强光下似有似无,空洞轮廓被喷混面的纹理搅得稀碎,更别提隧道里那盏探照灯打在岩壁上那种半明半暗的光照环境。这也是我们当初把 YOLO13-SEG 和 RFAConv 结合到一起的原因——不是追新,是被现场数据逼着改。这篇就把整个改进思路、底层逻辑和踩坑过程完整拆开,给同样在做围岩缺陷检测、地质病害识别的朋友一个能直接参考的方案。
这篇文章适合三类人看:正在用 YOLO 系列做实例分割、但感觉精度差口气的;做隧道、边坡、矿山等工程安全检测,被小目标和复杂背景折磨的;以及单纯想了解 RFAConv 这个注意力卷积模块到底怎么用、为什么有用的算法工程师。全文不堆公式,尽量用工程语言把事情说清楚。
先交代项目背景。我们当时接到的任务是对某一段隧道围岩进行表面缺陷识别,最终要输出的是缺陷类别、像素级轮廓、面积估算和位置坐标,用于辅助地质工程师做围岩分级和病害排查。试过纯检测框方案,发现框根本描述不了裂缝的走向和长度,空洞的面积也完全算不准,所以一开始就锁定实例分割路线。模型选型在 YOLO13-SEG 上,理由很简单:单阶段出掩膜、速度快、部署链路成熟,然后在它的 Backbone 和 Neck 里插入了 RFAConv 小模块,最终把 mask mAP50 从之前的 68.2% 拉到了 74.7%。涨点幅度谈不上惊天动地,但漏检率明显下降,尤其是细长裂缝和小空洞这两类最头疼的目标。
1. 项目定位:隧道围岩缺陷识别到底难在哪
1.1 我们要识别的不是一个框,而是缺陷的完整轮廓
隧道围岩缺陷不是日常理解的“给物体画个方框”就能搞定的任务。裂缝可能是从画面左上角一路延伸到右下角的连续折线,节理是一组平行或网状的细长纹路,空洞是边缘深度不一的凹陷区域,渗水痕迹则往往呈现片状、条带状的不规则分布。如果用检测框去框这些目标,一个框里可能同时包含缺陷和正常岩面,导致长度、面积、走向这些关键地质参数全部失真。
所以这个项目从一开始就确定为实例分割任务,需要在每个缺陷实例上输出精确到像素的多边形掩膜。后续工程师拿到掩膜之后,可以直接按像素面积换算成实际尺寸,再结合裂缝宽度、贯通性、填充物情况来做围岩等级判定。这比给一个“置信度 + 坐标框”实用得多,但也意味着模型的设计约束完全不同:它不仅要判断“这里有没有缺陷”,还要把缺陷的边界抠出来,这对特征的空间分辨率、边缘保持能力和感受野都有更高要求。
1.2 现场数据对模型的三种“恶意”
第一重恶意是光照极度不均匀。隧道内的人工光源集中在几个固定位置,岩壁表面又是凹凸不平的,迎光面和背光面的灰度差可以拉开到几十个像素级别。裂缝在背光区域几乎隐形,在迎光区域又会因为过曝而断成碎段。我们在训练集中试过只做常规的随机亮度扰动,效果非常有限,因为现场不同拍摄角度下同一段裂缝的光照表现差异远大于增强器能模拟的范围。
第二重恶意是表面纹理的强干扰。裸露岩面有风化痕迹、爆破凿痕、喷射混凝土的骨料纹路,这些纹理在局部特征上和裂缝、节理高度相似。算法如果只关注局部纹理而忽略全局结构,很容易把一条凿痕认成裂缝,或者把层理误判成节理。
第三重恶意是小目标和极端长宽比。裂缝本质上是一维流形在二维图像上的展开,动不动就是几十上百像素的长度,但宽度可能只有两三个像素。空洞在远景图像里则可能只有十几个像素的直径。常规特征提取网络在连续下采样后,这类目标的响应很容易被周边背景淹没,这是很多通用分割模型在围岩场景失效的最直接原因。
1.3 选型逻辑:为什么不是 Mask R-CNN,而是 YOLO13-SEG
实例分割领域最经典的 Baseline 是 Mask R-CNN,我们在预研阶段也跑过,精度确实能到相对不错的水平。但落到工程上有三个现实问题:两阶段结构在嵌入式设备上的推理延迟不可控;检测分支和掩膜分支的解耦设计导致小目标掩膜边界偏糊;后处理和部署链路相对复杂,转换到 TensorRT 时要处理的算子更多。
YOLO13-SEG 作为单阶段实例分割模型,最大的优势在于检测头和分割头共享 Backbone 特征,输出层同时预测类别、框和一组原型掩膜系数,再通过线性组合生成最终掩膜。这种设计牺牲了少量理论上限精度,换来了足够的帧率和工程友好度。我们的目标设备是边缘计算盒子,需要在 640 分辨率下跑到 30 FPS 以上,YOLO13-SEG 在这个约束下是当前平衡点最好的选择。
2. YOLO13-SEG 的改进入口:RFAConv 模块
2.1 RFAConv 到底改了什么
RFAConv 全称是 Receptive Field Attention Convolution,核心思想是给卷积操作加一层“感受野注意力”。普通卷积对每个位置的感受野是固定的,比如 3×3 卷积就是周围 8 个邻居加自己,不管这个位置是在裂缝边缘还是平坦岩面上,权重分配逻辑完全一样。RFAConv 的做法是在卷积之前先计算一个注意力张量,这个注意力张量让模型动态决定“当前这个位置应该更关注哪个感受野范围内的信息”。
具体流程可以拆成两步:第一步,用输入特征图生成一组调制因子,对原始特征做分组调制;第二步,把调制后的特征通过池化和卷积计算空间注意力,乘回特征图,再做常规卷积。我把它的行为理解成一个“可学习的多尺度取景框”——模型训练之后会发现,裂缝这种细长目标需要长条形的上下文视野,空洞这种目标需要覆盖周边较大区域的全局视野,而平坦岩面只需要局部一致性,于是不同的空间位置会激活不同的注意力模式。
它的参数量增加不大,我实现的版本比原版 3×3 卷积只多了约 12% 的通道注意力模块开销,推理时延增加可以控制在 3~5% 以内,这在工业项目里是完全可以接受的代价。
2.2 为什么这个模块对围岩缺陷特别对症
围岩缺陷识别最典型的两个特征是:类别边界模糊、目标尺度跨度极大。这两个特征恰恰是固定感受野卷积的死穴。
拿裂缝来说,它本质上是图像上的连续低强度线状结构,但裂缝周围可能有斑点状的风化区域、有不同灰度的岩块边界、有喷射混凝土的骨料纹理。固定 3×3 卷积核只能看到局部三像素邻域,根本无法判断“这一小段暗色像素到底是裂缝还是噪声颗粒”,必须依赖更大范围的结构信息,比如这段像素是否与上下方的暗色像素连成线。RFAConv 的可变感受野注意力能让模型在裂缝位置放大对线状延伸方向的关注权重,相当于在特征层面帮模型“脑补”出完整的裂缝走向。
空洞和溶蚀区域的问题相反,它们边界不规则、纹理复杂,但局部特征往往和正常岩面的碎石背景相似,需要更大的上下文来确认“这是一个凹陷区域”而不是“一堆岩块缝隙”。RFAConv 的多尺度调制让模型能在这类位置主动扩大有效感受野,把空洞周围的边缘走向和阴影关系纳进来,从而减少误判。
我们后面做消融实验时发现,加入 RFAConv 之后提升最明显的恰恰是这两类目标,这从指标层面也验证了上述推理。
2.3 把 RFAConv 装进 YOLO13-SEG 的具体做法
我改造的 YOLO13-SEG 采用了最小侵入式方案,没有改动原有的检测头和分割头结构,只动了特征提取和特征融合两条链路:
- 在 Backbone 每个 C2f 模块内的 Bottleneck 中,把标准 3×3 卷积替换为 RFAConv 卷积;
- 在 Neck 的 P3、P4、P5 特征融合之前,各插入一个独立的 RFAConv 模块作为特征精炼,不改变输入输出通道数。
这样的好处是权重加载、预训练迁移、后续转 ONNX 导出都不受影响,Head 部分完全不动,模型训练时只需要重新学习特征层参数。下面贴一个我在项目里实际使用的简化 RFAConv 实现,方便大家直接跑通:
import torch import torch.nn as nn import torch.nn.functional as F class RFAConv(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, groups=1): super().__init__() self.kernel_size = kernel_size self.stride = stride self.groups = groups self.in_channels = in_channels # 调制分支:生成组内权重,对输入特征进行通道分组调制 self.modulation = nn.Conv2d( in_channels, in_channels, kernel_size=kernel_size, stride=stride, padding=kernel_size // 2, groups=groups ) # 空间注意力分支:压缩通道后计算每个位置的注意力图 self.attention = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channels, in_channels // 4, 1), nn.ReLU(inplace=True), nn.Conv2d(in_channels // 4, in_channels, 1), nn.Sigmoid() ) # 感受野分组:多尺度接合的感受野池化 self.receptive_pool = nn.ModuleList([ nn.AdaptiveAvgPool2d((1, 1)), nn.AdaptiveAvgPool2d((2, 2)), nn.AdaptiveAvgPool2d((4, 4)) ]) self.fuse = nn.Conv2d(in_channels * 3, in_channels, 1) # 最终卷积 self.conv = nn.Conv2d( in_channels, out_channels, kernel_size=kernel_size, stride=1, padding=kernel_size // 2, groups=groups, bias=False ) def forward(self, x): mod = self.modulation(x) # 求平均注意力并对特征加权 attn_feat = [] for pool in self.receptive_pool: attn_feat.append(F.interpolate(pool(mod), size=x.shape[2:], mode='nearest')) mod = torch.cat(attn_feat, dim=1) mod = self.fuse(mod) attention = self.attention(mod) out = self.conv(x * attention) return out注意这段代码是工程简化版,和论文原始实现有出入,核心思路一致:先对输入做调制,再通过不同尺寸池化模拟多尺度感受野,最后池化出注意力权重对原特征加权,再进行卷积。我自己测试下来,这个版本在 YOLO 系列中导入最干净,不需要改动数据集加载和训练循环。
插入方式上,找到 Backbone 中 Bottleneck 的定义位置,把原本cv2 = Conv(c_, c_, 3)改成cv2 = RFAConv(c_, c_, 3)即可。Neck 部分则在通道拼接后加一个RFAConv(c_out, c_out, 3)精炼层。如果不想动结构,也可以把整个 RFAConv 作为一个旁路模块,与原特征相加后再后续卷积,不过实践下来前一种方式精度更高。
2.4 参数和显存成本估算
以输入 640×640、Backbone 用 Base 配置为例,我测过一组参考数据:
| 模型配置 | 参数量(M) | 计算量(GFLOPs) | 显存占用(GB) | 推理延迟(ms) |
|---|---|---|---|---|
| YOLO13-SEG 原版 | 约 52.4 | 约 226 | 约 8.2 | 约 8.5 |
| + RFAConv (Backbone) | 约 58.6 | 约 238 | 约 8.9 | 约 9.2 |
| + RFAConv (Backbone+Neck) | 约 65.1 | 约 254 | 约 9.6 | 约 10.1 |
表里是单卡 4090 上测的延迟,部署到边缘盒子上再算上量化优化,总体增量在可接受范围。关键收益是 mask 精度涨了 6.5 个百分点,这比单纯把模型放大一个版本划算得多。
3. 数据、标注与训练策略
3.1 数据怎么来、怎么标,决定了模型的上限
围岩缺陷数据不像公开数据集,有海量清晰标注好的现成资源。我们的原始数据来自两部分:巡检车车载相机连续拍摄的隧道壁面视频抽帧,以及技术人员用手持相机在重点部位补拍的局部特写。视频抽帧的好处是量大,坏处是相邻帧高度相似,如果直接全部丢进训练集,会造成严重的分布偏移,模型会对某一小段岩壁过拟合。我们是按时间间隔抽帧、再用感知哈希去重,把相似度高的帧合并成同一个样本组,确保训练集覆盖不同区段而不是同一段岩壁的几百个重复帧。
标注环节走的是多边形标注工具,输出 COCO 格式。这里有一条非常重要的经验:标注小目标时,标框要严谨,掩膜更要严谨。小空洞目标往往只有十几个像素,标注员一旦把边界画松一两个像素,区域面积误差就会放大到不可接受。我们后来规定所有缺陷掩膜的标注误差控制在 1 到 2 个像素以内,面积大于 5000 像素的缺陷目标允许边缘有 3 个像素以内的出入。这条标准的确立,比后面改模型结构带来的收益还要直观。
3.2 长尾分布问题与针对性增强
统计完标注结果,分布是一目了然的头重脚轻:
| 缺陷类别 | 标注实例数 | 样本占比 |
|---|---|---|
| 裂缝(含延伸裂缝) | 4620 | 51.3% |
| 节理/层理 | 1830 | 20.3% |
| 渗水/水痕 | 1430 | 15.9% |
| 空洞/溶蚀 | 520 | 5.8% |
| 破碎带/松动岩块 | 610 | 6.7% |
裂缝和节理占据了七成以上的样本,空洞和破碎带加起来不到两成。直接用原始分布训练,模型会把大部分表达能力花在裂缝上,空洞类目几乎学不出来。我们针对性做了三件事:CopyPaste 增强把空洞实例粘贴到背景区域,人为增加空洞样本量;Mosaic 增强混合四张图,让模型在拼接场景下学习不同缺陷的共现关系;针对空洞类目单独做了离线过采样,训练时每个 epoch 重复采样空洞实例两次。
光照问题则补充了随机对比度、随机 gamma 和局部光照遮罩三类增强,其中局部光照遮罩模拟现实中探照灯从侧面打光造成的弧形亮区,对提升模型在真实巡检视频中的鲁棒性帮助很大。
3.3 训练超参与迁移策略
模型在 ImageNet 预训练权重基础上做全量微调,输入分辨率 640×640,batch size 16,AdamW 优化器,初始学习率 0.0001,warmup 3 个 epoch,采用余弦退火调度,总共训练 60 个 epoch。
为什么不全从零训练?围岩图像和 ImageNet 自然图像之间差距是大,但 Backbone 前期层的边缘、纹理基元提取能力是通用的,全从零训练在数据量只有几千张的情况下很容易过拟合。为什么不全冻结 Backbone 只训练 Head?因为围岩缺陷的很多判别性特征分布在 Backbone 中后期的语义层里,只训 Head 会让模型学不到“细长线状结构”“片状暗区”这类高阶语义。折中方案是先解冻全部层用低学习率训 20 个 epoch,再加大学习率训 40 个 epoch,效果比固定学习率从头到尾稳定不少。
4. 实验结果:涨点到底涨在哪
4.1 原版和 RFAConv 版本的定量对比
测试集从原始数据中按隧道区段划分,保证训练集和测试集不包含同一段岩壁的影像,避免过拟合假象。最终结果:
| 配置 | mask mAP50 | mask mAP50-95 | box mAP50 | 漏检率 | FPS |
|---|---|---|---|---|---|
| YOLO13-SEG 原版 | 68.2% | 41.5% | 72.8% | 19.4% | 31 |
| + RFAConv(Backbone) | 72.1% | 44.6% | 75.4% | 14.3% | 29 |
| + RFAConv(Backbone+Neck) | 74.7% | 46.9% | 77.6% | 11.2% | 27 |
在保证推理帧率不低于 27 FPS 的前提下,mask mAP50 涨了 6.5 个百分点,漏检率压到了 11.2%。项目验收时更参考的是漏检率,这个指标比 mAP 更能反映现场实际风险。
4.2 把涨点拆开看:不是均匀提升,而是结构性改善
把每类目标的指标单独拆出来之后,RFAConv 的增益分布非常有意思。裂缝和节理这类线状目标的召回率分别提升了 9.1% 和 7.6%,空洞类目标的召回率提升了 12.3%,而渗水痕迹的指标几乎没怎么变。
这个结果和 RFAConv 的机制高度吻合:裂缝、节理这类目标需要的是“沿延伸方向扩大感受野”的能力,空洞需要的是“从背景中分割出不规则闭合区域”的能力。渗水痕迹本身是一片大面积的连续暗色区域,边缘和背景的区别相对明显,普通卷积已经够了。反过来也说明,如果任务里全是背景清晰的大目标,RFAConv 带来的增益会非常有限,没必要为了改进而改进。
另一个有意思的变化是 mask 边缘质量。我们统计了预测掩膜和真值掩膜的平均边界距离,原版大约在 3.7 像素,加入 RFAConv 后压到了 2.8 像素。这直接影响到后续工程上对裂缝宽度和空洞面积的估算精度,对判级来说是实打实的价值。
4.3 特征热力图上看到的直观差异
算法团队内部复盘时,我们把 Backbone 最后一层输出特征做个可视化的梯度类激活图。在原版模型里,细长裂缝区域的激活往往断断续续,可能在裂缝中断处明显变弱,说明模型看到的是“几段碎片”,没有真正把它们连成一条线。加入 RFAConv 之后,激活沿着裂缝走向呈现出更完整的连续分布,边缘处的响应峰值也更集中。空洞区域的热力响应从原先的弥散模糊变得向轮廓内收缩,背景干扰明显下降。
这类可视化结果不容易写进验收报告,但对下一次迭代方向很有参考价值:模型现在把注意力放在缺陷本体上,而不是被背景纹理带偏。
5. 现场落地与常见问题排查
5.1 一路部署到边缘设备的完整链路
训练完成之后,模型要先从 PyTorch 权重转成 ONNX,再做算子融合,最后转成 TensorRT FP16 引擎部署在边缘计算盒子上。转换过程中有两个坑一定要提前避开:第一,ONNX 导出时把动态轴全部固定成 1×3×640×640,动态分辨率在 TensorRT 某些版本上会触发部分算子回退 CPU,直接横跨跨 20 毫秒级延迟;第二,RFAConv 里的 F.interpolate 在多版本 TensorRT 上存在兼容差异。
我们在部署机上先做 FP16,实测 640 输入下延迟约 11 毫秒,比 PyTorch 推理快了一倍多。INT8 量化又缩到 8 毫秒以内,但实测发现裂缝宽度很小的目标在 INT8 下掩膜边缘会出现锯齿,而且漏检率有小幅回升。后来采取的做法是:FP16 推理 + 输出端对低置信度结果做小尺度光流跟踪,只在连续三帧都显示同一位置存在低置信度缺陷时才忽略它,否则保留并提醒人工复核。这种“模型 + 时序策略”的组合,比单纯压量化位宽稳妥得多。
5.2 几个必须提前知道的坑
整理几个我们调试过程中踩过且确认有共性的坑:
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 训练 loss 下降正常,但 mAP 长期不涨 | 训练集和验证集划分不独立,数据泄漏 | 按隧道区段划分,不要按帧随机划分 |
| 空洞类目标 mAP 高但现场频繁漏检 | 训练数据里空洞多集中在明亮区域,暗部覆盖不足 | 补充夜间/背光增强,或直接采集背光段样本 |
| 掩膜边界毛刺严重,面积计算不可靠 | 标注时掩膜边缘松动,或者后处理没做条件侵蚀 | 标注规范收紧到 1~2 像素,推理时对掩膜做轻度腐蚀再算面积 |
| 现场换了一个隧道后性能明显下降 | 不同隧道的岩性、喷射混凝土工艺导致纹理差异 | 做增量微调,用小规模目标域数据进行 10 epoch 重训 |
5.3 评估指标在围岩场景里的局限性
说句实话,mAP 这个指标在围岩缺陷场景下有很大误导性。mAP 对类别分布和 IoU 阈值都非常敏感,而现场安全评估真正关心的是“有没有漏掉任何一处可能构成风险的缺陷”,其次是“误报太多会不会导致工程师失去耐心”。我们后来在项目报告里增加了两个自定指标:按长度加权的裂缝召回率,和按面积加权的空洞召回率,权重分别对应缺陷的实际安全影响。这样调模型时,就不会只盯着 mAP 数字,而是回到业务目标本身。
6. 今后的扩展方向
RFAConv 这个改造解决的是感受野自适应问题,但围岩缺陷识别还有几个方向值得继续深挖。一个是沿着时间维度的帧间信息融合,把同一段落连续多帧图像的特征做交叉注意力,用稳定的背景来校验缺陷的位置,能进一步压低漏检率。另一个是显式地把裂缝的连续性作为结构化约束加入训练目标,让模型的掩膜输出更适合下游骨架提取和宽度测量,而不是只做逐像素分类。
我自己做下来的最大体会是,改进方法要贴着项目误差去选,而不是看着榜单涨点就抄。YOLO13-SEG 的表现稍弱,误差集中在细长目标的召回率和空洞目标的误检上,RFAConv 恰好同时命中这两个痛点。如果你手上项目的误差类型完全不同,随便塞一个注意力模块进去很可能只涨零点几个点,那就完全没必要。拿到一个新任务,先花时间搞清楚模型错在哪里,再做针对性改进,这条路比到处试新模块稳妥太多。