01 论文信息

  • 论文题目: Misaligned Visible-Thermal Object Detection: A Drone-Based Benchmark and Baseline
  • 论文作者: Kechen Song, Xiaotong Xue, Hongwei Wen, Yingying Ji, Yunhui Yan, Qinggang Meng
  • 发表单位: 东北大学,拉夫堡大学
  • 发表会议\期刊: TIV 2024
  • 代码链接: https://github.com/VDT-2048/DVTOD

02 论文主要贡献

1.构建了首个基于无人机的错位可见光 - 热红外目标检测数据集 DVTOD
2.提出一种新型跨模态对齐检测器,解决了错位可见光 - 热红外目标检测问题;
3.在 DVTOD 数据集及另外两个具有挑战性的目标检测数据集上进行了大量实验,结果表明 CMA-Det 的性能优于其他对比方法。


03.方法

3.1 框架概述

在这里插入图片描述
如图 5 所示,由于热红外模态不受光照影响,本文将热红外图像作为参考图像,可见光图像作为感知图像。扩展 YOLOv5 [37] 框架以实现可见光 - 热红外目标检测,采用两个并行的骨干网络分别提取可见光与热红外特征。首先,将提取的特征输入目标搜索校正模块,捕捉参考特征与感知特征之间的相关性,对感知特征进行特征校正,并通过自注意力等操作对参考特征进行特征增强;然后,利用参考特征引导感知特征进行空间特征对齐;接着,对对齐后的特征与增强后的参考特征进行跨通道交互特征校正,进一步实现双模态特征的空间对齐与融合;最后,将融合后的特征输入特征金字塔网络(Feature Pyramid Network,FPN)[38],进行进一步的特征整合与细化。

3.2 空间特征对齐网络

由于热红外图像不受光照影响,本文将其作为参考图像,可见光图像作为感知图像。设计新型特征匹配器以获取从可见光特征到热红外特征的形变场,并基于该形变场进行重采样,实现模态对齐。特征匹配器捕捉模态间的特征位移( x x x, y y y )与维度缩放因子( w w w, h h h)。
在这里插入图片描述
如图 6 所示,给定经过校正的两种模态特征 F v F_ v Fv(可见光特征)和 F t F_t Ft(热红外特征),为降低计算量,通过直接相减两种模态特征得到新的特征映射 F s F_s Fs。受空间变换网络(Spatial Transformer Network,STN)[39] 的启发,设计变换矩阵,将感知特征图从原始坐标系转换到参考特征坐标系。具体而言,将 F s F_s Fs输入仿射网络(AffineNet),该网络用于回归四个参数( x x x, y y y, w w w, h h h)。该网络堆叠四个 4 倍下采样的卷积块,并连接全局平均池化层与卷积层,以预测位置偏移因子与尺度缩放因子。得到变换矩阵 M M M后,通过深度学习库 PyTorch 的积分函数可轻松得到扭曲场 φ φ φ,然后根据φ对可见光特征进行重采样,得到对齐后的感知特征。

