基於全卷積的孿生網路目標跟蹤(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
- 損失函式
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,不過它加入了線上更新。