單目標跟蹤算法SiamRPN


  目標跟蹤算法包括單目標跟蹤和多目標跟蹤,單目標跟蹤在每張圖片中只跟蹤一個目標。目前單目標跟蹤的主要方法分為兩大類,基於相關濾波(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()
anchor產生代碼

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM