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

Trainer 调用 Tracker。

TrainerSiamFC
TrackerSiamFC

打开文件加载参数,并选择对应模型的参数。

接口设计有 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.branchnet_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()

torch.optim.lr_scheduler.StepLR 将每个参数组的学习速率设置为每个step_size epoch 按 gamma 衰减的初始 lr。当last_epoch = -1时,将初始 lr 设置为 lr。

这里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_graphTrue。对于标量张量或不需要梯度的张量,不能指定任何值。如果 None 值是可接受的,则此参数是可选的。
  • retain_graphbool,可选)—如果为 False,将释放用于计算梯度的图。 请注意,几乎在所有情况下都不需要将此选项设置为True,并且通常可以以更有效的方式解决此问题。默认为create_graph的值。
  • create_graphbool,可选)—如果为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

BCEWeightedLoss
binary_cross_entropy_with_logits
    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 是批量大小。如果reduceTrue,那么
ℓ(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​[pn​tn​⋅logσ(xn​)+(1−tn​)⋅log(1−σ(xn​))],
其中 pnp_npn​ 是 nnn 类的正权重。pn&gt;1p_n&gt;1pn​>1 增加召回率,pn&lt;1pn&lt;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

track
init
update

根据帧数初始化变量。

        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

构造一个与得分图等大的 hanning 窗。

        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 计算xz的相关分数,加惩罚之后寻找最高得分。

        # 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_sztarget_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

_crop 根据sizes传入的尺寸从图像中裁剪出图像块。

numpy.tile 通过按照reps给出的次数重复A来构造数组。
如果reps的长度为d,则结果的维度为max(d, A.ndim)
如果A.ndim < d,则通过预先添加新轴将A提升为d维。 因此,形状(3,)的阵列被提升为(1, 3)用于2-D复制,或形状(1, 1, 3)用于3-D复制。如果这不是所需的行为,请在调用此函数之前手动将A提升为d维。

如果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

初始化zx,运行网络获得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)))

torch.autograd.set_grad_enabled 是上下文管理器,将梯度计算设置为打开或关闭。
eval() 设置模块为评估模式。这仅对某些模块有影响。有关其在训练/评估模式中的行为的详细信息,请参阅特定模块的文档,例如: Dropout,BatchNorm等。

        with torch.set_grad_enabled(False):self.branch.eval()return self.branch(image.to(self.device))

Pairwise

torch.utils.data.Dataset 表示数据集的抽象类。所有其他数据集都应该对其进行子类化。所有子类都应覆盖__len__,它提供数据集的大小,__getitem__,支持整数索引,范围从0到len(self)(不包含)。
pairs_per_video=25会影响对训练集的大小估计。

    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]

_sample_pair 返回随即的xz的索引。

        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, :]

构造item元组。

        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 需同时输入zx,没有对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 模块批量计算xz的互相关。
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 平面上进行处理。
bnlinear需要初始化权重参数。

    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()

cosineeuclidean为自行构造的函数。

    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 从文件中加载xz的均值和方差。

    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()

_acquire_augment

        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())

crop_pil 处理裁剪中的填充问题。

        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使正负损失均衡。

        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

r_pos为正样本半径,r_neg为负样本半径。

        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))

标签值为0-1。

        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

numpy.random.rand 给定形状的随机值。创建给定形状的数组,并使用来自[0,1]上的均匀分布的随机样本填充它。
如果进行拉伸延展,scale区间为[1-max_stretch, 1+max_stretch]。而且size<=patch_sz
这里patch_sz等于out_sizenp.minimum似乎有问题。
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)

layer[0]为卷积。

    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))]

如果不是最后一个卷积层,加载 BN 的参数。

        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

如果是最后一个卷积层,根据norm的类型加载相应参数。

        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 文件。

 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.')

