siamfc-pytorch代码讲解(二):train&siamfc「建议收藏」

siamfc-pytorch代码讲解(二):train&siamfc「建议收藏」siamfc-pytorch代码讲解(二):train&siamfc一、train.py二、siamfc.py2.1SiamFCTransforms2.2Pair2.3train_step下一篇这是第二篇的siamfc-pytorch代码讲解,主要顺着程序流讲解代码,上一篇讲解在这里:siamfc-pytorch代码讲解(一):backbone&headshowme…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

siamfc-pytorch代码讲解(二):train&siamfc

这是第二篇的siamfc-pytorch代码讲解,主要顺着程序流讲解代码,上一篇讲解在这里:
siamfc-pytorch代码讲解(一):backbone&head
show me code !!

一、train.py

因为作者使用了GOT-10k这个工具箱,train.py代码非常少,就下面几行:

from __future__ import absolute_import

import os
from got10k.datasets import *

from siamfc import TrackerSiamFC

if __name__ == '__main__':
    root_dir = os.path.expanduser('~/data/GOT-10k')
    seqs = GOT10k(root_dir, subset='train', return_meta=True)

    tracker = TrackerSiamFC()
    tracker.train_over(seqs)

首先我们就需要按照GOT-10k download界面去下载好数据集,并且按照这样的文件结构放好(因为现在用不到验证集和测试集,可以先不用下,训练集也只要先下载1个split,所以就需要把list.txt中只保留前500项,因为GOT-10k_Train_000001里面有500个squences):

  |-- GOT-10k/
     |-- train/
     |  |-- GOT-10k_Train_000001/
     |  |   ......
     |  |-- GOT-10k_Train_000500/
     |  |-- list.txt

这里可以打印一下seps到底是什么,因为他是train_over的入参:

print(seqs)
# <got10k.datasets.got10k.GOT10k object at 0x000002366865CF28>
print(seqs[0])
# 这里比较多,截取一部分
# seqs[0]就是指第一个序列GOT-10k_Train_000001,返回三个元素的元组
# 第一个元素是一个路径列表,第二个是np.ndarray,第三个是字典,包含具体信息
# (['D:\\GOT-10k\\train\\GOT-10k_Train_000001\\00000001.jpg', ...],
# array([[347., 443., 429., 272.],...[551., 467., 513., 318.]]),
# {'url': 'https://youtu.be/b0ZnfLI8YPw',...})

二、siamfc.py

顺着代码流看到调用了siamfc.py中类TrackerSiamFC的train_over方法,在这个类里面就是进行数据增强,构造和加载,然后进行训练,这里主要有三个地方需要深入思考一下:

2.1 SiamFCTransforms

SiamFCTransforms是transforms.py里面的一个类,主要是对输入的groung truth的z, x, bbox_z, bbox_x进行一系列变换,构成孪生网络的输入,这其中就包括了:

  • RandomStretch:主要是随机的resize图片的大小,其中要注意cv2.resize()的一点用法,可以参考我的这篇博客:cv2.resize()的一点小坑
  • CenterCrop:从img中间抠一块(size, size)大小的patch,如果不够大,以图片均值进行pad之后再crop
  • RandomCrop:用法类似CenterCrop,只不过从随机的位置抠,没有pad的考虑
  • Compose:就是把一系列的transforms串起来
  • ToTensor: 就是字面意思,把np.ndarray转化成torch tensor类型

类初始化里面针对self.transforms_zself.transforms_x数据增强方法中具体参数的设置可以参考 issue#21,作者提到在train phase和test phase embedding size不一样没太大的影响,而且255-16可以模拟测试阶段目标的移动(个人感觉这里没有完全就按照论文上来,但也不用太在意,自己可以试着改回来看哪一个效果好)。
下面具体讲里面的_crop函数:

    def _crop(self, img, box, out_size):
        # convert box to 0-indexed and center based [y, x, h, w]
        box = np.array([
            box[1] - 1 + (box[3] - 1) / 2,
            box[0] - 1 + (box[2] - 1) / 2,
            box[3], box[2]], dtype=np.float32)
        center, target_sz = box[:2], box[2:]

        context = self.context * np.sum(target_sz)
        size = np.sqrt(np.prod(target_sz + context))
        size *= out_size / self.exemplar_sz

        avg_color = np.mean(img, axis=(0, 1), dtype=float)
        interp = np.random.choice([
            cv2.INTER_LINEAR,
            cv2.INTER_CUBIC,
            cv2.INTER_AREA,
            cv2.INTER_NEAREST,
            cv2.INTER_LANCZOS4])
        patch = ops.crop_and_resize(
            img, center, size, out_size,
            border_value=avg_color, interp=interp)
        
        return patch

因为GOT-10k里面对于目标的bbox是以ltwh(即left, top, weight, height)形式给出的,上述代码一开始就先把输入的box变成center based,坐标形式变为[y, x, h, w],结合下面这幅图就非常好理解: y = t + h 2 y=t+\frac{h}{2} y=t+2h x = l + w 2 x=l+\frac{w}{2} x=l+2w
the annotation form of bbox in GOT-10k
crop_and_resize

def crop_and_resize(img, center, size, out_size,
                    border_type=cv2.BORDER_CONSTANT,
                    border_value=(0, 0, 0),
                    interp=cv2.INTER_LINEAR):
    # convert box to corners (0-indexed)
    size = round(size)  # the size of square crop
    corners = np.concatenate((
        np.round(center - (size - 1) / 2),
        np.round(center - (size - 1) / 2) + size))
    corners = np.round(corners).astype(int)

    # pad image if necessary
    pads = np.concatenate((
        -corners[:2], corners[2:] - img.shape[:2]))
    npad = max(0, int(pads.max()))
    if npad > 0:
        img = cv2.copyMakeBorder(
            img, npad, npad, npad, npad,
            border_type, value=border_value)

    # crop image patch
    corners = (corners + npad).astype(int)
    patch = img[corners[0]:corners[2], corners[1]:corners[3]]

    # resize to out_size
    patch = cv2.resize(patch, (out_size, out_size),
                       interpolation=interp)

    return patch

现在进入ops.py下的crop_and_resize,这个函数的作用就是crop一块以object为中心的,边长为size大小的patch(如下面的绿色虚线的正方形框),然后将其resize成out_size的大小:传入sizecenter计算出角落坐标形式的正方形patch,即 ( y m i n , x m i n , y m a x , x m a x ) (y_{min},x_{min},y_{max},x_{max}) (ymin,xmin,ymax,xmax),如下面两个点粗的绿点,因为这样扩大的坐标有可能会超出原来的图片(如粉红色线所表示),所以就要计算左上角和右下角相对原图片超出多少,好进行pad,上面13-14行代码就是干这事。然后根据他们超出当中的最大值npad来在原图像周围pad(也就是最外面的蓝框),因为原图像增大了,所以corner相对坐标也变了,对应上面22行代码。然后就是crop下来绿色方形的那块,经过resize后返回。
关于_crop函数的图示
下面就是我的实验结果:
原图与抠下来的patch图对比

2.2 Pair

现在继续回到train_over方法,里面构造dataset的时候用了Pair类,所以从代码角度具体来看一下,因为继承了Dataset类,所以要overwrite __getitem____len__方法:

  • __getitem__:分析代码,这个方法就是通过index索引返回item = (z, x, box_z, box_x),然后经过transforms返回一对pair(z, x),就需要像论文里面说的:The images are extracted from two frames of a video that both contain the object and are at most T frames apart
    • _filter:通过该函数筛选符合条件的有效索引val_indices,这里不详细分析,因为我也不知道为什么会有这样的filter condition。
    • _sample_pair:如果有效索引大于2个的话,就从中随机挑选两个索引,这里取的间隔不超过T=100
  • __len__:这里定义的长度就是被索引到的视频序列帧数×每个序列提供的对数

2.3 train_step

现在来到siamfc.py里面最后一个关键的地方,数据准备好了,经过变换和加载进来就可以训练了,下面代码是常规操作,具体在train_step里面实现了训练和反向传播:

for epoch in range(self.cfg.epoch_num):
    # update lr at each epoch
    self.lr_scheduler.step(epoch=epoch)

    # loop over dataloader
    for it, batch in enumerate(dataloader):
        loss = self.train_step(batch, backward=True)
        print('Epoch: {} [{}/{}] Loss: {:.5f}'.format(
            epoch + 1, it + 1, len(dataloader), loss))
        sys.stdout.flush()

train_step里面难度又是在于理解_create_labels,具体的一些tensor的shape可以看我的注释,我好奇就把他打印出来了,看来本来__getitem__返回一对pair(z, x),经过dataloader的加载,还是z堆叠一起,x堆叠一起,并不是(z, x)绑定堆叠一起【主要自己对dataloader源码不是很熟,手动捂脸】
而且criterion使用的BalancedLoss,是调用F.binary_cross_entropy_with_logits,进行一个element-wise的交叉熵计算,所以创建出来的labels的shape其实就是和responses的shape是一样的:

def train_step(self, batch, backward=True):
    # set network mode
    self.net.train(backward)

    # parse batch data
    z = batch[0].to(self.device, non_blocking=self.cuda)
    x = batch[1].to(self.device, non_blocking=self.cuda)
    # print("batch_z shape:", z.shape) # torch.Size([8, 3, 127, 127])
    # print("batch_x shape:", x.shape) # torch.Size([8, 3, 239, 239])

    with torch.set_grad_enabled(backward):
        # inference
        responses = self.net(z, x)
        # print("responses shape:", responses.shape) # torch.Size([8, 1, 15, 15])

        # calculate loss
        labels = self._create_labels(responses.size())
        loss = self.criterion(responses, labels)

        if backward:
            # back propagation
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()

