道路缺陷检测:YOLO + 频域增强模块 FFA

张开发
2026/4/18 22:29:24 15 分钟阅读

分享文章

道路缺陷检测:YOLO + 频域增强模块 FFA
Rvosuke原创若有代码需求或者数据集随时私聊~我们的任务是道路缺陷检测。输入是一张道路图像输出是若干个缺陷目标的位置和类别。这类任务和“检测猫狗汽车”不同。道路缺陷往往不是轮廓完整的物体而更像一种异常纹理。裂缝是细长的坑洞边缘不规则修补痕迹与正常路面又很接近。所以单靠普通卷积去看空域纹理容易漏掉真正重要的异常边缘。我们使用的是 YOLO11可概括为 Backbone、Neck、Head 三段式结构Backbone 负责特征提取Neck 负责多尺度特征融合Head 负责解耦检测输出并在 P3、P4、P5 三个尺度上分别处理小、中、大目标。从项目角度看YOLO11 有三个现实优势它是成熟的检测基线。训练、验证、推理、导出都很顺。它本身已经兼顾速度和精度。文档里提到相比 YOLOv8YOLO11 在参数量接近甚至更少的情况下mAP 还有提升并适合边缘设备部署。它的结构比较利于插模块。尤其是 Backbone 的中间层本来就在做纹理和局部结构建模很适合插入一种对高频异常更敏感的分支。YOLO11 的问题如果直接把 YOLO11 用在道路缺陷检测上它当然能工作但会遇到一个很典型的问题卷积更擅长提取局部空域模式却不一定显式地区分“平滑路面纹理”和“异常高频边缘”。而道路裂缝、坑洞边界本质上正是高频信息更强的区域。频域分析对这类纹理异常比纯空域卷积更敏感因此我们提出把一个可插拔的 FFA 模块嵌入到 YOLO11 Backbone 中用频域分支增强对缺陷纹理的建模。这就是本文最核心的思路用 YOLO11 做主干检测用频域模块补足它对高频异常纹理的感知。FFA 模块FFA 全称可以理解为 Frequency Feature Attention。它仅是在 Backbone 的特定层替换 C3k2 模块。我们优先替换 S3再视情况考虑 S4。 S3 对应 80×80 分辨率频域信息仍然比较丰富S4 到 40×40 后频谱可表达的信息会少很多。它可以拆成 5 步来看。先降维输入特征图是B × C × H × W。在进入频域前先用1×1 Conv把通道从C压到C我们建议取C//2。这样做是因为 FFT 的开销与通道数直接相关。通道先压缩可以明显降低后续频域计算量。对特征图做 2D rFFT然后对降维后的特征图做torch.fft.rfft2。注意这里用的是rfft2而不是fft2因为输入是实数特征图频谱具有共轭对称性只保留一半就够了。输出形状会从B × C × H × W变成B × C × H × (W//21)而且数据类型变成复数。在复数频谱上做 MLP这一点很关键。复数不能直接丢给普通线性层于是我们采用“实部一路、虚部一路”的方式分别对real和imag做 MLP再重新组合成复数。这样网络可以分别学习频谱幅度和相位的变换。用频率门控强调高频这是 FFA 最有针对性的部分。我们给出一个可学习的mask它和频谱同形状并通过sigmoid变成门控系数。初始化时让“远离 DC 的频率位置”有更高偏置等于给模型一个先验高频更可能包含裂缝和坑洞边缘。训练过程中再让模型自己决定保留哪些频率。回到空域再走残差经过门控后的频谱通过irfft2回到空域再用1×1 Conv升维到原通道数最后与输入做残差相加。这样即使频域分支学得不好主干信息也不会被破坏训练会稳很多。如果只看公式会有些抽象。直觉上可以这样理解平滑路面的大部分能量集中在低频。裂缝、坑洞边缘、突然的纹理变化会在高频位置有更强响应。这也是我们提出的物理基础道路裂缝在频谱中对应远离中心的高频区域而正常路面的能量更靠近 DC 附近。因此FFA 的目标是补上一条卷积天然没有显式建模的路径让网络在训练时直接学会“哪些频率对缺陷真的有用”。简化版 PyTorch 实现importtorchimporttorch.nnasnnimporttorch.nn.functionalasFclassFreqMLP(nn.Module):def__init__(self,channels,hidden_ratio4):super().__init__()hiddenchannels*hidden_ratio self.real_mlpnn.Sequential(nn.Linear(channels,hidden),nn.GELU(),nn.Linear(hidden,channels))self.imag_mlpnn.Sequential(nn.Linear(channels,hidden),nn.GELU(),nn.Linear(hidden,channels))defforward(self,x):# x: [B, C, H, Wf], complexrealx.real.permute(0,2,3,1)# [B, H, Wf, C]imagx.imag.permute(0,2,3,1)realself.real_mlp(real).permute(0,3,1,2)imagself.imag_mlp(imag).permute(0,3,1,2)returntorch.complex(real,imag)classFFAModule(nn.Module):def__init__(self,in_channels,freq_ratio0.5,mlp_ratio4):super().__init__()freq_cmax(1,int(in_channels*freq_ratio))self.proj_innn.Sequential(nn.Conv2d(in_channels,freq_c,1,biasFalse),nn.BatchNorm2d(freq_c),nn.GELU())self.freq_mlpFreqMLP(freq_c,hidden_ratiomlp_ratio)self.proj_outnn.Sequential(nn.Conv2d(freq_c,in_channels,1,biasFalse),nn.BatchNorm2d(in_channels))self.gatesnn.ParameterDict()def_get_gate(self,H,Wf,device):keyf{H}_{Wf}ifkeynotinself.gates:gatenn.Parameter(torch.zeros(1,1,H,Wf,devicedevice))fytorch.fft.fftfreq(H,devicedevice).abs()fxtorch.linspace(0,0.5,Wf,devicedevice)dist(fy[:,None]**2fx[None,:]**2).sqrt()gate.datadist/(dist.max()1e-6)self.gates[key]gatereturnself.gates[key]defforward(self,x):B,C,H,Wx.shape shortcutx zself.proj_in(x)zftorch.fft.rfft2(z,normortho)zfself.freq_mlp(zf)gateself._get_gate(H,W//21,x.device)zfzf*torch.sigmoid(gate)ztorch.fft.irfft2(zf,s(H,W),normortho)outself.proj_out(z)shortcutreturnF.gelu(out)如果想验证rfft2的输出形状最简单的测试如下xtorch.randn(2,64,80,80)outtorch.fft.rfft2(x,normortho)print(out.shape)# [2, 64, 80, 41]print(out.dtype)# complex64x_backtorch.fft.irfft2(out,s(80,80),normortho)print(torch.allclose(x,x_back,atol1e-5))# Truenormortho的意义它能让 FFT 和 iFFT 的数值尺度更稳定避免随着分辨率变化而引起幅值漂移。模块位置我们建议S1、S2 保留原结构优先在 S3 替换 C3k2有收益后再尝试 S4如果 S3S4 一起替换反而没提升很可能是 S4 分辨率太低频谱信息不足。道路缺陷尤其是裂缝需要一定的空间分辨率才能在频域里表现出有效差异。层太浅语义不足层太深分辨率又太低。S3 正好处在一个比较平衡的位置。

更多文章