ATOM:Accurate Tracking by Overlap Maximization
关于siamese系列的跟踪网络,之前已经看得比较详细了,无论是anchor-free的还是anchor-based。然而,一直想完成一下Martin Danelljan的基于判别力的两个跟踪器的分析,今天终于有时间记录一下ATOM(CVPR2019 oral)了,之后有时间再来写DIMP(ICCV2019 oral)。
论文:ATOM: Accurate Tracking by Overlap Maximization
代码:https://github.com/visionml/pytracking
Results
这里先放一下结果吧,有个直观的性能印象:
速度:
ATOM | 30 FPS | GTX-1080 |
---|
Architecture
ATOM整体架构包括两部分:target estimate和classification component。其中target estimate是offline training的,在跟踪过程中保持不变了,主要就是一个IoU Predictor,根据输入的gt bbox的特征和proposals的特征,来直接预测两者之间的IoU;classification是online learning的【这部分还没看,所以先不敢乱说】,如下图所示:
Offline Training
离线训练只是针对target estimate,为的是让网络学习一种general representation for IoU prediction。因为motivation借鉴自目标检测里面的IoU-Net,所以对于跟踪这种target-specific的任务怎么去学习,作者就论证要充分利用第一帧的信息,这里就有三种方法:
- concatenation: reference和test image的激活值在送入最后预测IoU之前拼接在一起
- siamese: reference和test分支使用相同的网络结构,最后输出一个标量的IoU
- modulation based: 把第一帧的特征作为调制向量来融入到IoU预测中(顺便说一句,这个思想他们用的还挺多的,比如KYS中的state vector)
结果证明第三种方法最好,这一部分的IoU Predictor的网络如下图所示:其中为了得到固定的输出大小,还使用了PrPooling。
The IoU-predictor takes four inputs: i) backbone features from current frame, ii) bounding box estimates in the current frame, iii) backbone features from a reference frame, iv) the target bounding box in the reference frame.
具体的做法就是在reference image中获得图片,然后送入backbone获得特征,然后把对应目标位置的特征pooling下来,最后生成modulation vector,在test image中会在目标周围生成16个proposals,然后也把他们的特征pooling下来,和modulation vector相乘之后再concatenate之后再经过全连接得到16个IoU。(代码里面稍微有点不同,代码里面是先与modulation vector相融合再prpooling)
可以看一下代码的部分:
# Create network and actor
net = atom_models.atom_resnet18(backbone_pretrained=True) # include backbone_net, iou_predictor
objective = nn.MSELoss()
actor = actors.AtomActor(net=net, objective=objective)
# Optimizer: keep backbone fixed
optimizer = optim.Adam(actor.net.bb_regressor.parameters(), lr=1e-3)
lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=15, gamma=0.2)
# Create trainer
trainer = LTRTrainer(actor, [loader_train, loader_val], optimizer, settings, lr_scheduler)
# Run training (set fail_safe=False if you are debugging)
trainer.train(50, load_latest=True, fail_safe=True)
# Compute loss
# iou_pred shape: (4,16) iou_gt shape: (4,16) assume batch_size=4
loss = self.objective(iou_pred, iou_gt)
从上面可以看出:
- 使用了预训练的resnet18,定义了更新bb_regressor的参数初始学习率,也就是iou_predictor部分,backbone不更新参数
- 用了mean square error来衡量IoU之间的损失
- 在训练的时候同时用了训练数据和验证数据来看看训练的效果(验证集跑的频率会低一点)
这里其实是有个小问题的,就是PrPooling部分,送入的proposals的位置是原图上的,而不是对应到特征图上的位置,但是PrRoIPool2D(3, 3, 1/8)使用时会有第三个参数,就是原图到特征图的stride或者叫做scale factor,这样就能从特征图中pooling下来对应目标位置的特征了
更加直白的讲,就是给定一个真实目标框的特征,然后输入其他一些随机的框(已知与目标框的真实的IoU),然后训练iou_predictor,达到一个预测IoU的目的,可看下面的图示意:
他们之间的真实IoU分别是: 0.1749, 0.9444, 0.2505, 0.5073, 0.8344, 0.4946, 0.3811, 0.8253, 0.8629, 0.1295, 0.9223, 0.8482, 0.9558, 0.9312, 0.3689, 0.7361
Online classification
因为target estimation module提供了准确的bounding box的回归,但是缺少对目标和背景干扰物的辨别能力,所以用一个taget classification module去补上这块,提出两层的全卷积(兼顾效率和收敛性)来在线分类,提供一个粗略的2D目标位置,以j减少错误的检测来提高鲁棒性。Online classification用的是resnet18 block4的特征,而target estimation用的是block3和block4的特征。模型如下:
f
(
x
;
w
)
=
ϕ
2
(
w
2
∗
ϕ
1
(
w
1
∗
x
)
)
(2)
f(x;w)=\phi_{2}(w_{2}*\phi_{1}(w_{1}*x)) \tag{2}
f(x;w)=ϕ2(w2∗ϕ1(w1∗x))(2)
其中
ϕ
1
\phi_{1}
ϕ1,
ϕ
2
\phi_{2}
ϕ2是两个激活函数,,
w
1
w_{1}
w1是一个
1
∗
1
1*1
1∗1,输出通道为64的卷积层,
w
2
w_{2}
w2一个
4
∗
4
4*4
4∗4,输出通道为1的卷积层。这里的输入
x
x
x就是training sample的从backbone提取出来的特征。
然后我们可以把在线训练的目标函数写成一个L2形式的优化问题:
L
(
w
)
=
∑
j
=
1
m
γ
j
∥
f
(
x
j
;
w
)
−
y
j
∥
2
+
∑
k
λ
k
∥
w
k
∥
2
(3)
L(w)=\sum_{j=1}^{m} \gamma_{j} \Vert f(x_{j};w)-y_{j}\Vert ^2+\sum_{k} \lambda_{k} \Vert w_{k} \Vert^2 \tag{3}
L(w)=j=1∑mγj∥f(xj;w)−yj∥2+k∑λk∥wk∥2(3)
我们把残差记作:
r
j
(
w
)
=
γ
j
(
f
(
x
j
;
w
)
−
y
j
)
,
j
∈
{
1
,
.
.
.
,
m
}
r_{j}(w)=\sqrt{\gamma_{j}}(f(x_{j};w)-y_{j}), j \in \{{1,...,m}\}
rj(w)=γj(f(xj;w)−yj),j∈{1,...,m}和
r
m
+
k
(
w
)
=
λ
k
w
k
,
k
=
1
,
2
r_{m+k}(w)=\sqrt{\lambda_{k}}w_{k}, k=1,2
rm+k(w)=λkwk,k=1,2,然后我们就能把目标函数写作
L
(
w
)
=
∥
r(w)
∥
2
L(w)= \Vert \textbf{r(w)} \Vert^2
L(w)=∥r(w)∥2,
r(w)
\textbf{r(w)}
r(w)是所有
r
j
(
w
)
r_{j}(w)
rj(w)的concatenation。而这种平方和最小化问题是可以用一种特定的优化方法来解决的:高斯牛顿法。
下面我们来推导一下论文中的几个公式:
一阶泰勒展开这是容易的:
r
(
w
+
Δ
w
)
≈
r
w
+
∂
r
∂
w
Δ
w
=
r
w
+
J
w
Δ
w
r(w+\Delta w) \approx r_{w}+\frac{\partial r}{\partial w}\Delta w=r_{w}+J_{w}\Delta w
r(w+Δw)≈rw+∂w∂rΔw=rw+JwΔw
又因为
L
(
w
)
=
∥
r(w)
∥
2
L(w)= \Vert \textbf{r(w)} \Vert^2
L(w)=∥r(w)∥2,所以
L
(
w
+
Δ
w
)
=
∥
r
(
w
+
Δ
w
)
∥
2
=
∥
r
w
+
J
w
Δ
w
∥
2
=
Δ
w
T
J
w
T
J
w
Δ
w
+
2
Δ
w
T
J
w
T
r
w
+
r
w
T
r
w
(4)
L(w+\Delta w)= \Vert r(w+\Delta w) \Vert^2=\Vert r_{w}+J_{w}\Delta w \Vert^2=\Delta w^TJ_{w}^TJ_{w}\Delta w+2\Delta w^TJ_{w}^T r_{w}+ r_{w} ^Tr_{w} \tag{4}
L(w+Δw)=∥r(w+Δw)∥2=∥rw+JwΔw∥2=ΔwTJwTJwΔw+2ΔwTJwTrw+rwTrw(4)
但是
L
~
w
(
Δ
w
)
≈
L
(
w
+
Δ
w
)
\widetilde{L}_{w}(\Delta w) \approx L(w+\Delta w)
L
w(Δw)≈L(w+Δw)我还是不太明白,估计就是转移到学习
Δ
w
\Delta w
Δw上来。而共轭梯度法是解决线性方程组问题的。论文后面的算法部分确实看不懂了,虽然花了点时间看了Conjugate Gradient还有Gauss-Newton还是啃不动论文理论部分(这就是follow马丁难的一部分原因),这里还是贴一下吧:
看不懂上面的没事,只要明白下面的也行:
- 第一帧: 在第一帧用translation, rotation, blur, dropout数据增强手段产生30个初始的training samples
x
j
x_{j}
xj,然后用算法1进行
N
G
N
=
6
N_{GN}=6
NGN=6次的高斯牛顿,
N
C
G
=
10
N_{CG}=10
NCG=10次的共轭梯度优化
w
1
w_{1}
w1,
w
2
w_{2}
w2,这里可视化一下训练样本和标签,有一个直观的感受:
这是23个initial patched,另外的7个是从提取到的特征再用dropout生成7个特征,所以最后就是这样30个训练样本
下面左边这幅图就是30个resnet18 block4的经过数据增强的特征图(代码中的layer3),最后7个特征图是第一幅图经过dropout得到的,所以和第一幅图比较像,右边是他们对应的标签,可以看到位置是对应的。
- 后续帧:当后续帧输入进来的时候,就先经过classification model更新得到当前一个粗糙的2D位置,而根据前一帧的宽高信息就会得到一个初始框B,当然这时输入给IoU predictor就会得到一个预测出来的IoU,可能并不会很高,这时候就要去优化refine。这时作者提出更好的做法是根据B生成10个proposals,用5步梯度上升来优化框的坐标来获得更高的IoU,这其中也会每10帧进行
N
G
N
=
1
N_{GN}=1
NGN=1次的高斯牛顿,
N
C
G
=
5
N_{CG}=5
NCG=5次的共轭梯度优化
w
2
w_{2}
w2,取最后的最高IoU的三个框的平均值为最后预测结果,这一部分主要在
refine_target_box
函数。【第一次看会不会觉得很绕,可是就是这样,其实这里面还有很多坐标平滑,判断跟踪flag为’not_found’,‘hard_negative’,'uncertain’等不同情况而用不同的量去update state,所以代码的话是有很多细节堆起来的】