【小白筆記】目標跟蹤CFNet論文及源碼筆記



這是CVPR17的一篇文章。項目地址
該論文主要基於SiameseFC網絡的改進,主要加入了CF層,並推導了前向和反向公式,也是端到端的網絡,使得網絡在淺層特徵下也能有不錯的性能,是整體網絡輕量化,速度也更快。下面的分析將結合具體的代碼,有不對的地方歡迎討論~

1. 網絡結構

基礎及背景這裏就不說了,直接來看模型。
在這裏插入圖片描述
爲什麼要加這個CF層:
結合傳統的DCF進行對比,DCF就是線性核的KCF,DCF分爲訓練和檢測兩步,訓練時用最小二乘法解一個嶺迴歸問題把CF模板w求出來,檢測時w和搜索域的patch卷積就得到了response,所以CFNet的原版SiameseFC沒有訓練直接把目標和搜索域做相關,比較naive,所以CFNet把DCF的一套都整合到了網絡中,是一個端到端的模型,由於加入了最小二乘的思想所以淺層也能有不錯的性能,但是這樣做的問題是把CF的邊界問題也帶到網絡中去了,所以作者又加了crop層僅保留中間的一部分,這就把邊界效應降低了。
CF層怎麼設計:
這裏首先要會推導嶺迴歸的最小二乘解w,然後把計算w的過程拆成能用網絡表示的3步如下:
在這裏插入圖片描述
不要忘了DCF的重要性質樣本x是循環樣本,這樣可以利用頻域加速求解,卷積變成了點乘,如下:
在這裏插入圖片描述
這樣就可以表示成一個網絡流的形式如下圖:
在這裏插入圖片描述

2. 構建基礎特徵網絡

CFNet的兩個支流的提特徵部分是完全一樣的,這也是Siamese的意思,訓練也是隻要訓一支就好了,就是共享參數,這裏使用了AlexNet的五層卷積結構,代碼中解釋如下:
以第一層爲例,包括卷積,normalization和pooling操作

net = add_block(net, opts, '1', 11, 11, ...
                opts.num_in(i), opts.num_out(i), ...
                opts.conv_stride(i), 0, 1) ;
net = add_norm(net, opts, '1') ;
net.layers{end+1} = struct('type', 'pool', 'name', 'pool1', ...
                           'method', 'max', ...
                           'pool', [3 3], ...
                           'stride', opts.pool_stride(i), ...
                           'pad', 0) ;

(11,11)是濾波器大小,pooling的stride爲2,且只有前兩層有pooling,卷積層stride除了第一層是2,其餘都是1;
卷積層輸入輸出通道數表示如下,至於輸出爲什麼是輸入的兩倍這是因爲AlexNet網絡是用2臺GPU跑的,計算量分到了兩臺GPU上。

層數 1 2 3 4 5
輸入 3 48 256 192 192
輸出 96 256 384 384 256

3. 構建CF層

在構建CF層之前作者加了加窗層,目的和傳統一樣,抑制邊界效應。

join.addLayer('cf_window', MulConst(), ...
                  {'in1'}, {'cf_example'}, {'window'});

這裏的MulConst函數是作者設計的,MulConst中前傳反傳的核心代碼在mul_const函數中如下:

function varargout = mul_const(x, h, der_y)
% x is [m1, m2, p, b]
% h is [m1, m2]
% der_y is same size as x

if nargin < 3
    der_y = [];
end

if isempty(der_y)
    y = bsxfun(@times, x, h);
    varargout = {y};
else

else
    der_x = bsxfun(@times, der_y, h);
    der_h = sum(sum(der_y .* x, 3), 4);
    varargout = {der_x, der_h};
end

end

反傳的時候的誤差是用前一層的誤差和窗點乘,再按通道和batch求和。
然後加入CF層

join.addLayer('cf', ...
                      CorrFilter('lambda', join_opts.lambda, ...
                                 'bias', join_opts.bias), ...
                      {'cf_example'}, cf_outputs, {'cf_target'});

看看具體CF函數是如何構建的,代碼在corr_filter.m中,按照流程圖前向代碼如下:

y_f = fft2(y);
x_f = fft2(x);
k_f = 1/n*sum(conj(x_f).*x_f, 3) + opts.lambda;
a_f = 1/n*bsxfun(@times, y_f, 1./k_f);
w_f = bsxfun(@times, conj(a_f), x_f);
w = real(ifft2(w_f));

y是target高斯label預先定義好的,所以前向過程僅輸入一個變量x,輸出w,過程完全符合流程圖。
那麼反向傳播如何呢,反傳增加一個輸入der_w,即w的誤差,前傳的前面4行都一樣除了最後兩行,看看代碼如何寫的

der_w_f = fft2(der_w);
der_a_f = sum(x_f .* conj(der_w_f), 3);
der_x_f = bsxfun(@times, a_f, der_w_f);
der_y_f = 1/n*sum(der_a_f .* conj(1 ./ k_f), 4);
der_y = real(ifft2(der_y_f));
der_y = real(ifft2(der_y_f));
der_x_f = der_x_f + 2/n*bsxfun(@times, real(der_k_f), x_f);
der_x = real(ifft2(der_x_f));

最後得到了需要的輸入誤差der_x和der_y,式子論文中給出了推導結果
在這裏插入圖片描述
至於如何推導的看看論文的補充材料應該可以理解。

4. 構建Crop層

CF層後緊接了一個Crop層還是爲了緩解邊界效應。來看看前向如何實現

x = inputs{1};
sz = size_min_ndims(x, 4);
p = obj.margin;
y = x(1+p:end-p, 1+p:end-p, :, :);
outputs = {y};

目的很簡單,僅僅取出中間目標的部分。反向如下:

x = inputs{1};
dldy = derOutputs{1};
dldx = zeros(size(x), class(x));
p = obj.margin;
dldx(1+p:end-p, 1+p:end-p, :, :) = dldy;
derInputs = {dldx};

也很簡單,就是把中間目標的誤差直接作爲輸入誤差,其他部分置0。

5. 構建相關層

這裏已經是最後一層了,將上面crop得到的模板特徵和搜索域上提取的特徵作相關,即滑窗點乘,最後得到響應圖,對應最大值的位置就是目標的相對位置了。相關層在代碼中代號“xcorr”,來看看具體代碼,先是前傳部分

y = vl_nnconv(x, z, []);

可以看到核心就是直接用卷積操作,代碼做了一些額外的處理,如有無bias的處理,這裏就不貼了。反傳的時候輸入y的誤差der_y,

[der_x_, der_z] = vl_nnconv(x_, z, [], der_y);

可以看到核心仍然是卷積層代碼,只是把卷積核換成了模板特徵x。

6. 實驗結果

最後看看CFNet的實驗結果
在這裏插入圖片描述
可以看到速度相比baseline有了不錯的提升,因爲CF層的引入提升了特徵的判別力所以淺層就可以獲得不錯的精度。歡迎與我討論~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章