文章目录
前言
本文是SiamRPN代码分析的最后一篇test,代码从init和unpate(tracking)进行分析。
1、init
init的主要任务是获得上分支的feature map与后续帧的feature map互相关,在获得127x127x3大小的目标模板图像后,要对其增加一个维度再输入网络,返回的分类卷积核和回归卷积核的shape分别为[10,256,4,4]、[20,256,4,4],这里名没有直接return返回这两个核,而是将它们存放在self里(SiamRPNTracker类),可以翻看第一篇architecture的分析;其次,init还要获取当前目标的位置,高宽等,后续update要对其进行更新,也就是预测新的目标位置和目标大小。
#初始帧
def init(self, frame, bbox):
#[l,t,w,h]->[center_x,center_y,w,h]
self.bbox = np.array([bbox[0]-1 + (bbox[2]-1) / 2 , bbox[1]-1 + (bbox[3]-1) / 2 , bbox[2], bbox[3]])
#获取目标中心点[c_x,c_y]以便后续使用
self.pos = np.array([bbox[0]-1 + (bbox[2]-1) / 2 , bbox[1]-1 + (bbox[3]-1) / 2])
#获取目标宽高[w,h]以便后续使用
self.target_sz = np.array([bbox[2], bbox[3]])
#获取目标宽高[w,h]以便后续使用
self.origin_target_sz = np.array([bbox[2], bbox[3]])
#获取需要图像R/G/B均值 img_mean.shape=(1,1,3)
self.img_mean = np.mean(frame, axis=(0, 1))
#获取模板图像
#返回127x127x3大小的图像
exemplar_img = get_exemplar_image(frame, self.bbox,config.exemplar_size, config.context_amount, self.img_mean)
#增加一个batch维度再输入网络
exemplar_img = self.transforms(exemplar_img)[None, :, :, :]
self.model.track_init(exemplar_img.cuda())
2、update(tracking)
update过程还是follow代码流程来分析
def update(self, frame):
#传入上一帧的bbox,以及保持初始帧的img_mean不变来填充后续所有帧
#返回271x271x3大小的图片、缩放因子是271/(上下文信息x271/127)
instance_img_np, _, _, scale_x = get_instance_image(frame, self.bbox, config.exemplar_size,config.instance_size, config.context_amount, self.img_mean)
"""————————————————得到分类分数和回归参数————————————"""
#增加一个batch维度再送入网络
instance_img = self.transforms(instance_img_np)[None, :, :, :]
#返回score.shape=[1,10,19,19],regression.shape=[1,20,19,19]
pred_score, pred_regression = self.model.track_update(instance_img.cuda())
#[1,10,19,19]->[1,2,5*19*19]->[1,1805,2] conf即置信度
pred_conf = pred_score.reshape(-1, 2, config.anchor_num * config.score_size * config.score_size).permute(0,2,1)
#[1,20,19,19]->[1,4,5*19*19]->[1,1805,4] offset即位移,对anchor微调
pred_offset = pred_regression.reshape(-1, 4,config.anchor_num * config.score_size * config.score_size).permute(0,2,1)
先获得271x271x3大小的检测图像,扩维后将其输入网络,得到互相关的输入,再调用track_update方法得到分类分数和回归参数,之后就是这两者shape变换,以便后续使用。
#传入的anchor(1805,4) delta(1805,4),delta是回归参数,对anchor进行调整,返回调整后的anchor,即pre_box(1805,4)
box_pred = box_transform_inv(self.anchors, delta)
#pred_conf=[1,1805,2]
#score_pred.shape=torch.Size([1805]) 取1,表示取正样本
score_pred = F.softmax(pred_conf, dim=2)[0, :, 1].cpu().detach().numpy()#计算预测分类得分
将1805组回归参数应用于1805个anchor,使其位置信息更加准确,并对分类分数的前景值进行softmax处理,得到1805个和为1的数,之后找到最大的数,该最大值对应的anchor就是预测框。
#尺度惩罚 一个>1的数
s_c = change(sz(box_pred[:, 2], box_pred[:, 3]) / (sz_wh(self.target_sz * scale_x)))
#比例惩罚 一个>1的数
r_c = change((self.target_sz[0] / self.target_sz[1]) / (box_pred[:, 2] / box_pred[:, 3]))
# 尺度惩罚和比例惩罚 penalty_k=0.22,penalty最大为1,即不惩罚
penalty = np.exp(-(r_c * s_c - 1.) * config.penalty_k)
pscore = penalty * score_pred#对每一个anchors的正样本分类预测分数×惩罚因子
pscore = pscore * (1 - config.window_influence) + self.window * config.window_influence #再乘以余弦窗
max_pscore_id= np.argmax(pscore) #返回最大得分的索引id
惩罚的前提是假设目标在相邻帧的大小(尺度以及高宽比例)变化,所以增加了尺度和比例两个惩罚项,又假设目标在相邻帧的位置变化也不会太大,所以使用余弦窗来抑制大位移,正如论文所言,使用 p e n a l t y penalty penalty来抑制尺度和比例的大变化,余弦窗口抑制大位移。在代码 p e n a l t y penalty penalty公式中,倘若anchor的大小和比例与上一帧接近,s_c和r_c这两个值就都为1, p e n a l t y penalty penalty=e^0=1,即不惩罚。通过 p e n a l t y penalty penalty和余弦窗之后,得到最大的的前景分值索引,记为max_pscore_id。
得到max_pscore_id后意味着找到了前景分数最大的anchor作为预测框,上面,已经对其进行了微调,所以现在得到的anchor就是最终的预测框了。接下来就是拿取新的预测框的中心位置和大小去更新目标状态,就不分析了。
还有一点忘了说,nms操作在测试过程并没有使用,而论文却提到要使用nms来获取最终的包围框,但我个人理解貌似并没太大意义,跟踪过程只要找到最大的前景分值即可,并不需要nms,nms是用来剔出重叠anchor的,而获取最大前景分值不涉及anchor的重叠关系,在检测任务当中nms是很重要的一步。
谢谢观看!