论文阅读笔记8——Track to Detect and Segment:An Online Multi-Object Tracker(TraDeS)


前言:
前段时间读了一些基于Transformer的方法,和它们一样,TraDeS同样也是端到端的方式,主要基于DCN和CenterNet,它最大的亮点是用跟踪结果来辅助检测,进而在困难场景下有更好的表现。而且TraDeS可以解决2D、3D和分割的问题。

论文: 论文


0.Abstract

前面提到,TraDeS仍然是联合检测跟踪(Joint detection and tracking,JDT)的模型,它利用跟踪的线索来辅助检测。它利用cost volume来预测跟踪偏移,这个跟踪偏移也被用来传播之前目标的信息,从而提高当前帧的检测和分割效果。

cost volume解释:
cost volume是立体视觉中的名词,用于深度估计和光流估计。它的本意是衡量双目匹配中衡量左右视图相似性的一个4D tensor,在本文中也是一个4D tensor,衡量两帧之间嵌入特征的相似度。

  \space  

1.Introduction

首先diss TBD(Tracking by detection)的缺点,常规操作了。

之后说当前的JDT算法有两个问题:

  1. 检测步骤仍然比较独立,并没有利用好跟踪的信息。作者认为跟踪结果有助于检测,尤其是一些困难的场景(例如部分遮挡和运动模糊)

  2. 通常的Re-ID loss和detection loss在一个骨干网络中并不兼容,甚至会损伤检测的精度。原因是re-ID主要关注类内方差,而检测的目的是扩大类间差异,最小化类内方差。

最后一个观点跟FairMOT中提到的类似。FairMOT的文章中说对于将Re-ID任务视为另一阶段的任务(也就是Re-ID是个相对独立的任务),这样Re-ID的精度会很大程度上依赖于检测的精度,且两个任务会相互竞争。

  \space  
TraDes解决上述两个问题的方法是将跟踪结合到检测中,并且用一个自习设计的Re-ID的学习策略。

具体地,作者提出了基于Cost volume的关联模块(Cost Volume based Association,CVA)和运动指导的特征整合模块(Motion-guided Feature Warper,MFW)。

与CenterNet相似,TraDeS中的feature map中的点不是代表目标就是代表背景。

CVA和MFW的大致工作流程:

  • CVA逐点(point wise)通过主干网络提取Re-ID的特征,来构建一个cost volume,这个cost volume可以存储相邻两帧的嵌入特征对的匹配相似度。

  • 之后,用cost volume来推算追踪偏移(tracking offset),也就是所有两帧中潜在目标中心的时空位移。追踪偏移和嵌入特征一起,用来指导简单的两轮长时间的数据关联。

  • 之后,MFW将追踪偏移作为运动线索来将前一帧的目标特征传递到当前帧。

  • 最后,传递过来的特征和当前特征一起被用来得到检测和分割。

大体的工作流程如下(数学细节见第3部分):
在这里插入图片描述

  \space  
之前说Re-ID和检测不能很好兼容的原因是Re-ID关注类内方差,而检测关注类间方差。作者提出的CVA模块中cost volume监督Re-ID的嵌入特征,隐式地考虑了不同目标类别和背景区域。因此Re-ID就可以较多地关注到类间方差。

此外,由于追踪偏移是基于外观特征相似度来预测的,因此在快速运动场景、低帧率、跨数据集(train和test用不同的数据集)都可以取得比较好的结果。因此,预测的追踪偏移就可以作为一个鲁棒的运动线索来指导MFW中的特征传播。当前帧被遮挡或者模糊的目标在之前的帧中可能是清晰的,所以通过MFW模块,前帧特征传播可以支撑当前帧特征去恢复潜在的不可见目标。
  \space  

2. Preliminaries

由于TraDeS是基于CenterNet的,因此在这里简单介绍了一下CenterNet。

对于三通道输入图像 I ∈ R H × W × 3 \bm I\in \mathbb R^{H \times W \times 3} IRH×W×3并且产生feature map f = ϕ ( I ) ∈ R H / 4 × W / 4 × 64 \bm f=\phi(\bm I)\in \mathbb R^{H/4 \times W/4 \times 64} f=ϕ(I)RH/4×W/4×64.

在产生feature map之后,一系列卷积head分支就会产生热度图 P ∈ R H / 4 × W / 4 × N c l s \bm P\in \mathbb R^{H/4 \times W/4 \times N_{cls}} PRH/4×W/4×Ncls(其中 N c l s N_{cls} Ncls是类别的数目)和其他为特定任务服务的maps,例如2D和3D的目标大小map。