glob.glob 返回与pathname匹配的可能为空的路径名列表,路径名必须是包含路径规范的字符串。pathname可以是绝对的(如/usr/src/Python-1.5/Makefile)或 相对的(如../../Tools/*/*.gif),也可以包含 shell 样式的通配符。 结果中包含损坏的符号链接(如在 shell 中)。

        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__

检查index是否在序列名列表中。

        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))

读取 xml 文件中的’object’字段。

        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相关推荐

  1. Siamese FC

    关于Siamese FC 的理解 摘要 1.传统上的目标跟踪解决:使用单一视频学习外观模型,(目标在视频会改变,特征也在改变特征提取太单一了)进行目标在线更新,这限制了学习模型的丰富性.2.决定采用深 ...

  2. PyTorch之—Siamese网络

    文章目录 1. 数据集处理 2. 网络与损失函数 3. 代码如下: 本来是想做检测图像的相似度的,偶然见到这篇文章.于是写下了这篇博文. 本文参考于: github and PyTorch 中文网人脸 ...

  3. 单目标跟踪算法:Siamese RPN论文解读和代码解析

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者:周威 | 来源:知乎 https://zhuanlan.zhihu.com/p/16198364 ...

  4. 超实时性单目标跟踪网络——Siamese RPN(CVPR2018 spotlight论文)

    今年sensetime在CVPR上的表现力压国内其他科研机构,直逼谷歌.以44篇论文(3oral,11spotlight,28poster)在国内一骑绝尘.其中有一篇北航大四学生李博为一作的论文Sia ...

  5. object怎么转list_PaddleOCR识别模型转Pytorch全流程记录

    这篇文章主要负责记录自己在转PaddleOCR 模型过程中遇到的问题,以供大家参考. 重要的话说在最前面,以免大家不往下看: 本篇文章是把 "整个" ppocr 模型 转成了 py ...

  6. 基于孪生网络的跟踪算法汇总

    目录 1 SINT--[论文链接](https://arxiv.org/pdf/1605.05863.pdf)--[项目链接](https://taotaoorange.github.io/proje ...

  7. 【CV夏季划】告别入门,提升眼界,从掌握最有价值的那些CV方向开始

    有三AI CV夏季划是言有三亲自一对一带领的深度学习和计算机视觉学习计划,目标是在新手入门的基础之上,掌握最有价值的若干CV方向,同时提升工程代码经验.学习不限时间,答疑永久有效,2021年农历年后截 ...

  8. [ConvNeXt] A ConvNet for the 2020s

    <A ConvNet for the 2020s> Facebook AI Research(FAIR) 论文链接: https://arxiv.org/pdf/2201.03545.pd ...

  9. ConvNeXt:手把手教你改模型

    作者:镜子(香港中文大学 计算机科学硕士) 本文经过作者同意转载 A ConvNet for the 2020s 我愿称之为2022年cv算法工程师抄作业必备手册,手把手教你改模型,把ResNet50 ...

  10. 暹罗网络目标跟踪_暹罗网络的友好介绍

    暹罗网络目标跟踪 In the modern Deep learning era, Neural networks are almost good at every task, but these n ...

最新文章

  1. 今日最佳:导师给你指了一个研究方向后...
  2. Linux学习 Unit 4
  3. 查看Linux硬件信息命令的使用
  4. SQL Server索引进阶第六篇:书签
  5. scala中的隐式转换、隐式参数和隐式类
  6. 使用Git前的准备工作
  7. 国科大prml11-降维
  8. python画彩虹爱心_用python画一颗彩虹色爱心送给女朋友!!!
  9. 【记录一下】训练yolov5解决can not train without labels
  10. 使用容联云通讯实现短信验证登入
  11. fai 自动安装debian 7.4
  12. 【上电即上华为云】华为云smart智联Cat.1+PLC无线网关_3121N-IED_MC615-CN-L610-CN
  13. java兔子繁殖总数_兔子繁殖问题即斐波那契数列的java实现
  14. 问题 2111: 连环阵
  15. 【CV学习笔记】图像预处理warpaffine
  16. 解密韦德之膝:康复因再世华佗
  17. Createprocess 函数运行出错的原因和解决办法
  18. MDM市场诸侯混战,烽火发布MobileArk战略布局
  19. 幼儿抽象逻辑思维举例_熊孩子到底在想什么—幼儿思维干货速记!
  20. 物流运输商业采购管理系统:简化业务流程,加速物流运输企业数字化转型

热门文章

  1. c#使用Flash控件AxShockwaveFlash时,报“未注册类”。
  2. 关于证书,这里有你想知道的一切
  3. php开源论坛系统,十款开源论坛系统推荐(二)
  4. 2010中国互联网哈哈榜1:十大网络流行语
  5. 7 月 24 号腾讯副总张小龙内部讲座《通过微信谈产品》
  6. 解决pip下载速度慢的问题:更换国内源
  7. 关于switch_to的用法
  8. compareTo()用法
  9. 一天一看————计算机网络参数模型与5G协议!
  10. 奥哲网络张华:蛰伏十五年的低代码平台或成未来主流