open-vot:PyTorch 实现 Siamese-FC
open-vot 集成了 KCF、SiamFC、GOTURN 等8种跟踪算法,采用模块化设计,利于不同算法的比较及扩展。本文主要介绍其中的 SiamFC 实现。
运行要求
代码运行需安装以下依赖包:
conda install matplotlib shapely
conda install -c conda-forge tensorboardx
对于 Python3 ,直接安装 urllib3:
conda install urllib3:
而 python2.7 需要参考 pip install urllib2失败 进行以下修改:
#from urllib.request import urlretrieve
import urllib2
#return urlretrieve(url, filename, _reporthook)return urllib2.urlretrieve(url, filename, _reporthook)
模型和数据
预训练模型可直接在项目主页下载:
- color
- color+gray
alexnet-owt-4df8aa71.pth 可通过迅雷下载。
数据下载参考:CFNet视频目标跟踪源码运行笔记(2)——training and then tracking。
- ILSVRC2015_VID.tar.gz
- cfnet_ILSVRC2015.stats.mat 密码:hqhbwm
VID 训练集拥有3862个片段,平均帧数为290。
代码概述
SiamFC 训练主要涉及到 TrainerSiamFC、TrackerSiamFC 、SiameseNet、Pairwise 和 TransformSiamFC 几个对象。训练示例可参考 TestManagerSiamFC 。
TrackerSiamFC 组织模型训练,TrackerSiamFC 实现了跟踪器训练和推理的功能。SiameseNet 由 AlexNet 基础网络和 XCorr、Adjust2d 附加操作组成,Pairwise 对基本数据集进行封装,从中读取样本对。TransformSiamFC 对数据进行处理。
TrainerSiamFC
打开文件加载参数,并选择对应模型的参数。
接口设计有 bug,没有传入
net_path
。
def __init__(self, branch='alexv1', cfg_file=None):cfg = {}if cfg_file is not None:with open(cfg_file, 'r') as f:cfg = json.load(f)cfg = cfg[branch]
构造一个 TrackerSiamFC 跟踪器。
这里并不需要
self.branch
,net_path
没有赋值。self.tracker.cfg
在这里似乎也并不需要。
Logger 继承 tensorboardX 的 SummaryWriter 类。
self.branch = branchself.tracker = TrackerSiamFC(branch=branch, net_path=None, **cfg)self.cfg = self.tracker.cfgself.logger = Logger(log_dir='logs/siamfc')self.cuda = torch.cuda.is_available()
train
initialize_weights 对不同的层参数进行初始化。
这里是一个 bug,初始化应放在 Tracker 中,而这里调用会覆盖已加载的模型。
tracker = self.trackerinitialize_weights(tracker.model)transform = TransformSiamFC(stats_path, **self.cfg._asdict())
multiprocessing.cpu_count() 返回系统中的CPU数量。此数字不等于当前进程可以使用的 CPU 数量。可以使用len(os.sched_getaffinity(0))
获得可用 CPU 的数量。
epoch_num = self.cfg.epoch_numcpu_num = multiprocessing.cpu_count()
vot_dataset
没有用到。
if vot_dir is not None:vot_dataset = VOT(vot_dir, return_rect=True, download=True)base_dataset = ImageNetVID(vid_dir, return_rect=True)
Pairwise 产生图像对。根据欧式距离生成dataset_train
中的标签。
torch.utils.data.DataLoader 数据加载器。组合数据集和采样器,并在数据集上提供单进程或多进程迭代器。
# training datasetdataset_train = Pairwise(base_dataset, transform, subset='train')dataloader_train = DataLoader(dataset_train, batch_size=self.cfg.batch_size, shuffle=True,pin_memory=self.cuda, drop_last=True, num_workers=cpu_num)
# validation datasetdataset_val = Pairwise(base_dataset, transform, subset='val')dataloader_val = DataLoader(dataset_val, batch_size=self.cfg.batch_size, shuffle=False,pin_memory=self.cuda, drop_last=True, num_workers=cpu_num)
train_iters = len(dataloader_train)val_iters = len(dataloader_val)
训练。
for epoch in range(epoch_num):# training looploss_epoch = 0for it, batch in enumerate(dataloader_train):loss = tracker.step(batch, update_lr=(it == 0))loss_epoch += loss# loggingstep = epoch * train_iters + itself.logger.add_text('train/iter_loss', '--Epoch: {}/{} Iter: {}/{} Loss: {:.6f}'.format(epoch + 1, epoch_num, it + 1, train_iters, loss), step)self.logger.add_scalar('train/iter_loss', loss, step)loss_epoch /= train_iters# loggingself.logger.add_text('train/epoch_loss', 'Epoch: {}/{} Loss: {:.6f}'.format(epoch + 1, epoch_num, loss_epoch), epoch)self.logger.add_scalar('train/epoch_loss', loss_epoch, epoch)
测试。
# validation looploss_val = 0for it, batch in enumerate(dataloader_val):loss = tracker.step(batch, backward=False)loss_val += lossloss_val /= val_iters# loggingself.logger.add_text('train/val_epoch_loss', 'Epoch: {}/{} Val. Loss: {:.6f}'.format(epoch + 1, epoch_num, loss_val), epoch)self.logger.add_scalar('train/val_epoch_loss', loss_val, epoch)
在 VOT 上测试。self.track
应该是tracker.track
。不过这里与 track 的接口并不一致。
# tracking loop if vot_dir is availableif vot_dir is not None:self.track(vot_dir, visualize=False)
添加检查点。
torch.nn.Module.state_dict 返回包含模块整个状态的字典。包括参数和持久缓冲区(例如,运行平均值)。键是对应的参数和缓冲区名称。
add_checkpoint 保存字典到文件。
# add checkpointself.logger.add_checkpoint('siamfc', self.tracker.model.module.state_dict(),(epoch + 1) // 100 + 1)
TrackerSiamFC(object)
对象初始化过程中调用 setup_model 和 setup_optimizer 函数。
self.name
并不需要。
def __init__(self, branch='alexv2', net_path=None, **kargs):self.name = 'SiamFC'self.parse_args(**kargs)self.cuda = torch.cuda.is_available()self.device = torch.device('cuda:0' if self.cuda else 'cpu')self.setup_model(branch, net_path)self.setup_optimizer()
step
为什么仅在第一次运行self.scheduler.step()
?
这里
self.scheduler.step()
的调用应放在调用 step 的循环之外,否则就变成了每次迭代调整学习率。
if backward:if update_lr:self.scheduler.step()self.model.train()else:self.model.eval()
torch.Tensor.to 执行 Tensor dtype 和(或)设备转换。 从self.to(*args, **kwargs)
的参数推断出 torch.dtype 和 torch.device。
如果
self
Tensor 已经有正确的 torch.dtype 和 torch.device,则返回self
。否则,返回的张量是具有所需 torch.dtype 和 torch.device 的self
的副本。
函数传入4个数据。
z, x, labels, weights = \batch[0].to(self.device), batch[1].to(self.device), \batch[2].to(self.device), batch[3].to(self.device)
torch.optim.Optimizer.zero_grad 清除所有优化 torch.Tensor 的梯度。
torch.autograd.set_grad_enabled 上下文管理器,将梯度计算设置为打开或关闭。
依次运行前向->损失函数->反向->优化器。
torch.Tensor.backward 计算当前张量关于图叶子的梯度。
使用链式法则差分图。如果张量是非标量的(即其数据具有多于一个元素)并且需要梯度,则该函数还需要指定gradient
。它应该是类型和位置匹配的张量,包含差分函数关于自身的梯度。
此函数在叶子中累积梯度 - 您可能需要在调用之前将它们归零。
参数:
gradient
(Tensor或 None)—梯度相关张量。如果是张量,它将自动转换为不需要求梯度的张量,除非create_graph
为True
。对于标量张量或不需要梯度的张量,不能指定任何值。如果 None 值是可接受的,则此参数是可选的。retain_graph
(bool
,可选)—如果为False
,将释放用于计算梯度的图。 请注意,几乎在所有情况下都不需要将此选项设置为True
,并且通常可以以更有效的方式解决此问题。默认为create_graph
的值。create_graph
(bool
,可选)—如果为True
,将构造派生图,允许计算更高阶的导数。默认为False
。
torch.optim.Optimizer.step 执行单个优化步骤(参数更新)。
torch.Tensor.item 以标准 Python 数返回此张量的值。这仅适用于具有一个元素的张量。对于其他情况,请参阅tolist()。此操作不可差分。
self.optimizer.zero_grad()with torch.set_grad_enabled(backward):pred = self.model(z, x)loss = self.criterion(pred, labels, weights)if backward:loss.backward()self.optimizer.step()return loss.item()
setup_optimizer
torch.nn.Module.named_parameters 返回模块参数的迭代器,同时产生参数名称和参数本身。
.0
表示 conv,.1
表示 bn。
这里应检查param.requires_grad
,不应将冻结的参数加入到列表中。
params = []for name, param in self.model.named_parameters():lr = self.cfg.initial_lrweight_decay = self.cfg.weight_decayif '.0' in name: # convif 'weight' in name:lr *= self.cfg.lr_mult_conv_weightweight_decay *= 1elif 'bias' in name:lr *= self.cfg.lr_mult_conv_biasweight_decay *= 0elif '.1' in name or 'bn' in name: # bnif 'weight' in name:lr *= self.cfg.lr_mult_bn_weightweight_decay *= 0elif 'bias' in name:lr *= self.cfg.lr_mult_bn_biasweight_decay *= 0elif 'linear' in name:if 'weight' in name:lr *= self.cfg.lr_mult_linear_weightweight_decay *= 1elif 'bias' in name:lr *= self.cfg.lr_mult_linear_biasweight_decay *= 0params.append({'params': param,'initial_lr': lr,'weight_decay': weight_decay})
self.optimizer = optim.SGD(params, lr=self.cfg.initial_lr,weight_decay=self.cfg.weight_decay)gamma = (self.cfg.final_lr / self.cfg.initial_lr) ** \(1 / (self.cfg.epoch_num // self.cfg.step_size))self.scheduler = StepLR(self.optimizer, self.cfg.step_size, gamma=gamma)self.criterion = BCEWeightedLoss().to(self.device)
BCEWeightedLoss
def __init__(self):super(BCEWeightedLoss, self).__init__()
torch.nn.functional.binary_cross_entropy_with_logits 测量目标和输出logits之间的二进制交叉熵的函数。
torch.nn.BCEWithLogitsLoss 将 Sigmoid 层和 BCELoss 组合在一个单独的类中。 这个版本在数值上比使用普通的 Sigmoid 后跟 BCELoss 更稳定,因为通过将操作组合成一个层,我们利用 log-sum-exp 技巧来实现数值稳定性。
损失可以描述为:
ℓ(x,y)=L={l1,…,lN}⊤\ell(x, y) = L = \{l_1,\dots,l_N\}^\top ℓ(x,y)=L={l1,…,lN}⊤
ln=−wn[tn⋅logσ(xn)+(1−tn)⋅log(1−σ(xn))]l_n = - w_n \left[ t_n \cdot \log \sigma(x_n)+ (1 - t_n) \cdot \log (1 - \sigma(x_n)) \right] ln=−wn[tn⋅logσ(xn)+(1−tn)⋅log(1−σ(xn))]
其中 NNN 是批量大小。如果reduce
为True
,那么
ℓ(x,y)={mean(L),if  size_average=True,sum(L),if  size_average=False.\ell(x, y) = \begin{cases} \operatorname{mean}(L), & \text{if}\; \text{size\_average} = \text{True},\\ \operatorname{sum}(L), & \text{if}\; \text{size\_average} = \text{False}. \end{cases} ℓ(x,y)={mean(L),sum(L),ifsize_average=True,ifsize_average=False.
这用于测量例如自动编码器中的重建误差。注意,目标 tit_iti 应该是0到1之间的数字。
通过在正例中添加权重,可以权衡召回和精确度。在这种情况下,损失可以描述为:
ℓ(x,y)=L={l1,…,lN}⊤\ell(x, y) = L = \{l_1,\dots,l_N\}^\top ℓ(x,y)=L={l1,…,lN}⊤
ln=−wn[pntn⋅logσ(xn)+(1−tn)⋅log(1−σ(xn))],l_n = - w_n \left[ p_n t_n \cdot \log \sigma(x_n)+ (1 - t_n) \cdot \log (1 - \sigma(x_n)) \right], ln=−wn[pntn⋅logσ(xn)+(1−tn)⋅log(1−σ(xn))],
其中 pnp_npn 是 nnn 类的正权重。pn>1p_n>1pn>1 增加召回率,pn<1pn<1pn<1 增加精度。
或者,如果数据集包含单个类的100个正数和300个负数示例,则该类的 pos_weight 应该等于 300100=3\frac{300}{100}=3100300=3。损失将表现为数据集包含数学:3×100=3003\times 100=3003×100=300 个正例。
def forward(self, input, target, weight=None):return F.binary_cross_entropy_with_logits(input, target, weight, size_average=True)
track
根据帧数初始化变量。
frame_num = len(img_files)bndboxes = np.zeros((frame_num, 4))bndboxes[0, :] = init_rectspeed_fps = np.zeros(frame_num)
进入循环,使用 Python Imaging Library 读取图像。
for f, img_file in enumerate(img_files):image = Image.open(img_file)if image.mode == 'L':image = image.convert('RGB')
第1帧进行初始化,后续进行位置预测。
start_time = time.time()if f == 0:self.init(image, init_rect)else:bndboxes[f, :] = self.update(image)elapsed_time = time.time() - start_timespeed_fps[f] = elapsed_time
init
init 根据初试框初始化跟踪器。
获取目标的中心和宽高。
根据目标面积确定背景扩展大小。
# initialize parametersself.center = init_rect[:2] + init_rect[2:] / 2self.target_sz = init_rect[2:]context = self.cfg.context * self.target_sz.sum()self.z_sz = np.sqrt((self.target_sz + context).prod())self.x_sz = self.z_sz * self.cfg.search_sz / self.cfg.exemplar_sz
_deduce_network_params 推断出得分图大小和网络的总步长。
计算上采样后的得分图大小。
self.scale_factors = self.cfg.scale_step ** np.linspace(-(self.cfg.scale_num // 2),self.cfg.scale_num // 2, self.cfg.scale_num)self.score_sz, self.total_stride = self._deduce_network_params(self.cfg.exemplar_sz, self.cfg.search_sz)self.final_score_sz = self.cfg.response_up * (self.score_sz - 1) + 1
hann_1d = np.expand_dims(np.hanning(self.final_score_sz), axis=0)self.penalty = np.transpose(hann_1d) * hann_1dself.penalty = self.penalty / self.penalty.sum()
# extract template featurescrop_z = crop(image, self.center, self.z_sz,out_size=self.cfg.exemplar_sz)self.z = self._extract_feature(crop_z)
update
# update scaled sizesscaled_exemplar = self.scale_factors * self.z_szscaled_search_area = self.scale_factors * self.x_szscaled_target = self.scale_factors[:, np.newaxis] * self.target_sz
_crop 截取图像块并进行填充和缩放。
_calc_score 计算x
和z
的相关分数,加惩罚之后寻找最高得分。
# locate targetcrops_x = self._crop(image, self.center, scaled_search_area,out_size=self.cfg.search_sz)x = self._extract_feature(crops_x)score, scale_id = self._calc_score(self.z, x)
更新x_sz
和target_sz
。
_locate_target 计算目标中心位置。
self.x_sz = (1 - self.cfg.scale_lr) * self.x_sz + \self.cfg.scale_lr * scaled_search_area[scale_id]self.center = self._locate_target(self.center, score, self.final_score_sz,self.total_stride, self.cfg.search_sz,self.cfg.response_up, self.x_sz)self.target_sz = (1 - self.cfg.scale_lr) * self.target_sz + \self.cfg.scale_lr * scaled_target[scale_id]
如果设置了模板学习率则更新模板特征,否则仅更新z_sz
似乎应与论文中不同。这里z_sz
似乎应与z
同步。
# update the template# self.z_sz = (1 - self.cfg.scale_lr) * self.z_sz + \# self.cfg.scale_lr * scaled_exemplar[scale_id]if self.cfg.z_lr > 0:crop_z = crop(image, self.center, self.z_sz,out_size=self.cfg.exemplar_sz)new_z = self._extract_feature(crop_z)self.z = (1 - self.cfg.z_lr) * self.z + \self.cfg.z_lr * new_zself.z_sz = (1 - self.cfg.scale_lr) * self.z_sz + \self.cfg.scale_lr * scaled_exemplar[scale_id]
numpy.concatenate 沿现有轴加入一系列数组。
返回目标框的对角线坐标。
return np.concatenate([self.center - self.target_sz / 2, self.target_sz])
_crop
如果A.ndim> d
,则通过前填1来将reps
提升为A.ndim
。因此,对于形状A
(2, 3, 4, 5),(2, 2)的reps
被视为(1, 1, 2, 2)。
注意:尽管可以使用 tile 进行广播,但强烈建议使用 numpy 的广播操作和功能。
如果sizes
仅为一个值,则将其构造为行向量。
sizes = np.array(sizes)if sizes.ndim == 1:sizes = np.tile(sizes, (2, 1)).T
求尺度数组中的最大值,依此截取图像块。
max_size = np.max(sizes, axis=0)anchor_patch = crop(image, center, max_size, padding=padding)
计算每个尺寸相对于图像块的偏移,依此截取。
patches = []for i, size in enumerate(sizes):if np.all(size == max_size):patch = anchor_patchelse:offset = (max_size - size) / 2patch = anchor_patch.crop((int(offset[0]),int(offset[1]),int(offset[0] + round(size[0])),int(offset[1] + round(size[1]))))if out_size is not None:patch = patch.resize((out_size, out_size), Image.BILINEAR)patches.append(patch)
如果仅有一个尺寸,修改patches
的类型。
if len(sizes) == 1:patches = patches[0]return patches
_deduce_network_params
初始化z
和x
,运行网络获得score_sz
。实际运行的话成本过高,应通过计算获得。
z = torch.zeros(1, 3, exemplar_sz, exemplar_sz).to(self.device)x = torch.zeros(1, 3, search_sz, search_sz).to(self.device)with torch.set_grad_enabled(False):self.model.eval()y = self.model(z, x)score_sz = y.size(-1)
total_stride = 1for m in self.model.modules():if isinstance(m, (nn.Conv2d, nn.MaxPool2d)):stride = m.stride[0] if isinstance(m.stride, tuple) else m.stridetotal_stride *= stridereturn score_sz, total_stride
_extract_feature
torchvision.transforms.functional.to_tensor 将 PIL 图像或 numpy.ndarray 转换为张量。
if isinstance(image, Image.Image):image = (255.0 * TF.to_tensor(image)).unsqueeze(0)elif isinstance(image, (list, tuple)):image = 255.0 * torch.stack([TF.to_tensor(c) for c in image])else:raise Exception('Incorrect input type: {}'.format(type(image)))
with torch.set_grad_enabled(False):self.branch.eval()return self.branch(image.to(self.device))
Pairwise
def __init__(self, base_dataset, transform=None, pairs_per_video=25,frame_range=100, causal=False, return_index=False,rand_choice=True, subset='train', train_ratio=0.95):super(Pairwise, self).__init__()assert subset in ['train', 'val']
self.base_dataset = base_datasetself.transform = transformself.pairs_per_video = pairs_per_videoself.frame_range = frame_rangeself.causal = causalself.return_index = return_indexself.rand_choice = rand_choice
base_dataset
是 ImageNetVID 的返回值。
n = len(self.base_dataset)split = int(n * train_ratio)split = np.clip(split, 10, n - 10)if subset == 'train':self.indices = np.arange(0, split, dtype=int)self.indices = np.tile(self.indices, pairs_per_video)elif subset == 'val':self.indices = np.arange(split, n, dtype=int)
__getitem__
if index >= len(self):raise IndexError('list index out of range')
numpy.random.choice 从给定的1-D 阵列生成随机样本。
if self.rand_choice:index = np.random.choice(self.indices)else:index = self.indices[index]img_files, anno = self.base_dataset[index]
rand_z, rand_x = self._sample_pair(len(img_files))img_z = Image.open(img_files[rand_z])img_x = Image.open(img_files[rand_x])if img_z.mode == 'L':img_z = img_z.convert('RGB')img_x = img_x.convert('RGB')bndbox_z = anno[rand_z, :]bndbox_x = anno[rand_x, :]
if self.return_index:item = (img_z, img_x, bndbox_z, bndbox_x, rand_z, rand_x)else:item = (img_z, img_x, bndbox_z, bndbox_x)
TransformSiamFC 对图像进行处理,主要包括切图和生成标签。
if self.transform is not None:return self.transform(*item)else:return item
SiameseNet
SiameseNet 需同时输入z
和x
,没有对z
进行暂存。这样方便训练,而在测试时越过 SiameseNet 直接调用 branch
。
def __init__(self, branch, norm='bn'):super(SiameseNet, self).__init__()self.branch = branchself.norm = Adjust2d(norm=norm)self.xcorr = XCorr()def forward(self, z, x):assert z.size()[:2] == x.size()[:2]z = self.branch(z)x = self.branch(x)out = self.xcorr(z, x)out = self.norm(out, z, x)return out
XCorr
XCorr 模块批量计算x
和z
的互相关。
torch.cat 在给定维度中连接给定的seq
张量序列。所有张量必须具有相同的形状(在连接维度中除外)或为空。
def __init__(self):super(XCorr, self).__init__()def forward(self, z, x):out = []for i in range(z.size(0)):out.append(F.conv2d(x[i, :].unsqueeze(0),z[i, :].unsqueeze(0)))return torch.cat(out, dim=0)
Adjust2d
Adjust2d 模块在2D 平面上进行处理。
bn
和linear
需要初始化权重参数。
def __init__(self, norm='bn'):super(Adjust2d, self).__init__()assert norm in [None, 'bn', 'cosine', 'euclidean', 'linear']self.norm = normif norm == 'bn':self.bn = nn.BatchNorm2d(1)elif norm == 'linear':self.linear = nn.Conv2d(1, 1, 1, bias=True)self._initialize_weights()
def forward(self, out, z=None, x=None):if self.norm == 'bn':out = self.bn(out)elif self.norm == 'linear':out = self.linear(out)elif self.norm == 'cosine':n, k = out.size(0), z.size(-1)norm_z = torch.sqrt(torch.pow(z, 2).view(n, -1).sum(1)).view(n, 1, 1, 1)norm_x = torch.sqrt(k * k * F.avg_pool2d(torch.pow(x, 2), k, 1).sum(1, keepdim=True))out = out / (norm_z * norm_x + 1e-32)out = (out + 1) / 2elif self.norm == 'euclidean':n, k = out.size(0), z.size(-1)sqr_z = torch.pow(z, 2).view(n, -1).sum(1).view(n, 1, 1, 1)sqr_x = k * k * \F.avg_pool2d(torch.pow(x, 2), k, 1).sum(1, keepdim=True)out = out + sqr_z + sqr_xout = out.clamp(min=1e-32).sqrt()elif self.norm == None:out = outreturn out
def _initialize_weights(self):if self.norm == 'bn':self.bn.weight.data.fill_(1)self.bn.bias.data.zero_()elif self.norm == 'linear':self.linear.weight.data.fill_(1e-3)self.linear.bias.data.zero_()
TransformSiamFC
load_siamfc_stats 从文件中加载x
和z
的均值和方差。
def __init__(self, stats_path=None, **kargs):self.parse_args(**kargs)self.stats = Noneif stats_path:self.stats = load_siamfc_stats(stats_path)
def parse_args(self, **kargs):# default branch is AlexNetV1default_args = {'exemplar_sz': 127,'search_sz': 255,'score_sz': 17,'context': 0.5,'r_pos': 16,'r_neg': 0,'total_stride': 8,'ignore_label': -100,# augmentation parameters'aug_translate': True,'max_translate': 4,'aug_stretch': True,'max_stretch': 0.05,'aug_color': True}for key, val in default_args.items():if key in kargs:setattr(self, key, kargs[key])else:setattr(self, key, val)
_crop 截取图像块并进行填充和缩放。
_create_labels
def __call__(self, img_z, img_x, bndbox_z, bndbox_x):crop_z = self._crop(img_z, bndbox_z, self.exemplar_sz)crop_x = self._crop(img_x, bndbox_x, self.search_sz)labels, weights = self._create_labels()
crop_z = self._acquire_augment(crop_z, self.exemplar_sz, self.stats.rgb_variance_z)crop_x = self._acquire_augment(crop_x, self.search_sz, self.stats.rgb_variance_x)
F.to_tensor 将 PIL Image 转为 [0,1] 之间的值。
crop_z = (255.0 * F.to_tensor(crop_z)).float()crop_x = (255.0 * F.to_tensor(crop_x)).float()labels = torch.from_numpy(labels).float()weights = torch.from_numpy(weights).float()return crop_z, crop_x, labels, weights
_crop
bndbox
格式为[x1,y1,x2,y2],类型为 np.array。
center = bndbox[:2] + bndbox[2:] / 2size = bndbox[2:]
背景为宽高和的一半。计算拓展面积,参照exemplar_sz
计算图像块大小。
context = self.context * size.sum()patch_sz = out_size / self.exemplar_sz * \np.sqrt((size + context).prod())
return crop_pil(image, center, patch_sz, out_size=out_size)
_create_labels
_create_logisticloss_labels 生成大小为score_sz
的标签,半径r_pos
范围内的标签为正,其余为负。
labels = self._create_logisticloss_labels()
weights = np.zeros_like(labels)pos_num = np.sum(labels == 1)neg_num = np.sum(labels == 0)weights[labels == 1] = 0.5 / pos_numweights[labels == 0] = 0.5 / neg_numweights *= pos_num + neg_num
labels = labels[np.newaxis, :]weights = weights[np.newaxis, :]return labels, weights
_create_logisticloss_labels
label_sz = self.score_szr_pos = self.r_pos / self.total_strider_neg = self.r_neg / self.total_stridelabels = np.zeros((label_sz, label_sz))
for r in range(label_sz):for c in range(label_sz):dist = np.sqrt((r - label_sz // 2) ** 2 +(c - label_sz // 2) ** 2)if dist <= r_pos:labels[r, c] = 1elif dist <= r_neg:labels[r, c] = self.ignore_labelelse:labels[r, c] = 0return labels
_acquire_augment
center = (out_size // 2, out_size // 2)patch_sz = np.asarray(patch.size)if self.aug_stretch:scale = (1 + self.max_stretch * (-1 + 2 * np.random.rand(2)))size = np.round(np.minimum(out_size * scale, patch_sz))else:size = patch_sz
如果进行平移增强,计算平移范围。
size
<patch_sz
,意味着进行拉伸的情况下平移才生效。
if self.aug_translate:mx, my = np.minimum(self.max_translate, np.floor((patch_sz - size) / 2))rx = np.random.randint(-mx, mx) if mx > 0 else 0ry = np.random.randint(-my, my) if my > 0 else 0dx = center[0] - size[0] // 2 + rxdy = center[1] - size[1] // 2 + ryelse:dx = center[0] - size[0] // 2dy = center[1] - size[1] // 2patch = patch.crop((int(dx), int(dy),int(dx + round(size[0])),int(dy + round(size[1]))))patch = patch.resize((out_size, out_size), Image.NEAREST)
numpy.random.randn 从“标准正态”分布中返回一个(或多个)样本。
如果使用颜色增强,减去一个随机颜色值。
if self.aug_color:offset = np.reshape(np.dot(rgb_variance, np.random.randn(3)), (1, 1, 3))out = Image.fromarray(np.uint8(patch - offset))else:out = patchreturn out
load_siamfc_from_matconvnet
assert isinstance(model.branch, (AlexNetV1, AlexNetV2))if isinstance(model.branch, AlexNetV1):p_conv = 'conv'p_bn = 'bn'p_adjust = 'adjust_'elif isinstance(model.branch, AlexNetV2):p_conv = 'br_conv'p_bn = 'br_bn'p_adjust = 'fin_adjust_bn'
load_matconvnet 从文件中读取到参数名和值的列表。
conv1f 为卷积核的信息,conv1b 为卷积的 bias 信息。
params_names_list, params_values_list = load_matconvnet(filename)params_values_list = [torch.from_numpy(p) for p in params_values_list]for l, p in enumerate(params_values_list):param_name = params_names_list[l]if 'conv' in param_name and param_name[-1] == 'f':p = p.permute(3, 2, 0, 1)p = torch.squeeze(p)params_values_list[l] = p
net = (model.branch.conv1,model.branch.conv2,model.branch.conv3,model.branch.conv4,model.branch.conv5)
for l, layer in enumerate(net):layer[0].weight.data[:] = params_values_list[params_names_list.index('%s%df' % (p_conv, l + 1))]layer[0].bias.data[:] = params_values_list[params_names_list.index('%s%db' % (p_conv, l + 1))]
if l < len(net) - 1:layer[1].weight.data[:] = params_values_list[params_names_list.index('%s%dm' % (p_bn, l + 1))]layer[1].bias.data[:] = params_values_list[params_names_list.index('%s%db' % (p_bn, l + 1))]bn_moments = params_values_list[params_names_list.index('%s%dx' % (p_bn, l + 1))]layer[1].running_mean[:] = bn_moments[:, 0]layer[1].running_var[:] = bn_moments[:, 1] ** 2
elif model.norm.norm == 'bn':model.norm.bn.weight.data[:] = params_values_list[params_names_list.index('%sm' % p_adjust)]model.norm.bn.bias.data[:] = params_values_list[params_names_list.index('%sb' % p_adjust)]bn_moments = params_values_list[params_names_list.index('%sx' % p_adjust)]model.norm.bn.running_mean[:] = bn_moments[0]model.norm.bn.running_var[:] = bn_moments[1] ** 2elif model.norm.norm == 'linear':model.norm.linear.weight.data[:] = params_values_list[params_names_list.index('%sf' % p_adjust)]model.norm.linear.bias.data[:] = params_values_list[params_names_list.index('%sb' % p_adjust)]return model
load_siamfc_stats
Stats = namedtuple('Stats', ['rgb_mean_z','rgb_variance_z','rgb_mean_x','rgb_variance_x'])
mat = h5py.File(stats_path, mode='r')
numpy.linalg.eig 计算正方形阵列的特征值和右特征向量。
rgb_mean_z = mat['z']['rgbMean'][:]d, v = np.linalg.eig(mat['z']['rgbCovariance'][:])rgb_variance_z = 0.1 * np.dot(np.sqrt(np.diag(d)), v.T)rgb_mean_x = mat['x']['rgbMean'][:]d, v = np.linalg.eig(mat['z']['rgbCovariance'][:])rgb_variance_x = 0.1 * np.dot(np.sqrt(np.diag(d)), v.T)
stats = Stats(rgb_mean_z,rgb_variance_z,rgb_mean_x,rgb_variance_x)return stats
ImageNetVID
def __init__(self, root_dir, return_rect=False,subset='train', rand_choice=True, download=False):r'''TODO: make the track_id sampling deterministic'''super(ImageNetVID, self).__init__()self.root_dir = root_dirself.return_rect = return_rectself.rand_choice = rand_choiceif download:self._download(self.root_dir)if not self._check_integrity():raise Exception('Dataset not found or corrupted. ' +'You can use download=True to download it.')
if subset == 'val':self.seq_dirs = sorted(glob.glob(os.path.join(self.root_dir, 'Data/VID/val/ILSVRC2015_val_*')))self.seq_names = [os.path.basename(s) for s in self.seq_dirs]self.anno_dirs = [os.path.join(self.root_dir, 'Annotations/VID/val', s) for s in self.seq_names]elif subset == 'train':self.seq_dirs = sorted(glob.glob(os.path.join(self.root_dir, 'Data/VID/train/ILSVRC*/ILSVRC*')))self.seq_names = [os.path.basename(s) for s in self.seq_dirs]self.anno_dirs = [os.path.join(self.root_dir, 'Annotations/VID/train',*s.split('/')[-2:]) for s in self.seq_dirs]else:raise Exception('Unknown subset.')
__getitem__
if isinstance(index, six.string_types):if not index in self.seq_names:raise Exception('Sequence {} not found.'.format(index))index = self.seq_names.index(index)elif self.rand_choice:index = np.random.randint(len(self.seq_names))
anno_files = sorted(glob.glob(os.path.join(self.anno_dirs[index], '*.xml')))objects = [ET.ElementTree(file=f).findall('object')for f in anno_files]
# choose the track id randomlytrack_ids, counts = np.unique([obj.find('trackid').text for group in objects for obj in group], return_counts=True)track_id = random.choice(track_ids[counts >= 2])
anno = []for f, group in enumerate(objects):for obj in group:if not obj.find('trackid').text == track_id:continueframes.append(f)anno.append([int(obj.find('bndbox/xmin').text),int(obj.find('bndbox/ymin').text),int(obj.find('bndbox/xmax').text),int(obj.find('bndbox/ymax').text)])
img_files = [os.path.join(self.seq_dirs[index], '%06d.JPEG' % f) for f in frames]anno = np.array(anno)if self.return_rect:anno[:, 2:] = anno[:, 2:] - anno[:, :2] + 1return img_files, anno
OTB
__otb13_seqs = ['Basketball', 'Bolt', 'Boy', 'Car4', 'CarDark','CarScale', 'Coke', 'Couple', 'Crossing', 'David','David2', 'David3', 'Deer', 'Dog1', 'Doll', 'Dudek','FaceOcc1', 'FaceOcc2', 'Fish', 'FleetFace','Football', 'Football1', 'Freeman1', 'Freeman3','Freeman4', 'Girl', 'Ironman', 'Jogging', 'Jumping','Lemming', 'Liquor', 'Matrix', 'Mhyang', 'MotorRolling','MountainBike', 'Shaking', 'Singer1', 'Singer2','Skating1', 'Skiing', 'Soccer', 'Subway', 'Suv','Sylvester', 'Tiger1', 'Tiger2', 'Trellis', 'Walking','Walking2', 'Woman']__tb50_seqs = ['Basketball', 'Biker', 'Bird1', 'BlurBody', 'BlurCar2','BlurFace', 'BlurOwl', 'Bolt', 'Box', 'Car1', 'Car4','CarDark', 'CarScale', 'ClifBar', 'Couple', 'Crowds','David', 'Deer', 'Diving', 'DragonBaby', 'Dudek','Football', 'Freeman4', 'Girl', 'Human3', 'Human4','Human6', 'Human9', 'Ironman', 'Jump', 'Jumping','Liquor', 'Matrix', 'MotorRolling', 'Panda', 'RedTeam','Shaking', 'Singer2', 'Skating1', 'Skating2', 'Skiing','Soccer', 'Surfer', 'Sylvester', 'Tiger2', 'Trellis','Walking', 'Walking2', 'Woman']__tb100_seqs = ['Bird2', 'BlurCar1', 'BlurCar3', 'BlurCar4', 'Board','Bolt2', 'Boy', 'Car2', 'Car24', 'Coke', 'Coupon','Crossing', 'Dancer', 'Dancer2', 'David2', 'David3','Dog', 'Dog1', 'Doll', 'FaceOcc1', 'FaceOcc2', 'Fish','FleetFace', 'Football1', 'Freeman1', 'Freeman3','Girl2', 'Gym', 'Human2', 'Human5', 'Human7', 'Human8','Jogging', 'KiteSurf', 'Lemming', 'Man', 'Mhyang','MountainBike', 'Rubik', 'Singer1', 'Skater','Skater2', 'Subway', 'Suv', 'Tiger1', 'Toy', 'Trans','Twinnings', 'Vase']
__otb15_seqs = __tb50_seqs + __tb100_seqs__version_dict = {2013: __otb13_seqs,2015: __otb15_seqs,'otb2013': __otb13_seqs,'otb2015': __otb15_seqs,'tb50': __tb50_seqs,
'tb100': __tb100_seqs}
__init__
检查版本。_check_integrity 获取路径下的子文件夹,检查是否都存在。
chain.from_iterable chain() 的替代构造函数。获取来自延迟计算的单个可迭代参数的链式输入。
def __init__(self, root_dir, version=2015, download=True):super(OTB, self).__init__()assert version in self.__version_dictself.root_dir = root_dirself.version = versionif download:self._download(root_dir, version)self._check_integrity(root_dir, version)valid_seqs = self.__version_dict[version]self.anno_files = list(chain.from_iterable(glob.glob(os.path.join(root_dir, s, 'groundtruth*.txt')) for s in valid_seqs))# remove empty annotation files# (e.g., groundtruth_rect.1.txt of Human4)self.anno_files = self._filter_files(self.anno_files)self.seq_dirs = [os.path.dirname(f) for f in self.anno_files]self.seq_names = [os.path.basename(d) for d in self.seq_dirs]# rename repeated sequence names# (e.g., Jogging and Skating2)
self.seq_names = self._rename_seqs(self.seq_names)
__getitem__
if isinstance(index, six.string_types):if not index in self.seq_names:raise Exception('Sequence {} not found.'.format(index))index = self.seq_names.index(index)img_files = sorted(glob.glob(os.path.join(self.seq_dirs[index], 'img/*.jpg')))# special sequences# (visit http://cvlab.hanyang.ac.kr/tracker_benchmark/index.html for detail)seq_name = self.seq_names[index]if seq_name.lower() == 'david':img_files = img_files[300-1:770]elif seq_name.lower() == 'football1':img_files = img_files[:74]elif seq_name.lower() == 'freeman3':img_files = img_files[:460]elif seq_name.lower() == 'freeman4':img_files = img_files[:283]elif seq_name.lower() == 'diving':img_files = img_files[:215]# to deal with different delimeterswith open(self.anno_files[index], 'r') as f:anno = np.loadtxt(io.StringIO(f.read().replace(',', ' ')))assert len(img_files) == len(anno)assert anno.shape[1] == 4return img_files, anno
参考文献
- huanglianghua/siamfc-pytorch
- huanglianghua/open-vot
- torrvision/siamfc-tf
- zlj199502/siamfc_pytorch
- PyTorch(六)——梯度反向传递(BackPropogate)的理解
- PyTorch入门学习(二):Autogard之自动求梯度
- Task 4 CNN back-propagation 反向传播算法
- CFNet视频目标跟踪源码运行笔记(2)——training and then tracking
- Loading weights from pretrained model with different module names
- MatConvNet实现深度学习
- pytorch如何使用多块gpu?
- PyTorch使用tensorboardX
- rafellerc/Pytorch-SiamFC
open-vot:PyTorch 实现 Siamese-FC相关推荐
- Siamese FC
关于Siamese FC 的理解 摘要 1.传统上的目标跟踪解决:使用单一视频学习外观模型,(目标在视频会改变,特征也在改变特征提取太单一了)进行目标在线更新,这限制了学习模型的丰富性.2.决定采用深 ...
- PyTorch之—Siamese网络
文章目录 1. 数据集处理 2. 网络与损失函数 3. 代码如下: 本来是想做检测图像的相似度的,偶然见到这篇文章.于是写下了这篇博文. 本文参考于: github and PyTorch 中文网人脸 ...
- 单目标跟踪算法:Siamese RPN论文解读和代码解析
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者:周威 | 来源:知乎 https://zhuanlan.zhihu.com/p/16198364 ...
- 超实时性单目标跟踪网络——Siamese RPN(CVPR2018 spotlight论文)
今年sensetime在CVPR上的表现力压国内其他科研机构,直逼谷歌.以44篇论文(3oral,11spotlight,28poster)在国内一骑绝尘.其中有一篇北航大四学生李博为一作的论文Sia ...
- object怎么转list_PaddleOCR识别模型转Pytorch全流程记录
这篇文章主要负责记录自己在转PaddleOCR 模型过程中遇到的问题,以供大家参考. 重要的话说在最前面,以免大家不往下看: 本篇文章是把 "整个" ppocr 模型 转成了 py ...
- 基于孪生网络的跟踪算法汇总
目录 1 SINT--[论文链接](https://arxiv.org/pdf/1605.05863.pdf)--[项目链接](https://taotaoorange.github.io/proje ...
- 【CV夏季划】告别入门,提升眼界,从掌握最有价值的那些CV方向开始
有三AI CV夏季划是言有三亲自一对一带领的深度学习和计算机视觉学习计划,目标是在新手入门的基础之上,掌握最有价值的若干CV方向,同时提升工程代码经验.学习不限时间,答疑永久有效,2021年农历年后截 ...
- [ConvNeXt] A ConvNet for the 2020s
<A ConvNet for the 2020s> Facebook AI Research(FAIR) 论文链接: https://arxiv.org/pdf/2201.03545.pd ...
- ConvNeXt:手把手教你改模型
作者:镜子(香港中文大学 计算机科学硕士) 本文经过作者同意转载 A ConvNet for the 2020s 我愿称之为2022年cv算法工程师抄作业必备手册,手把手教你改模型,把ResNet50 ...
- 暹罗网络目标跟踪_暹罗网络的友好介绍
暹罗网络目标跟踪 In the modern Deep learning era, Neural networks are almost good at every task, but these n ...
最新文章
- 今日最佳:导师给你指了一个研究方向后...
- Linux学习 Unit 4
- 查看Linux硬件信息命令的使用
- SQL Server索引进阶第六篇:书签
- scala中的隐式转换、隐式参数和隐式类
- 使用Git前的准备工作
- 国科大prml11-降维
- python画彩虹爱心_用python画一颗彩虹色爱心送给女朋友!!!
- 【记录一下】训练yolov5解决can not train without labels
- 使用容联云通讯实现短信验证登入
- fai 自动安装debian 7.4
- 【上电即上华为云】华为云smart智联Cat.1+PLC无线网关_3121N-IED_MC615-CN-L610-CN
- java兔子繁殖总数_兔子繁殖问题即斐波那契数列的java实现
- 问题 2111: 连环阵
- 【CV学习笔记】图像预处理warpaffine
- 解密韦德之膝:康复因再世华佗
- Createprocess 函数运行出错的原因和解决办法
- MDM市场诸侯混战,烽火发布MobileArk战略布局
- 幼儿抽象逻辑思维举例_熊孩子到底在想什么—幼儿思维干货速记!
- 物流运输商业采购管理系统:简化业务流程,加速物流运输企业数字化转型