在CenterNet的基础上,TraDeS增加了一个分支,来预测tracking offset map O B ∈ R H / 4 × W / 4 × 2 \bm O^B \in \mathbb R^{H/4 \times W/4 \times 2} OBRH/4×W/4×2 O B \bm O^B OB计算的是所有点在 t t t时刻相对于 t − τ t-\tau tτ时刻的时空位移。
  \space  

3.TraDeS tracker

在这里插入图片描述

这个图…无力吐槽

待补充 有空了回来写(精华都在开头的图里了)。这篇文章的启示意义比较大,用Cost volume来衡量了两时刻之间目标的位移,就是“用追踪结果反哺检测”的所在。

之前一些基于Transformer的方法在帧间传递信息的方法是传递queries,或者也将多帧的特征进行融合。TraDeS用了一种可解释性更强和更显式的方式。

最终的损失函数与CenterNet相似,但是多了一个CVA损失。CVA损失也是交叉熵的形式,如果当前帧 t t t目标 ( i , j ) (i,j) (i,j) t − τ t-\tau tτ时刻的时候在 ( k , l ) (k,l) (k,l)处,记 Y i , j , k , l = 1 Y_{i,j,k,l}=1 Yi,j,k,l=1,否则为0.当 Y i , j , k , l = 1 Y_{i,j,k,l}=1 Yi,j,k,l=1时,loss取将cost volume池化后的向量 C i , j , l W C_{i,j,l}^W Ci,j,lW C i , j , k H C_{i,j,k}^H Ci,j,kH的加权对数和,否则为0,如下式所示:

在这里插入图片描述

最终的loss为:

L = L C V A + L d e t + L m a s k L=L_{CVA}+L_{det}+L_{mask} L=LCVA+Ldet+Lmask

其中 L d e t L_{det} Ldet是2D和3D检测损失, L m a s k L_{mask} Lmask是分割损失。

4. 代码解读

由于TraDeS结构有点复杂, 因此对代码作简单解读以备忘

顺着执行顺序整理.

4.1 前向传播

主要的模型都在在src/lib/model/base_model.py中. 我们先看forward函数:

forward函数首先提取当前图像特征, 如图中的 P h i Phi Phi部分, 以DLA34作为backbone:

def forward(self, x, pre_img=None, pre_hm=None, addtional_pre_imgs=None, addtional_pre_hms=None, inference_feats=None):
"""
      x: 当前图像
      pre_img: 前一帧图像
      pre_hm: 前一帧head embedding
      addtional_pre_imgs: 更多的之前的图像
      addtional_pre_hms: 更多的之前的图像的head embedding
      inference_feats: 训练时传入的为None
      """
     
      cur_feat = self.img2feats(x)  # 转换为特征图
      

其中

    def img2feats(self, x):
        x = self.base(x)  # DLA34网络
        x = self.dla_up(x)  # DLAup

        y = []
        for i in range(self.last_level - self.first_level):
            y.append(x[i].clone())
        self.ida_up(y, 0, len(y))  # IDAup

        return [y[-1]]

随后进入TraDeS的剩余部分, 被封装在self.TraDeS中.

if self.opt.trades:
          feats, embedding, tracking_offset, dis_volume, h_volume_aux, w_volume_aux \
              = self.TraDeS(cur_feat, pre_img, pre_hm, addtional_pre_imgs, addtional_pre_hms, inference_feats)  # TraDes主要模块 前向传播

在得到加强特征feats后, 进入不同的head进行不同任务的推理:

if self.opt.model_output_list:  # 转为onnx才会用到
        for s in range(self.num_stacks):
          z = []
          for head in sorted(self.heads):
              z.append(self.__getattr__(head)(feats[s]))
          out.append(z)
      else:  # 正常走这里
        for s in range(self.num_stacks):
          z = {
    
    }
          if self.opt.trades:
              z['embedding'] = embedding
              z['tracking_offset'] = tracking_offset
              if not self.opt.inference:
                  z['h_volume'] = dis_volume[0]
                  z['w_volume'] = dis_volume[1]
                  assert len(h_volume_aux) == self.opt.clip_len - 2
                  for temporal_id in range(2, self.opt.clip_len):
                      z['h_volume_prev{}'.format(temporal_id)] = h_volume_aux[temporal_id-2]
                      z['w_volume_prev{}'.format(temporal_id)] = w_volume_aux[temporal_id-2]
          for head in self.heads:
              z[head] = self.__getattr__(head)(feats[s])  # 进行不同任务的预测
          out.append(z)
      if self.opt.inference:
          return out, cur_feat[0].detach().cpu().numpy()
      else:
          return out

