Discriminatively Trained Part Based Models 論文及代碼分析
代碼已經脫離了作者最初的那篇文章(object Detection with discriminatively Trained Part Based Models)所介紹的模型。目前在代碼(release 4)中所使用的模型是Grammar Model。我們有必要首先介紹一下什么是Grammar Model(見Object Detection Grammars和Object Detection with Grammar Models)。
Grammar Model最初來自於自然語言理解。給出一句話,我們使用不同的語法規則對語句進行分解,如下圖所示:
當然我們要對使用不同的語法規則對語句進行分解進行打分(為什么呢?不同的語法規則體現了一段話的不同語義,只有最合適的語法規則才能體現一句話的真正含義)。
我們怎么將這個來自於NLP的算法模型引入到Object Detection中呢?看看聰明的作者怎么做的吧?
(下面這段話來自於Object Detection with Grammar Models)
讓表示一系列Nonterminal Symbols,
表示一系列Terminal Symbols。我們將Terminal 看做是可以在圖像中發現的基本構建塊。而Nonterminal Symbols被定義成一些虛擬對象,它的出現僅僅是為了表示如何基於Terminal Symbols進行展開。
定義是在一副圖像中一個Symbol的所有可能的位置集合。
為定義在位置
處的
。這時我們就可以寫出語法模型的結構了:
其中。
我們可以對一個Nonterminal Symbol進行展開,展開成若干個Terminal Symbol。基於這個模型,我們可以將的展開構造成一個以
為根的樹
。這個樹
的葉節點是Terminal Symbol,這個樹的Interminal 節點是Nonterrminal Symbols。
這時我們就要問了,由於一個Nonterminal Symbols需要被展開成若干個Terminal Symbols,並且一個Termnial Symbol對應着具體的圖像塊,這里的展開也就對應着語法規則對語句的解析,那么我們怎么對這種展開打分呢?
我們為每個Symbol(其中包括Nonterminal Symbol 和 Terminal Symbol)附加一個屬性"語法規則"。這一屬性其實就是一個如何給其對應的展開或對應的圖像塊打分的計算公式。我們暫且將其稱之為rule。
由於整棵樹其實就是對一個圖像解析,就像是對一句話的語法解析一樣。這個圖像的解析的分數我們定義成:
其中就是使用rule對當前Nonterminal Symbol 展開所產生的分數;
就是對當前的Terminal Symbol 所對應的圖像塊產生的分數。
我們可以將這棵樹看做是解析一個固定圖像內容的模型。得到這個解析樹模型就是我們的終極目標。
現在我們可能產生的新問題是如何設計針對於每個Symbol上的rule呢?其實這就是所謂的語法規則。針對不同的Symbol 類型有不同的rule設計。首先來說Terminal Symbol。由於其是針對於具體的圖像塊的,我們將其定義成一個filter(未知,我們需要學習)與圖像塊的卷積響應。針對於Nonterminal Symbol我們又存在兩種rule,分別為Structure Scheme和 Deformation Scheme。它們分別又是什么含義呢?對於Structure Scheme,正如其名字所顯示,其表示固定的結構(也就是位置固定)。舉例來說,一個Noterminal Symbol關聯一個Terminal Symbol, 這個關聯規則是Structure Shceme(維護着一個固定的anchor量)。這也就是說通過這個規則得到的Nonterminal Symbol展開成Terminal Symbol 的分數,是Terminal Symbol 在位置anchor處所產生的分數。現在說說Deformation Scheme吧!在這里也有一個anchor量,但是Terminal Symbol並不一定在這個anchor位置,而是允許它有一些偏移dx,dy。基於此,我們定義這種展開的分數為在anchor+(dx,dy)位置處的Terminal Symbol的分數加上由於位置偏移帶來的懲罰項。
至此,文中所使用的Object detection gramma model的基本結構已經出來。就是要建立一個解析樹,使其對檢測物得到的分數最大,但對其他圖像的分數較小。
接下來,我們具體分析一下文中的這種解析樹如何建立的(注意考慮鏡像,多尺度)。
Structure Schema:
對於每個位置,A Structure Shema 指定了這樣一個展開形式:
其中指定了在feature map pyramid中的常數位置。
這種Structure Schema 的分數計算方式如下:
Deformation Schema:
定義是在一個feature map pyramid中某一尺度下的一系列可能變形偏移的集合。與Structure Schema的差別可以簡單理解成,Deformation Schema在計算分數時並不是一個固定位置處,而是在固定位置處有一個偏移的分數再加上一個偏移的懲罰項,通過找到一個最大分數,來作為當前分數。在位置
和偏移
下的展開形式為:
其中是關於偏移距離的特征向量,
是變形參數(需要學習)。我們使用和
來計算偏移懲罰項。在這種Deformation Schema下的分數計算方式如下:
對圖像塊打分規則:
Terminal Symbol直接對應着某個圖像塊,那么附着在其上的規則應該如何打分呢?引入濾波思想。當然這里的濾波是在這個圖像塊的特征空間中進行的。具體打分方式如下:
其中濾波系數,
是一個在位置
上的圖像塊(一個矩形窗口內的圖像塊)的特征。
我們利用現在對打分規則的認識,來重述一下給所構建解析樹打分的過程(這里僅考慮簡單的單尺度情況)。
背景:在圖像中檢測某一object的位置,我們使用一個矩形檢測框box進行指定。
解析樹構建:
對這個解析樹如何解釋呢?Start NonTerminal 是這個解析樹的根節點。通過一個Structure Schema rule關聯到三個NonTerminal 上,這三個NonTerminal對應三個Fixed anchor。第一個NonTerminal 通過一個Deformation Schema rule 鏈接一個Root Terminal Symbol(直接與圖像關聯),由於是由Deformation Schema rule關聯的,因而在計算分數時允許存在一個局部的偏移。第二個NonTerminal通過一個Deformation Shcema rule 鏈接一個Part1 Terminal Symbol(直接與圖像關聯)。第三個NonTerminal類似。
計算解析樹的分數過程形象描述:
首先給Start NonTerminal 一個全局位置。為了計算Start NonTerminal 的分數
,我們需要計算
,
和
。
其中,
和
是預先指定的。
記住
為了計算,我們需要計算以及偏移懲罰項
。其中,位置
。大家會問,這里不確定的偏移量
怎么辦呢?我們選取能夠使
達到最大的。
為了計算,我們需要使用Root Terminal Symbol 所指定的濾波系數(某一固定長寬的濾波器),以及在
位置處的圖像塊HOG特征(與濾波器同等長寬)進行點積操作得到的濾波響應值。
至此我們進行回推計算,就可以得到,這就是這個圖像內容解析樹模型在位置
處的分數。
現在有人又要問了,這個root 和 parts模型中anchor怎么指定啊?其實anchor是相對於全局檢測位置的一個固定偏移。我們可以采用這種策略,首先我們指定所有為默認值,然后使用這個模型在圖像的所有位置進行打分。這是第一次迭代。然后,挑選出分數最大的一個位置
。再然后,我們基於這個位置,尋找最佳位置
。緊接着,就是最佳偏移位置
。
現在有人又要問了,這里面還涉及到一些參數呢?如Terminal Symbol中的濾波器參數,它們如何確定呢?這就要涉及學習問題了。在我們得到了,和后,就可以將一些模型參數放入學習框架了(后面詳述)。
這里這個模型是相當簡單的,為了增加模型效果,文章中增加模型的復雜度。
1. 引入了鏡像考慮
在模型中引入鏡像節點。
2. 引入多尺度考慮
如何引入多尺度呢?當進行展開時,在高分辨率下進行計算分數。用標准化的式子描述如下:
變量定義:索引頂層的part,
索引subtype(在這里指的是鏡像對象)。
指定分辨率索引,
指定展開part 的索引,它們的取值范圍是
,其中
是
的subpart的個數。
現在進入代碼部分:
上面所涉及的計算模型分數的所有過程均在gdeetect.m文件中實現。
function [dets, boxes, info] = gdetect(pyra, model, thresh, bbox, overlap)
其中pyra是圖像的特征金字塔;model是解析樹模型;thresh是解析樹分數閾值,只有在這一閾值之上的位置,可以作為候選位置;bbox是手工標注的目標位置(原始圖像層的坐標系下);overlap,檢索框與bbox的重疊度不能低於這個指定的重疊度(overlap)。返回值是什么呢?
dets: 每一個檢測位置一行,一共6列。1~4列給出每一個檢測bounding box的像素坐標(x1,y1,x2,y2)。5列指定用於檢測的model commponent。6列指定每個檢測的分數(解析樹模型在指定位置的分數)。
bboxes: 每個檢測一行。每4列構成一個model filter bounding box的像素坐標。注意,這個序列的索引與model.filters中的索引一致。(每個Terminal Symbol 中包含一個filter,其需要直接操作圖像塊的特征)
info:關於檢測詳細信息,包括最佳, 最佳,等信息。
在這個函數中,我們需要首先調用function model = filterresponses(model, pyra, latent, bbox, overlap)
這個函數用來計算所有filters在圖像所有有效的特征尺度上每一位置處的響應值。怎么定義有效的圖像特征尺度呢?這就需要使用overlap來計算在給定的尺度上的檢索框的大小和位置與對應的bbox(手工標注的目標物體位置)之間的重疊度,只有重疊度達到一定閾值我們才能認為這一尺度為有效的尺度。對於所有無效的尺度我們直接對這一層的所有位置的濾波器響應值賦予-inf。
下面詳細看看這個函數的實現過程:
% gather filters for computing match quality responses i = 1; filters = {}; filter_to_symbol = []; for s = model.symbols if s.type == 'T' filters{i} = model.filters(s.filter).w; filter_to_symbol(i) = s.i; i = i + 1; end end %得到所有濾波器,以及每個濾波器對應的Terminal Symbol |
%檢測有效層索引 [model, levels] = validatelevels(model, pyra, latent, bbox, overlap);
function [model, levels] = validatelevels(model, pyra, latent, bbox, overlap) % model object model % pyra feature pyramid % latent true => latent positive detection mode % bbox ground truth bbox % overlap overlap threshold
if ~latent levels = 1:length(pyra.feat); else levels = []; for l = model.interval+1:length(pyra.feat) if ~testoverlap(l, model, pyra, bbox, overlap) % no overlap at level l for i = 1:model.numfilters model.symbols(model.filters(i).symbol).score{l} = -inf; model.scoretpt{l} = 0; end else levels = [levels l l-model.interval]; end end end
%我們先不管latent有什么用處(當使用正樣本時將其設置成true,當使用負樣本時將其設置成false) 我們首先看到初始檢測層是model.interval+1,為什么是這一層呢?這一層的尺度為1,也就是原始圖像。 最底層圖像是原始圖像分辨率的二倍。
接下來我們進入函數testoverlap(l, model, pyra, bbox, overlap)
function ok = testoverlap(level, model, pyra, bbox, overlap) % level pyramid level % model object model % pyra feature pyramid % bbox ground truth bbox % overlap overlap threshold
ok = false; scale = model.sbin/pyra.scales(level); for r = 1:length(model.rules{model.start}) detwin = model.rules{model.start}(r).detwindow; o = computeoverlap(bbox, detwin(1), detwin(2), ... size(pyra.feat{level},1), ... size(pyra.feat{level},2), ... scale, pyra); inds = find(o >= overlap); if ~isempty(inds) ok = true; break; end end
在這里我們看到了什么呢?看到了一個奇怪的量 scale = model.sbin/pyra.scales(level); 這個scale什么意思呢?這里我需要指出,我們所建立的金字塔不是圖像的金字塔,而是HOG特征金字塔。在建立HOG特征金字塔時,為了消除噪聲影響,將基於像素上的方向分布構造成基於cell上的方向分布,一個cell 大小由model.sbin=8進行控制。如果當前的金字塔尺度pyra.scales(level)是1,那么使用這個式子計算出來的scale=8。於是要問了,有什么用呢?我們知道detwin = model.rules{model.start}(r).detwindow;這個量是檢測窗口的大小。當我們在特征金字塔不同層上使用這個檢測窗口進行檢測時,我們需要將對應的這個檢測窗口的位置(起始點,長寬)換算到原始圖像的坐標系下,因為我們要利用人工標注的目標物體的標注矩形框bbox。我們接下來走入 o = computeoverlap(bbox, fdimy, fdimx, dimy, dimx, scale, pyra)函數看看,進行換算從而得到與標注bbox重疊度的計算的。
function o = computeoverlap(bbox, fdimy, fdimx, dimy, dimx, scale, pyra) % bbox bounding box image coordinates [x1 y1 x2 y2] % fdimy number of rows in filter % fdimx number of cols in filter % dimy number of rows in feature map % dimx number of cols in feature map % scale image scale the feature map was computed at % padx x padding added to feature map % pady y padding added to feature map
padx = pyra.padx; pady = pyra.pady; imsize = pyra.imsize;
%原始圖像大小 imarea = imsize(1)*imsize(2);
%手工標注的目標對象區域 bboxarea = (bbox(3)-bbox(1)+1)*(bbox(4)-bbox(2)+1);
% corners for each placement of the filter (in image coordinates) %dimx,dimy是當前檢測的特征金字塔層的寬度和高度 %我們需要得到所有位置的重疊度判斷,因而使用[1:dimx]方式。乘以scale 表示轉換到原始圖像的 %坐標系下。 %fdimx, fdimy檢測窗口的寬和高。 x1 = ([1:dimx] - padx - 1) * scale + 1; y1 = ([1:dimy] - pady - 1) * scale + 1; x2 = x1 + fdimx*scale - 1; y2 = y1 + fdimy*scale - 1; if bboxarea / imarea < 0.7 % clip detection window to image boundary only if % the bbox is less than 70% of the image area x1 = min(max(x1, 1), imsize(2)); y1 = min(max(y1, 1), imsize(1)); x2 = max(min(x2, imsize(2)), 1); y2 = max(min(y2, imsize(1)), 1); end
% intersection of the filter with the bounding box %得到檢測窗口和人工標注窗口的重疊區域 xx1 = max(x1, bbox(1)); yy1 = max(y1, bbox(2)); xx2 = min(x2, bbox(3)); yy2 = min(y2, bbox(4));
% e.g., [x1(:) y1(:)] == every upper-left corner [x1 y1] = meshgrid(x1, y1); [x2 y2] = meshgrid(x2, y2); [xx1 yy1] = meshgrid(xx1, yy1); [xx2 yy2] = meshgrid(xx2, yy2);
% compute width and height of every intersection box %計算重疊區域的寬和高,從而計算所有可能重疊區域的面積inter w = xx2(:)-xx1(:)+1; h = yy2(:)-yy1(:)+1; inter = w.*h;
% a = area of (possibly clipped) detection windows %檢測框的面積 a = (x2(:)-x1(:)+1) .* (y2(:)-y1(:)+1);
% b = area of bbox %人工標注的區域面積 b = (bbox(3)-bbox(1)+1) * (bbox(4)-bbox(2)+1);
% intersection over union overlap %重疊率計算公式 o = inter ./ (a+b-inter); % set invalid entries to 0 overlap o(w <= 0) = 0; o(h <= 0) = 0;
一旦在特征金字塔的某一層上存在有效的檢測位置(在某個檢測位置上,檢測框與人工標注區域的重疊率大於指定閾值),那么我們將這一層的序號進行保存到levels變量中(在validatelevels(...)函數中) levels = [levels l l-model.interval];
對於那些無效的特征金子塔層,我們直接定義所有位置對所有濾波器(我們知道濾波器是由Terminal Symbol 進行控制)的分數為 -inf for i = 1:model.numfilters model.symbols(model.filters(i).symbol).score{l} = -inf; model.scoretpt{l} = 0; end
緊接着我們就回到了filterresponses(...)函數中的如下代碼塊中: for level = levels % compute filter response for all filters at this level r = fconv(pyra.feat{level}, filters, 1, length(filters)); % find max response array size for this level s = [-inf -inf]; for i = 1:length(r) s = max([s; size(r{i})]); end % set filter response as the score for each filter terminal for i = 1:length(r) % normalize response array size so all responses at this % level have the same dimension spady = s(1) - size(r{i},1); spadx = s(2) - size(r{i},2); r{i} = padarray(r{i}, [spady spadx], -inf, 'post'); model.symbols(filter_to_symbol(i)).score{level} = r{i}; end model.scoretpt{level} = zeros(s); end
我們對所有特征金字塔的有效層進行遍歷,分別讓每一層與所有濾波器進行操作,從而得到這一層在濾波器作用下在所有位置處的響應值,這些值就是控制這個濾波器的Terminal Symbol在每層的每個位置處的分數。 model.symbols(filter_to_symbol(i)).score{level} = r{i}; |
到目前為止,我們已經得到所有Terminal Symbol在所有有效金字塔層上的分數了。利用這些分數我們可以回推,從而得到整個解析樹模型在所有有效金字塔層上的每一點處的分數。我們繼續往下看吧。
% compute parse scores L = model_sort(model); for s = L for r = model.rules{s} model = apply_rule(model, r, pyra.pady, pyra.padx); end model = symbol_score(model, s, latent, pyra, bbox, overlap); end
首先我們需要調用model_sort對模型中的所有Nonterminal Symbol進行排序,排序的結果是從解析樹模型的最底層到最高層。從而我們有了一個有效的順序進行回推計算整個解析樹模型的分數。
這兩個循環很重要,最外層循環控制着所有NonTerminal Symbol。內層循環,控制着附着在每個Nonterminal Symbol上的規則。我們首先得到每個規則下的得分,然后再搜集起當前的NonTerminal Symbol的所有規則的分數,從而形成這個NonTerminal Symbol的分數。
現在我們進入每個函數進行細看。首先進入 % compute score pyramid for rule r function model = apply_rule(model, r, pady, padx) % model object model % r structural|deformation rule % pady number of rows of feature map padding % padx number of cols of feature map padding
if r.type == 'S' model = apply_structural_rule(model, r, pady, padx); else model = apply_deformation_rule(model, r); end
當然了,作用在NonTerminal Symbol上的規則有兩種,一個是Structure rule, 一個是Deformation rule。 我們先看 Deformation rule吧! function model = apply_deformation_rule(model, r) % model object model % r deformation rule
% deformation rule -> apply distance transform def = r.def.w; score = model.symbols(r.rhs(1)).score; for i = 1:length(score) % Note: dt has been changed so that we no longer have to pass in -score{i} [score{i}, Ix{i}, Iy{i}] = dt(score{i}, def(1), def(2), def(3), def(4)); score{i} = score{i} + r.offset.w; end model.rules{r.lhs}(r.i).score = score; model.rules{r.lhs}(r.i).Ix = Ix; model.rules{r.lhs}(r.i).Iy = Iy;
作者默認,一個Deformation rule 只能關聯一個Symbol,這一點與作者對Structure rule的設計不同。但是我認為可以將Structure rule設計成同樣的風格,更易於進行理解,其實內部實現的方式是一樣的。可以自己理解,為什么我這么說。 %得到這個變形規則的變形參數(論文中的 def = r.def.w;
遍歷特征金字塔所有層,在每個位置處檢索一個最佳偏移,然后得到更新后的分數(記住需要靠偏移懲罰項啊) [score{i}, Ix{i}, Iy{i}] = dt(score{i}, def(1), def(2), def(3), def(4)); score{i} = score{i} + r.offset.w;
r.offset.w是什么呢?偏移權重,它需要被學習。
接下來,我們將計算得到的規則分數保存 model.rules{r.lhs}(r.i).score = score; model.rules{r.lhs}(r.i).Ix = Ix; model.rules{r.lhs}(r.i).Iy = Iy;
糾結與細節的朋友就會問r.i是什么?表明規則自身在規則池中的序號。
好了我們現在可以看看apply_structural_rule了!
function model = apply_structural_rule(model, r, pady, padx) % model object model % r structural rule % pady number of rows of feature map padding % padx number of cols of feature map padding
% structural rule -> shift and sum scores from rhs symbols % prepare score for this rule score = model.scoretpt;
特征金子塔的每一層給出分數偏移權重,這一權重需要被學習得到。 for i = 1:length(score) score{i}(:) = r.offset.w; end
% sum scores from rhs (with appropriate shift and down sample) %由於設計思想與Deformation rule不一致,導致一個Structure rule可以關聯多個右節點。 %好吧,我們對所有關聯的右節點進行遍歷 %記住我們的最終目標是要計算這個規則的左節點在特征金字塔的每一層的每個位置處進行展開的分數。 %這個函數只是計算當前這個規則對這個目標的貢獻。 %我們對左節點在每一層每一位置展開時,對展開后的右節點計算分數時不一定是在與左節點同等的特征層 %上,在哪個特征層由anchor決定。
for j = 1:length(r.rhs) 得到anchor,注意這個anchor 是預先指定的,每個右節點有不同的位置。 ax = r.anchor{j}(1); ay = r.anchor{j}(2); ds = r.anchor{j}(3); % step size for down sampling
注意ds的取值方式,如果我們想要在與當前的Nonterminal Symbol同等分辨率下(同等特征層)計算展開后的Symbol的分數我們需要設定ds=0,這時step=1。如果我們想要在2倍分辨率下進行展開,則需要設定ds=1,這是step=2。我想說到這里,應該已經知道step的意思了吧。對於Nonterminal Symbol(左節點)的當前分辨率(特征金字塔的某層)下的每個位置,在做展開后(由於在不同的分辨率下展開,論文中使用2倍分辨率下展開)位置不是連續的,有一個間隔step。
step = 2^ds; % amount of (virtual) padding to halucinate
得到新的填充邊界 virtpady = (step-1)*pady; virtpadx = (step-1)*padx; % starting points (simulates additional padding at finer scales)
得到展開的開始位置 starty = 1+ay-virtpady; startx = 1+ax-virtpadx;
% starting level 得到檢測開始層(這個層序號是指當前的做節點,做展開的那個節點所在層) startlevel = model.interval*ds + 1;
% score table to shift and down sample s = model.symbols(r.rhs(j)).score; for i = startlevel:length(s)
得到展開后的節點的層(i - model.interval*ds就可以得到在若干倍分辨率下的層序號) level = i - model.interval*ds;
% ending points 得到展開的結束位置(使用min只是為了得到有效的結束位置) endy = min(size(s{level},1), starty+step*(size(score{i},1)-1)); endx = min(size(s{level},2), startx+step*(size(score{i},2)-1));
% y sample points 為了對應到左側節點的位置,我們需要間隔step進行采樣 iy = starty:step:endy; oy = sum(iy < 1); iy = iy(iy >= 1); % x sample points ix = startx:step:endx; ox = sum(ix < 1); ix = ix(ix >= 1); % sample scores
我們得到在這些位置處的分數 sp = s{level}(iy, ix); sz = size(sp);
% sum with correct offset stmp = -inf(size(score{i})); stmp(oy+1:oy+sz(1), ox+1:ox+sz(2)) = sp;
累計所有右節點得到的分數 score{i} = score{i} + stmp; end end
賦予當前規則分數 model.rules{r.lhs}(r.i).score = score;
現在我們得到了與每個Nonterminal Symbol的rule的分數了,那么現在就應該整合分數從而得到這個Nonterminal Symbol的分數(在特征金子塔每一層每一位置)。我們進入函數 function model = symbol_score(model, s, latent, pyra, bbox, overlap) % model object model % s grammar symbol
if latent && s == model.start % mark detection window locations that do not yield % sufficient overlap with score = -inf for i = 1:length(model.rules{model.start}) detwin = model.rules{model.start}(i).detwindow; for level = model.interval+1:length(model.rules{model.start}(i).score) scoresz = size(model.rules{model.start}(i).score{level}); scale = model.sbin/pyra.scales(level); o = computeoverlap(bbox, detwin(1), detwin(2), ... scoresz(1), scoresz(2), ... scale, pyra); inds = find(o < overlap); model.rules{model.start}(i).score{level}(inds) = -inf; end end end
% take pointwise max over scores for each rule with s as the lhs rules = model.rules{s}; score = rules(1).score;
for r = rules(2:end) for i = 1:length(r.score) score{i} = max(score{i}, r.score{i}); end end model.symbols(s).score = score;
其實就是在每一層的每一位置處在不同規則下的最大響應值,從而就得到了這個Nonterminal 在每一層每一位置上的響應分數。 |
現在我們就回到了gdetect函數了。繼續往下運行
% find scores above threshold X = zeros(0, 'int32'); Y = zeros(0, 'int32'); I = zeros(0, 'int32'); L = zeros(0, 'int32'); S = []; for level = model.interval+1:length(pyra.scales) score = model.symbols(model.start).score{level}; tmpI = find(score > thresh); [tmpY, tmpX] = ind2sub(size(score), tmpI); X = [X; tmpX]; Y = [Y; tmpY]; I = [I; tmpI]; L = [L; level*ones(length(tmpI), 1)]; S = [S; score(tmpI)]; end
[ign, ord] = sort(S, 'descend'); % only return the highest scoring example in latent mode % (the overlap requirement has already been enforced) if latent && ~isempty(ord) ord = ord(1); end X = X(ord); Y = Y(ord); I = I(ord); L = L(ord); S = S(ord);
找到候選全局位置 |
接下來我們將要更新模型中的一些變量如anchor, deformation 。調用函數
[dets, boxes, info] = getdetections(model, pyra.padx, pyra.pady, ... pyra.scales, X, Y, L, S);
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下面進入學習部分:
這里,我們只是說明如何建立權重和特征序列,然后將其放入學習算法當中,學習算法如何實現的稍后詳述。
我們直接進入函數
function [boxes, count] = gdetectwrite(pyra, model, boxes, info, label, ...
fid, id, maxsize, maxnum)
參數描述 % pyra feature pyramid % model object model % boxes detection boxes % info detection info from gdetect.m % label +1 / -1 binary class label % fid cache's file descriptor from fopen() % id id for use in long label (e.g., image number the detection is from) % maxsize max cache size in bytes % maxnum max number of feature vectors to write
count = writefeatures(pyra, model, info, label, fid, id, maxsize);
function count = writefeatures(pyra, model, info, label, fid, id, maxsize) % pyra feature pyramid % model object model % info detection info from gdetect.m % label +1 / -1 binary class label % fid cache's file descriptor from fopen() % id id for use in long label (e.g., image number the detection is from) % maxsize max cache size in bytes
% indexes into info from getdetections.cc DET_USE = 1; % current symbol is used DET_IND = 2; % rule index DET_X = 3; % x coord (filter and deformation) DET_Y = 4; % y coord (filter and deformation) DET_L = 5; % level (filter) DET_DS = 6; % # of 2x scalings relative to the start symbol location DET_PX = 7; % x coord of "probe" (deformation) DET_PY = 8; % y coord of "probe" (deformation) DET_VAL = 9; % score of current symbol DET_SZ = 10; % <count number of constants above>
count = 0; for i = 1:size(info,3) r = info(DET_IND, model.start, i); x = info(DET_X, model.start, i); y = info(DET_Y, model.start, i); l = info(DET_L, model.start, i); ex = []; ex.fid = fid; ex.maxsize = maxsize; ex.header = [label id l x y 0 0]; ex.blocks(model.numblocks).w = [];
for j = 1:model.numsymbols % skip unused symbols if info(DET_USE, j, i) == 0 continue; end
if model.symbols(j).type == 'T' ex = addfilterfeat(model, ex, ... info(DET_X, j, i), ... info(DET_Y, j, i), ... pyra.padx, pyra.pady, ... info(DET_DS, j, i), ... model.symbols(j).filter, ... pyra.feat{info(DET_L, j, i)}); else ruleind = info(DET_IND, j, i); if model.rules{j}(ruleind).type == 'D' bl = model.rules{j}(ruleind).def.blocklabel; dx = info(DET_PX, j, i) - info(DET_X, j, i); dy = info(DET_PY, j, i) - info(DET_Y, j, i); def = [-(dx^2); -dx; -(dy^2); -dy]; if model.rules{j}.def.flip def(2) = -def(2); end if isempty(ex.blocks(bl).w) ex.blocks(bl).w = def; else ex.blocks(bl).w = ex.blocks(bl).w + def; end end bl = model.rules{j}(ruleind).offset.blocklabel; ex.blocks(bl).w = 1; end end status = exwrite(ex); count = count + 1; if ~status break end end
首先按照Symbol的順序排列,針對於每個Symbol,首先判斷它是Terminal Symbol還是Nonterminal Symbol。如果是Terminal Symbol,調用: ex = addfilterfeat(model, ex, ... info(DET_X, j, i), ... info(DET_Y, j, i), ... pyra.padx, pyra.pady, ... info(DET_DS, j, i), ... model.symbols(j).filter, ... pyra.feat{info(DET_L, j, i)});
function ex = addfilterfeat(model, ex, x, y, padx, pady, ds, fi, feat) % model object model % ex example that is being extracted from the feature pyramid % x, y location of filter in feat (with virtual padding) % padx number of cols of padding % pady number of rows of padding % ds number of 2x scalings (0 => root level, 1 => first part level, ...) % fi filter index % feat padded feature map
fsz = model.filters(fi).size; % remove virtual padding fy = y - pady*(2^ds-1); fx = x - padx*(2^ds-1); f = feat(fy:fy+fsz(1)-1, fx:fx+fsz(2)-1, :);
% flipped filter if model.filters(fi).symmetric == 'M' && model.filters(fi).flip f = flipfeat(f); end
% accumulate features bl = model.filters(fi).blocklabel; if isempty(ex.blocks(bl).w) ex.blocks(bl).w = f(:); else ex.blocks(bl).w = ex.blocks(bl).w + f(:); end
我們針對filter 的大小,得到對應的feature,並將其保存到ex.blocks(bl).w中。
如果當前的Symbol是Nonterminal Symbol,那么我們還需要考慮是Structure rule 還是 Deformation rule。如果是Deformation rule,我們需要得到最佳偏移def = [-(dx^2); -dx; -(dy^2); -dy];然后是權重偏移ex.blocks(bl).w = 1; 如果是Structure rule,那么只有ex.blocks(bl).w = 1; 我們依靠blocklabel的順序將它們寫入到文件中。
當有了這些量之后,必然還要有w啊!它們也需要根據bolcklabel的順序寫入文件中。
接下來就可以調用學習算法了。 |
參考文獻:
1. Object Detection with discriminatively Trained Part Based Models
2. Object Detection with Grammar Models
3. Cascade Object Detection with Deformable Part Models
4. Object Detection Grammars