精读双模态目标检测论文系列四|C²DFF-Net 架构的二次改进创新(附可运行代码 + 二次顶刊创新思路)

张开发
2026/4/8 13:03:15 15 分钟阅读

分享文章

精读双模态目标检测论文系列四|C²DFF-Net 架构的二次改进创新(附可运行代码 + 二次顶刊创新思路)
大家好这里是双模态遥感目标检测精读系列第四篇在系列一中我们精读 IEEE TGRS 2025 顶刊论文C²DFF-Net聚焦可见光 - 红外双模态遥感小目标检测的这一篇论文。这是一篇非常经典、思路较为新颖的双模态小目标检测的论文但是盲目地、生搬硬套把模块融入自己的模型很有可能不会出现较大的涨点现在我们就C²DFF-Net提出的模块进行一些改进。https://blog.csdn.net/2201_75517551/article/details/159857444?spm1001.2014.3001.55021.跨模态差分特征交互模块 CDFIM的重新思考1.1 Spatial Attention Enhancement的必要性Spatial Attention Enhancement空间注意力增强 是 CDFIM 模块的核心后半段专门在空间维度对差分特征做强化。核心作用多尺度空间感受野用 5×5、1×77×1、1×1111×1、1×2121×1 并行卷积覆盖从小目标到细长目标车、船、飞机。轻量化空间聚焦全部用深度可分离卷积groupschannels计算量极低同时让模型 “看” 准目标位置。细长目标专属优化条形卷积strip convolution非常适合遥感里车辆、卡车、巴士、船舶等长条形状目标。抑制背景噪声生成空间注意力图给目标区域加权、背景区域降权解决小目标淹没在背景里的问题。通道 - 空间双维增强前面是通道注意力后面是空间注意力形成双维度互补强化。注意Spatial Attention Enhancement是在做完Channel Attention Enhancement对双模态增强后在进行的但是将该模块融入我的模型训练时发现去掉Spatial Attention Enhancement之后代码不仅参数减少了同时检测的精度也提高了。去掉之后的模块代码如下大家不妨试试import torch import torch.nn as nn import torch.nn.functional as F class CPCA_ChannelAttention(nn.Module): # 通道注意力保留这部分是有效核心 def __init__(self, input_channels, internal_neurons): super(CPCA_ChannelAttention, self).__init__() self.fc1 nn.Conv2d(input_channels, internal_neurons, 1, 1, biasTrue) self.fc2 nn.Conv2d(internal_neurons, input_channels, 1, 1, biasTrue) self.input_channels input_channels def forward(self, inputs): x1 F.adaptive_avg_pool2d(inputs, (1, 1)) x1 self.fc1(x1) x1 F.relu(x1, inplaceTrue) x1 self.fc2(x1) x1 torch.sigmoid(x1) x2 F.adaptive_max_pool2d(inputs, (1, 1)) x2 self.fc1(x2) x2 F.relu(x2, inplaceTrue) x2 self.fc2(x2) x2 torch.sigmoid(x2) x x1 x2 x x.view(-1, self.input_channels, 1, 1) return inputs * x # 简化版 CPCA无空间注意力 class CPCA(nn.Module): def __init__(self, c1, c2, channelAttention_reduce4): super().__init__() channels sum(c1) if isinstance(c1, list) else c1 * 2 out_channels c2 self.conv1 nn.Conv2d(channels, channels, 1, 1) self.ca CPCA_ChannelAttention(channels, channels // channelAttention_reduce) self.conv2 nn.Conv2d(channels, out_channels, 1, 1) self.act nn.GELU() def forward(self, x): # 跨模态 concat inputs torch.cat((x[0], x[1]), dim1) inputs self.conv1(inputs) inputs self.act(inputs) inputs self.ca(inputs) # 仅保留通道注意力 out self.conv2(inputs) return out # 简化版 CDFIM无空间注意力 class CDFIM(nn.Module): def __init__(self, c1, c2, channelAttention_reduce4): super().__init__() channels c1[0] if isinstance(c1, list) else c1 out_channels c2 self.conv1 nn.Conv2d(channels, channels, 1, 1) self.ca CPCA_ChannelAttention(channels, channels // channelAttention_reduce) self.conv2 nn.Conv2d(channels, out_channels, 1, 1) self.act nn.GELU() def forward(self, x): # 跨模态差分 inputs x[0] - x[1] inputs self.conv1(inputs) inputs self.act(inputs) inputs self.ca(inputs) # 仅保留通道注意力 # 原版残差模态补偿保留有效 inputs0 inputs x[0] inputs1 inputs x[1] inputs inputs0 inputs1 out self.conv2(inputs) return out分析原因1.多尺度 strip 卷积1×7/7×1…产生「空间噪声」你代码里的空间注意力用了5×51×7 7×11×11 11×11×21 21×1问题遥感小目标车、船、飞机尺寸很小大卷积核11、21会把背景噪声也一起加权进来相当于让模型 “看错地方”。→ 去掉后模型不再被大卷积干扰定位更准。2.CDFIM 是差分模态x [0]-x [1]空间注意力会放大模态差异噪声CDFIM 输入是inputs x[0] - x[1] # 可见光 - 红外模态差异本身就有噪声、偏移、不对齐。空间注意力一加权 →噪声被放大梯度变得不稳定。→ 去掉后梯度更干净训练更稳定。1.2 Channel Attention Enhancement中CPCA_ChannelAttention 的重新思考必要性双池化互补平均池化擅长抓全局分布最大池化擅长抓显著目标。遥感小目标 多模态可见光 红外非常需要这种互补。无空间维度操作极其稳定它只在通道维度做注意力不引入空间噪声。这就是为什么你去掉空间注意力后精度上升但通道注意力依然有效。轻量化、无额外结构负担只有两个 1×1 卷积计算量极小不增加训练难度。多模态融合非常友好你的 CDFIM / CPCA 是做可见光 - 红外差异融合通道注意力能自动判断哪些通道来自可见光、哪些来自红外、哪些是差异特征并自动加权非常适合模态对齐。就源代码来看就这个CPCA_ChannelAttention 稍微简单了点我们可就此进行创新。1.2.1 换MLP想要比较大的创新我可以提供一个思路使用KAN的强大的非线性拟合能力来进行替换。1.2.2 在双模态差分增强之前加上空间注意力我提供一个简单的示例使用CBAM这个经典的空间注意力进行替换代码如下import torch import torch.nn as nn import torch.nn.functional as F # # 标准 CBAM 注意力官方实现 # class ChannelAttention(nn.Module): def __init__(self, in_channels, reduction16): super(ChannelAttention, self).__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.fc nn.Sequential( nn.Conv2d(in_channels, in_channels // reduction, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(in_channels // reduction, in_channels, 1, biasFalse) ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.fc(self.avg_pool(x)) max_out self.fc(self.max_pool(x)) out avg_out max_out return self.sigmoid(out) class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super(SpatialAttention, self).__init__() self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size//2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out, _ torch.max(x, dim1, keepdimTrue) x torch.cat([avg_out, max_out], dim1) x self.conv(x) return self.sigmoid(x) class CBAM(nn.Module): def __init__(self, in_channels, reduction16): super(CBAM, self).__init__() self.channel_att ChannelAttention(in_channels, reduction) self.spatial_att SpatialAttention() def forward(self, x): # 先通道 → 再空间 x x * self.channel_att(x) x x * self.spatial_att(x) return x # # CBAM 版 CPCAconcat 融合模态 # class CPCA_CBAM(nn.Module): def __init__(self, c1, c2, reduction16): super().__init__() channels sum(c1) if isinstance(c1, list) else c1 * 2 out_channels c2 self.conv1 nn.Conv2d(channels, channels, 1, 1) self.cbam CBAM(channels, reduction) # 替换为 CBAM self.conv2 nn.Conv2d(channels, out_channels, 1, 1) self.act nn.GELU() def forward(self, x): inputs torch.cat((x[0], x[1]), dim1) inputs self.conv1(inputs) inputs self.act(inputs) inputs self.cbam(inputs) # CBAM 增强 out self.conv2(inputs) return out # # CBAM 版 CDFIM差分融合模态 # 你最常用、精度最高的核心模块 # class CDFIM_CBAM(nn.Module): def __init__(self, c1, c2, reduction16): super().__init__() channels c1[0] if isinstance(c1, list) else c1 out_channels c2 self.conv1 nn.Conv2d(channels, channels, 1, 1) self.cbam CBAM(channels, reduction) # 替换为 CBAM self.conv2 nn.Conv2d(channels, out_channels, 1, 1) self.act nn.GELU() def forward(self, x): # 多模态差分可见光 - 红外 inputs x[0] - x[1] inputs self.conv1(inputs) inputs self.act(inputs) inputs self.cbam(inputs) # CBAM 增强 # 模态残差补偿必须保留非常有效 inputs0 inputs x[0] inputs1 inputs x[1] inputs inputs0 inputs1 out self.conv2(inputs) return out2. 跨域门控自注意力模块 CGSACGSA 由CFE 跨域特征提取 → SAFF 自注意力融合 → AG 自适应门控三部分组成实现空间‑频域联合建模。但是经过实验表明这个加上FFT创新固然很好但是非常容易大致梯度的一个爆炸我们不妨试试去掉FFT操作实验表明会更好训练速度提高参数量下降同时精度略有提高。代码如下import torch import torch.nn as nn import torch.nn.functional as F # --------------------------------------------------------- # 依赖模块: AG 门控多模态层 (保留核心的门控融合思想) # --------------------------------------------------------- class GatedMultimodalLayer(nn.Module): def __init__(self, in_channels, out_channels64): super(GatedMultimodalLayer, self).__init__() self.hidden1 nn.Conv2d(in_channels, out_channels, kernel_size1, padding0, biasFalse) self.hidden2 nn.Conv2d(in_channels, out_channels, kernel_size1, padding0, biasFalse) self.hidden_sigmoid nn.Conv2d(out_channels * 2, 1, kernel_size1, biasFalse) # 激活函数 self.tanh_f nn.Tanh() self.sigmoid_f nn.Sigmoid() def forward(self, x1, x2): # 应用卷积层和激活函数 h1 self.tanh_f(self.hidden1(x1)) h2 self.tanh_f(self.hidden2(x2)) # 将结果拼接 x torch.cat((x1, x2), dim1) # 计算门控信号 (类似于 LSTM 的遗忘门/输入门) z self.sigmoid_f(self.hidden_sigmoid(x)) # 计算加权交叉和 return z * h1 (1 - z) * h2 # --------------------------------------------------------- # 核心替换模块: CGSA (纯净极速版已移除频域操作) # --------------------------------------------------------- class CGSA(nn.Module): # 修改了接口接收 c1, c2 完美兼容 YOLO def __init__(self, c1, c2): super().__init__() # 接口适配提取单路通道数 channel c1[0] if isinstance(c1, list) else c1 self.ch_wv nn.Conv2d(channel, channel // 2, kernel_size(1, 1)) self.ch_wq nn.Conv2d(channel, 1, kernel_size(1, 1)) self.softmax_channel nn.Softmax(1) self.ch_wz nn.Conv2d(channel // 2, channel, kernel_size(1, 1)) self.ln nn.LayerNorm(channel) self.sigmoid nn.Sigmoid() # LSTM 门控融合 self.ga1 GatedMultimodalLayer(channel, channel) self.ga2 GatedMultimodalLayer(channel, channel) # 输出通道对齐 c2 (完美替代 Concat 的维度) self.conv nn.Conv2d(channel * 2, c2, kernel_size(1, 1)) def forward(self, x): # 接收 YOLO 传来的双路列表 rgb, ir x[0], x[1] b, c, h, w rgb.size() # 移除了频率处理直接进行跨模态通道交互 # 1. 跨模态联合通道注意力 (Channel-only Self-Attention) channel_wv_rgb self.ch_wv(rgb).reshape(b, c // 2, -1) channel_wq_rgb self.ch_wq(rgb).reshape(b, -1, 1) channel_wv_ir self.ch_wv(ir).reshape(b, c // 2, -1) channel_wq_ir self.ch_wq(ir).reshape(b, -1, 1) # 特征激活计算将 RGB 和 IR 的注意力查询相加产生联合注意力掩码 channel_wq_rgb_ir channel_wq_rgb channel_wq_ir channel_wq_rgb_ir self.softmax_channel(channel_wq_rgb_ir) # 反哺 RGB 分支 channel_wz_rgb torch.matmul(channel_wv_rgb, channel_wq_rgb_ir).unsqueeze(-1) channel_weight_rgb self.sigmoid(self.ln(self.ch_wz(channel_wz_rgb).reshape(b, c, 1).permute(0, 2, 1))).permute(0, 2, 1).reshape(b, c, 1, 1) # 反哺 IR 分支 channel_wz_ir torch.matmul(channel_wv_ir, channel_wq_rgb_ir).unsqueeze(-1) channel_weight_ir self.sigmoid(self.ln(self.ch_wz(channel_wz_ir).reshape(b, c, 1).permute(0, 2, 1))).permute(0, 2, 1).reshape(b, c, 1, 1) out_rgb channel_weight_rgb * rgb out_ir channel_weight_ir * ir # 2. 门控融合 (Gated Multimodal Fusion) channel_out_rgb self.ga1(out_ir, rgb) channel_out_ir self.ga2(out_rgb, ir) # 3. 拼接与降维对齐 out torch.cat((channel_out_rgb, channel_out_ir), 1) out self.conv(out) return out同时也不妨使用没有改进的PSA代码如下import torch import torch.nn as nn class PSA(nn.Module): ###########################串联 QV互乘############################################# # 修复 1接口改为 YOLO 标准的 c1, c2 def __init__(self, c1, c2): super().__init__() # 修复 2如果 c1 是列表(如 [512, 512])则提取第一个通道数 512如果是整数则直接用 channel c1[0] if isinstance(c1, list) else c1 self.ch_wv nn.Conv2d(channel, channel // 2, kernel_size(1, 1)) self.ch_wq nn.Conv2d(channel, 1, kernel_size(1, 1)) self.softmax_channel nn.Softmax(1) self.softmax_spatial nn.Softmax(-1) self.ch_wz nn.Conv2d(channel // 2, channel, kernel_size(1, 1)) self.ln nn.LayerNorm(channel) self.sigmoid nn.Sigmoid() self.sp_wv nn.Conv2d(channel, channel // 2, kernel_size(1, 1)) self.sp_wq nn.Conv2d(channel, channel // 2, kernel_size(1, 1)) self.agp nn.AdaptiveAvgPool2d((1, 1)) self.conv1 nn.Conv2d(2, 1, kernel_size(1, 1)) self.conv2 nn.Conv2d(channel, channel // 2, kernel_size(1, 1)) # 修复 3输出通道对齐 YOLO 的 c2 (完美替代 Concat) self.conv nn.Conv2d(channel * 2, c2, kernel_size(1, 1)) def forward(self, x): rgb, ir x[0], x[1] # 1.64.3232 b, c, h, w x[0].size() # 1,64,32,32 # Channel-only Self-Attention channel_wv_rgb self.ch_wv(rgb) # bs,c//2,h,w 1,32,32,32 channel_wq_rgb self.ch_wq(rgb) # bs,1,h,w 1,32,32,32 channel_wv_rgb channel_wv_rgb.reshape(b, c // 2, -1) # bs,c//2,h*w 1,32,1024 channel_wq_rgb channel_wq_rgb.reshape(b, -1, 1) # bs,h*w,1 1,64*641024,1 channel_wv_ir self.ch_wv(ir) # bs,c//2,h,w 1,32,3232 channel_wq_ir self.ch_wq(ir) # bs,1,h,w1,1,3232 channel_wv_ir channel_wv_ir.reshape(b, c // 2, -1) # bs,c//2,h*w 1321024 channel_wq_ir channel_wq_ir.reshape(b, -1, 1) # bs,h*w,1 110241 # 用QV相乘交互 channel_wq_rgb self.softmax_channel(channel_wq_rgb) # (110241) channel_wq_ir self.softmax_channel(channel_wq_ir) # (110241) channel_wz_rgb torch.matmul(channel_wv_rgb, channel_wq_ir).unsqueeze( -1) # bs,c//2,1,1 1,32,1024*(110241)13211 channel_wz_ir torch.matmul(channel_wv_ir, channel_wq_rgb).unsqueeze(-1) # bs,c//2,1,1 13211 channel_weight_rgb self.sigmoid( self.ln(self.ch_wz(channel_wz_rgb).reshape(b, c, 1).permute(0, 2, 1))).permute(0, 2, 1).reshape(b, c, 1, 1) # bs,c,1,116411 channel_weight_ir self.sigmoid(self.ln(self.ch_wz(channel_wz_ir).reshape(b, c, 1).permute(0, 2, 1))).permute( 0, 2, 1).reshape(b, c, 1, 1) # bs,c,1,1 16411 ######################################################################################## out_rgb channel_weight_rgb * rgb # 16411*16432321643232 out_ir channel_weight_ir * ir channel_out_rgb torch.add(out_ir, rgb) # 1643232 channel_out_ir torch.add(out_rgb, ir) # Spatial-only Self-Attention 串行 spatial_wv_rgb self.sp_wv(channel_out_rgb) # bs,c//2,h,w1323232 spatial_wq_rgb self.sp_wq(channel_out_rgb) # bs,c//2,h,w1323232 spatial_wq_rgb self.agp(spatial_wq_rgb) # bs,c//2,1,1 13211 spatial_wv_rgb spatial_wv_rgb.reshape(b, c // 2, -1) # bs,c//2,h*w 1321024 spatial_wq_rgb spatial_wq_rgb.permute(0, 2, 3, 1).reshape(b, 1, c // 2) # bs,1,c//2 1132 spatial_wv_ir self.sp_wv(channel_out_ir) # bs,c//2,h,w1323232 spatial_wq_ir self.sp_wq(channel_out_ir) # bs,c//2,h,w spatial_wq_ir self.agp(spatial_wq_ir) # bs,c//2,1,1 spatial_wv_ir spatial_wv_ir.reshape(b, c // 2, -1) # bs,c//2,h*w spatial_wq_ir spatial_wq_ir.permute(0, 2, 3, 1).reshape(b, 1, c // 2) # bs,1,c//2 1132 spatial_wq_rgb self.softmax_spatial(spatial_wq_rgb) # (1,1,32) spatial_wq_ir self.softmax_spatial(spatial_wq_ir) # (1,1,32) spatial_wz_rgb torch.matmul(spatial_wq_ir, spatial_wv_rgb) # bs,1,h*w #(1,1,32)mul()1321024(1,1,1024) spatial_weight_rgb self.sigmoid(spatial_wz_rgb.reshape(b, 1, h, w)) # bs,1,h,w reshape后为113232 out_rgb spatial_weight_rgb * channel_out_rgb # 113232*(1643232(1,64,32,32) spatial_wz_ir torch.matmul(spatial_wq_rgb, spatial_wv_ir) # bs,1,h*w spatial_weight_ir self.sigmoid(spatial_wz_ir.reshape(b, 1, h, w)) # bs,1,h,w out_ir spatial_weight_ir * channel_out_ir spatial_out_rgb torch.add(out_ir, channel_out_rgb) spatial_out_ir torch.add(out_rgb, channel_out_ir) # (1,64,32,32) out torch.cat((spatial_out_rgb, spatial_out_ir), 1) # (1,128,32,32) out self.conv(out) # 为了不改变通道数用卷积通道数减半/对齐c2 return out以上是我对于该论文的一些简单的思考欢迎大家一起交流学习后续将进行更新以及进行二次创新发顶刊必备。。。敬请关注笔者整理双模态检测的专属论文资料免费分享给粉丝需要关注后领

更多文章