4.2 TraDeS模块

下面来看self.TraDes(). 该部分的主要作用是把当前帧和过去帧的特征进行计算与打包, 传给CVA和MFW模块.

注意, MFW中的(5)式是在这部分计算的, 也就是代码里的support feature:

在这里插入图片描述

def TraDeS(self, cur_feat, pre_img, pre_hm, addtional_pre_imgs, addtional_pre_hms, inference_feats):
        """
        cur_feat: 当前特征
        pre_img, pre_hm, addtional_pre_imgs, addtional_pre_hms: 同self.forward()
        inference_feats: None
        """
        feat_list = []  # 存储帧的特征
        feat_list.append(cur_feat[0])  # 首先加入当前帧特征
        support_feats = []  # 论文(5)式 用以计算特征图和类别无关热度图的乘积
        if self.opt.inference:
            for prev_feat in inference_feats:
                feat_list.append(torch.from_numpy(prev_feat).to(self.opt.device)[:, :, :, :])
            while len(feat_list) < self.opt.clip_len:  # only operate in the initial frame
                feat_list.append(cur_feat[0])

            for idx, feat_prev in enumerate(feat_list[1:]):
                pre_hm_i = addtional_pre_hms[idx]
                pre_hm_i = self.avgpool_stride4(pre_hm_i)
                support_feats.append(pre_hm_i * feat_prev)
        else:
            feat2 = self.img2feats_prev(pre_img)  # 获取之前帧的feature
            pre_hm_1 = self.avgpool_stride4(pre_hm)  # 为了减少计算量 进行池化
            feat_list.append(feat2[0])  # 将之前帧的feature加入到feature list
            support_feats.append(feat2[0] * pre_hm_1)
            for ff in range(len(addtional_pre_imgs) - 1):  # 同理 加入更多的之前帧的特征与support feature
                feats_ff = self.img2feats_prev(addtional_pre_imgs[ff])
                pre_hm_i = self.avgpool_stride4(addtional_pre_hms[ff])
                feat_list.append(feats_ff[0][:, :, :, :])  # 构成之前多少帧feature的集合
                support_feats.append(feats_ff[0][:, :, :, :]*pre_hm_i)

        return self.CVA_MFW(feat_list, support_feats)  # 主要模块 CVA + MFW

4.3 CVA_MFW模块

这是TraDeS的核心部分. 我们在有当前帧和之前帧的特征图后, 就要进入CVA进行运动的预测, 之后进入MFW对运动线索进一步预测, 和之前帧的特征结合得到更强的传播特征.

