最近才真正開(kāi)始研究目標(biāo)跟蹤領(lǐng)域(好吧,是真的慢)凌外。就先看了一篇論文:
Fully-Convolutional Siamese Networks for Object Tracking【ECCV2016 workshop】
又因?yàn)閷W(xué)的是PyTorch框架,所以找了一份比較clean的代碼涛浙,還是pytorch1.0的:
https://github.com/huanglianghua/siamfc-pytorch
因?yàn)檫@個(gè)作者也是GOT-10k toolkit的主要貢獻(xiàn)者康辑,所以用上這個(gè)工具箱之后顯得training和test會(huì)clean一些,要能跑訓(xùn)練和測(cè)試代碼轿亮,還得去下載GOT-10k數(shù)據(jù)集疮薇,訓(xùn)練數(shù)據(jù)分成了19份,如果只是為了跑一下下一份就行我注。
論文概述
SiamFC這篇論文算是將深度神經(jīng)網(wǎng)絡(luò)較早運(yùn)用于tracking的按咒,比它還早一點(diǎn)的就是SINT了,主要是運(yùn)用了相似度學(xué)習(xí)的思想但骨,采用孿生網(wǎng)絡(luò)励七,把127×127的exemplar image 和255×255的search image 輸入同一個(gè)backbone(論文中就是AlexNet)也叫Embedding Network,生成各自的Embedding奔缠,然后這兩個(gè)Embedding經(jīng)過(guò)互相關(guān)計(jì)算的得到score map掠抬,其上大的位置就代表對(duì)應(yīng)位置上的Embedding相似度大,反之亦然校哎。整個(gè)訓(xùn)練流程可以用下圖表示:
個(gè)人感覺(jué)两波,訓(xùn)練就是為了優(yōu)化Embedding Network,在見(jiàn)到的序列中生成一個(gè)更好embedding,從而使生成的score map和生成的ground truth有更小的logistic loss雨女。更多細(xì)節(jié)在之后的幾篇會(huì)和代碼一起分析谚攒。
backbones.py分析
from __future__ import absolute_import
import torch.nn as nn
__all__ = ['AlexNetV1', 'AlexNetV2', 'AlexNetV3']
class _BatchNorm2d(nn.BatchNorm2d):
def __init__(self, num_features, *args, **kwargs):
super(_BatchNorm2d, self).__init__(
num_features, *args, eps=1e-6, momentum=0.05, **kwargs)
class _AlexNet(nn.Module):
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
return x
class AlexNetV1(_AlexNet):
output_stride = 8
def __init__(self):
super(AlexNetV1, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 96, 11, 2),
_BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 2))
self.conv2 = nn.Sequential(
nn.Conv2d(96, 256, 5, 1, groups=2),
_BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 2))
self.conv3 = nn.Sequential(
nn.Conv2d(256, 384, 3, 1),
_BatchNorm2d(384),
nn.ReLU(inplace=True))
self.conv4 = nn.Sequential(
nn.Conv2d(384, 384, 3, 1, groups=2),
_BatchNorm2d(384),
nn.ReLU(inplace=True))
self.conv5 = nn.Sequential(
nn.Conv2d(384, 256, 3, 1, groups=2))
class AlexNetV2(_AlexNet):
output_stride = 4
def __init__(self):
super(AlexNetV2, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 96, 11, 2),
_BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 2))
self.conv2 = nn.Sequential(
nn.Conv2d(96, 256, 5, 1, groups=2),
_BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 1))
self.conv3 = nn.Sequential(
nn.Conv2d(256, 384, 3, 1),
_BatchNorm2d(384),
nn.ReLU(inplace=True))
self.conv4 = nn.Sequential(
nn.Conv2d(384, 384, 3, 1, groups=2),
_BatchNorm2d(384),
nn.ReLU(inplace=True))
self.conv5 = nn.Sequential(
nn.Conv2d(384, 32, 3, 1, groups=2))
class AlexNetV3(_AlexNet):
output_stride = 8
def __init__(self):
super(AlexNetV3, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 192, 11, 2),
_BatchNorm2d(192),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 2))
self.conv2 = nn.Sequential(
nn.Conv2d(192, 512, 5, 1),
_BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 2))
self.conv3 = nn.Sequential(
nn.Conv2d(512, 768, 3, 1),
_BatchNorm2d(768),
nn.ReLU(inplace=True))
self.conv4 = nn.Sequential(
nn.Conv2d(768, 768, 3, 1),
_BatchNorm2d(768),
nn.ReLU(inplace=True))
self.conv5 = nn.Sequential(
nn.Conv2d(768, 512, 3, 1),
_BatchNorm2d(512))
這個(gè)module主要實(shí)現(xiàn)了3個(gè)AlexNet版本作為backbone,開(kāi)頭的__all__ = ['AlexNetV1', 'AlexNetV2', 'AlexNetV3']
主要是為了讓別的module導(dǎo)入這個(gè)backbones.py的東西時(shí)氛堕,只能導(dǎo)入__all__
后面的部分馏臭。
后面就是三個(gè)類AlexNetV1、AlexNetV2讼稚、AlexNetV3括儒,他們都集成了類_AlexNet,所以他們都是使用同樣的forward函數(shù)锐想,依次通過(guò)五個(gè)卷積層帮寻,每個(gè)卷積層使用nn.Sequential()堆疊,只是他們各自的total_stride和具體每層卷積層實(shí)現(xiàn)稍有不同(當(dāng)然跟原本的AlexNet還是有些差別的赠摇,比如通道數(shù)上):
-
AlexNetV1和AlexNetV2:
- <font color=blue>共同點(diǎn):</font>conv2固逗、conv4、conv5這幾層都用了groups=2的分組卷積藕帜,這跟原來(lái)的AlexNet會(huì)更接近一點(diǎn)
- <font color=red>不同點(diǎn):</font>conv2中的MaxPool2d的stride不一樣大烫罩,conv5層的輸出通道數(shù)不一樣
- AlexNetV1和AlexNetV3:前兩層的MaxPool2d是一樣的,但是中間層的卷積層輸入輸出通道都不一樣洽故,最后的輸出通道也不一樣贝攒,AlexNetV3最后輸出經(jīng)過(guò)了BN
- AlexNetV2和AlexNetV3:conv2中的MaxPool2d的stride不一樣,AlexNetV2最后輸出通道數(shù)小很多
其實(shí)感覺(jué)即使有這些區(qū)別时甚,但是這并不是很重要隘弊,這一部分也是整體當(dāng)中容易理解的,所以不必太去糾結(jié)為什么不一樣荒适,最后作者用的是AlexNetV1梨熙,論文中是這樣的結(jié)構(gòu),其實(shí)也就是AlexNetV1:
注意:有些人會(huì)感覺(jué)這里輸入輸出通道對(duì)不上吻贿,這是因?yàn)橄裨続lexNet分成了2個(gè)group串结,所以會(huì)有48->96, 192->384這樣。
也可以在此py文件下面再加一段代碼舅列,測(cè)試一下打印出的tensor的shape:
if __name__ == '__main__':
alexnetv1 = AlexNetV1()
import torch
z = torch.randn(1, 3, 127, 127)
output = alexnetv1(z)
print(output.shape) # torch.Size([1, 256, 6, 6])
x = torch.randn(1, 3, 256, 256)
output = alexnetv1(x)
print(output.shape) # torch.Size([1, 256, 22, 22])
# 換成AlexNetV2依次是:
# torch.Size([1, 32, 17, 17])肌割、torch.Size([1, 32, 49, 49])
# 換成AlexNetV3依次是:
# torch.Size([1, 512, 6, 6])、torch.Size([1, 512, 22, 22])
heads.py
先放代碼為敬:
class SiamFC(nn.Module):
def __init__(self, out_scale=0.001):
super(SiamFC, self).__init__()
self.out_scale = out_scale
def forward(self, z, x):
return self._fast_xcorr(z, x) * self.out_scale
def _fast_xcorr(self, z, x):
# fast cross correlation
nz = z.size(0)
nx, c, h, w = x.size()
x = x.view(-1, nz * c, h, w)
out = F.conv2d(x, z, groups=nz) # shape:[nx/nz, nz, H, W]
out = out.view(nx, -1, out.size(-2), out.size(-1)) #[nx, 1, H, W]
return out
- 為什么這里會(huì)有個(gè)out_scale帐要,根據(jù)作者說(shuō)是因?yàn)椋?和互相關(guān)之后的值太大把敞,經(jīng)過(guò)sigmoid函數(shù)之后會(huì)使值處于梯度飽和的那塊,梯度太小榨惠,乘以out_scale就是為了避免這個(gè)奋早。
- _fast_xcorr函數(shù)中最關(guān)鍵的部分就是F.conv2d函數(shù)了盛霎,可以通過(guò)官網(wǎng)查詢到用法
torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1) → Tensor
- input – input tensor of shape (, ,,)
- weight – filters of shape (,,,)
所以根據(jù)上面條件,可以得到:x shape:[nx/nz, nz*c, h, w] 和 z shape:[nz, c, hz, wz]耽装,最后out shape:[nx, 1, H, W]
其實(shí)最后真實(shí)喂入此函數(shù)的z embedding shape:[8, 256, 6, 6], x embedding shape:[8, 256, 20, 20], output shape:[8, 1, 15, 15]【這個(gè)之后再回過(guò)來(lái)看也行】
同樣的愤炸,也可以用下面一段代碼測(cè)試一下:
if __name__ == '__main__':
import torch
z = torch.randn(8, 256, 6, 6)
x = torch.randn(8, 256, 20, 20)
siamfc = SiamFC()
output = siamfc(z, x)
print(output.shape) # torch.Size([8, 1, 15, 15])
好了,這部分先講到這里掉奄,這一塊還是算簡(jiǎn)單的规个,一般看一下應(yīng)該就能理解,之后的代碼會(huì)更具挑戰(zhàn)性姓建,嘻嘻诞仓,放一個(gè)輔助鏈接,下面這個(gè)版本中有一些動(dòng)圖速兔,還是會(huì)幫助理解的:
還有下面是GOT-10k的toolkit墅拭,可以先看一下,但是訓(xùn)練部分代碼還不是涉及很多: