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

点击上方“3D视觉工坊”,选择“星标”

干货第一时间送达

作者:周威 | 来源:知乎

https://zhuanlan.zhihu.com/p/161983646

本文仅做学术分享,如有侵权,请联系删除。

1.前言

深度学习【目标追踪】专栏写过一篇对Siamese FC网络的解析。接着Siamese网络在单目标追踪任务(SOT)上的应用,我们展开对Siamese RPN的论文解读和代码解析。

提到RPN层,了解双阶段法目标检测模型(FasterRCNN)的各位必定不会陌生。RPN层全称叫做Region-Proposal-Network,中文翻译:区域提议网络,通过该网络,产生一些图像中前景和背景的候选框。但是就只是前景和背景,并没有对前景候选框中的类别进行详述。这里放上FasterRCNN中的RPN层,如下图

FasterRCNN中的RPN层

可以看出来,RPN层是有两个分支的,上面一个特征图通道为18(18=9x2),用来获得所有anchor(先验框)前景和背景置信度(简单来说,就是判别这个先验框anchor中是不是前景或者背景),下面一个特征图通道数为36(36=9x4),用来获得所有anchor(先验框)的位置调整变量(简单来说,就是每个先验框anchor位置是固定的,所以需要通过这四个变量进行调整,成为预测框)。至此,对RPN的简单介绍就完毕了。

那么RPN层和Siamese网络如何进行融合,做到比传统的核方法以及上一篇提到的Siamese FC更好的结果呢?

这个疑问将通过本文的代码解析和论文解读进行分析,强烈推荐大家去读原文。代码链接如下,很轻便的代码,就两个py文件:https://github.com/huanglianghua/siamrpn-pytorch

本文将对Siamese RPN进行详细解析,主要从以下几个部分进行解析

  • (1)网络结构

  • (2)损失函数

2.网络结构

原论文中给了Siamese RPN的两个网络结构图,我们先看第一个,图上红色字体是我标注的一些符号,便于后面说明。

由此可见,Siamese RPN网络主要有Siamese 和 RPN网络构成。

(1)Siamese网络

Siamese网络翻译为孪生网络比较合适,因为它由两个分支构成(如上图),这两个分支的卷积神经网络共享权重(实质就是同一个网络,为了便于理解才这么说的)。原论文中提到,

Here we use the modified AlexNet , where the groups from conv2 and conv4 are removed .

这里说明Siamese RPN用到了非常简单的AlexNet网络进行图像特征的提取。有人会问,这论文2018年发表的,那时候特征提取网络满天飞,怎么还在用老古董AlexNet?

这是因为研究者们也尝试将ResNet作为Siamese图像特征提取网络,但是发现收效甚微,这个原因将在SiameseRPN++进行解析,本篇不做涉及。

代码中实现也是非常简单的,定义如下:

