1. 程式人生 > >基於全卷積的孿生網路目標跟蹤(Fully-Convolutional Siamese Networks for Object Tracking)

基於全卷積的孿生網路目標跟蹤(Fully-Convolutional Siamese Networks for Object Tracking)

tensorflow+python程式碼:tensorflow程式碼(GitHub上搜索的...)

貼上之前看過的一篇參考部落格 https://blog.csdn.net/autocyz/article/details/53216786

一.總體思路

 圖1  網路結構圖

上圖集中體現了這篇論文的精髓,首先我們從訓練環節和實際追蹤環節來解釋這張圖:

1.訓練 part

首先作者從ILSVRC15 (ImageNet Large Scale Visual Recognition Challenge  資料集上,選取很多組pair圖片作為訓練集,比如論文中給出的圖片樣例:


圖2 訓練樣本示例圖

  • 比如圖2這個狗,小圖對應於圖1中目標模板z(127×127
    ×3),下圖(較大的那個)對應圖1的搜尋圖區域(255×255×3),兩者都有紅色標註的目標框區域圖,分別將兩個大小不一的目標模板圖和待搜尋圖,送到相同的神經網路φ中,這個φ的結構類似於alexnet結構,論文中第7頁table 1給出了結構,可自行參考,最終127×127×3和255×255×3的目標圖和待搜尋圖從φ出來後,得到了6×6×128和22×22×128的feature map(FCN網路中最終輸出的feature map依賴於輸入的目標圖尺寸,因為沒有了全連線層,因此對輸入影象的大小沒啥限制)。之後,這兩個特徵圖進行 cross-correlation操作,(類似於卷積),得到17×17×1的響應圖,這個步驟對應論文中
    這一步可以這麼理解,6×6×128的feature map作為真實幀目標提取的特徵圖,而22×22×128為待搜尋區域的特徵圖,兩者進行相關操作(卷積),得到的17×17×1的每個畫素值就代表了22×22×128搜尋圖中每個位置與目標模板圖的6×6×128相似程度,畫素值越高,證明兩者越相似。在圖1中,x的紅色區域和藍色字候選目標區域圖(都是127×127×3),就對映到17×17×1中的兩個紅色點和藍色點了,如果紅色點的值比藍色點值大,意味著圖1左側搜尋區域中紅色候選目標框相比藍色候選目標框更接近真實目標。
  • 損失函式

where v is the real-valued score of a single exemplar-candidate pair and y ∈{+1,−1} is its ground-truth label。 effectively
generating many examples per pair. We define the loss of a score map to be the mean of the individual losses.
requiring a true label y[u] ∈ {+1,−1} for each position u ∈ D in the score map.

損失函式採用logistic函式,按照論文中的意思,就是一個生成的score map,如圖1的17×17×1圖,其中score map圖經過歸一化,那麼計算每個v[u]和y[u]的乘積和,就得到一個pair訓練樣本的loss和,如公式(4)。其中y[u]根據公式(6)確定:可以理解為,以目標中心為中心,半徑R,作為目標區域,賦值1,其餘賦值0。細節看程式碼就行,待補充。

之後,作者選取了大量的的影象對,作為訓練樣本,不限於跟蹤種類。其他訓練的具體細節,可以參照程式碼,待補充。

2.跟蹤part

假設模型已經訓練好了,我們可以直接使用來進行目標跟蹤,如下為跟蹤的主程式碼(tensorflow版本)。

def main(_):
    print('run tracker...')      
    opts = configParams()
    opts = getOpts(opts)     #呼叫引數,包括batchsize,輸入影象大小(127和255)的設定等等

    exemplarOp = tf.placeholder(tf.float32, [1, opts['exemplarSize'], opts['exemplarSize'], 3])  #模板影象的tensor,固定大小127*127*3
    instanceOp = tf.placeholder(tf.float32, [opts['numScale'], opts['instanceSize'], opts['instanceSize'], 3])搜尋影象的tensor,3*255*255*3,第一個3是尺寸,就是目標在跟蹤過程中,會變大變小或者不變,這個就是設定的三個尺寸
    exemplarOpBak = tf.placeholder(tf.float32, [opts['trainBatchSize'], opts['exemplarSize'], opts['exemplarSize'], 3])
    instanceOpBak = tf.placeholder(tf.float32, [opts['trainBatchSize'], opts['instanceSize'], opts['instanceSize'], 3])
    isTrainingOp = tf.convert_to_tensor(False, dtype='bool', name='is_training')
    sn = SiameseNet()
    scoreOpBak = sn.buildTrainNetwork(exemplarOpBak, instanceOpBak, opts, isTraining=False)
    saver = tf.train.Saver()
    writer = tf.summary.FileWriter(opts['summaryFile'])
    sess = tf.Session()
    saver.restore(sess, opts['modelName'])
    zFeatOp = sn.buildExemplarSubNetwork(exemplarOp, opts, isTrainingOp)


    imgs, targetPosition, targetSize = loadVideoInfo(opts['seq_base_path'], opts['video'])
 
    nImgs = len(imgs)
    startFrame = 0


    im = imgs[startFrame]
    if(im.shape[-1] == 1):
        tmp = np.zeros([im.shape[0], im.shape[1], 3], dtype=np.float32)
        tmp[:, :, 0] = tmp[:, :, 1] = tmp[:, :, 2] = np.squeeze(im)
        im = tmp


    avgChans = np.mean(im, axis=(0, 1))# [np.mean(np.mean(img[:, :, 0])), np.mean(np.mean(img[:, :, 1])), np.mean(np.mean(img[:, :, 2]))]
    wcz = targetSize[1]+opts['contextAmount']*np.sum(targetSize)
    hcz = targetSize[0]+opts['contextAmount']*np.sum(targetSize)
    print(targetSize[1])
    print(targetSize[0])
    print(wcz)
    print(hcz)


    sz = np.sqrt(wcz*hcz)
    scalez = opts['exemplarSize']/sz


    zCrop, _ = getSubWinTracking(im, targetPosition, (opts['exemplarSize'], opts['exemplarSize']), (np.around(sz), np.around(sz)), avgChans)


    if opts['subMean']:
        pass


    dSearch = (opts['instanceSize']-opts['exemplarSize'])/2
    pad = dSearch/scalez
    sx = sz+2*pad


    minSx = 0.2*sx
    maxSx = 5.0*sx


    winSz = opts['scoreSize']*opts['responseUp']
    if opts['windowing'] == 'cosine':
        hann = np.hanning(winSz).reshape(winSz, 1)
        window = hann.dot(hann.T)
    elif opts['windowing'] == 'uniform':
        window = np.ones((winSz, winSz), dtype=np.float32)


    window = window/np.sum(window)
    scales = np.array([opts['scaleStep'] ** i for i in range(int(np.ceil(opts['numScale']/2.0)-opts['numScale']), int(np.floor(opts['numScale']/2.0)+1))])
    print("ddfff");
   # print(range(int(np.ceil(opts['numScale']/2.0)-opts['numScale']),int(np.floor(opts['numScale']/2.0)+1)))
   
    zCrop = np.expand_dims(zCrop, axis=0)
    zFeat = sess.run(zFeatOp, feed_dict={exemplarOp: zCrop})
    zFeat = np.transpose(zFeat, [1, 2, 3, 0])
    zFeatConstantOp = tf.constant(zFeat, dtype=tf.float32)
    scoreOp = sn.buildInferenceNetwork(instanceOp, zFeatConstantOp, opts, isTrainingOp)
    writer.add_graph(sess.graph)


    resPath = os.path.join(opts['seq_base_path'], opts['video'], 'res')
    bBoxes = np.zeros([nImgs, 4])


    tic = time.time()
    for i in range(startFrame, nImgs):
        if i > startFrame:
            im = imgs[i]


            if(im.shape[-1] == 1):
                tmp = np.zeros([im.shape[0], im.shape[1], 3], dtype=np.float32)
                tmp[:, :, 0] = tmp[:, :, 1] = tmp[:, :, 2] = np.squeeze(im)
                im = tmp


            scaledInstance = sx * scales
            scaledTarget = np.array([targetSize * scale for scale in scales])
            xCrops = makeScalePyramid(im, targetPosition, scaledInstance, opts['instanceSize'], avgChans, None, opts)
            # sio.savemat('pyra.mat', {'xCrops': xCrops})
            score = sess.run(scoreOp, feed_dict={instanceOp: xCrops})
            sio.savemat('score.mat', {'score': score})
            newTargetPosition, newScale = trackerEval(score, round(sx), targetPosition, window, opts)
            targetPosition = newTargetPosition
            sx = max(minSx, min(maxSx, (1-opts['scaleLr'])*sx+opts['scaleLr']*scaledInstance[newScale]))
            targetSize = (1-opts['scaleLr'])*targetSize+opts['scaleLr']*scaledTarget[newScale]
        else:
            pass


        rectPosition = targetPosition-targetSize/2.
        tl = tuple(np.round(rectPosition).astype(int)[::-1])
        br = tuple(np.round(rectPosition+targetSize).astype(int)[::-1])
        imDraw = im.astype(np.uint8)
        cv2.rectangle(imDraw, tl, br, (0, 255, 255), thickness=3)
        cv2.imshow("tracking", imDraw)
        cv2.waitKey(1)


    print(time.time()-tic)
    return
    

原始碼比較容易閱讀,尤其精通tensorflow的童鞋,在這裡補充幾個看程式碼得到的細節:

(1)一副目標圖中,目標區域大小不可能總是127×127,論文通過opencv的cvresize()強行調整至127×127。

(2)在目標跟蹤中,人為給定第一幀目標座標大小,選取該幀的目標作為z,然後通過神經網路φ,得到zfeatconstantOp,在之後的視訊序列幀,這個就固定不動了,作為參考。第二幀,以第一幀目標區域中心,選取255×255的搜尋區域,作為搜尋圖x,送到網路得到特徵圖,與featconstantOp進行相關操作,得到response map圖,再通過線性插值,鎖定第二幀的目標區域大小和位置;並且第三幀的更新中,以第二幀的目標座標為中心,再次選取255×255的搜尋區域,以此類推......

(3)網路模型是線下訓練的,訓練樣本選取的pair,種類繁多,因此網路模型不針對一種特定跟蹤目標,適應性比較強。在實際跟蹤時候,理論上,可跟蹤任意種類的目標,正因如此,這個演算法執行速度比較快。

(4)真實目標在移動過程中,大小是發生改變的,作者提供了三種尺寸,具體我忘了,一個0.9幾,一個1,一個1.0幾。對應著縮小,不變,變大。在與zfeatconstantOp比較的過程中,選擇response map最高的的尺寸,然後更新目標位置和大小。

(5)由於目標真實區域第一幀就固定了,因此如果之後目標出現嚴重變形和遮擋,跟蹤就會失敗。並且,是offline的模型,是沒有考慮跟蹤特定物體資訊的加成。

(6)其他細節,待補充。

至於實驗就不講了,論文中說的挺清楚。

3.總結比較

本質上,論文目的是為了跟蹤任意物體。訓練的神經網路的模型往往是跟蹤特定物體的,模型不具備普遍性。目前存在一些線上學習更新的演算法,如TLD和KCF,但線上學習資料量畢竟有限,而且線上學習的跟蹤模型速度很慢,如2015年Visual Tracking with Fully Convolutional Networks,GPU只有2fps.實時性不高。本文的方法就是利用offline的訓練好的CNN模型,提取特徵,再利用correlation filters,比較特徵相似度,進而實現跟蹤,利用到這種思想的還有17年cvpr的ECO,不過它加入了線上更新。