class MAM2(nn.Module):
    """多模态对齐模块"""
    def __init__(self, in_channel):
        super(MAM2, self).__init__()  # 调用父类初始化
        self.channel264 = nn.Sequential(  # 通道降维序列(使用单个输入)
            Conv(in_channel, in_channel//2, 3, 2, 1),  # 第一个3x3卷积:通道数减半,步长2
            convblock(in_channel//2, in_channel//4, 3, 1, 1),  # 第二个3x3卷积:通道数再减半
            convblock(in_channel//4, in_channel//8, 3, 1, 0),  # 第三个3x3卷积:通道数再减半
            convblock(in_channel//8, in_channel//16, 3, 1, 1),  # 第四个3x3卷积:通道数再减半
            convblock(in_channel//16, 16, 1, 1, 1),  # 第五个1x1卷积:降到16通道
        )
        self.xy = nn.Sequential(  # 平移参数预测序列
            nn.AdaptiveAvgPool2d(1),  # 自适应平均池化到1x1
            nn.Conv2d(16, 2, 1, 1, 0)  # 1x1卷积:输出2个平移参数(x, y)
        )
        self.scale1 = nn.Sequential(  # 第一个缩放参数预测序列
            nn.AdaptiveAvgPool2d(1),  # 自适应平均池化到1x1
            nn.Conv2d(16, 1, 1, 1, 0)  # 1x1卷积:输出1个缩放参数(x方向)
        )
        self.scale2 = nn.Sequential(  # 第二个缩放参数预测序列
            nn.AdaptiveAvgPool2d(1),  # 自适应平均池化到1x1
            nn.Conv2d(16, 1, 1, 1, 0)  # 1x1卷积:输出1个缩放参数(y方向)
        )
        # 从恒等变换开始
        self.xy[-1].weight.data.normal_(mean=0.0, std=5e-4)  # 使用正态分布初始化平移权重
        self.xy[-1].bias.data.zero_()  # 将平移偏置初始化为0
        self.scale1[-1].weight.data.normal_(mean=0.0, std=5e-4)  # 使用正态分布初始化第一个缩放权重
        self.scale1[-1].bias.data.zero_()  # 将第一个缩放偏置初始化为0
        self.scale2[-1].weight.data.normal_(mean=0.0, std=5e-4)  # 使用正态分布初始化第二个缩放权重
        self.scale2[-1].bias.data.zero_()  # 将第二个缩放偏置初始化为0
        # self.fus1 = Conv(in_channel * 2, in_channel, 1, 1, 0)  # 注释掉的融合卷积
def forward(self, x):
        gr = x[0]  # 获取第一个输入
        gt = x[1]  # 获取第二个输入
       
        in_ = gt -gr  # 计算差分(gt减去gr)
        n1 = self.channel264(in_)  # 通过通道降维序列
        identity_theta = torch.tensor([1, 0, 0, 0, 1, 0], dtype=torch.float).requires_grad_(False)  # 恒等变换矩阵
      
        shift_xy = self.xy(n1)  # 预测平移参数(x, y)
        shift_s1 = self.scale1(n1)  # 预测第一个缩放参数(x方向)
        shift_s2 = self.scale2(n1)  # 预测第二个缩放参数(y方向)
        bsize = shift_xy.shape[0]  # 获取批次大小
        identity_theta = identity_theta.view(-1, 2, 3).repeat(bsize, 1, 1).cuda()  # 将恒等变换矩阵扩展为批次大小并移动到GPU
        identity_theta[:, :, 2] += shift_xy.squeeze()  # 在平移列(第3列)上加上预测的平移参数
        identity_theta[:, :1, :1] += shift_s1.squeeze(2)  # 在x方向缩放(左上角元素)上加上预测的缩放参数
        identity_theta[:, 1, 1] += shift_s2.squeeze()  # 在y方向缩放(右下角元素)上加上预测的缩放参数
        
         wrap_grid = F.affine_grid(identity_theta.view(-1, 2, 3), in_.size(), align_corners=True).permute(0, 3, 1,2).half() 
        #  根据仿射变换矩阵生成采样网格
        #   - identity_theta.view(-1, 2, 3): 将变换矩阵重塑为(batch, 2, 3)格式
        #     * 2x3矩阵表示2D仿射变换:[a, b, tx; c, d, ty]
        #     * a,d: x和y方向的缩放因子
        #     * b,c: 旋转/剪切参数
        #     * tx,ty: x和y方向的平移
        #   - in_.size(): 目标输出尺寸 (batch, channels, height, width)
        #   - align_corners=True: 对齐角点,确保角点像素精确对齐
        #   - .permute(0, 3, 1, 2): 将网格从(batch, H, W, 2)转为(batch, 2, H, W)
        #   - .half(): 转换为半精度浮点数(float16),节省内存和加速计算
        # 输出wrap_grid: (batch, 2, H, W),每个位置存储归一化到[-1, 1]的采样坐标
      
         wrap_gr = F.grid_sample(gr, wrap_grid.permute(0, 2, 3, 1), mode='bilinear', padding_mode='zeros', align_corners=True) 
        #根据采样网格对输入特征图进行双线性插值采样
        #   - gr: 输入特征图 (batch, channels, height, width),需要被对齐的特征(如RGB特征)
        #   - wrap_grid.permute(0, 2, 3, 1): 将网格转回(batch, H, W, 2)格式
        #     * grid_sample要求输入格式为(batch, H, W, 2)
        #   - mode='bilinear': 使用双线性插值进行采样
        #   - padding_mode='zeros': 超出边界的位置用0填充
        #   - align_corners=True: 与affine_grid保持一致的对齐方式
 
      
        
        return wrap_gr  # 返回对齐后的gr

3.3 目标搜索校正策略

在这里插入图片描述
由于图像对存在错位,两种模态之间不存在强像素级对应关系。然而,双流网络提取的特征感受野尺寸相同,这可能导致可见光特征图中不存在与热红外特征对应的特征。一种简单的思路是增大可见光特征的感受野,但卷积神经网络(Convolutional Neural Network,CNN)的卷积算子仍是非全局感受野,因此仅能在局部区域搜索信息。与之不同,Transformer [40] 得益于自注意力机制的应用,具有全局感受野。为更好地学习模态间的特征相关性,如图 7 所示,本文未采用自注意力机制,而是设计跨模态注意力机制,以更好地捕捉目标与搜索区域之间的相关性。

首先,通过线性映射得到三个矩阵 Q K V {QKV} QKV,并将其分块为 q t , k t , v t {q}_t, {k}_t, {v}_t qt,kt,vt(表示目标区域)和 q s , k s , v s {q}_s, {k}_s, {v}_s qs,ks,vs(表示搜索区域)。利用模态内与模态间的交互生成两个注意力图:
A t t e n t i o n t = Softmax ( q t k t ⊤ d ) v t Attention s = Softmax ( q s k t ⊤ d ) v s {Attention}_t = \text{Softmax}\left( \frac{\boldsymbol{q}_t \boldsymbol{k}_t^\top}{\sqrt{d}} \right) \boldsymbol{v}_t \\ \text{Attention}_s = \text{Softmax}\left( \frac{\boldsymbol{q}_s \boldsymbol{k}_t^\top}{\sqrt{d}} \right) \boldsymbol{v}_s Attentiont=Softmax(d qtkt)vtAttentions=Softmax(d qskt)vs
其中, d d d表示关键向量(key)的维度, A t t e n t i o n t Attention _t Attentiont A t t e n t i o n s Attention_s Attentions分别为自相关与互相关注意力图。

为提升模型提取不同模态特征的能力,将两个注意力图拼接,并采用多头注意力机制对不同头的特征进行加权。最后,通过两个全连接层整合特征:
h e a d i = Attention ( Q W i Q , K W i K , V W i V ) {head}_i = \text{Attention}(Q W_i^Q, K W_i^K, V W_i^V) headi=Attention(QWiQ,KWiK,VWiV)
M S A ( Q , K , V ) = Concat ( head 1 , … , head h ) W O {MSA}(Q,K,V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O MSA(Q,K,V)=Concat(head1,,headh)WO
其中, h h h 表示头的数量, MSA ( ⋅ ) \text{MSA}(\cdot) MSA() 表示多头自注意力机制, W O W^O WO 为拼接后的特征( Concat ( head 1 , … , head h ) \text{Concat}(\text{head}_1, \dots, \text{head}_h) Concat(head1,,headh))的映射矩阵。

与 Transformer 不同的是,本文不仅采用跨模态注意力机制,还从通道与空间两个维度学习长程依赖关系,并在特征层面整合全局上下文信息,以更好地捕捉目标与搜索区域之间的相关性。
给定可见光中间特征图 F v ∈ R C × H × W F_v \in \mathbb{R}^{C \times H \times W} FvRC×H×W 和热红外中间特征图 F t ∈ R C × H × W F_t \in \mathbb{R}^{C \times H \times W} FtRC×H×W(其中 H H H W W W 分别表示特征图的高度与宽度, C C C 表示通道维度)。受 CFT [23] 的启发,采用全局平均池化以降低计算量。将展平后的特征拼接后,添加可学习的位置编码,以更好地学习像素间的位置关联。对特征进行位置编码后,输入改进的编码器模块,然后对得到的特征进行上采样:
S k = TB k ( h k + p k ) S_k = \text{TB}_k (h_k + p_k) Sk=TBk(hk+pk)
S k v , S k t = Upsample ( Split ( S k ) ) S_{kv}, S_{kt} = \text{Upsample}\left( \text{Split}(S_k) \right) Skv,Skt=Upsample(Split(Sk))
其中, TB k \text{TB}_k TBk 表示改进后的编码器模块, h k h_k hk 表示拼接后的特征, p k p_k pk 表示位置编码, S k S_k Sk 表示经过编码器处理后的空间特征, Upsample ( ⋅ ) \text{Upsample}(\cdot) Upsample() 表示上采样函数, k = 1 , 2 , 3 , 4 k=1,2,3,4 k=1,2,3,4 表示第 1 层到第 4 层。

利用可见光 - 热红外通道信息的交互,可更好地学习通道间的相关性,并稳健地捕捉可见光与热红外图像之间的潜在交互。首先,将两种模态的特征拼接,然后进行全局平均池化以降低计算量,在通道维度进行位置编码,最终得到每个通道的权重信息,并将其应用于拼接后的特征,得到通道级特征 I k v I_{kv} Ikv I k t I_{kt} Ikt

最后,将空间维度与通道维度的特征相加,用于后续的空间特征对齐:
{ F output_v = ∑ k = 1 4 S k v + I k v F output_t = ∑ k = 1 4 S k t + I k t \left\{ \begin{aligned} F_{\text{output\_v}} &= \sum_{k=1}^{4} S_{kv} + I_{kv} \\ F_{\text{output\_t}} &= \sum_{k=1}^{4} S_{kt} + I_{kt} \end{aligned} \right. Foutput_vFoutput_t=k=14Skv+Ikv=k=14Skt+Ikt
其中, F output_v F_{\text{output\_v}} Foutput_v F output_t F_{\text{output\_t}} Foutput_t 分别为校正后的可见光增强特征与热红外增强特征;
S k v S_{kv} Skv S k t S_{kt} Skt 分别为可见光、热红外特征的第 k k k 个分量;
I k v I_{kv} Ikv I k t I_{kt} Ikt 分别为可见光、热红外特征的第 k k k 个补偿项。

class SelfAttention(nn.Module):
    """
    多头掩码自注意力层
    """

    def __init__(self, d_model, d_k, d_v, h, attn_pdrop=.1, resid_pdrop=.1):
        '''
        :param d_model: 模型的输出维度
        :param d_k: Query和Key的维度
        :param d_v: Value的维度
        :param h: 注意力头数
        '''
        super(SelfAttention, self).__init__()  # 调用父类初始化
        assert d_k % h == 0  # 确保d_k能被h整除
        self.d_model = d_model  # 模型维度
        self.d_k = d_model // h  # 每个头的query和key维度
        self.d_v = d_model // h  # 每个头的value维度
        self.h = h  # 注意力头数

        # 所有头的key、query、value投影
        self.que_proj = nn.Linear(d_model, h * self.d_k)  # query投影层
        self.key_proj = nn.Linear(d_model, h * self.d_k)  # key投影层
        self.val_proj = nn.Linear(d_model, h * self.d_v)  # value投影层
        self.out_proj = nn.Linear(h * self.d_v, d_model)  # 输出投影层

        # 正则化
        self.attn_drop_t = nn.Dropout(attn_pdrop)  # 注意力dropout(t分支)
        self.attn_drop_s = nn.Dropout(attn_pdrop)  # 注意力dropout(s分支)
        self.resid_drop = nn.Dropout(resid_pdrop)  # 残差连接dropout
        self.init_weights()  # 初始化权重

    def init_weights(self):
        """权重初始化函数"""
        for m in self.modules():  # 遍历所有模块
            if isinstance(m, nn.Conv2d):  # 如果是2D卷积层
                init.kaiming_normal_(m.weight, mode='fan_out')  # 使用Kaiming正态分布初始化权重
                if m.bias is not None:  # 如果有偏置
                    init.constant_(m.bias, 0)  # 将偏置初始化为0
            elif isinstance(m, nn.BatchNorm2d):  # 如果是2D批归一化层
                init.constant_(m.weight, 1)  # 将权重初始化为1
                init.constant_(m.bias, 0)  # 将偏置初始化为0
            elif isinstance(m, nn.Linear):  # 如果是线性层
                init.normal_(m.weight, std=0.001)  # 使用正态分布初始化权重(标准差0.001)
                if m.bias is not None:  # 如果有偏置
                    init.constant_(m.bias, 0)  # 将偏置初始化为0

    def forward(self, x, attention_mask=None, attention_weights=None):
        '''
        计算自注意力
        Args:
            x (tensor): 输入(token)维度为(b_s, nx, c),
                b_s 表示批次大小
                nx 表示长度,对于CNN,等于H*W,即特征图的长度
                c 表示通道数,即特征图的通道数
            attention_mask: 注意力值的掩码 (b_s, h, nq, nk)。True表示掩码。
            attention_weights: 注意力值的乘法权重 (b_s, h, nq, nk)。
        Return:
            output (tensor): 维度为(b_s, nx, c)
        '''

        b_s, nq = x.shape[:2]  # 获取批次大小和query序列长度
        nk = x.shape[1]  # 获取key序列长度
        q = self.que_proj(x).view(b_s, nq, self.h, self.d_k).permute(0, 2, 1, 3)  # query投影并重塑为(b_s, h, nq, d_k)
        k = self.key_proj(x).view(b_s, nk, self.h, self.d_k).permute(0, 2, 3, 1)  # key投影并重塑为(b_s, h, d_k, nk) K^T
        v = self.val_proj(x).view(b_s, nk, self.h, self.d_v).permute(0, 2, 1, 3)  # value投影并重塑为(b_s, h, nk, d_v)

        q_s, q_t = torch.chunk(q,2,2)  # 将query分成两部分(可能是RGB和IR)
        k_s, k_t = torch.chunk(k,2,3)  # 将key分成两部分
        v_s, v_t = torch.chunk(v,2,2)  # 将value分成两部分

        # 自注意力
        #  :math:`(\text(Attention(Q,K,V) = Softmax((Q*K^T)/\sqrt(d_k))`  # 注意力公式
        # att = torch.matmul(q, k) / np.sqrt(self.d_k)  # (b_s, h, nq, nk)  # 注释掉的原始注意力计算
        att_s = torch.matmul(q_s, k_t) / np.sqrt(self.d_k)  # 计算s分支的注意力分数(交叉注意力):(b_s, h, nq, nk)
        # att_s = torch.matmul(q_s, k_s) / np.sqrt(self.d_k)  # (b_s, h, nq, nk)  # 注释掉的代码
        att_t = torch.matmul(q_t, k_t) / np.sqrt(self.d_k)  # 计算t分支的注意力分数:(b_s, h, nq, nk)
        # att_t = torch.matmul(q_t, k_s) / np.sqrt(self.d_k)  # (b_s, h, nq, nk)  # 注释掉的代码

        # 权重和掩码
        # if attention_weights is not None:  # 注释掉的权重处理
        #     att = att * attention_weights
        # if attention_mask is not None:  # 注释掉的掩码处理
        #     att = att.masked_fill(attention_mask, -np.inf)

        # 获取注意力矩阵
        att_s = torch.softmax(att_s, -1)  # 对s分支的注意力分数应用softmax
        att_s = self.attn_drop_s(att_s)  # 应用dropout

        att_t = torch.softmax(att_t, -1)  # 对t分支的注意力分数应用softmax
        att_t = self.attn_drop_t(att_t)  # 应用dropout

        # 输出
        # out = torch.matmul(att, v).permute(0, 2, 1, 3).contiguous().view(b_s, nq, self.h * self.d_v)  # (b_s, nq, h*d_v)  # 注释掉的原始输出计算
        out_t = torch.matmul(att_t, v_t).permute(0, 2, 1, 3).contiguous().view(b_s, nq//2, self.h * self.d_v)  # t分支的输出:(b_s, nq//2, h*d_v)
        out_s = torch.matmul(att_s, v_s).permute(0, 2, 1, 3).contiguous().view(b_s, nq//2, self.h * self.d_v)  # s分支的输出:(b_s, nq//2, h*d_v)
        out1 = torch.cat([out_s, out_t], dim=1)  # 在序列维度上拼接两个分支的输出
        out = self.resid_drop(self.out_proj(out1))  # 通过输出投影层并应用dropout:(b_s, nq, d_model)
        return out  # 返回输出



class myTransformerBlock(nn.Module):
    """ Transformer块 """

    def __init__(self, d_model, d_k, d_v, h, block_exp, attn_pdrop, resid_pdrop):
        """
        :param d_model: 模型的输出维度
        :param d_k: Query和Key的维度
        :param d_v: Value的维度
        :param h: 注意力头数
        :param block_exp: MLP(前馈网络)的扩展因子
        """
        super().__init__()  # 调用父类初始化
        self.ln_input = nn.LayerNorm(d_model)  # 输入层归一化
        self.ln_output = nn.LayerNorm(d_model)  # 输出层归一化
        self.sa = SelfAttention(d_model,d_k,d_v,h)  # 自注意力模块
        # self.sa = Attention(d_model)  # 注释掉的另一种注意力实现
        self.mlp = nn.Sequential(  # 前馈网络(MLP)
            nn.Linear(d_model, block_exp * d_model),  # 第一个全连接层:扩展维度
            # nn.SiLU(),  # 从GELU改为SiLU(注释掉)
            nn.GELU(),  # GELU激活函数(从GELU改为GELU)
            nn.Linear(block_exp * d_model, d_model),  # 第二个全连接层:恢复维度
            nn.Dropout(resid_pdrop),  # Dropout层
        )

    def forward(self, x):
        bs, nx, c = x.size()  # 获取批次大小、序列长度、特征维度
        # x = x + self.sa(self.ln_input(x))  # 注释掉的代码
        x = x + self.sa(self.ln_input(x))  # 自注意力 + 残差连接
        x = x + self.mlp(self.ln_output(x))  # 前馈网络 + 残差连接
        return x  # 返回输出


class CEM(nn.Module):
    """通道增强模块(Channel Enhancement Module)"""
    def __init__(self, channels):
        super().__init__()  # 调用父类初始化
        self.transformer = Transformer(dim=1, depth=1, heads=1, dim_head=16, mlp_dim=8, dropout=0.1)  # Transformer模块
        self.attention_weight = nn.AdaptiveAvgPool2d((1, 1))  # 自适应平均池化(未使用)
        self.dropout = nn.Dropout(0.1)  # Dropout层
        self.pos_embedding = nn.Parameter(torch.randn(1, channels, 1))  # 位置嵌入参数
        self.gap = nn.AdaptiveAvgPool2d((1, 1))  # 全局平均池化

    def forward(self, x):
        """前向传播函数"""
        b, c, h, w = x.size()  # 获取批次大小、通道数、高度、宽度:32, 256, 72, 36
        input = self.gap(x).squeeze(-1)  # 全局平均池化并压缩最后一个维度:32, 256, 72*36=2592
        _, c, _ = input.shape  # 获取通道数
        input = input + self.pos_embedding[:, :(c)]  # 添加位置嵌入
        input = self.dropout(input)  # 应用Dropout
        output = self.transformer(input)  # 通过Transformer:32, 256, 1
        output = torch.unsqueeze(output, dim=3)  # 在维度3上增加维度:32, 256, 1, 1
        weight = torch.sigmoid(output)  # 应用sigmoid得到权重:32, 256, 1, 1
        final = weight * x  # 用权重加权输入
        return final  # 返回最终结果


class GPT(nn.Module):

    def __init__(self, d_model, h=8, block_exp=4,
                 n_layer=4, vert_anchors=8, horz_anchors=8,
                 embd_pdrop=0.1, attn_pdrop=0.1, resid_pdrop=0.1):
        super().__init__()  # 调用父类初始化

        self.n_embd = d_model  # 嵌入维度
        self.vert_anchors = vert_anchors  # 垂直锚点数
        self.horz_anchors = horz_anchors  # 水平锚点数

        d_k = d_model  # query和key的维度
        d_v = d_model  # value的维度

        # 位置嵌入参数(可学习),rgb_fea + ir_fea
        self.pos_emb = nn.Parameter(torch.zeros(1, 2 * vert_anchors * horz_anchors, self.n_embd))  # 位置嵌入:2倍锚点数(RGB+IR)

        # transformer
        self.trans_blocks = nn.Sequential(*[myTransformerBlock(d_model, d_k, d_v, h, block_exp, attn_pdrop, resid_pdrop)
                                            for layer in range(n_layer)])  # Transformer块序列

        # 解码器头
        self.ln_f = nn.LayerNorm(self.n_embd)  # 最终层归一化

        # 正则化
        self.drop = nn.Dropout(embd_pdrop)  # 嵌入dropout

        # 平均池化
        self.avgpool = nn.AdaptiveAvgPool2d((self.vert_anchors, self.horz_anchors))  # 自适应平均池化到锚点尺寸

        # 初始化权重
        self.apply(self._init_weights)  # 应用权重初始化
        self.cem = CEM1(d_model)  # 通道增强模块
    @staticmethod
    def _init_weights(module):
        """权重初始化函数"""
        if isinstance(module, nn.Linear):  # 如果是线性层
            module.weight.data.normal_(mean=0.0, std=0.02)  # 使用正态分布初始化权重(均值0,标准差0.02)
            if module.bias is not None:  # 如果有偏置
                module.bias.data.zero_()  # 将偏置初始化为0
        elif isinstance(module, nn.LayerNorm):  # 如果是层归一化
            module.bias.data.zero_()  # 将偏置初始化为0
            module.weight.data.fill_(1.0)  # 将权重初始化为1.0

    def forward(self, x):
        """
        前向传播
        """
        rgb = x[0]  # 获取RGB输入
        ir = x[1]  # 获取IR(红外)输入
        assert rgb.shape[0] == ir.shape[0]  # 确保批次大小相同
        bs, c, h, w = rgb.shape  # 获取批次大小、通道数、高度、宽度
        # -------------------------------------------------------------------------
        # 平均池化
        # -------------------------------------------------------------------------
        # 平均池化用于降低维度以减少计算开销
        rgb_fea = self.avgpool(rgb)  # RGB特征的平均池化
        ir_fea = self.avgpool(ir)  # IR特征的平均池化

        # 展平:(B, C, H, W) → (B, C, H×W)
        rgb_fea_flat = rgb_fea.view(bs, c, -1)  
        ir_fea_flat = ir_fea.view(bs, c, -1)  
        # 拼接:RGB和IR的空间token合并 → (B, C, 2×H×W)
        token_embeddings = torch.cat([rgb_fea_flat, ir_fea_flat], dim=2)  
        # 转置:适配Transformer输入格式 → (B, 2×H×W, C)(序列长度=2×H×W,每个token维度=C)
        token_embeddings = token_embeddings.permute(0, 2, 1).contiguous()  

        # transformer
        x = self.drop(self.pos_emb + token_embeddings)  # 位置嵌入+dropout正则化
        x = self.trans_blocks(x)  # 多层Transformer块(自注意力+前馈网络)
        x = self.ln_f(x)  # 最终层归一化,稳定训练
        
        # 重塑:(B, 2×H×W, C) → (B, 2, H, W, C)(拆分RGB/IR分支)
        x = x.view(bs, 2, self.vert_anchors, self.horz_anchors, self.n_embd)  
        # 转置:(B, 2, C, H, W)(恢复通道优先格式)
        x = x.permute(0, 1, 4, 2, 3)  
        # 拆分RGB/IR分支:(B, C, H, W)
        rgb_fea_out = x[:, 0, :, :, :].contiguous().view(bs, self.n_embd, self.vert_anchors,              self.horz_anchors)  
        ir_fea_out = x[:, 1, :, :, :].contiguous().view(bs, self.n_embd, self.vert_anchors, self.horz_anchors)  

         # 上采样:还原到原始输入尺寸(比如16×16→256×256)
        rgb_fea_out = F.interpolate(rgb_fea_out, size=([h, w]), mode='bilinear')  
        ir_fea_out = F.interpolate(ir_fea_out, size=([h, w]), mode='bilinear')  
        
       
        return rgb_fea_out, ir_fea_out  # 返回增强后的RGB和IR特征

3.3 空间特征对齐网络

由于热红外图像不受光照影响,本文将其作为参考图像,可见光图像作为感知图像。设计新型特征匹配器以获取从可见光特征到热红外特征的形变场,并基于该形变场进行重采样,实现模态对齐。特征匹配器捕捉模态间的特征位移( x x x, y y y )与维度缩放因子( w w w, h h h)。
在这里插入图片描述
如图 6 所示,给定经过校正的两种模态特征 F v F_ v Fv(可见光特征)和 F t F_t Ft(热红外特征),为降低计算量,通过直接相减两种模态特征得到新的特征映射 F s F_s Fs。受空间变换网络(Spatial Transformer Network,STN)[39] 的启发,设计变换矩阵,将感知特征图从原始坐标系转换到参考特征坐标系。具体而言,将 F s F_s Fs输入仿射网络(AffineNet),该网络用于回归四个参数( x x x, y y y, w w w, h h h)。该网络堆叠四个 4 倍下采样的卷积块,并连接全局平均池化层与卷积层,以预测位置偏移因子与尺度缩放因子。得到变换矩阵 M M M后,通过深度学习库 PyTorch 的积分函数可轻松得到扭曲场 φ φ φ,然后根据φ对可见光特征进行重采样,得到对齐后的感知特征。

class MAM2(nn.Module):
    """多模态对齐模块"""
    def __init__(self, in_channel):
        super(MAM2, self).__init__()  # 调用父类初始化
        self.channel264 = nn.Sequential(  # 通道降维序列(使用单个输入)
            Conv(in_channel, in_channel//2, 3, 2, 1),  # 第一个3x3卷积:通道数减半,步长2
            convblock(in_channel//2, in_channel//4, 3, 1, 1),  # 第二个3x3卷积:通道数再减半
            convblock(in_channel//4, in_channel//8, 3, 1, 0),  # 第三个3x3卷积:通道数再减半
            convblock(in_channel//8, in_channel//16, 3, 1, 1),  # 第四个3x3卷积:通道数再减半
            convblock(in_channel//16, 16, 1, 1, 1),  # 第五个1x1卷积:降到16通道
        )
        self.xy = nn.Sequential(  # 平移参数预测序列
            nn.AdaptiveAvgPool2d(1),  # 自适应平均池化到1x1
            nn.Conv2d(16, 2, 1, 1, 0)  # 1x1卷积:输出2个平移参数(x, y)
        )
        self.scale1 = nn.Sequential(  # 第一个缩放参数预测序列
            nn.AdaptiveAvgPool2d(1),  # 自适应平均池化到1x1
            nn.Conv2d(16, 1, 1, 1, 0)  # 1x1卷积:输出1个缩放参数(x方向)
        )
        self.scale2 = nn.Sequential(  # 第二个缩放参数预测序列
            nn.AdaptiveAvgPool2d(1),  # 自适应平均池化到1x1
            nn.Conv2d(16, 1, 1, 1, 0)  # 1x1卷积:输出1个缩放参数(y方向)
        )
        # 从恒等变换开始
        self.xy[-1].weight.data.normal_(mean=0.0, std=5e-4)  # 使用正态分布初始化平移权重
        self.xy[-1].bias.data.zero_()  # 将平移偏置初始化为0
        self.scale1[-1].weight.data.normal_(mean=0.0, std=5e-4)  # 使用正态分布初始化第一个缩放权重
        self.scale1[-1].bias.data.zero_()  # 将第一个缩放偏置初始化为0
        self.scale2[-1].weight.data.normal_(mean=0.0, std=5e-4)  # 使用正态分布初始化第二个缩放权重
        self.scale2[-1].bias.data.zero_()  # 将第二个缩放偏置初始化为0
        # self.fus1 = Conv(in_channel * 2, in_channel, 1, 1, 0)  # 注释掉的融合卷积
def forward(self, x):
        gr = x[0]  # 获取第一个输入
        gt = x[1]  # 获取第二个输入
       
        in_ = gt -gr  # 计算差分(gt减去gr)
        n1 = self.channel264(in_)  # 通过通道降维序列
        identity_theta = torch.tensor([1, 0, 0, 0, 1, 0], dtype=torch.float).requires_grad_(False)  # 恒等变换矩阵
      
        shift_xy = self.xy(n1)  # 预测平移参数(x, y)
        shift_s1 = self.scale1(n1)  # 预测第一个缩放参数(x方向)
        shift_s2 = self.scale2(n1)  # 预测第二个缩放参数(y方向)
        bsize = shift_xy.shape[0]  # 获取批次大小
        identity_theta = identity_theta.view(-1, 2, 3).repeat(bsize, 1, 1).cuda()  # 将恒等变换矩阵扩展为批次大小并移动到GPU
        identity_theta[:, :, 2] += shift_xy.squeeze()  # 在平移列(第3列)上加上预测的平移参数
        identity_theta[:, :1, :1] += shift_s1.squeeze(2)  # 在x方向缩放(左上角元素)上加上预测的缩放参数
        identity_theta[:, 1, 1] += shift_s2.squeeze()  # 在y方向缩放(右下角元素)上加上预测的缩放参数
        
         wrap_grid = F.affine_grid(identity_theta.view(-1, 2, 3), in_.size(), align_corners=True).permute(0, 3, 1,2).half() 
        #  根据仿射变换矩阵生成采样网格
        #   - identity_theta.view(-1, 2, 3): 将变换矩阵重塑为(batch, 2, 3)格式
        #     * 2x3矩阵表示2D仿射变换:[a, b, tx; c, d, ty]
        #     * a,d: x和y方向的缩放因子
        #     * b,c: 旋转/剪切参数
        #     * tx,ty: x和y方向的平移
        #   - in_.size(): 目标输出尺寸 (batch, channels, height, width)
        #   - align_corners=True: 对齐角点,确保角点像素精确对齐
        #   - .permute(0, 3, 1, 2): 将网格从(batch, H, W, 2)转为(batch, 2, H, W)
        #   - .half(): 转换为半精度浮点数(float16),节省内存和加速计算
        # 输出wrap_grid: (batch, 2, H, W),每个位置存储归一化到[-1, 1]的采样坐标
      
         wrap_gr = F.grid_sample(gr, wrap_grid.permute(0, 2, 3, 1), mode='bilinear', padding_mode='zeros', align_corners=True) 
        #根据采样网格对输入特征图进行双线性插值采样
        #   - gr: 输入特征图 (batch, channels, height, width),需要被对齐的特征(如RGB特征)
        #   - wrap_grid.permute(0, 2, 3, 1): 将网格转回(batch, H, W, 2)格式
        #     * grid_sample要求输入格式为(batch, H, W, 2)
        #   - mode='bilinear': 使用双线性插值进行采样
        #   - padding_mode='zeros': 超出边界的位置用0填充
        #   - align_corners=True: 与affine_grid保持一致的对齐方式
 
      
        
        return wrap_gr  # 返回对齐后的gr

3.4 双向特征校正融合模块

在这里插入图片描述
无人机采集的图像常受到复杂背景的干扰。如图 8 所示,本文通过模态间的交互,利用一种模态的特征对另一种模态的噪声信息进行校正与校准。
首先,分别对可见光特征 F v F_v Fv和热红外特征 F t F_t Ft进行平均池化、最大池化与求和操作,得到 F a v g F_ {avg} Favg(平均池化特征)和 F m a x F_ {max} Fmax(最大池化特征)。多层感知机(Multilayer Perceptron,MLP)能够学习长程依赖关系,将拼接后的平均池化特征与最大池化特征输入通道级 MLP,得到两种模态的权重向量,实现模态间的交互:
C v , C t = Split ( σ ( MLP ( Concat ( F avg , F max ) ) ) ) C_v, C_t = \text{Split}\left( \sigma\left( \text{MLP}\left( \text{Concat}(F_{\text{avg}}, F_{\text{max}}) \right) \right) \right) Cv,Ct=Split(σ(MLP(Concat(Favg,Fmax))))
其中, C v C_v Cv C t C_t Ct 分别为可见光与热红外的通道权重向量, Split ( ⋅ ) \text{Split}(\cdot) Split() 表示特征分割操作, σ \sigma σ 表示 Sigmoid 激活函数, MLP ( ⋅ ) \text{MLP}(\cdot) MLP() 表示多层感知机函数。然后,利用这两个权重向量得到两种模态的校正特征:
{ F v 1 = F v + C v ∗ F v F t 1 = F t + C t ∗ F t \begin{cases} F_{v1} = F_v + C_v * F_v \\ F_{t1} = F_t + C_t * F_t \end{cases} {Fv1=Fv+CvFvFt1=Ft+CtFt
其中, F v 1 F_{v1} Fv1 F t 1 F_{t1} Ft1 分别为去除通道噪声后的可见光特征与热红外特征。
将通道级增强特征( F v 1 , F t 1 F_{v1}, F_{t1} Fv1,Ft1)输入空间级 MLP,得到空间权重信息( S v , S t S_v, S_t Sv,St),实现自适应噪声抑制。 F v 2 F_{v2} Fv2 F t 2 F_{t2} Ft2 分别为去除通道与空间噪声后的可见光特征与热红外特征。最后,将两种模态的特征相加:
o u t = F v 2 + F t 2 {out} = F_{v2} + F_{t2} out=Fv2+Ft2

class ChannelWeights(nn.Module):
    """通道权重模块(与cmx.py中的类似但实现略有不同)"""
    def __init__(self, dim, reduction=1):
        super(ChannelWeights, self).__init__()  # 调用父类初始化
        self.dim = dim  # 特征维度
        # self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 注释掉的自适应平均池化
        self.max_pool = nn.AdaptiveMaxPool2d(1)  # 自适应最大池化
        self.mlp = nn.Sequential(  # 多层感知机
            nn.Linear(self.dim * 2, self.dim * 2 // reduction),  # 第一个全连接层:输入dim*2,输出dim*2//reduction
            nn.ReLU(inplace=True),  # ReLU激活函数
            nn.Linear(self.dim * 2 // reduction, self.dim * 2),  # 第二个全连接层:输出dim*2
            nn.Sigmoid())  # Sigmoid激活函数

    def forward(self, x1, x2):
        """前向传播函数"""
        B, _, H, W = x1.shape  # 获取批次大小、高度、宽度
        # x = torch.cat((x1, x2), dim=1)  # 注释掉的拼接操作
        avg1 = self.avg_pool(x1).view(B, self.dim)  # x1平均池化
        avg2 = self.avg_pool(x2).view(B, self.dim)  # x2平均池化
        max1 = self.max_pool(x1).view(B, self.dim)  # x1的最大池化
        max2 = self.max_pool(x2).view(B, self.dim)  # x2的最大池化
        avg = avg1+avg2  # 两个平均值的和
        max = max1+max2  # 两个最大值的和
        y = torch.cat((max, avg), dim=1)  # 拼接最大值和平均值
        y = self.mlp(y).view(B, self.dim * 2, 1)  # 通过MLP并重塑
        channel_weights = y.reshape(B, 2, self.dim, 1, 1).permute(1, 0, 2, 3, 4)  # 重塑并转置为(2, B, C, 1, 1)
        return channel_weights  # 返回通道权重


class SpatialWeights(nn.Module):
    """空间权重模块"""
    def __init__(self, dim, reduction=1):
        super(SpatialWeights, self).__init__()  # 调用父类初始化
        self.dim = dim  # 特征维度
        self.mlp = nn.Sequential(  # 多层感知机(使用卷积实现)
            nn.Conv2d(self.dim * 2, self.dim // reduction, kernel_size=1),  # 第一个1x1卷积
            nn.ReLU(inplace=True),  # ReLU激活函数
            nn.Conv2d(self.dim // reduction, 2, kernel_size=1),  # 第二个1x1卷积:输出2通道
            nn.Sigmoid())  # Sigmoid激活函数

    def forward(self, x1, x2):
        """前向传播函数"""
        B, _, H, W = x1.shape  # 获取批次大小、高度、宽度
        x = torch.cat((x1, x2), dim=1)  # 在通道维度上拼接,形状为B 2C H W
        spatial_weights = self.mlp(x).reshape(B, 2, 1, H, W).permute(1, 0, 2, 3, 4)  # 通过MLP,重塑并转置为(2, B, 1, H, W)
        return spatial_weights  # 返回空间权重


class FRM(nn.Module):
    """特征修正模块(Feature Rectify Module)"""
    def __init__(self, dim, reduction=1, lambda_c=.5, lambda_s=.5):
        super(FRM, self).__init__()  # 调用父类初始化
        self.lambda_c = lambda_c  # 通道权重系数
        self.lambda_s = lambda_s  # 空间权重系数
        self.channel_weights = ChannelWeights(dim=dim, reduction=reduction)  # 创建通道权重模块
        self.spatial_weights = SpatialWeights(dim=dim, reduction=reduction)  # 创建空间权重模块

    def _init_weights(self, m):
        """权重初始化函数"""
        if isinstance(m, nn.Linear):  # 如果是线性层
            trunc_normal_(m.weight, std=.02)  # 使用截断正态分布初始化权重
            if isinstance(m, nn.Linear) and m.bias is not None:  # 如果有偏置
                nn.init.constant_(m.bias, 0)  # 将偏置初始化为0
        elif isinstance(m, nn.LayerNorm):  # 如果是层归一化
            nn.init.constant_(m.bias, 0)  # 将偏置初始化为0
            nn.init.constant_(m.weight, 1.0)  # 将权重初始化为1.0
        elif isinstance(m, nn.Conv2d):  # 如果是卷积层
            fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels  # 计算输出通道的扇出
            fan_out //= m.groups  # 除以组数
            m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))  # 使用正态分布初始化权重
            if m.bias is not None:  # 如果有偏置
                m.bias.data.zero_()  # 将偏置初始化为0

    def forward(self, x1, x2):
        """前向传播函数,与cmx.py中的实现不同"""
        channel_weights = self.channel_weights(x1, x2)  # 计算通道权重
        # out_x1 = x1 + self.lambda_c * channel_weights[1] * x2 + self.lambda_s * spatial_weights[1] * x2  # 注释掉的原始实现
        x1 = x1 + self.lambda_c * channel_weights[0] * x1  # x1加上加权的x1(自增强)
        # out_x2 = x2 + self.lambda_c * channel_weights[0] * x1 + self.lambda_s * spatial_weights[0] * x1  # 注释掉的原始实现
        x2 = x2 + self.lambda_c * channel_weights[1] * x2  # x2加上加权的x2(自增强)
        spatial_weights = self.spatial_weights(x1, x2)  # 计算空间权重
        out_x1 = x1 + self.lambda_s * spatial_weights[0] * x1  # x1加上空间加权的x1
        out_x2 = x2 + self.lambda_s * spatial_weights[1] * x2  # x2加上空间加权的x2
        out = out_x1 + out_x2  # 将两个输出相加
        return out  # 返回融合后的输出

3.5 损失函数

本文的总损失包括预测框的回归损失、分类损失与置信度损失:
L total = L reg + L cls + L conf \mathcal{L}_{\text{total}} = \mathcal{L}_{\text{reg}} + \mathcal{L}_{\text{cls}} + \mathcal{L}_{\text{conf}} Ltotal=Lreg+Lcls+Lconf
其中, L reg \mathcal{L}_{\text{reg}} Lreg L cls \mathcal{L}_{\text{cls}} Lcls L conf \mathcal{L}_{\text{conf}} Lconf 分别表示预测框的回归损失、分类损失与置信度损失。

首先,采用 GIoU(Generalized Intersection over Union)损失 [41] 作为回归损失。与仅关注重叠区域的 IoU 损失不同,GIoU 损失不仅关注重叠区域,还关注非重叠区域,能更准确地反映两个边界框的重叠情况:
L reg = ∑ i = 0 S 2 ∑ j = 0 N 1 i , j obj ⋅ ( 1 − GIoU i ) = ∑ i = 0 S 2 ∑ j = 0 N 1 i , j obj ⋅ [ 1 − IoU − A i c − U A i c ] = ∑ i = 0 S 2 ∑ j = 0 N 1 i , j obj ⋅ [ 1 − B i p ∩ B i g B i p ∪ B i g + A i c − ( B i p ∪ B i g ) A i c ] \begin{aligned} \mathcal{L}_{\text{reg}} &= \sum_{i=0}^{S^2} \sum_{j=0}^{N} \mathbb{1}_{i,j}^{\text{obj}} \cdot (1 - \text{GIoU}_i) \\ &= \sum_{i=0}^{S^2} \sum_{j=0}^{N} \mathbb{1}_{i,j}^{\text{obj}} \cdot \left[ 1 - \text{IoU} - \frac{A_i^c - U}{A_i^c} \right] \\ &= \sum_{i=0}^{S^2} \sum_{j=0}^{N} \mathbb{1}_{i,j}^{\text{obj}} \cdot \left[ 1 - \frac{B_i^p \cap B_i^g}{B_i^p \cup B_i^g} + \frac{A_i^c - (B_i^p \cup B_i^g)}{A_i^c} \right] \end{aligned} Lreg=i=0S2j=0N1i,jobj(1GIoUi)=i=0S2j=0N1i,jobj[1IoUAicAicU]=i=0S2j=0N1i,jobj[1BipBigBipBig+AicAic(BipBig)]
其中, S 2 S^2 S2 表示预测图像网格的数量, N N N 表示每个网格中预测框的数量, 1 i , j obj \mathbb{1}_{i,j}^{\text{obj}} 1i,jobj 表示第 i i i 个网格的第 j j j 个预测框是否为正样本(指示函数), B i p B_i^p Bip 表示预测框, B i g B_i^g Big 表示真值框, A i c A_i^c Aic 表示由 B i p B_i^p Bip B i g B_i^g Big 构成的最小包围框的面积, U U U 表示 B i p B_i^p Bip B i g B_i^g Big 的并集面积。

为更好地优化预测框的分类问题,采用交叉熵损失作为分类损失:
L cls = ∑ i = 0 S 2 ∑ j = 0 N 1 i , j obj ⋅ ∑ c ∈ classes p i ( c ) log ⁡ ( p ^ i ( c ) ) \mathcal{L}_{\text{cls}} = \sum_{i=0}^{S^2} \sum_{j=0}^{N} \mathbb{1}_{i,j}^{\text{obj}} \cdot \sum_{c \in \text{classes}} p_i(c) \log\left( \hat{p}_i(c) \right) Lcls=i=0S2j=0N1i,jobjcclassespi(c)log(p^i(c))
其中, p ^ i ( c ) \hat{p}_i(c) p^i(c) 表示网络预测样本属于类别 c c c 的概率, p i ( c ) p_i(c) pi(c) 表示样本属于类别 c c c 的真实概率。

为衡量预测框是否包含目标的预测误差,采用均方损失计算每个预测框置信度得分的预测值与真实值之间的距离:
L conf = ∑ i = 0 S 2 ∑ j = 0 N 1 i , j obj ⋅ ( c i − c ^ i ) 2 + ∑ i = 0 S 2 ∑ j = 0 N 1 i , j noobj ⋅ ( c i − c ^ i ) 2 \mathcal{L}_{\text{conf}} = \sum_{i=0}^{S^2} \sum_{j=0}^{N} \mathbb{1}_{i,j}^{\text{obj}} \cdot (c_i - \hat{c}_i)^2 + \sum_{i=0}^{S^2} \sum_{j=0}^{N} \mathbb{1}_{i,j}^{\text{noobj}} \cdot (c_i - \hat{c}_i)^2 Lconf=i=0S2j=0N1i,jobj(cic^i)2+i=0S2j=0N1i,jnoobj(cic^i)2
其中, c i c_i ci 表示置信度的真实值, c ^ i \hat{c}_i c^i 表示网络预测的置信度值, 1 i , j noobj \mathbb{1}_{i,j}^{\text{noobj}} 1i,jnoobj 表示第 i i i 个网格的第 j j j 个预测框是否为负样本。

04.实验分析

4.1 对比实验结果

4.1.1 DVTOD 数据集上的对比

在这里插入图片描述
在相同设置下,将 CMA-Det 与其他方法在 DVTOD 数据集上进行对比,结果如表 2 所示。现有先进融合检测方法的 mAP 分别比 CMA-Det 低 5.8%、3.4% 和 2.3%。错位的多模态特征不仅导致多模态检测器的检测精度下降,还使其训练过程难以收敛。本文以热红外模态为参考模态,可见光模态为感知模态。在可见光模态检测精度方面,由于错位图像对的中部偏差较小、两侧偏差较大,且数据集中包含大量弱光照图像对,导致可见光模态的检测精度较低。相比之下,CMA-Det 在热红外模态上的检测精度也分别比 5 种对比方法高 2.3%、2.2%、0.6%、7.2% 和 1.7%。这些结果证明了本文检测方法的有效性。

4.1.2 CVC14 数据集上的对比

在这里插入图片描述
CVC14 数据集虽经过后处理,但相机校准效果仍较差,对齐质量不佳。在该数据集上对 CMA-Det 进行评估,将其与当前先进方法对比,实验结果如表 3 所示。CMA-Det 取得了优异的检测性能。在 CVC14 数据集上,本文选择可见光模态作为参考模态,红外模态作为感知模态。由于两种模态中不同目标的位置与尺寸存在差异,热红外单模态的检测精度也较低。即便如此,CMA-Det 的检测精度仍比可见光单模态高 1.6%;在双模态方法中,CMA-Det 的精度比 CFT 高 6%、比 CMX 高 15.3%、比 YOLOv5+Add 高 3.5%。双模态融合方法不适用于错位目标检测,这三种方法的检测精度均显著低于单模态方法。这些实验结果证明了所提 CMA-Det 的有效性,也体现了其在更贴合实际的数据集上的优势。

4.1.3LLVIP 数据集上的对比

在这里插入图片描述
LLVIP 是在弱光环境下采集的对齐数据集。为将 CMA-Det 与当前先进方法对比,选取该数据集的未更新版本进行实验,结果如表 4 所示。CMA-Det 表现优异:在仅使用可见光模态的情况下,其精度比最佳算法高 6.3%;在仅使用热红外模态的情况下,精度比最佳算法高 2.5%;与当前先进双模态算法相比,精度分别高 3.9%、1.4% 和 0.8%。实验结果表明,CMA-Det 不仅能处理错位数据,还能处理对齐数据,证明了其鲁棒性。

4.2 定性分析

在这里插入图片描述
在 DVTOD 数据集上,对基线方法、CMX、CFT 和 CMA-Det 的检测结果进行可视化,定性实验结果证明了 CMA-Det 在部分挑战性场景下的鲁棒性。如图 9 所示,左两列中,汽车的高速运动导致目标模糊,上述三种方法均出现误检或漏检情况;第三、四列中,面对小目标时,CMA-Det 展现出更好的鲁棒性;第五、六列中,由于无人机视角的原因,目标之间常存在遮挡,上述三种检测器会使用更大的边界框标注目标,而 CMA-Det 的检测精度更高;最后一列中,过曝光导致热红外模态的成像质量变差,目标与背景特征相似,CMX 和 CFT 出现漏检情况。定性实验结果表明,CMA-Det 在挑战性属性下仍具有鲁棒性。

4.3 消融实验

在这里插入图片描述
空间特征对齐(SFA)网络的作用:将 YOLOv5 扩展为多光谱目标检测模型,作为基线方法。在基线方法中添加 SFA 网络,保持其余网络结构不变。与表 5 第一行数据对比,mAP 提升了 3%。实验结果表明,SFA 网络在一定程度上消除了错位空间特征对检测结果的影响,实现了模态间空间特征的对齐。
目标搜索校正(OSR)策略的作用:如表 5 第三行所示,在上述基础上采用目标搜索校正策略,训练与测试过程中保持其余网络结构不变。与表 5 第二行的指标对比,mAP 提升了 1.3%。实验结果表明,该策略建立了目标与搜索区域之间的相关性,增大了可见光特征提取的感受野尺寸,提升了模态对齐的鲁棒性。
双向特征校正融合(BFCF)模块的作用:如表 5 第四行所示,同时采用 BFCFM、OSR 和 SFA,训练与测试过程中保持其余网络结构不变。与表 5 第三行的指标对比,添加 BFCFM 后 mAP 提升了 1.5%。该


05 个人声明

本文为作者对原论文的学习笔记与心得分享,受个人学识与理解所限,文中对论文内容的解读或有不够周全之处,一切以原论文正式表述为准。本文仅用于学术交流与传播,内容均由作者独立整理完成,不代表本公众号立场。如文中所涉文字、图片等内容存在版权争议,请及时与作者联系,作者将在第一时间核实并妥善处理。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