self.feature = nn.Sequential(# conv1            nn.Conv2d(3, 192, 11, 2),            nn.BatchNorm2d(192),            nn.ReLU(inplace=True),            nn.MaxPool2d(3, 2),# conv2            nn.Conv2d(192, 512, 5, 1),            nn.BatchNorm2d(512),            nn.ReLU(inplace=True),            nn.MaxPool2d(3, 2),# conv3            nn.Conv2d(512, 768, 3, 1),            nn.BatchNorm2d(768),            nn.ReLU(inplace=True),# conv4            nn.Conv2d(768, 768, 3, 1),            nn.BatchNorm2d(768),            nn.ReLU(inplace=True),# conv5            nn.Conv2d(768, 512, 3, 1),            nn.BatchNorm2d(512))

结构简单,清晰明了呀!从上面的图可以清晰看出,该孪生网络接受两个输入,一个叫做template frame(模板帧),是从视频第一帧中人为框出来(可以理解为原图中裁剪的一个区域)的物体位置;另一个输入叫做detection frame(检测帧),是被检测的视频段除了第一帧之外的其他帧。该Siamese(AlexNet)网络将这两个图像分别映射为6x6x256大小的特征图  和22x22x256大小的特征图  。

(2)RPN网络

上面我们有提到过RPN层有两个分支,一个分支做前景背景区分的(后面称为分支1),另一个分支用来调整anchor(先验框)的位置(后面称为分支2)。我们从上面的图中也可以看出,该RPN网络也是由两个分支组成的,每个分支接受两个输入(所以看起来有点像四个分支,其实是两个,看输出就知道了)。

该RPN网络的每个分支接受Siamese网络的两个输出  和  通过卷积层 ( 改变通道维度) 后的特征图作为输入,对分支1来说,分别为  和  ,对分支2来说,分别为  和  。

值得注意的是,  的通道维度为2k x 256, 的通道维度为4k x 256,这里的k是anchor的数量,至于为什么有这个2k和4k,相信接触过FasterRCNN中RPN层的小伙伴们必定不会陌生。2k中存放着每个cell的k个先验框中物体为前景和背景的置信度为多少,由于存放两个信息,所以就是2k。同理,4k包含着每个cell的k个先验框为了靠近真实框而做的位置移动信息。解释到这里,会有人问了,这个256是什么?

其实是这样的,网络的输出 和  是通道为2k和4k的特征图,如上图所示。那么这个  和  相当于卷积核参数,被卷积的对象是 和  。图中的五角星运算符就是卷积运算的意思。

那么这个256就是被卷积对象的维度,2k和4k就是卷积核的个数。

结合代码来看,如下

class SiamRPN(nn.Module):
def __init__(self, anchor_num=5):super(SiamRPN, self).__init__()self.anchor_num = anchor_numself.feature = nn.Sequential(# conv1            nn.Conv2d(3, 192, 11, 2),            nn.BatchNorm2d(192),            nn.ReLU(inplace=True),            nn.MaxPool2d(3, 2),# conv2            nn.Conv2d(192, 512, 5, 1),            nn.BatchNorm2d(512),            nn.ReLU(inplace=True),            nn.MaxPool2d(3, 2),# conv3            nn.Conv2d(512, 768, 3, 1),            nn.BatchNorm2d(768),            nn.ReLU(inplace=True),# conv4            nn.Conv2d(768, 768, 3, 1),            nn.BatchNorm2d(768),            nn.ReLU(inplace=True),# conv5            nn.Conv2d(768, 512, 3, 1),            nn.BatchNorm2d(512))
self.conv_reg_z = nn.Conv2d(512, 512 * 4 * anchor_num, 3, 1)self.conv_reg_x = nn.Conv2d(512, 512, 3)self.conv_cls_z = nn.Conv2d(512, 512 * 2 * anchor_num, 3, 1)self.conv_cls_x = nn.Conv2d(512, 512, 3)self.adjust_reg = nn.Conv2d(4 * anchor_num, 4 * anchor_num, 1)
def forward(self, z, x):return self.inference(x, **self.learn(z))
def learn(self, z):        z = self.feature(z)        kernel_reg = self.conv_reg_z(z)        kernel_cls = self.conv_cls_z(z)
        k = kernel_reg.size()[-1]        kernel_reg = kernel_reg.view(4 * self.anchor_num, 512, k, k)        kernel_cls = kernel_cls.view(2 * self.anchor_num, 512, k, k)
return kernel_reg, kernel_cls
def inference(self, x, kernel_reg, kernel_cls):        x = self.feature(x)        x_reg = self.conv_reg_x(x)        x_cls = self.conv_cls_x(x)
        out_reg = self.adjust_reg(F.conv2d(x_reg, kernel_reg))        out_cls = F.conv2d(x_cls, kernel_cls)
return out_reg, out_cls

网络先定义了一些相关层,比如使用AlexNet作为Siamese网络的特征提取网络。代码中的x和z分别对应上图中  和  ,其他对应关系如下:

  • ·x_reg对应 

  • ·x_cls对应 

  • ·kernel_reg对应 

  • ·kernel_cls对应 

  • ·out_reg 对应 

  • ·out_cls 对应 

out_reg = self.adjust_reg(F.conv2d(x_reg, kernel_reg))
 out_cls = F.conv2d(x_cls, kernel_cls)

这两行就是图中五角星符号表示的卷积运算。

至此,对SiameseRPN的网络结构解析就结束了。可以看出来,SiameseRPN网络并不复杂。

有点尴尬的是这段代码不包含训练过程,我重新找了一段代码,代码连接如下:

https://github.com/zllrunning/SiameseX.PyTorch/issues

根据上面代码中的siamese RPN的部分代码,我们对Siamese RPN的训练过程进行解析。

3.训练过程详细解析

上面对Siamese RPN网络的结构进行了详细的解析,这里我们根据网络结构对训练过程的一些细节进行详细解析。具体包括:

  • ·数据集的准备

  • ·网络输入和标签的准备

  • ·损失函数的设定

(1)数据集的准备

上述链接代码中的  文件中的函数  前面有这样的定义,如下

train_loader = torch.utils.data.DataLoader(dataset.listDataset(args.ilsvrc, args.youtube, args.data_type,shuffle=True,transform=transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]),]),train=True,
batch_size=args.batch_size,num_workers=args.workers, coco=coco),batch_size=args.batch_size)

这里引出了一个简单的数据集定义  ,我们进入该数据集定义中,找到 __getitem__(self, index)定义中的下面代码:

 elif self.data_type == 'RPN':
            z, x, gt_box, regression_target, label = load_data_rpn(pair_infos, self.coco, rpnpp=self.rpnpp)
if self.transform is not None:                z = self.transform(z)                x = self.transform(x)
            regression_target = torch.from_numpy(regression_target)            label = torch.from_numpy(label)
return z, x, regression_target, label

可见以下代码就是产生网络输入和标签文件的。

(2)网络输入和标签文件

我们进入函数load_data_rpn中,代码定义如下

