算法主页:http://www.robots.ox.ac.uk/~luca/siamese-fc.html )
matlab版本代码:https://github.com/bertinetto/siamese-fc (作者提供源代码)
tensorflow+python代码:tensorflow代码(GitHub上搜索的...)
一.总体思路
图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.
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,不过它加入了在线更新。