def CVA_MFW(self, feat_list, support_feats):
        prop_feats = []  # 帧间传播的特征
        attentions = []  # 为了计算特征增强的权重
        h_max_for_loss_aux = []
        w_max_for_loss_aux = []
        feat_cur = feat_list[0]  # 当前帧特征
        batch_size = feat_cur.shape[0]
        h_f = feat_cur.shape[2]  # 当前特征图高
        w_f = feat_cur.shape[3]  # 当前特征图宽
        h_c = int(h_f / 2)
        w_c = int(w_f / 2)

        prop_feats.append(feat_cur)  # 传播特征列表先加入当前特征
        embedding = self.embedconv(feat_cur)  # embedding conv为图中的\sigma网络 三层卷积层组成
        embedding_prime = self.maxpool_stride2(embedding)  # 减少计算量, 进行池化
        # (B, 128, H, W) -> (B, H*W, 128):
        embedding_prime = embedding_prime.view(batch_size, self.embed_dim, -1).permute(0, 2, 1)
        attention_cur = self.attention_cur(feat_cur)  # 预测当前特征的类别无关热度图 self.attention_cur 为一层卷积 shape: (bs, H, W, 1)
        attentions.append(attention_cur)
        for idx, feat_prev in enumerate(feat_list[1:]):  # 对于以前的帧
            # Sec. 4.1: Cost Volume based Association
            # 对当前帧进行与之前帧的cost volume计算  得到预测的运动tracking offset
            c_h, c_w, tracking_offset = self.CVA(embedding_prime, feat_prev, batch_size, h_c, w_c)

            # tracking offset output and CVA loss inputs
            if idx == 0:
                tracking_offset_output = tracking_offset
                h_max_for_loss = c_h
                w_max_for_loss = c_w
            else:
                h_max_for_loss_aux.append(c_h)
                w_max_for_loss_aux.append(c_w)

            # Sec. 4.2: Motion-guided Feature Warper
            # 经过MFW 产生传播得到特征
            prop_feat = self.MFW(support_feats[idx], tracking_offset, feat_cur, feat_prev, batch_size, h_f, w_f)
            prop_feats.append(prop_feat)
            attentions.append(self.attention_prev(prop_feat))  # 对传播特征也预测attention 用以计算特征增强的权重

        attentions = torch.cat(attentions, dim=1)  # (B,T,H,W) T为帧数
        adaptive_weights = F.softmax(attentions, dim=1)  # shape: (bs, 1, H, W)
        adaptive_weights = torch.split(adaptive_weights, 1, dim=1)  # 3*(B,1,H,W)
        # feature aggregation (MFW)
        enhanced_feat = 0
        for i in range(len(adaptive_weights)):
            enhanced_feat += adaptive_weights[i] * prop_feats[i]  # 论文(6)式 为了解决遮挡等问题提出的特征增强
            # adaptive_weights通过softmax计算 自动赋予特征的权重

        return [enhanced_feat], embedding, tracking_offset_output, [h_max_for_loss, w_max_for_loss], h_max_for_loss_aux, w_max_for_loss_aux

其中的CVA模块:

def CVA(self, embedding_prime, feat_prev, batch_size, h_c, w_c):
        embedding_prev = self.embedconv(feat_prev)
        _embedding_prev = self.maxpool_stride2(embedding_prev)
        _embedding_prev = _embedding_prev.view(batch_size, self.embed_dim, -1)
        # Cost Volume Map
        # 将当前帧的每个位置的embedding都与之前帧的进行相似度计算
        c = torch.matmul(embedding_prime, _embedding_prev)  # (B, H*W/4, H*W/4)
        c = c.view(batch_size, h_c * w_c, h_c, w_c)  # (B, H*W, H, W)

        c_h = c.max(dim=3)[0]  # (B, H*W, H)  # 求W维度的最大值 即最大池化
        c_w = c.max(dim=2)[0]  # (B, H*W, W)  # 求H维度的最大值 即最大池化
        c_h_softmax = F.softmax(c_h * self.tempature, dim=2) # 在W维度作softmax
        c_w_softmax = F.softmax(c_w * self.tempature, dim=2) # 在H维度作softmax
        v = torch.tensor(self.v, device=self.opt.device)  # (1, H*W, H)
        m = torch.tensor(self.m, device=self.opt.device)
        # 一通改变维度
        off_h = torch.sum(c_h_softmax * v, dim=2, keepdim=True).permute(0, 2, 1)  # 与模板相乘 预测offset
        off_w = torch.sum(c_w_softmax * m, dim=2, keepdim=True).permute(0, 2, 1)
        off_h = off_h.view(batch_size, 1, h_c, w_c)
        off_w = off_w.view(batch_size, 1, h_c, w_c)
        off_h = nn.functional.interpolate(off_h, scale_factor=2)  # 插值
        off_w = nn.functional.interpolate(off_w, scale_factor=2)

        tracking_offset = torch.cat((off_w, off_h), dim=1)

        return c_h, c_w, tracking_offset

MFW模块:

def MFW(self, support_feat, tracking_offset, feat_cur, feat_prev, batch_size, h_f, w_f):
        # deformable conv offset input
        # 将CVA预测的offset与特征图之差结合(结合通过concat实现)进一步对运动进行预测
        off_deform = self.gamma(tracking_offset, feat_cur, feat_prev, batch_size, h_f, w_f)
        mask_deform = torch.tensor(np.ones((batch_size, 9, off_deform.shape[2], off_deform.shape[3]),
                                           dtype=np.float32)).to(self.opt.device)
        # feature propagation
        # 通过可变型卷积 以gamma输出的offset 和 与类别无关中心热度图相乘的特征 为输入 计算可变型卷积
        prop_feat = self.dcn1_1(support_feat, off_deform, mask_deform)  # 得到传播特征

        return prop_feat

猜你喜欢

转载自blog.csdn.net/wjpwjpwjp0831/article/details/121771208