目標跟蹤算法包括單目標跟蹤和多目標跟蹤,單目標跟蹤在每張圖片中只跟蹤一個目標。目前單目標跟蹤的主要方法分為兩大類,基於相關濾波(correlation filter)的跟蹤算法, 如CSK, KCF, DCF, SRDCF等;基於深度學習的跟蹤算法,如SiamFC, SiamRPN, SiamRPN++等。相比之下,相關濾波的速度更快,深度學習的准確性更高。
跟蹤算法綜述:https://www.zhihu.com/question/26493945, https://zhuanlan.zhihu.com/p/26415984
跟蹤相關算法如下:
這里主要記錄下對SIamRPN跟蹤算法的學習過程,SiamRPN是商湯在2018年的論文High Performance Visual Tracking with Siamese Region Proposal Network中提出,隨后在其基礎上商湯又發展出DaSiamRPN, SiamRPN++, SiamMask等算法,公開代碼地址https://github.com/STVIR/pysot。
對於SiamRPN的理解,從網絡結構,anchor設置,損失函數,跟蹤流程四個方面簡單介紹下。
1. SiamRPN網絡結構
SiamRPN的網絡結構如下圖所示,主要包括Siamese Network和Region Proposal Network(RPN)兩部分,Siamese Network用來提取圖片特征,RPN用來預測目標的位置和置信度。
SiamRPN詳細的網絡結構如下,Siamese Network采用的是Alexnet特征提取網絡,訓練時整個網絡的計算流程為:
1. 第一幀圖片中截取尺寸為(127, 127, 3)的Template Image,下一幀圖片中截取的尺寸為(255, 255, 3)的Search Image,將Template和Search分別送入alexnet提取特征
2. Teamplate通過Alexnet提取特征后尺寸為(1, 256, 6, 6), Search通過Alexnet提取特征后尺寸為(1, 256, 22, 22)
3. 尺寸為(1, 256, 6, 6)的特征和尺寸為(1, 256, 22, 22)的特征,送入RPN網絡的class分支,輸出尺寸為(1, 10, 17, 17)的類別預測結果,其中10表示5個anchor,每個anchor屬於背景和目標的類別的概率
4. 尺寸為(1, 256, 6, 6)的特征和尺寸為(1, 256, 22, 22)的特征,送入RPN網絡的box分支,輸出尺寸為(1, 20, 17, 17)的位置回歸結果,其中20表示5個anchor,每個anchor的[x, y, w, h]偏移量
其中有兩點值得注意下:
Siamese Network: Template Image和Search Image輸入的是同一個Alexnet網絡進行特征提取(即siamese network),所以兩個目標越相似,得到的特征也越相似;
Depthwise Convolution: RPN的兩個分支中將template的特征和search的特征進行了depthwise convolution,即在每個channel上分別進行卷積,卷積響應值越高的位置表示特征越相似(在每個channel上,template的特征圖(4x4)作為卷積核,在search的特征圖(20x20)上進行卷積)
2. SiamRPN的anchor設置
SiamRPN的RPN網絡中,在每個位置設置了5個anchor,5個anchor的寬高比分別為[3, 2, 1, 1/2, 1/3],由於最后網絡的輸出特征圖尺寸為17*17,則共設置了1445(17x17x5)個anchor,如下圖所示。需要注意的是,這些anchor的中心點對應search image中的位置並不是整個search image,只是search image中心128x128的區域,這是由於兩幀圖片時間間隔短,目標中心點移動后落在search image邊界區域的概率較小。
和目標檢測一樣,SiamRPN網絡在訓練時,為了平衡正負樣本的比例,會根據anchor和gt_box的IOU挑選64個樣本給RPN網絡學習 ,其挑選規則如下:
1.計算所有anchor和gt_box的IOU, IOU>0.6的為正樣本,IOU<0.3的為負樣本 2.隨機挑選出64個樣本,正樣本16個,負樣本48。(若正樣本不夠16個時,有多少取多少,若正樣本超過16個,隨機選取16個正樣本,多余的標注為忽略樣本;負樣本一般會多余48個,隨機選取48個負樣本,多余的標注為忽略樣本)
生成anchor的代碼如下所示:

