CoTracker: It is Better to Track Together使用Transform的时间与空间注意力机制的密集点联合追踪算法详细解析
文章概括总结:在之前学习的Tracking Everything Everywhere All at Once(2023 ICCV最佳学生论文)与RAFT: Recurrent All-Pairs Field Transforms for Optical Flow(2020 ECCV最佳论文的基础上)我阅读了谷歌发表的CoTracker: It is Better to Track Together联合跟踪的论文(2024 ECCV)并详细学习了论文的源码对使用Transform时间与空间注意力机制的密集点联合追踪进行一定的说明
论文名:CoTracker: It is Better to Track Together
论文作者:Nikita Karaev et.al.
发表时间:2024-1
论文地址:https://arxiv.org/abs/2307.07635
源码:https://github.com/facebookresearch/co-tracker
摘要概况总结
在摘要概况总结的部分我们首先对整个跟踪器的主要的内容进行一定的介绍和总结。
-
CoTracker是一个基于Transform的在2d空间条件下对其中像素点进行跟踪的跟踪器。
-
CoTracker是一个联合跟踪器考虑到了上下文之间的关系,这一点在后面会有一定的介绍。
-
CoTracker是一个基于短窗口的在线跟踪算法,其利用unrolled windows窗口进行循环的训练和推理。
是一种结合光流思想的点跟踪运动估计算法,取得了良好的成绩。CoTracker引入了几个技术创新,包括虚拟轨迹的概念,这使得CoTracker能够在单个GPU上联合跟踪多达70k点。此外,CoTracker在短窗口上进行因果操作(适用于在线任务),但通过在更长的视频序列上展开窗口进行训练。
先对论文中包含的信息进行说明,在结合代码对具有的细节的部分进行描述。
我自己总结可以将整个算法流程的讲解分为三个部分来进行展开说明。
-
对整个基于Tramsform的网络架构进行介绍,主要就是类似于主干网络RestNet(不是标准的RestNet进行说明。)也就是Encode的部分。和之后进行迭代的类似解码器的部分(
堆叠空间注意力和时间注意力机制的模块部分
) -
对于输入到迭代的Transform结构之前的各个变量的关系进行说明(结合一定的代码部分)
-
其中提到的滑动窗口机制和提及的联合跟踪于之前的RATF两个帧之间计算相关性的区别在哪?(也是通过循环的RNN输出)。
对于这些部分对我们的两个实现图和其中的公式细节进行解读,构成了我们的整篇论文算法的一个介绍。
光流相关工作与发展
对于相关工作的介绍部分我不在进行翻译的说明了,大体上是介绍了以下的几个方面的发展和之前的工作
- 光流估计(Optical flow)。
- 多帧光流(Multi-frame optical flow):传统的卡尔曼滤波。
- 联合跟踪(Joint Tracking):手工标注。
- 跟踪任意点(Tracking any point):TAP-Vid和PIPS方法。
- 合成数据集的使用
提出了联合跟踪的一个优势非关键的背景点不会随着物体进行运动。
我们给出其中的参考文献之中提到的光流的相关的技术。
按时间顺序和技术复杂度列出光流估计的一些技术:
-
色彩恒定性方法 [16, 24, 4, 5]:最初的光流估计方法,通过解决由色彩恒定性引起的简单微分方程来处理。
-
FlowNet [12]:第一个端到端的用于光流估计的卷积网络,由Dosovitskiy等人引入。
-
FlowNet2 [18]:在FlowNet的基础上,通过堆叠架构和图像配准进一步改进的光流估计方法。
-
DCFlow [46]:提出了构建4D成本体积的方法,该成本体积捕捉了源图像和目标图像特征之间的关系。
-
PWC-Net [39]:使用了DCFlow中的成本体积,并引入金字塔处理和图像配准以降低计算成本。
-
RAFT
[41]:保留了高分辨率的光流并增量更新光流场,启发了后续的研究工作。 -
Flowformer [17]:从RAFT汲取灵感,提出了基于Transformer的方法,对4D成本体积进行了分词处理。
-
GMFlow [48]:将更新网络替换为具有自注意力的softmax以进行优化。
Perceiver IO [19]:提出了一个统一的Transformer框架,可应用于多个任务,包括光流估计。 -
VideoFlow [33]:将光流扩展到三个和五个连续帧,通过在光流优化期间集成前向和后向运动特征。
-
TAP-Vid [10]:提出了在视频中跟踪任何物理点的问题,并为其提供了基准测试和简单的基线方法。
-
Particle Video Revisited [15]:通过引入用于点遮挡跟踪的模型,重新审视了经典的Particle Video问题。
-
OmniMotion [44]:在测试时优化每个视频的体积表示,在规范空间中优化估计的对应关系。
其中标注为黑色重点的部分是之前在接触点跟踪方向的时候接触过的部分网络。并结合学习了相关的代码技术实现细节。
CoTracker算法整体实现部分-论文描述
首先还是先解释论文中提到的一些内容会额外的补充一些技术细节,后面在对其进行代码细节的说明和解析。
目标和问题形式化
-
目标是在视频的持续时间内跟踪2D点,输入是一个RGB的视频帧序列,给出的是预测点的位置坐标以及其可见性的标志 。
-
CoTracker算法以视频V和N个轨迹的起始位置和时间是已知的,其他值需要进行预测 。
公式符号细节解读:
- 视频帧的定义:
V = ( I t ) t = 1 T , I t ∈ R 3 × H × W V=\left(I_{t}\right)_{t=1}^{T},I_{t} \in \mathbb{R}^{3 \times H \times W} V=(It)t=1T,It∈R3×H×W
V视频序列 It代表的是第t帧的输入的三通道彩色图像(3 x H x W)整个视频的总的长度为T 输入视频从第一帧到第T帧之间进行全部加载。
- 预测的目标点的符号定义:
P t i = ( x t i , y t i ) ∈ R 2 , t = t i , … , T , i = 1 , … , N t i ∈ { 1 , … , T } P_{t}^{i}=\left(x_{t}^{i}, y_{t}^{i}\right) \in \mathbb{R}^{2}, t=t^{i}, \ldots, T, i=1, \ldots, N t^{i} \in\{1, \ldots, T\} Pti=(xti,yti)∈R2,t=ti,…,T,i=1,…,Nti∈{1,…,T}
我们的输入其实是通过网格距离初始化的100个目标点,作为输入,预测的目标即为我们上面的公式:
第t帧的第i个目标点(代码中一共有100个目标点
)的2维的xy坐标值。起始帧为ti 从当前帧一直定义到T 位置坐标点即为1到100 N=100
- 可见性值的定义:
v t i ∈ { 0 , 1 } v_{t}^{i} \in\{0,1\} vti∈{0,1}
0表示可见 1表示不可见。we assume that each point is visible at the start of the track(假设初始点都是可见的)
- 输入起始位置得到运动的估计位置信息的定义:
( P t i i , t i ) i = 1 N ( P ^ t i = ( x ^ t i , y ^ t i ) , v ^ t i ) \left(P_{t^{i}}^{i}, t^{i}\right)_{i=1}^{N} \left(\hat{P}_{t}^{i}=\left(\hat{x}_{t}^{i}, \hat{y}_{t}^{i}\right), \hat{v}_{t}^{i}\right) (Ptii,ti)i=1N(P^ti=(x^ti,y^ti),v^ti)
也就是结合可见性进行补充估计值在代码中会得到一个序列最后进行综合。
Transformer formulation
Ψ : G → O . \Psi: G \rightarrow O . Ψ:G→O.
The goal of this transformer is to improve an initial estimate of the
tracks:使用Transformer结构对初始的轨迹G进行轨迹,也就是通过O来进行输出的。
- G和O其实都是对应代码中的token结构。论文中也进行了进一步的细化说明。
G t i i = 1 , … , N t = 1 , … , T G_{t}^{i} i=1, \ldots, Nt=1, \ldots, T Gtii=1,…,Nt=1,…,T
- i代表的也是第i个点
- t代表的是第t帧特征图
- 整个为我们三个部分(相关性矩阵,通过p表示的光流,v和maks掩码)整个的部分作为Transformer的输入(代码中得到的)
output tokens O t i . \text { output tokens } O_{t}^{i} \text {. } output tokens Oti.
Image features部分
ϕ ( I t ) ∈ R d × H k × W k \phi\left(I_{t}\right) \in R^{d \times \frac{H}{k} \times \frac{W}{k}} ϕ(It)∈Rd×kH×kW
和其通过自定义的步长得到的缩放版本。
ϕ ( I t ; s ) ∈ R d × H s k × W s k \phi\left(I_{t} ; s\right) \in R^{d \times \frac{H}{s k} \times \frac{W}{s k}} ϕ(It;s)∈Rd×skH×skW
都是通过我们的CNN或者说是endcode部分提取
图像的特征得到的信息值。
里面的CNN细节部分我们在第三部分在进行详细的一个介绍。
Track features
The appearance of the tracks is captured by feature vectors-轨迹的外观特征由向量Q来进行定义。
Q t i ∈ d Q_{t}^{i} \in d Qti∈d
第t帧的第i点的轨迹特征代码中的d=128维的一个
向量。
通过广播(扩增)作为输出,并在Transform中不断的迭代M=6次进行更新。
Spatial correlation features 相关特征的计算
和RATF一样也是采用全点积的方式来计算像素之间的坐标,同样在代码中也是在多个尺度上进行计算的。但是区别在于其计算的是一个序列窗口的也是8帧的一个联合的计算方式。
- 相关特征的表示这里的S其实就是之前提到的8帧即一个窗口的序列长度。
C t i ∈ R S C_{t}^{i} \in \mathbb{R}^{S} Cti∈RS
- 相关性的计算方法论文中给出了如下的描述方式:
[ C t i ] s δ = ⟨ Q t i , ϕ s ( I t ) [ P ^ t i / k s + δ ∣ ⟩ , \left[C_{t}^{i}\right]_{s \delta}=\left\langle Q_{t}^{i}, \quad \phi_{s}\left(I_{t}\right)\left[\hat{P}_{t}^{i} / k s+\delta| \rangle,\right.\right. [Cti]sδ=⟨Qti,ϕs(It)[P^ti/ks+δ∣⟩,
这里的s其实代表的就是一个下采样的比例我们的Q也就是轨迹特征(代码中对应在特征图中得到
)与我们的多层的特征图上根据半径die和缩放的坐标位置选取的像素点
进行一个点积的相关性运算。
Tokens部分
tokens部分也就是对应的我们的G作为Transform的一个输入。也就是上面提到的三个部分的concat连接得到的结果
tokens
G
(
P
^
,
v
^
,
Q
)
\text { tokens } G(\hat{P}, \hat{v}, Q)
tokens G(P^,v^,Q)
我们使用的是Transform结构因此要对其对其进行正余弦的位置编码操作我们表示为:
encoding η \text { encoding } \eta encoding η
使用了时间注意力和空间注意力的堆叠,因此对这两个部分进行编码的操作。
G t i = ( P ^ t i − P ^ 1 i , v ^ t i , Q t i , C t i , η ( P ^ t i − P ^ 1 i ) ) + η ′ ( P ^ 1 i ) + η ′ ( t ) . G_{t}^{i}=\left(\hat{P}_{t}^{i}-\hat{P}_{1}^{i}, \hat{v}_{t}^{i}, Q_{t}^{i}, C_{t}^{i}, \eta\left(\hat{P}_{t}^{i}-\hat{P}_{1}^{i}\right)\right)+\eta^{\prime}\left(\hat{P}_{1}^{i}\right)+\eta^{\prime}(t) . Gti=(P^ti−P^1i,v^ti,Qti,Cti,η(P^ti−P^1i))+η′(P^1i)+η′(t).
迭代更新的部分
O t i = ( Δ P ^ t i , Δ Q t i ) O_{t}^{i}=\left(\Delta \hat{P}_{t}^{i}, \Delta Q_{t}^{i}\right) Oti=(ΔP^ti,ΔQti)
O ( Δ P ^ , Δ Q ) = Ψ ( G ( P ^ ( m ) , v ^ ( 0 ) , Q ( m ) ) ) O(\Delta \hat{P}, \Delta Q)=\Psi\left(G\left(\hat{P}^{(m)}, \hat{v}^{(0)}, Q^{(m)}\right)\right) O(ΔP^,ΔQ)=Ψ(G(P^(m),v^(0),Q(m)))
对下面的结果进行多层的迭代更新操作。更新的时候就可以使用下面的公式来进行。
P ^ ( m + 1 ) = P ^ ( m ) + Δ P ^ and Q ( m + 1 ) = Q ( m ) + Δ Q . \hat{P}^{(m+1)}=\hat{P}^{(m)}+\Delta \hat{P} \text { and } Q^{(m+1)}=Q^{(m)}+\Delta Q . P^(m+1)=P^(m)+ΔP^ and Q(m+1)=Q(m)+ΔQ.
对于我们的可见性我们最后会结合sigmoid函数并乘以浮点数10判断与1的接近程度。-代码中
v ^ ( M ) = σ ( W Q ( M ) ) \hat{v}^{(M)}=\sigma\left(W Q^{(M)}\right) v^(M)=σ(WQ(M))
滑动窗口推断
Transformer 设计的一个优势是它可以轻松支持滑动窗口应用,以处理非常长的视频。特别考虑一个长度为T ′ > T 的视频V 其长度超过了架构支持的最大窗口长度T 。
其每次移动的步长即为T/2.
为了在整个视频V 中跟踪点,将视频分成 J = ⌈ 2 T ′ / T − 1 ⌉ 个长度为T的窗口,其中窗口之间有 T / 2 帧的重叠。
之后的过程就比较简单了我就截取了网络上关于论文的一个翻译来进行粘贴。
误差损失函数
在训练过程中的误差损失函数我们可以分为两个部分来进行说明,其损失主要是包括了两个部分的损失。
- 第m次迭代得到的位置点与真实点之间的损失值。
- 点可见性之间的损失值,应为只有两个值0 1可以看作是一个分类的任务。
L 1 ( P ^ , P ) = ∑ j = 1 J ∑ m = 1 M γ M − m ∥ P ^ ( m , j ) − P ( j ) ∥ , \mathcal{L}_{1}(\hat{P}, P)=\sum_{j=1}^{J} \sum_{m=1}^{M} \gamma^{M-m}\left\|\hat{P}^{(m, j)}-P^{(j)}\right\|, L1(P^,P)=j=1∑Jm=1∑MγM−m P^(m,j)−P(j) ,
L 2 ( v ^ , v ) = ∑ j = 1 J CE ( v ^ ( M , j ) , v ( j ) ) . \mathcal{L}_{2}(\hat{v}, v)=\sum_{j=1}^{J} \operatorname{CE}\left(\hat{v}^{(M, j)}, v^{(j)}\right) . L2(v^,v)=j=1∑JCE(v^(M,j),v(j)).
实现细节
最后论文中除了实验的部分之外给出了一点关于网络细节的说明。我们只给出最后的生成结构的样式,并在代码部分进行详细的描述。
CoTracker(
(fnet): BasicEncoder(
(norm1): InstanceNorm2d(64, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(256, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3))
(relu1): ReLU(inplace=True)
(layer1): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(64, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(64, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
(1): ResidualBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(64, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(64, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(layer2): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(64, 96, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(conv2): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(96, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(96, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm3): InstanceNorm2d(96, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(downsample): Sequential(
(0): Conv2d(64, 96, kernel_size=(1, 1), stride=(2, 2))
(1): InstanceNorm2d(96, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(1): ResidualBlock(
(conv1): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(96, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(96, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(96, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(layer3): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(96, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm3): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(downsample): Sequential(
(0): Conv2d(96, 128, kernel_size=(1, 1), stride=(2, 2))
(1): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(1): ResidualBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(layer4): Sequential(
(0): ResidualBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm3): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(downsample): Sequential(
(0): Conv2d(128, 128, kernel_size=(1, 1), stride=(2, 2))
(1): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(1): ResidualBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu): ReLU(inplace=True)
(norm1): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
(norm2): InstanceNorm2d(128, eps=1e-05, momentum=0.1, affine=False, track_running_stats=False)
)
)
(conv2): Conv2d(416, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU(inplace=True)
(conv3): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
)
(updateformer): UpdateFormer(
(input_transform): Linear(in_features=456, out_features=384, bias=True)
(flow_head): Linear(in_features=384, out_features=130, bias=True)
(time_blocks): ModuleList(
(0-5): 6 x AttnBlock(
(norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=False)
(attn): Attention(
(qkv): Linear(in_features=384, out_features=1152, bias=True)
(q_norm): Identity()
(k_norm): Identity()
(attn_drop): Dropout(p=0.0, inplace=False)
(proj): Linear(in_features=384, out_features=384, bias=True)
(proj_drop): Dropout(p=0.0, inplace=False)
)
(norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=False)
(mlp): Mlp(
(fc1): Linear(in_features=384, out_features=1536, bias=True)
(act): GELU(approximate='tanh')
(drop1): Dropout(p=0, inplace=False)
(norm): Identity()
(fc2): Linear(in_features=1536, out_features=384, bias=True)
(drop2): Dropout(p=0, inplace=False)
)
)
)
(space_blocks): ModuleList(
(0-5): 6 x AttnBlock(
(norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=False)
(attn): Attention(
(qkv): Linear(in_features=384, out_features=1152, bias=True)
(q_norm): Identity()
(k_norm): Identity()
(attn_drop): Dropout(p=0.0, inplace=False)
(proj): Linear(in_features=384, out_features=384, bias=True)
(proj_drop): Dropout(p=0.0, inplace=False)
)
(norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=False)
(mlp): Mlp(
(fc1): Linear(in_features=384, out_features=1536, bias=True)
(act): GELU(approximate='tanh')
(drop1): Dropout(p=0, inplace=False)
(norm): Identity()
(fc2): Linear(in_features=1536, out_features=384, bias=True)
(drop2): Dropout(p=0, inplace=False)
)
)
)
)
(norm): GroupNorm(1, 128, eps=1e-05, affine=True)
(ffeat_updater): Sequential(
(0): Linear(in_features=128, out_features=128, bias=True)
(1): GELU(approximate='none')
)
(vis_predictor): Sequential(
(0): Linear(in_features=128, out_features=1, bias=True)
)
)
算法流程与代码描述
原图流程细节
第一副图是一个连续跟踪的图的整体概况通过滑动窗口机制如何的进行计算。
- 输入帧(input frames It):
输入是一系列视频帧,从It到It+n。视频帧中有多个目标需要跟踪。
- 起始位置(starting locations P):
这些是需要跟踪的目标的初始位置。P的维度是(N,2),其中N是目标的数量,2代表每个目标的位置坐(x,y)。
- 卷积神经网络(CNN)
每一帧通过CNN提取特征,生成特征图(image features ϕ(It))。这些特征图表示为彩色的热图,每个像素包含特征信息。
- 特征广播:
起始位置P在特征图中进行双线性插值,得到起始位置在特征图上的表示。这些位置被广播成与特征图相同的维度,用于后续的处理。
- 跟踪特征(track features Q):
初始化跟踪特征Q,维度是(N,d),其中d是特征维度。Q通过特征图中的起始位置P提取。
- 滑动窗口处理(sliding window processing):
使用滑动窗口对特征图进行处理,每个窗口包含连续的多个帧。
在每个滑动窗口中,跟踪特征Q随着时间进行更新,以捕捉目标的运动。
- 估计的轨迹(estimated tracks P^):
最终输出是估计的轨迹P^,维度是(T′,N,2),其中T′是时间步数,N是目标数量,2代表每个目标的位置坐标。这些轨迹表示目标在视频中的运动路径。
第二幅图则是对M=6次的迭代过程进行进一步细化的描述主要涉及的也就是解码的那一个部分的技术细节描述
- 输入处理:
输入视频序列的初始帧被处理以初始化跟踪轨迹,初始轨迹包括目标的位置和特征。
- 特征提取:
对每帧图像I_t,通过卷积神经网络提取特征映射φ(I_t)。
- 相关性特征计算:
使用多尺度相关性计算特征C^(m),这些特征帮助捕捉目标之间的关系。
- 迭代更新:
初始的轨迹位置P̂(0)和特征Q(0)被输入,每次迭代更新后计算新的轨迹位置P̂(m)和特征Q(m)。在每次迭代中,利用相关性特征C(m)和轨迹特征Q(m)进行位置预测和特征更新。
- 跨轨迹/时间注意力机制:
使用Transformer网络进行跨轨迹和时间的注意力机制,整合不同时间帧和轨迹的信息,提高预测的准确性。Transformer网络进行6次迭代,每次迭代都在跨时间和轨迹的信息上进行整合和更新。
- 输出结果:
最终输出估计的轨迹和更新后的特征,包括目标在每个帧中的位置和特征信息。
代码流程细节展示
代码部分时间原因想在组会上去说,时间原因只展示一部分的细节实现吧需要的可以在私信联系作者
官方的demo.py生成的旋转苹果的跟踪效果。
pred_tracks, pred_visibility = model( # 模型预测输出的是p:轨迹点坐标 + 可见性
video,
grid_size=args.grid_size,
grid_query_frame=args.grid_query_frame, # 从0帧
backward_tracking=args.backward_tracking,
# segm_mask=segm_mask
)
按照给定的size使用网格采取100个跟踪的点。
grid_pts = get_points_on_a_grid(grid_size, self.interp_shape, device=video.device) # 在给定的网格尺寸 grid_size 和插值形状 self.interp_shape 上生成一个规则分布的点的集合。这些点可以用于初始化跟踪点,或者在图像处理任务中作为采样点
if segm_mask is not None:
segm_mask = F.interpolate(
segm_mask, tuple(self.interp_shape), mode="nearest"
)
point_mask = segm_mask[0, 0][
(grid_pts[0, :, 1]).round().long().cpu(),
(grid_pts[0, :, 0]).round().long().cpu(),
].bool()
grid_pts = grid_pts[:, point_mask]
queries = torch.cat(
[torch.ones_like(grid_pts[:, :, :1]) * grid_query_frame, grid_pts],
dim=2,
) # 两个张量沿着指定的维度拼接起来(大概率还是坐标值)
坐标缩放p与定义变量进行广播操作。
coords_init = queries[:, :, 1:].reshape(B, 1, N, 2).repeat(
1, self.S, 1, 1
) / float(self.stride) # 包含了调整后的查询点坐标,这些坐标被缩放到跟踪操作的适当尺度(根据下采样的4倍步长)
rgbs = 2 * (rgbs / 255.0) - 1.0
traj_e = torch.zeros((B, T, N, 2), device=device) # 进行广播操作 是时间序列长度
vis_e = torch.zeros((B, T, N), device=device) # B, T, N)的张量vis_e,用于存储每个点在每个时间点的可见性状态
最后传入到解码器之前的部分变量的合并细节
fcorrs = fcorr_fn.sample(coords) # B, S, N, LRR 存储从fcorr_fn.sample函数返回的特征相关性 LRR可能是表示坐标维度的参数,例如在二维空间中,LRR可能等于2
LRR = fcorrs.shape[3]
fcorrs_ = fcorrs.permute(0, 2, 1, 3).reshape(B * N, S, LRR)
flows_ = (coords - coords[:, 0:1]).permute(0, 2, 1, 3).reshape(B * N, S, 2) #这个代表论文中的p-p'的部分 处理坐标数据,计算点随时间移动的光流(optical flow),并重新排列和调整张量的形状 从(B, S, N, 2)重新排列为(B, N, S, 2)
flows_cat = get_2d_embedding(flows_, 64, cat_coords=True) # 加pos
ffeats_ = ffeats.permute(0, 2, 1, 3).reshape(B * N, S, self.latent_dim)
if track_mask.shape[1] < vis_init.shape[1]:
track_mask = torch.cat(
[
track_mask,
torch.zeros_like(track_mask[:, 0]).repeat(
1, vis_init.shape[1] - track_mask.shape[1], 1, 1
),
],
dim=1,
)
concat = (
torch.cat([track_mask, vis_init], dim=2)
.permute(0, 2, 1, 3)
.reshape(B * N, S, 2)
) # 可见性和掩码进行拼接
transformer_input = torch.cat([flows_cat, fcorrs_, ffeats_, concat], dim=2) # 加入位置编码的p-p:表示光流移动 fcorrs_:相关性特征 ffeats_:轨迹特征query concat:可见性加掩码
标签:False,features,密集点,Track,affine,Transform,track,1e,128
From: https://blog.csdn.net/weixin_46167190/article/details/143240498