siammask:Fast Online Object Tracking and Segmentation: A Unifying Approach论文解读和代码详解

    xiaoxiao2025-07-21  62

    论文地址 作者开源代码地址 还未公布训练代码,作者等VOT竞赛结束之后会公布训练代码 今天为大家带来一篇重量级文章,其实这篇是cvpr2019的poster不是oral,但在我看来仍然是重量级的,因为这是使用孪生网络做视频目标分割(VOS)的第一篇工作,意味着后续会有更多基于此改进的VOS方法会被提出,我自己也是做VOS的,更要好好读一下,期待自己能做出一些东西!!! 下面进入正文

    思想来源

    目前目标跟踪很多是使用相关滤波,最近两年,孪生网络为基础的目标跟踪取得了更不错的结果,而且速度和精度都有提高。作者依然采用孪生网络结构,同时补充了一个二值分割的任务。其实用另一个相关任务来提升一个任务效果的做法还有DFN,face++的语义分割模型。即作者想通过学习对特定目标的分割来refine跟踪结果。总体形成了一个简单高效的框架,在精度上是VOT2018上是state of the art的,在VOS任务目前是最快的(谷歌今年的FEELVOS估计没他快,但siammask用VOS精度不高,比2017年的OSVOS还差一点,我相信后续工作会基于此做更好的VOS的,我也会朝这个方向走!)

    主要贡献

    作者在论文中没写主要贡献,我这里自己总结下

    设计了一种基于孪生网络的框架,满足同时完成目标检测和视频目标分割两种任务通过设计了mask 分支,提高了目标跟踪的性能,并且能预测目标的旋转框(非水平框)在VOT2018达到了最高精度(不确定是不是,siamRPN+不知道表现有没有它好),同时对于VOS任务,速度是最快的。

    我们先大体介绍,最后分模块仔细介绍。绝对要比其他博客详细哦,觉得好留个关注,点个赞,留下评论都是对我的支持

    大体介绍

    论文在方法那一章(第三章)先介绍了siamFC和siamRPN,因为siammask的两种类型的结构(siammask2b和siammask是基于这两种结构修改的)。我们在之后的细节部分说一下这两个网络的作用。 孪生网络其实是想寻找模板z(小点的图像)和搜索区域x(较大的图像)的区域相似度。而且模板来自第一帧由用户选择要跟踪哪一个目标。孪生网络是两个相同的网络,并且参数是共享的,最早用于人脸的相似度判断(人脸验证)。 模板z是用户框的,搜索区域x是以z的中心为中心,crop出一个更大的区域作为搜索区域(因为帧之间相同目标移动应该不大。)z和x都经过一个backbone,作者选择resnet50(实际上没有用到conv5_x),那么就得到了两个特征,其中z对应的特征标记为zf,大小为15x15x256;x对应的特征标记为xf,大小为31x31x256。zf和xf经过一个叫做depthwise correlation操作,得到了一个更小的特征,标记为ROWs,大小是17x17x256。其实对于每一个分支,都有一个depthwise correlation操作,并不是上论文图中表示的那样只有一个depthwise correlation,代码中有三个depthwise correlation。至于这个depthwise correlation我们在代码详解部分解释。ROWs按照空间维度的每一个单元,称作ROW。整个ROWs送到对应的分支中继续计算特征,就是那三个绿色方块那里, h ϕ , s ψ , b σ h_\phi,s_\psi,b_\sigma hϕ,sψ,bσ。我们可以在(a)中看到,下面两个分支其实和siamRPN很像,(b)如果仅仅保留score分支,则和siamFC很像,也许这就是reviewer觉得不能作为oral的原因吧。 OK,我仅介绍图(a),下面就是RPN的结构,所以训练方式和RPN一致。mask 分支还有一个refine模块,作者放到了附录A部分,结构也是和语义分割模型差不多的,我放到后面介绍。 下面分模块仔细介绍

    siamFC

    siamFC仅仅有一个分支,使用的cross correlation操作得到的特征仅仅有一个通道(当然这个时候不能叫做特征了,应该叫相似度度量,我们记cross correlation的结果为 g θ ( z , x ) = f θ ( z ) ⋆ f θ ( x ) g_\theta (z,x)=f_\theta (z) \star f_\theta(x) gθ(z,x)=fθ(z)fθ(x) 其中 g θ ( z , x ) g_\theta (z,x) gθ(z,x)中的空间维度上的元素记作RoW,代表着模板z和搜索区域中的某一个区域的相似度。比如 g θ n ( z , x ) g^n_\theta (z,x) gθn(z,x)就是说z和在x中的第n个区域的相似度。siamFC的目的是让 g θ ( z , x ) g_\theta (z,x) gθ(z,x)这个map的最大值出现的地方,还原回原图的位置恰好就是模板中的目标,从而达到跟踪的目的。作者和SiamFC有一点的不同的是使用depthwise cross-correlation来代替产生单通道的cross-correlation。这个分支使用logistic loss,我们记作 l s i m l_{sim} lsim

    siamRPN

    siamRPN是在siamFC的基础上,增加了RPN结构,使得网络评估目标在下一帧的位置能够使用不同比例的框来预测。在siamRPN中,对每一个RoW,都对应有k个box和对应的前景背景分数。就是说siamRPN有两个分支,box和score,其中box是对每一个ROW都预测k个框,score为k个框描述是前景的概率。box分支使用 smooth L1作为损失函数,score使用交叉熵,这部分和faster rcnn是一样的。两个损失函数分别记作 l b o x , l s c o r e l_{box},l_{score} lbox,lscore

    siamMask 的新分支 mask branch

    注意,使用depthwise cross correlation得到的是多通道的(论文中是256个通道)特征,每一个row都是1x1x256维的,记住Row是代表z和在x中某一个区域的相似度,希望能为第n个Row,即 g θ n ( z , x ) g^n_\theta (z,x) gθn(z,x)分配一个标签 y n y_n yn y n y_n yn为1则说明第n个区域和z很像,第一个关键点就是如何给 y n y_n yn分配标签。

    如何给 y n y_n yn分配标签? 一种方法是求Row的均值,就是多通道的特征值加起来看看大不大,如果够大则网络认为这个RoW和z蛮相似的,但这种方法没有利用到RPN。 论文的做法是用RPN的box分支的结果,和训练样本的标签做IOU,IOU大于0.6则 y n y_n yn为1,反之亦然。 而对于两个分支的siammask2b,作者直接选取score分支最大位置的row的标签为1,其他的row的标签都是-1

    当然我们还要有目标的分割标签,训练数据集应该给出,我们把它记作 c n c_n cn,尺寸是wxh的,和模板z一致。 我们还要根据Row还原回wxh尺寸的分割结果。这个过程涉及到refine 模块,放到后面讲,现在仅仅将refine 模块的过程看做下面的公式 m n = h ϕ ( g θ n ( z , x ) ) m_n=h_\phi(g_\theta^n(z,x)) mn=hϕ(gθn(z,x)) m n m_n mn就是针对第n个搜索区域得到的mask 那么,mask分支的loss就是这样的

    一共有17x17个候选区域,所有n的变化是从1到17x17的,看 1 + y n 2 w h \frac{1+y_n}{2wh} 2wh1+yn这一项,仅仅对被认为是目标临近区域(IOU>0.6)的第n个搜索区域才计算mask loss;再往后看, Σ \Sigma Σ的下标是i,j,就是说对wxh的像素遍历,求每个像素的loss还求和。使用的是 logistic regression loss。


    我觉得siammask分割精度低的原因很有可能就是上面的loss,对很多个符合IOU>0.6的候选区域都进行了分割处理,如果实际上有特征的偏移,仍然强行用固定的label训练,自然是不合适的


    mask分支的具体结构

    我们注意到绿色的小块 h ϕ h_\phi hϕ的输出和g是一样的,这个 h ϕ h_\phi hϕ的结构是两个1x1的卷积,在refine之前设计这个模块是有重大意义的。我们知道孪生网络有个缺点,就是不能抑制在搜索区域x内和模板z很相似的目标的响应值。作者希望通过两个1x1的卷积,能够区分开相似的实例个体,请见论文原文:

    总的损失函数

    针对两个变体,siammask2b和siammask,总的函数如上,参数设置如下。

    另外呢,因为DAVIS数据集只有mask label,作者考虑了三种策略为DAVIS数据集生成bounding box 的label。不过论文没有提如何为VOT数据集生成mask label 呢,真是奇怪呢 一种是 水平齐的框,另一种就是旋转框,最后是根据VOT2016的bencnmark提出的方法为mask自动生成框,这是最优的策略

    refine 模块

    如果了解语义分割的同学,应该很容易看的明白,相当于解码器的作用,同时用到了低级特征。

    测试阶段的pipeline

    对于两个变体,都选择在score branch最大响应的row,抽取出来送到mask branch中得到mask。在siammask2b结构中,得到mask之后进而得到水平框。使用水平框作为参考,选择下一帧的搜索区域x。对于siammask,即三分支的网络,选择box 分支最高分数的box作为参考,即用score分支最大值对应的box分支的box。

    depthwise cross correlation

    class DepthCorr(nn.Module): def __init__(self, in_channels, hidden, out_channels, kernel_size=3): super(DepthCorr, self).__init__() # adjust layer for asymmetrical features 不对称的 self.conv_kernel = nn.Sequential( nn.Conv2d(in_channels, hidden, kernel_size=kernel_size, bias=False), nn.BatchNorm2d(hidden), nn.ReLU(inplace=True), ) self.conv_search = nn.Sequential( nn.Conv2d(in_channels, hidden, kernel_size=kernel_size, bias=False), nn.BatchNorm2d(hidden), nn.ReLU(inplace=True), ) self.head = nn.Sequential( nn.Conv2d(hidden, hidden, kernel_size=1, bias=False), nn.BatchNorm2d(hidden), nn.ReLU(inplace=True), nn.Conv2d(hidden, out_channels, kernel_size=1) # 没有batch和relu ) def forward_corr(self, kernel, input): kernel = self.conv_kernel(kernel) input = self.conv_search(input) feature = conv2d_dw_group(input, kernel) return feature def forward(self, kernel, search): feature = self.forward_corr(kernel, search) out = self.head(feature) return out

    我们先看看源码中是怎么写的。直接看forward部分。kernel代表zf,zf是之前标记的字符,代表模板z的特征,search是xf。这两个特征先经过forward_corr,从函数forward_corr(self, kernel, input)我们可以看出,zf和xf分别经过了不同的3x3的卷积(并不是论文中所述用1x1的卷积),然后才进行depth cross correlation的操作。 我们进而看看这个conv2d_dw_group函数

    def conv2d_dw_group(x, kernel): batch, channel = kernel.shape[:2] x = x.view(1, batch*channel, x.size(2), x.size(3)) # 1 * (b*c) * k * k kernel = kernel.view(batch*channel, 1, kernel.size(2), kernel.size(3)) # (b*c) * 1 * H * W out = F.conv2d(x, kernel, groups=batch*channel) out = out.view(batch, channel, out.size(2), out.size(3)) return out

    先做了reshape再conv再reshape,**大家看懂了吗,我是没看懂,为啥这样就能达到描述相似度的效果呢?**估计需要看看siamFC才能知道答案了。

    源码中模型主要结构

    class Custom(SiamMask): def __init__(self, pretrain=False, **kwargs): super(Custom, self).__init__(**kwargs) self.features = ResDown(pretrain=pretrain) self.rpn_model = UP(anchor_num=self.anchor_num, feature_in=256, feature_out=256) self.mask_model = MaskCorr() self.refine_model = Refine()

    self.features 用于提取特征,是resnet50作为backbone,其中没有用到最后一stage,同时在最高层加了一个1x1的卷积用于降维。 self.rpn_model是score分支和box分支的集合 self.mask_model是mask分支的head部分 self.refine就是解码器,用于把17x17的特征图变化成wxh的mask。

    实验结果

    模型正在下载中,未完待续! 速度真的没得说,就是棒!!

    写在后面

    这篇论文也是激起了相当活跃的讨论,见第一作者的知乎专栏 其中有几条我认为挺能突出细节的。

    最新回复(0)