1. 論文信息
- 論文題目:End-to-end representation learning for Correlation Filter based tracking
- 論文出處:CVPR 2017
- 論文作者:Jack Valmadre,Luca Bertinetto等人
- 論文主頁:http://www.robots.ox.ac.uk/~luca/cfnet.html
- 在線閱讀:http://openaccess.thecvf.com/content_cvpr_2017/papers/Valmadre_End-To-End_Representation_Learning_CVPR_2017_paper.pdf
- 源碼鏈接:https://github.com/bertinetto/cfnet
2. 網絡結構設計及實現
根據官方實際代碼,更加詳細一點的網絡結構如下圖所示,可以看出,與SiamFC的網絡結構類似,CFNet也包含兩個分支——z和x,其中z分支對應目標物體模板,可以理解爲目標在第 幀之內所有幀的模板數據加權融合(利用學習率來進行,與KCF算法類似),x分支對應目標物體搜索圖像,它是目標周圍的一大片區域,用於在區域內利用滑動窗口進行搜索,從而確定目標的真正位置。
3. join_cf_window層的實現
論文定義的join_cf_window層,其主要作用是對模板圖像進行加窗,抑制邊緣部分從而儘量減輕因樣本循環移位帶來的邊緣失真(邊界效應),join_cf_window層的定義位於make_net.m源碼中:
join = dagnn.DagNN();
% Apply window before correlation filter.
join.addLayer('cf_window', MulConst(), ...
{'in1'}, {'cf_example'}, {'window'});
p = join.getParamIndex('window');
join.params(p).value = single(make_window(in_sz, join_opts.window));
join.params(p).learningRate = join_opts.window_lr;
其中MulConst是作者自己定義的計算函數,其具體定義位於src/util目錄下的MulConst.m和mul_const.m文件中。
3.1 join_cf_window層的前向傳播
在mul_const.m文件中,關於前向傳播時,其實現細節爲:
y = bsxfun(@times, x, h);
varargout = {y};
其中,x表示join_cf_window層的輸入數據,h表示窗,可以看出加窗的過程實質是矩陣元素級乘法,這一點與KCF算法相同。
這裏需要注意的是:窗口h的定義位於src/training目錄下的make_window.m源碼文件中,如下所示:
function h = make_window(sz, type)
% sz = [m1, m2]
switch type
case ''
h = ones(sz(1:2));
case 'cos'
h = bsxfun(@times, reshape(hann(sz(1)), [sz(1), 1]), ...
reshape(hann(sz(2)), [1, sz(2)]));
otherwise
error(sprintf('unknown window: ''%s''', type));
end
end
3.2 join_cf_window層的反向傳播
在mul_const.m文件中,利用join_cf_window層進行反向傳播時,其實現細節爲:
der_x = bsxfun(@times, der_y, h);
der_h = sum(sum(der_y .* x, 3), 4);
varargout = {der_x, der_h};
這裏面有一個細節需要注意:作者在設計該層的方向傳播時,用的也是矩陣元素級乘法,與正向傳播的計算模式相同,並沒有求偏導的過程。個人推測這裏面的原因是:join_cf_window層的計算任務是對數據進行加窗,這樣的任務對正向和反向傳播是對等的,因此反向傳播計算方式與正向傳播類似。
4. join_cf層的實現
join_cf層是論文中非常關鍵的一層,文章的核心內容幾乎全體現在這一層裏面了。該層的定義位於make_net.m源碼中:
join.addLayer('cf', ...
CorrFilter('lambda', join_opts.lambda, 'bias', join_opts.bias), ...
{'cf_example'}, cf_outputs, {'cf_target'});
其中CorrFilter是作者自己定義的函數,它的具體定義位於src/util目錄下的CorrFilter.m和corr_filter.m文件中,兩者的關係是:CorrFilter.m調用corr_filter.m。首先分析CorrFilter.m,其關鍵代碼爲:
function outputs = forward(obj, inputs, params)
assert(numel(inputs) == 1, 'one input is needed');
assert(numel(params) == 1, 'one param is needed');
args = {'lambda', obj.lambda};
if obj.bias
[outputs{1}, outputs{2}] = corr_filter_bias(...
inputs{1}, params{1}, [], [], args{:});
else
outputs{1} = corr_filter(inputs{1}, params{1}, [], args{:});
end
end
function [derInputs, derParams] = backward(obj, inputs, params, derOutputs)
assert(numel(inputs) == 1, 'one input is needed');
assert(numel(params) == 1, 'one param is needed');
args = {'lambda', obj.lambda};
if obj.bias
assert(numel(derOutputs) == 2, 'expect two gradients');
[derInputs{1}, derParams{1}] = corr_filter_bias(...
inputs{1}, params{1}, derOutputs{1}, derOutputs{2}, args{:});
else
assert(numel(derOutputs) == 1, 'expect one gradient');
[derInputs{1}, derParams{1}] = corr_filter(...
inputs{1}, params{1}, derOutputs{1}, args{:});
end
end
從上述代碼可以看出,CorrFilter.m源碼文件中定義了join_cf層的前向傳播函數(對應tracking過程)和反向傳播函數(對應training過程),其中判斷分支if obj.bias無需理會,因爲代碼默認配置的該參數值爲false。前向傳播和反向傳播的詳細實現均位於src/util目錄下的corr_filter.m文件中,現將該源代碼完整貼出:
function varargout = corr_filter(x, y, der_w, varargin)
opts.lambda = nan;
opts = vl_argparse(opts, varargin);
% x is [m1, m2, p, b]
% y is [m1, m2]
% der_w is same size as x
sz = size_min_ndims(x, 4);
n = prod(sz(1:2));
y_f = fft2(y);
x_f = fft2(x);
% k = 1/n sum_i x_i corr x_i + lambda delta
assert(~isnan(opts.lambda), 'lambda must be specified');
k_f = 1/n*sum(conj(x_f).*x_f, 3) + opts.lambda;
% a must satisfy n (k conv a) = y
% The signal a contains a weight per example (shift)
a_f = 1/n*bsxfun(@times, y_f, 1./k_f);
if isempty(der_w)
% Use same weight a for all channels i.
% w[i] = a corr x[i]
w_f = bsxfun(@times, conj(a_f), x_f);
% w = ifft2(w_f, 'symmetric');
w = real(ifft2(w_f));
varargout = {w};
else
der_w_f = fft2(der_w);
% a, x -> w
% w[i] = a corr x[i]
% dw[i] = da corr x[i] + a corr dx[i]
% F dw[i] = conj(F da) .* F x[i] + conj(F a) .* F dx[i]
% <der_w, dw> = sum_i <der_w[i], dw[i]> = sum_i <F der_w[i], F dw[i]>
% = sum_i <F der_w[i], conj(F da) .* F x[i] + conj(F a) .* F dx[i]>
% = <F da, sum_i conj(F der_w[i]) .* F x[i]> + sum_i <F der_w[i] .* F a, F dx[i]>
der_a_f = sum(x_f .* conj(der_w_f), 3);
der_x_f = bsxfun(@times, a_f, der_w_f);
% k, y -> a
% k conv a = 1/n y
% dk conv a + k conv da = 1/n dy
% dk_f .* a_f + k_f .* da_f = 1/n dy_f
% <der_a, da> = <der_a_f, da_f>
% = <der_a_f, k_f^-1 .* (1/n dy_f - dk_f .* a_f)>
% = <1/n der_a_f .* conj(k_f^-1), dy_f> + <-der_a_f .* conj(k_f^-1 .* a_f), dk_f>
% = <der_y_f, dy_f> + <der_k_f, dk_f>
der_y_f = 1/n*sum(der_a_f .* conj(1 ./ k_f), 4); % accumulate gradients over batch
der_y = real(ifft2(der_y_f));
der_k_f = -der_a_f .* conj(a_f ./ k_f);
% x -> k
% k = 1/n sum_i x_i corr x_i + lambda delta
% dk = 1/n sum_i {dx[i] corr x[i] + x[i] corr dx[i]}
% F dk = 1/n sum_i {conj(F dx[i]) .* F x[i] + conj(F x[i]) .* F dx[i]}
% <der_k, dk> = <der_k, 1/n sum_i {dx[i] corr x[i] + x[i] corr dx[i]}>
% = sum_i <F der_k, 1/n conj(F dx[i]) .* F x[i] + conj(F x[i]) .* F dx[i]>
% = sum_i <F dx[i], 1/n conj(F der_k) .* F x[i]> + <1/n F der_k .* F x[i], F dx[i]>
% = sum_i <F dx[i], 1/n [F der_k + conj(F der_k)] .* F x[i]>
% = sum_i <F dx[i], 2/n real(F der_k) .* F x[i]>
% = sum_i <F der_x[i], F dx[i]>
der_x_f = der_x_f + 2/n*bsxfun(@times, real(der_k_f), x_f);
% der_x = ifft2(der_x_f, 'symmetric');
der_x = real(ifft2(der_x_f));
varargout = {der_x, der_y};
end
end
4.1 join_cf層的前向傳播
上述代碼即爲論文join_cf層的核心實現部分,函數內部通過isempty(der w)判斷來分別實現正向傳播和反向傳播過程,首先分析正向傳播過程,其對應於is empty(der w)=true的分支中,在該if判斷之前及其內部,下面四行代碼最爲關鍵:
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));
varargout = {w};
上述前三行代碼對應論文原文中的公式7(a)-7(c),如下所示:
其中表示目標物體模板(在代碼實現中它是一個融合了多幀圖像的移動平均值,因此叫做模板),符號表示矩陣元素級乘法,表示正則項係數,表示單位矩陣,表示目標物體自相關,表示期望的響應,表示期望響應與自相關的關聯,是join_cf層最終的輸出,它表示求解得到的濾波器。
4.2 join_cf層的反向傳播
現在分析join_cf層的反向傳播。由於論文將相關濾波作爲一個layer嵌入到網絡中並且還需要進行end-to-end訓練,因此有必要爲join_cf層設計反向傳播函數。同樣地,在corr_filter.m文件中,反向傳播最爲關鍵的核心代碼如下所示:
der_a_f = sum(x_f .* conj(der_w_f), 3);
der_y_f = 1/n*sum(der_a_f .* conj(1 ./ k_f), 4); % accumulate gradients over batch
der_k_f = -der_a_f .* conj(a_f ./ k_f);
der_x_f = der_x_f + 2/n*bsxfun(@times, real(der_k_f), x_f);
varargout = {der_x, der_y};
上述四行代碼對應論文原文中的公式10,如下所示:
反向傳播的最終輸出,是變量der_x和der_y。
關於join_cf層的正向傳播和反向傳播的詳細推導過程,可以參考本人的另一篇博客文章:CFNet視頻目標跟蹤推導筆記
5. join_crop_z層的實現
join_crop_z層的主要作用是對濾波器進行裁切。個人推測作者設計這一層的原因:只有進行了裁切,保留濾波器的中央核心區域,才能進行Siamese網絡最終的匹配模式進行跟蹤。
join_crop_z層的最初定義位於make_net.m源碼中:
join.addLayer('crop_z', ...
CropMargin('margin', 16), ...
cf_outputs, xcorr_inputs{1});
其中CropMargin是作者自己定義的函數,其內部包含了正向傳播函數和反向傳播函數。
5.1 join_crop_z層的前向傳播
首先看正向傳播代碼,位於src/util/CropMargin.m源碼文件中,如下所示:
function outputs = forward(obj, inputs, params)
assert(numel(inputs) == 1);
assert(numel(params) == 0);
x = inputs{1};
sz = size_min_ndims(x, 4);
p = obj.margin;
y = x(1+p:end-p, 1+p:end-p, :, :);
outputs = {y};
end
從上述代碼可以看出,變量p即爲需要裁掉的邊緣大小,起裁切作用是語句y = x(1+p:end-p, 1+p:end-p, :, :);,由於網絡中的數據是四維的,裁切只針對第一維和第二維,也就是平面視覺部分,因此後面兩個參數都沒有進行配置。
5.2 join_crop_z層的反向傳播
join_crop_z層的反向傳播代碼如下所示:
function [derInputs, derParams] = backward(obj, inputs, params, derOutputs)
assert(numel(inputs) == 1);
assert(numel(params) == 0);
assert(numel(derOutputs) == 1);
x = inputs{1};
dldy = derOutputs{1};
if isa(x, 'gpuArray')
dldx = gpuArray(zeros(size(x), classUnderlying(x)));
else
dldx = zeros(size(x), class(x));
end
p = obj.margin;
dldx(1+p:end-p, 1+p:end-p, :, :) = dldy;
derInputs = {dldx};
derParams = {};
end
從上述代碼可以看出,在進行反向傳播時,其最終要求得的變量derInputs相當於對變量dldy的周圍進行0元素填充,因此該處的反向傳播代碼也不涉及求偏導。
6. join_xcorr層的實現
join_xcorr層的主要作用是通過滑動窗口方式進行匹配,並形成response map用於最後的目標判別,它的最初定義位於src/training目錄下的make_net.m源碼文件的make_join_corr_filt函數中,如下所示:
join.addLayer('xcorr', XCorr('bias', join_opts.bias), ...
xcorr_inputs, {'out'});
其中XCorr是作者自己定義的函數,它位於src/util/XCorr.m源碼文件中,下面將分析該層的正向傳播和反向傳播實現。
6.1 join_xcorr層的前向傳播
在src/util/XCorr.m源碼文件中,join_xcorr層的前向傳播代碼如下所示:
function outputs = forward(obj, inputs, params)
if obj.bias
assert(numel(inputs) == 3, 'three inputs are needed');
else
assert(numel(inputs) == 2, 'two inputs are needed');
end
if obj.bias
outputs{1} = cross_corr(inputs{1:3});
else
outputs{1} = cross_corr(inputs{1:2}, []);
end
end
由於在CFNet源碼中,參數obj.bias的值默認被賦爲false,因此只需要關注代碼outputs{1} = cross_corr(inputs{1:2}, []);即可。在這一行代碼中,調用了cross_corr(z, x, c, der_y)函數,調用時傳遞的參數是inputs{1:2},它們的含義如下:
- inputs{1}:join_tmpl_cropped,對應分支z的輸出(目標模板)
- inputs{2}:br2_out,對應分支x的輸出(搜索圖像)
該函數位於src/util/cross_corr.m源碼文件中,源碼內包含了前向傳播和反向傳播的具體實現邏輯,其中前向傳播部分如下所示:
z_sz = size_min_ndims(z, 4);
x_sz = size_min_ndims(x, 4);
r_sz = [x_sz(1:2) - z_sz(1:2) + 1, 1, x_sz(4)];
x_ = reshape(x, [x_sz(1:2), prod(x_sz(3:4)), 1]);
r_ = vl_nnconv(x_, z, []);
assert(isequal(size_min_ndims(r_, 4), [r_sz(1:2), r_sz(4), 1]));
r = reshape(r_, r_sz);
y = r;
varargout = {y};
從上述源碼可以看出,滑動窗口卷積的核心代碼是r_ = vl_nnconv(x_, z, []);作者調用matconvnet裏面的函數實現了這一過程。
6.2 join_xcorr層的反向傳播
在src/util/XCorr.m源碼文件中,join_xcorr層的反向傳播代碼如下所示:
function [derInputs, derParams] = backward(obj, inputs, params, derOutputs)
if obj.bias
assert(numel(inputs) == 3, 'three inputs are needed');
else
assert(numel(inputs) == 2, 'two inputs are needed');
end
assert(numel(derOutputs) == 1, 'only one gradient should be flowing in this layer (dldy)');
if obj.bias
[derInputs{1:3}] = cross_corr(inputs{1:3}, derOutputs{1});
else
[derInputs{1:2}] = cross_corr(inputs{1:2}, [], derOutputs{1});
end
derParams = {};
end
前文已經提及,參數obj.bias的值默認被賦爲false,因此在進行反向傳播時重點關注代碼[derInputs{1:2}] = cross_corr(inputs{1:2}, [], derOutputs{1});即可,其傳入的參數中inputs{1}對應分支z的輸出(目標模板),inputs{2}對應分支x的輸出(搜索圖像),函數cross_corr位於src/util/cross_corr.m源碼文件中,其中反向傳播部分如下所示:
z_sz = size_min_ndims(z, 4);
x_sz = size_min_ndims(x, 4);
r_sz = [x_sz(1:2) - z_sz(1:2) + 1, 1, x_sz(4)];
x_ = reshape(x, [x_sz(1:2), prod(x_sz(3:4)), 1]);
der_r = der_y;
der_r_ = reshape(der_r, [r_sz(1:2), r_sz(4), 1]);
[der_x_, der_z] = vl_nnconv(x_, z, [], der_r_);
der_x = reshape(der_x_, x_sz);
varargout = {der_z, der_x};
從上述源碼可以看出,作者通過調用vl_nnconv函數來實現join_xcorr層中變量z和x的偏導數計算。
7. fin_adjust層的實現
fin_adjust層的主要作用是對join_xcorr層計算出來的response map矩陣進行校準,形成更加規範的response map,其實現主要通過MatConvNet來進行,其代碼如下所示:
final.layers = {};
final.layers{end+1} = struct(...
'type', 'conv', 'name', 'adjust', ...
'weights', {{single(1), single(-0.5)}}, ...
'learningRate', [1, 2], ...
'weightDecay', [0 0], ...
'opts', {convOpts});
8. 總結
CFNet論文在SiamFC跟蹤算法的基礎上,將相關濾波引入,形成一個獨立的網絡層參與end-to-end的訓練,在網絡結構設計上具有自己的特色,瞭解這些設計思想和方法,對我們的學習和工作具有較強的實用價值。
更多內容,歡迎掃碼關注“視覺邊疆”微信訂閱號