def load_data_rpn(pair_infos, discrim, train=True, rpnpp=False):if not rpnpp:anchors = generate_anchor(8, [8, ], [0.33, 0.5, 1, 2, 3], 17)gt = np.zeros((1, 17, 17))else:anchors = generate_anchor(8, [8, ], [0.33, 0.5, 1, 2, 3], 25)gt = np.zeros((1, 25, 25))gt[:, :, :] = -1 #所有都设置为-1gt[0, 8, 8] = 1. #中心设置为1
img_path1 = pair_infos[0][0] #图片地址img_path2 = pair_infos[1][0]
bs1 = pair_infos[0][1]  # xmin xmax ymin ymaxbs2 = pair_infos[1][1]
gt1 = Rectangle(bs1[0], bs1[2], bs1[1] - bs1[0], bs1[3] - bs1[2]) #xmin ymin w hgt2 = Rectangle(bs2[0], bs2[2], bs2[1] - bs2[0], bs2[3] - bs2[2])
gt1 = convert_bbox_format(gt1, to='center-based') # x_cer,y_cer,w,hgt2 = convert_bbox_format(gt2, to='center-based')
img1 = Image.open(img_path1).convert('RGB')img2 = Image.open(img_path2).convert('RGB')
zbox1 = get_zbox(gt1, 0.25)zbox2 = get_zbox(gt2, 0.25)
scales_w = 1.04 ** (random.random() * 6 - 3) # 1.08..scales_h = 1.04 ** (random.random() * 6 - 3)
zbox2_scaled = Rectangle(zbox2.x, zbox2.y, zbox2.width * scales_w, zbox2.height * scales_h)
dx = 0
dy = 0
xbox2 = get_xbox(zbox2_scaled, dx, dy)  # we assume second is the search region
z = gen_xz(img1, zbox1, to='z')x = gen_xz(img2, xbox2, to='x')
info = [dx, dy, gt2.width / scales_w / zbox2.width, gt2.height / scales_h / zbox2.height]
gt_box = np.array([-info[0] * 64, -info[1] * 64, info[2] * 128, info[3] * 128])
anchor_xctr = anchors[:, :1]anchor_yctr = anchors[:, 1:2]anchor_w = anchors[:, 2:3]anchor_h = anchors[:, 3:]gt_cx, gt_cy, gt_w, gt_h = gt_box
target_x = (gt_cx - anchor_xctr) / anchor_wtarget_y = (gt_cy - anchor_yctr) / anchor_htarget_w = np.log(gt_w / anchor_w)target_h = np.log(gt_h / anchor_h)regression_target = np.hstack((target_x, target_y, target_w, target_h))
iou = compute_iou(anchors, gt_box).flatten()    # print(np.max(iou))
pos_index = np.where(iou > 0.4)[0]neg_index = np.where(iou < 0.3)[0]
label = np.ones_like(iou) * -1label[pos_index] = 1label[neg_index] = 0
return z, x, gt_box, regression_target, label

看似很长,实则做了以下几件事

  • ·读取search image 和template image 并进行裁剪,获取相应的region

  • ·产生anchor,并获得ground true box。

  • ·anchor与ground true box 之间的offset进行编码,产生 regression_target

  • ·根据anchor与ground true box 之间的IOU产生正样本和负样本,作为置信度label

大家如果有耐心请慢慢细品。

最后,根据真实的标签和网络输出的预测值,进行损失函数的设定即可

(3)损失函数

这里我们回到  中的函数  中,找到以下代码段

 # 预测pred_score, pred_regression = model(z, x)
pred_conf = pred_score.reshape(-1, 2, 5 * 17 * 17).permute(0, 2, 1)
pred_offset = pred_regression.reshape(-1, 4, 5 * 17 * 17).permute(0, 2, 1)
        # 目标targetregression_target = regression_target.type(torch.FloatTensor).cuda()conf_target = conf_target.type(torch.LongTensor).cuda()
        # 损失cls_loss = rpn_cross_entropy(pred_conf, conf_target)reg_loss = rpn_smoothL1(pred_offset, regression_target, conf_target)
loss = cls_loss + reg_loss

我们发现Siamese RPN中分类损失采用了交叉熵,回归损失采用了smoothL1。

至此,对Siamese RPN的解析就结束了。有空的话大家可以跑一跑模型,或者对代码中的细节进行深度解读,相信大家会有更多收获的。

3.总结

本文我们介绍了一种单目标检测器Siamese RPN网络,并对该网络结构和训练的一些细节进行了详细的解析,当然由于时间问题并没有对一些更小的细节进行解析,如果后面时间充裕,会进行一些补充,谢谢大家支持!

本文仅做学术分享,如有侵权,请联系删文。

下载1

在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。

下载2

在「3D视觉工坊」公众号后台回复:3D视觉优质源码即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计源码汇总等。

下载3

在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近2000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

 圈里有高质量教程资料、可答疑解惑、助你高效解决问题

整理不易,请给工坊点赞和在看

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值