创建标签,论文里是这么说的:
y [ u ] = { + 1  if  k ∥ u − c ∥ ≤ R − 1  otherwise  y[u]=\left\{\begin{array}{ll} {+1} & {\text { if } k\|u-c\| \leq R} \\ {-1} & {\text { otherwise }} \end{array}\right. y[u]={
+11 if kucR otherwise 

因为我们的exemplar image z z z 和search image x x x都是以目标为中心的,所以labels的中心为1,中心以外为0。

def _create_labels(self, size):
    # skip if same sized labels already created
    if hasattr(self, 'labels') and self.labels.size() == size:
        return self.labels

    def logistic_labels(x, y, r_pos, r_neg):
        dist = np.abs(x) + np.abs(y)  # block distance
        labels = np.where(dist <= r_pos,
                          np.ones_like(x),
                          np.where(dist < r_neg,
                                   np.ones_like(x) * 0.5,
                                   np.zeros_like(x)))
        return labels

    # distances along x- and y-axis
    n, c, h, w = size
    x = np.arange(w) - (w - 1) / 2
    y = np.arange(h) - (h - 1) / 2
    x, y = np.meshgrid(x, y)

    # create logistic labels 这里除以stride,是相对score map上来说
    r_pos = self.cfg.r_pos / self.cfg.total_stride
    r_neg = self.cfg.r_neg / self.cfg.total_stride
    labels = logistic_labels(x, y, r_pos, r_neg)

    # repeat to size
    labels = labels.reshape((1, 1, h, w))
    labels = np.tile(labels, (n, c, 1, 1))

    # convert to tensors
    self.labels = torch.from_numpy(labels).to(self.device).float()

    return self.labels

其中关于np.tile、np.meshgrid、np.where函数的使用可以去看我这篇博客,最后出来的一个batch下某一个通道下的label就是下面这样的,有没有一种扫雷的既视感,?:
labels的创建
至此此份repo的训练应该差不多结束了,测试部分(inference phase)我还没怎么看,且涉及到GOT-10k的使用,下一次有空再看再写~

上下篇

上一篇:siamfc-pytorch代码讲解(一):backbone&head
下一篇:siamfc-pytorch代码讲解(三):demo&track

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/187096.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Git切换分支命令

    Git切换分支命令GIT切换分支命令1.查看远程分支2.查看本地分支3.切换分支程序员在开发和管理项目的时候,往往会会切除多个分支来进行使用,现在就来谈谈如何切换分支1.查看远程分支1.gitbranch-a先到你的项目根目录下打开Git,在命令行输入上面指令就能查出远程所有分支了。2.查看本地分支2.gitbranch这一步可有可无,只是让自己知道项目现在处于哪个分支之下3.切换分支3.gitcheckout-b想要的分支名(如果本地有这分支的话,-b就可以省略

    2022年6月20日
    52
  • java学生宿舍管理系统代码(java简单宿舍管理系统)

    分为三大类land.java(登陆界面)system.java(系统管理界面)sql.java(数据库操作,包括连接、断开、增删改查等操作)一、登陆界面及代码主要代码:登陆按钮事件publicvoidactionPerformed(ActionEvente){ //TODOAuto-generatedmethodstub if((JButton)e.ge…

    2022年4月14日
    59
  • 404 html代码,不懂代码,如何制作漂亮的404页面【新手简易教程】

    404 html代码,不懂代码,如何制作漂亮的404页面【新手简易教程】404是用户在访问页面时,搜索引擎常返回的状态码,常见的还有200,301,302,500等。搜索引擎通过http状态码识别网页状态,404状态码,常指所访问的页面不存在或已被删除。404页面,是用户访问不存在内容或者已删除网页时,呈现给用户的界面,又称404错误页面/404-NotFound。404页面常见误区①网站没有设置404页面404错误页面对用户而言:告诉浏览者其所请求的页面不存在或链…

    2022年7月27日
    62
  • 软件测试黑盒用例文档,看完这篇黑盒测试用例编写教程,我豁然开朗

    软件测试黑盒用例文档,看完这篇黑盒测试用例编写教程,我豁然开朗测试用例编写是软件测试的基本技能;也有很多人认为测试用例是软件测试的核心;软件测试中最重要的是设计和生成有效的测试用例;测试用例是测试工作的指导,是软件测试的必须遵守的准则。在这里我们不讨论以上的各种观点,但是综上所述,大家可以看出,测试用例编写这项软技能非常重要且是测试人的必备技能,相信很多人没有质疑。下面我们介绍下测试用例编写。我们将用例编写分为黑盒用例编写和白盒用例编写两大类。总体编写思路:…

    2022年6月9日
    37
  • HTML5 CSS3 精美案例 : 达到VCD盒个性幻灯片

    HTML5 CSS3 精美案例 : 达到VCD盒个性幻灯片

    2022年1月11日
    56
  • java手机号正则表达式验证,面试必会「建议收藏」

    java手机号正则表达式验证,面试必会「建议收藏」什么是Arthas?Arthas是一款开源在线诊断工具,采用命令行交互模式,支持web端在线诊断,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。这是一款开源一年多GitHubstar2万,99%的阿里研发小哥都在用的Java终极诊断利器!相对比直接下载使用,我推荐开发者可以试一下通过IDE插件CloudToolkit中使用Arthas来实现一键远程诊断功能。得益于Arthas强大且丰富的功能,让Arthas能做的事情超乎想象。下面仅仅列举几

    2022年9月17日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号