import math from collections import namedtuple import numpy as np Corner = namedtuple('Corner', 'x1 y1 x2 y2') BBox = Corner Center = namedtuple('Center', 'x y w h') def corner2center(corner): """ convert (x1, y1, x2, y2) to (cx, cy, w, h) Parameters ---------- conrner: list or np.ndarray Corner lefttop and rightdown location Return: Center location weight and height """ if isinstance(corner, Corner): x_min, y_min, x_max, y_max = corner return Center((x_min + x_max) * 0.5, (y_min + y_max) * 0.5, (x_max - x_min), (y_max - y_min)) else: x_min, y_min, x_max, y_max = corner[0], corner[1], corner[2], corner[3] center_x = (x_min + x_max) * 0.5 center_y = (y_min + y_max) * 0.5 bbox_w = x_max - x_min bbox_h = y_max - y_min return center_x, center_y, bbox_w, bbox_h def center2corner(center): """ convert (cx, cy, w, h) to (x1, y1, x2, y2) Parameters ---------- center: list or np.ndarray center location, weight and height Return: Corner lefttop and rightdown location """ if isinstance(center, Center): center_x, center_y, bbox_w, bbox_h = center return Corner(center_x - bbox_w * 0.5, center_y - bbox_h * 0.5, center_x + bbox_w * 0.5, center_y + bbox_h * 0.5) else: center_x, center_y, bbox_w, bbox_h = center[0], center[1], center[2], center[3] x_min = center_x - bbox_w * 0.5 y_min = center_y - bbox_h * 0.5 x_max = center_x + bbox_w * 0.5 y_max = center_y + bbox_h * 0.5 return x_min, y_min, x_max, y_max class Anchors: """This generate anchors. Parameters ---------- stride : int Anchor stride ratios : tuple Anchor ratios scales : tuple Anchor scales size : int anchor size """ def __init__(self, stride, ratios, scales, image_center=0, size=0): self.stride = stride self.ratios = ratios self.scales = scales self.image_center = image_center self.size = size self.anchor_num = len(self.scales) * len(self.ratios) self.anchors = None self.generate_anchors() def generate_anchors(self): """generate anchors based on predefined configuration""" self.anchors = np.zeros((self.anchor_num, 4), dtype=np.float32) size = self.stride * self.stride count = 0 for r in self.ratios: ws = int(math.sqrt(size*1. / r)) hs = int(ws * r) for s in self.scales: w = ws * s h = hs * s self.anchors[count][:] = [-w*0.5, -h*0.5, w*0.5, h*0.5][:] count += 1 def generate_all_anchors(self, im_c, size): """ generate all anchors Parameters ---------- im_c: int image center size: image size """ if self.image_center == im_c and self.size == size: return False self.image_center = im_c self.size = size a0x = im_c - size // 2 * self.stride ori = np.array([a0x] * 4, dtype=np.float32) zero_anchors = self.anchors + ori x1 = zero_anchors[:, 0] y1 = zero_anchors[:, 1] x2 = zero_anchors[:, 2] y2 = zero_anchors[:, 3] x1, y1, x2, y2 = map(lambda x: x.reshape(self.anchor_num, 1, 1), [x1, y1, x2, y2]) cx, cy, w, h = corner2center([x1, y1, x2, y2]) disp_x = np.arange(0, size).reshape(1, 1, -1) * self.stride disp_y = np.arange(0, size).reshape(1, -1, 1) * self.stride cx = cx + disp_x cy = cy + disp_y # broadcast zero = np.zeros((self.anchor_num, size, size), dtype=np.float32) cx, cy, w, h = map(lambda x: x + zero, [cx, cy, w, h]) x1, y1, x2, y2 = center2corner([cx, cy, w, h]) self.all_anchors = (np.stack([x1, y1, x2, y2]).astype(np.float32), np.stack([cx, cy, w, h]).astype(np.float32)) return True if __name__ == "__main__": train_search_size = 255 anchor_stride = 8 anchor_scales = [8] anchor_ratios = (0.33, 0.5, 1, 2, 3) train_base_size = 0 train_output_size = 17 anchors = Anchors(anchor_stride, anchor_ratios, anchor_scales) print(anchors.anchors) anchors.generate_all_anchors(im_c=train_search_size // 2, size=train_output_size) print(anchors.all_anchors[0].shape) print(anchors.all_anchors[0]) print(anchors.all_anchors[1].shape) print(anchors.all_anchors[1]) d = anchors.all_anchors[0].transpose((2, 3, 1, 0)) print(d) import cv2 mask = np.ones((255, 255, 3), dtype=np.uint8)*8 for i in range(17): for j in range(17): for k in range(5): box = d[i, j, k, :] # print(box) cv2.rectangle(mask, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2) cv2.imshow("img", mask) cv2.waitKey(0) cv2.destroyAllWindows() # cv2.imshow("img", mask) # cv2.waitKey(0) # cv2.destroyAllWindows()
3.損失函數
和目標檢測一樣,SiamRPN的loss函數包括分類損失cls_losses和坐標損失box_losses,cls_losses采用的交叉熵損失函數,box_losses采用的是smooth_L1損失函數。
4.跟蹤流程
訓練完成后保存網絡參數,在跟蹤時使用。整個跟蹤過程看代碼比較好理解,跟蹤流程簡要介紹如下:
1.從第一幀圖片中,以跟蹤目標的中心點截取127*127的區域,作為template 2.在隨后的圖片中,以上一幀跟蹤目標的中心點截取255*255的區域,作為search 3.將template,search送入siamrpn網絡預測出目標的box和score 4.對score進行window penalty,即采用窗函數(漢寧窗,余弦窗等)對距離中心點較遠的邊緣區域分數進行懲罰。 5.取分數最高的box中心點作為新的中心點,上一幀目標的寬高和box的寬高進行平滑加權作為新的寬高 6.采用新的中心點和寬高作為當前幀的box
參考:https://www.cnblogs.com/shyern/p/10669221.html