1.? 概述
CascadeClassifier為OpenCV中cv namespace下用來做目標(biāo)檢測的級聯(lián)分類器的一個(gè)類哩牍。該類中封裝的目標(biāo)檢測機(jī)制,簡而言之是滑動窗口機(jī)制+級聯(lián)分類器的方式儡率。OpenCV的早期版本中僅支持haar特征的目標(biāo)檢測挂据,分別在2.2和2.4.0(包含)之后開始支持LBP和HOG特征的目標(biāo)檢測。
2.? 支持的特征
對于Haar儿普、LBP和HOG崎逃,CascadeClassifier都有自己想對他們說的話:
1)? Haar:因?yàn)橹皬腛penCV1.0以來,一直都是只有用haar特征的級聯(lián)分類器訓(xùn)練和檢測(當(dāng)時(shí)的檢測函數(shù)稱為cvHaarDetectObjects眉孩,訓(xùn)練得到的也是特征和node放在一起的xml)个绍,所以在之后當(dāng)CascadeClassifier出現(xiàn)并統(tǒng)一三種特征到同一種機(jī)制和數(shù)據(jù)結(jié)構(gòu)下時(shí),沒有放棄原來的C代碼編寫的haar檢測浪汪,仍保留了原來的檢測部分巴柿。另外,Haar在檢測中無論是特征計(jì)算環(huán)節(jié)還是判斷環(huán)節(jié)都是三種特征中最簡潔的吟宦,但是筆者的經(jīng)驗(yàn)中他的訓(xùn)練環(huán)節(jié)卻往往是耗時(shí)最長的篮洁。
2)? LBP:LBP在2.2中作為人臉檢測的一種方法和Haar并列出現(xiàn),他的單個(gè)點(diǎn)的檢測方法(將在下面看到具體討論)是三者中較為復(fù)雜的一個(gè)殃姓,所以當(dāng)檢測的點(diǎn)數(shù)相同時(shí)袁波,如果不考慮特征計(jì)算時(shí)間,僅計(jì)算判斷環(huán)節(jié)蜗侈,他的時(shí)間是最長的篷牌。
3)? HOG:在2.4.0中才開始出現(xiàn)在該類中的HOG檢測,其實(shí)并不是OpenCV的新生力量踏幻,因?yàn)樵谳^早的版本中HOG特征已經(jīng)開始作為單獨(dú)的行人檢測模塊出現(xiàn)枷颊。比較起來,雖然HOG在行人檢測和這里的檢測中同樣是滑窗機(jī)制该面,但是一個(gè)是級聯(lián)adaboost夭苗,另一個(gè)是SVM;而且HOG特征為了加入CascadeClassifier支持的特征行列改變了自身的特征計(jì)算方式:不再有相鄰cell之間的影響隔缀,并且采用在Haar和LBP上都可行的積分圖計(jì)算题造,放棄了曾經(jīng)的HOGCache方式,雖然后者的加速性能遠(yuǎn)高于前者猾瘸,而簡單的HOG特征也使得他的分類效果有所下降(如果用SVM分類器對相同樣本產(chǎn)生的兩種HOG特征做分類界赔,沒有了相鄰cell影響的計(jì)算方式下的HOG特征不那么容易完成分類)翘簇。這些是HOG為了加入CascadeClassifier而做出的犧牲设捐,不過你肯定也想得到OpenCV保留了原有的HOG計(jì)算和檢測機(jī)制。另外杖们,HOG在特征計(jì)算環(huán)節(jié)是最耗時(shí)的揽思,但他的判斷環(huán)節(jié)和Haar一樣的簡潔。
關(guān)于三種特征在三個(gè)環(huán)節(jié)的耗時(shí)有下表:
3.? 內(nèi)部結(jié)構(gòu)
從CascadeClassifier開始钉汗,以功能和結(jié)構(gòu)來整體介紹內(nèi)部的組織和功能劃分锡宋。
CascadeClassifer中的數(shù)據(jù)結(jié)構(gòu)包括Data和FeatureEvaluator兩個(gè)主要部分特恬。Data中存儲的是從訓(xùn)練獲得的xml文件中載入的分類器數(shù)據(jù)徐钠;而FeatureEvaluator中是關(guān)于特征的載入、存儲和計(jì)算显拜。在此之外還有檢測框架的邏輯部分,是在Data和Featureevaluator之上的一層邏輯远荠。
3.1Data結(jié)構(gòu)
先來看Data的結(jié)構(gòu),如下圖:
首先失息,在Data中存儲著一系列的DTreeNode譬淳,該結(jié)構(gòu)體中記錄的是一個(gè)弱分類器。其中盹兢,feature ID表明它計(jì)算的是怎樣的一個(gè)特征邻梆,threshold1是它的閾值,據(jù)此判斷某個(gè)特征值應(yīng)當(dāng)屬于left還是right绎秒,后面的left leafvalue和right leaf value是左右葉節(jié)點(diǎn)的值浦妄。這里需要結(jié)合Stage的判斷環(huán)節(jié)來理解:
假設(shè)某個(gè)stage(也就是一個(gè)強(qiáng)分類器)中包含有num個(gè)弱分類器(也就是num個(gè)DTreeNode),按照下面的過程計(jì)算stage對某個(gè)采樣圖像im的結(jié)果见芹。
1)? 初始化sum = 0
2)? for i = 1:num
計(jì)算
if f > threshold1
? sum = sum + leftVal
else
? sum = sum + rightVal
end
3)if sum > threshold2
? output = 1
else
? output = 0
? ? ? 其中剂娄,ID、threshold1玄呛、leftVal和rightVal是第i個(gè)弱分類器中的變量阅懦,featureExtract表示對im提取第ID個(gè)特征值,是該強(qiáng)分類器中的閾值把鉴,當(dāng)結(jié)果為正時(shí)故黑,輸出output=1,否則為0.
另外庭砍,可以從上圖看到stage結(jié)構(gòu)中僅僅保存了第一個(gè)弱分類器的下標(biāo)first场晶、弱分類器數(shù)量num和自身的閾值threshold2,所有弱分類器或者說所有節(jié)點(diǎn)都是連續(xù)存儲在一個(gè)vector內(nèi)的怠缸。
3.2 FeatureEvaluator
如果Data結(jié)構(gòu)主要是在載入時(shí)保存分類器內(nèi)部的數(shù)據(jù)诗轻,F(xiàn)eatureEvaluator則是負(fù)責(zé)特征計(jì)算環(huán)節(jié)。這是一個(gè)基類揭北,在此之上衍生了HaarEvaluator扳炬、LBPEvaluator和HOGEvaluator三種特征各自的特征計(jì)算結(jié)構(gòu)吏颖。每個(gè)Evaluator中都保存了一個(gè)vector,這是在read環(huán)節(jié)中從分類器中載入的特征池(feature pool)半醉,前面提到的feature ID對應(yīng)的就是在這個(gè)vector內(nèi)的下標(biāo)缩多。三種Evaluator中的Feature定義有所不同衬吆,因?yàn)橛?jì)算特征所需的信息不同逊抡,具體如下:
Haar——Feature中保存的是3個(gè)包含權(quán)重的rect冒嫡,如果要計(jì)算下圖的特征灯谣,
對應(yīng)的rect為[(R2,-3),(R1,1)]和[(R1,1),(R2,-1)]胎许。這里的R1對應(yīng)于上圖中的紅色矩形辜窑,R2對應(yīng)綠色矩形穆碎,圓括號內(nèi)的第二個(gè)值為對應(yīng)的權(quán)重所禀。所有Haar特征的描述只需要至多3個(gè)加權(quán)矩形即可描述色徘,所以HaarEvaluator的Feature中保存的是三個(gè)加權(quán)矩形褂策;
LBP——Feature中僅保存一個(gè)rect耿焊,這里需要指出的是罗侯,LBP特征計(jì)算的不是一個(gè)3x3大小的區(qū)域中每個(gè)點(diǎn)與中心點(diǎn)的大小關(guān)系歇父,而是一個(gè)3x3個(gè)相同大小的矩形區(qū)域之間的對比關(guān)系,這也是為什么LBP特征計(jì)算過程也用到積分圖方法的原因翎冲。如下圖所示抗悍,
Feature中保存的就是紅色的矩形位置缴渊,而我們要先提取上圖中9個(gè)矩形內(nèi)的所有像素點(diǎn)的和衔沼,然后比較外圍8個(gè)矩形內(nèi)的值和中間矩形內(nèi)的值的關(guān)系指蚁,從而得到LBP特征凝化。
HOG——與LBP中類似搓劫,F(xiàn)eature中同樣僅一個(gè)rect枪向,HOG特征是在2x2個(gè)rect大小的范圍內(nèi)提取出的遣疯,也就是說給出的rect是HOG計(jì)算過程中4個(gè)block里的左上角的block缠犀。
除此之外辨液,Evaluator中還有另外一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu)——數(shù)據(jù)指針滔迈。這個(gè)結(jié)構(gòu)在三種Evaluator中同樣不同燎悍,但他們所指向的都是積分圖中的一個(gè)值谈山。在Haar和LBP中是先計(jì)算一個(gè)整圖的積分圖奏路,而HOG中則是計(jì)算梯度方向和梯度幅值鸽粉,然后按照梯度方向劃分的區(qū)間將梯度幅值圖映射成n個(gè)積分圖帚戳。每個(gè)特征的計(jì)算過程中要維護(hù)一系列指向積分圖中的指針销斟,通過訪問積分圖快速計(jì)算某個(gè)矩形內(nèi)的像素值的和蚂踊,從而加速特征計(jì)算環(huán)節(jié)犁钟。這里暫不詳細(xì)展開涝动。
3.3 檢測框架邏輯
這里的檢測框架簡而言之就是一個(gè)多尺度縮放+滑動窗口遍歷搜索的框架醋粟。在CascadeClassifier中包含detectMultiScale和detectSingleScale成員函數(shù)米愿,分別對應(yīng)多尺度和單尺度檢測育苟,其中多尺度檢測中會調(diào)用單尺度的方法违柏。
分類器僅能夠?qū)δ骋还潭╯ize的采樣圖像做判斷漱竖,給出當(dāng)前的采樣圖像是否為真實(shí)目標(biāo)的“非正即負(fù)”的結(jié)果(size是由訓(xùn)練數(shù)據(jù)決定的)闲孤。要找到某個(gè)圖像中的目標(biāo)位置讼积,就要以size大小的采樣窗口對圖像逐行逐列地掃描勤众,然后對每個(gè)采樣圖像判斷是否為正们颜,將結(jié)果以矩形位置保存下來就獲得了目標(biāo)的位置窥突。但是這僅僅是單尺度檢測阻问,也就是說称近,一個(gè)以40x40大小訓(xùn)練數(shù)據(jù)訓(xùn)練獲得的分類器只能檢測當(dāng)前圖像里40x40大小的目標(biāo)刨秆,要檢測80x80大小的目標(biāo)該如何做呢衡未?可以把原圖像縮放到原來的1/2如失,這樣原圖中80x80大小的目標(biāo)就變成40x40了岖常,再做一次上面的掃描檢測過程竭鞍,并且將得到的矩形換算到原圖中對應(yīng)的位置偎快,從而檢測到了80x80大小的目標(biāo)晒夹。實(shí)際上丐怯,我們每次對原圖進(jìn)行固定步長的縮放读跷,形成一個(gè)圖像金字塔效览,對圖像金字塔的每一層都掃描檢測丐枉,這就是多尺度檢測的框架瘦锹。
4.? 模塊功能
CascadeClassifier的使用中只要調(diào)用兩個(gè)外部接口沼本,一個(gè)是read抽兆,另一個(gè)是detectMultiScale辫红。
4.1? CascadeClassifier::read
4.1.1? 分類器的XML形式
read的過程就是對類的成員變量進(jìn)行初始化的過程贴妻,經(jīng)過這一步名惩,Data結(jié)構(gòu)按照之前已經(jīng)討論的邏輯被填充娩鹉。
先來看一下一個(gè)分類器的xml文件是怎樣組織的弯予。
整體上它包括stageType受楼、featureType艳汽、height骚灸、width、stageParams义郑、featureParams非驮、stages劫笙、features幾個(gè)節(jié)點(diǎn)填大。
這里的參數(shù)內(nèi)容就不展開了允华,主要來看一下stage結(jié)構(gòu)和feature在xml里是怎樣保存的,這樣訓(xùn)練結(jié)束后你可以自己打開這個(gè)文件看一下就明白訓(xùn)練了一個(gè)什么分類器出來了召耘。
下面是一個(gè)stage的內(nèi)部結(jié)構(gòu)污它,maxWeakCount是stage包含的弱分類器個(gè)數(shù)衫贬,stageThreshold是該stage的閾值祥山,也就是上面我們提到過的缝呕。接下來就是5個(gè)弱分類器了供常,每個(gè)弱分類器中包括internalNodes和leafValues兩個(gè)節(jié)點(diǎn)栈暇。前者分別是left和right標(biāo)記源祈、feature ID和threshold1香缺。
這里可以解釋一下featureID到底是指在哪里的ID了图张。下圖是分類器中的features節(jié)點(diǎn)中保存的該分類器使用到的各種特征值祸轮,feature ID就是在這些中的ID适袜,就是在這些之中的順序位置痪蝇。圖中的特征是一個(gè)HOG特征躏啰,rect節(jié)點(diǎn)中的前四個(gè)數(shù)字代表我們提到的矩形给僵,而最后的1表示要提取的特征值是block中提取的36維向量中的哪一個(gè)帝际。當(dāng)然蹲诀,Haar和LBP特征的feature節(jié)點(diǎn)與此不同脯爪,不過也是類似的結(jié)構(gòu)痕慢。
4.1.2? 讀取的過程
清楚了分類器的xml形式之后快骗,就要從文件中讀取內(nèi)容至cascadeClassifier中了方篮」。可以把這部分分為Data的讀取和features的讀取兩部分。
bool CascadeClassifier::read(constFileNode& root)
{
? if( !data.read(root) )//Data的讀取
? ? ? return false;
featureEvaluator = FeatureEvaluator::create(data.featureType);
? FileNode fn= root[CC_FEATURES];
? if( fn.empty() )
? ? ? return false;
? return featureEvaluator->read(fn);//features的讀取
}
4.1.2.1? Data的讀取
先來看看Data的讀取裕照,這里以HOG特征的分類器為例晋南,并且跳過stage的參數(shù)讀取部分负间,直接來看如何在Data中建立stage結(jié)構(gòu)的。
// load stages
? ? fn = root[CC_STAGES];
? ? if( fn.empty() )
? ? ? ? return false;
? ? stages.reserve(fn.size());//先給vector分配空間出來
? ? classifiers.clear();
? ? nodes.clear();
? ? FileNodeIteratorit =fn.begin(),it_end= fn.end();
? ? for( int si = 0; it != it_end; si++, ++it )//遍歷stages
{
? ? //進(jìn)入單個(gè)stage中
? ? ? ? FileNodefns = *it;
? ? ? Stagestage;//stage結(jié)構(gòu)中包含threshold趾访、ntrees和first三個(gè)變量
? ? ? ? stage.threshold = (float)fns[CC_STAGE_THRESHOLD]-THRESHOLD_EPS;
? ? ? ? fns= fns[CC_WEAK_CLASSIFIERS];
? ? ? ? if(fns.empty())
returnfalse;
? ? ? ? stage.ntrees = (int)fns.size();
? ? ? ? stage.first = (int)classifiers.size();//ntrees和first指出該stage中包含的樹的數(shù)目和起始位置
? ? ? ? stages.push_back(stage);//stage被保存在stage的vector(也就是stages)中
? ? ? ? classifiers.reserve(stages[si].first +stages[si].ntrees);//相應(yīng)地?cái)U(kuò)展classifiers的空間扼鞋,它存儲的是這些stage中的weak classifiers云头,也就是weak trees
? ? ? ? FileNodeIteratorit1 =fns.begin(),it1_end= fns.end();//遍歷weak classifier
? ? ? ? for( ; it1 != it1_end;++it1 )// weaktrees
? ? ? ? {
? ? ? ? ? ? FileNodefnw = *it1;
? ? ? ? ? ? FileNodeinternalNodes =fnw[CC_INTERNAL_NODES];
? ? ? ? ? ? FileNodeleafValues =fnw[CC_LEAF_VALUES];
? ? ? ? ? ? if(internalNodes.empty()||leafValues.empty())
? ? ? ? ? ? ? ? returnfalse;
? ? ? ? ? ? DTreetree;
? ? ? ? ? ? tree.nodeCount = (int)internalNodes.size()/nodeStep;//一個(gè)節(jié)點(diǎn)包含nodeStep個(gè)值溃槐,計(jì)算得到當(dāng)前的弱分類器中包含幾個(gè)節(jié)點(diǎn)昏滴,無論在哪種特征的分類器中這個(gè)值其實(shí)都可以默認(rèn)為1
? ? ? ? ? ? classifiers.push_back(tree);//一個(gè)弱分類器或者說一個(gè)weak tree中只包含一個(gè)int變量影涉,用它在classifiers中的位置和自身來指出它所包含的node個(gè)數(shù)
? ? ? ? ? ? nodes.reserve(nodes.size() +tree.nodeCount);
? ? ? ? ? ? leaves.reserve(leaves.size() +leafValues.size());//擴(kuò)展存儲node和leaves的vector結(jié)構(gòu)空間
? ? ? ? ? ? if(subsetSize > 0 )//關(guān)于subsetSize的內(nèi)容都是只在LBP分類器中使用
? ? ? ? ? ? ? ? subsets.reserve(subsets.size() +tree.nodeCount*subsetSize);
? ? ? ? ? ? FileNodeIteratorinternalNodesIter =internalNodes.begin(),internalNodesEnd= internalNodes.end();
//開始訪問節(jié)點(diǎn)內(nèi)部
? ? ? ? ? ? for(; internalNodesIter != internalNodesEnd; )//nodes
? ? ? ? ? ? {
? ? ? ? ? ? ? ? DTreeNodenode;//一個(gè)node中包含left蟹倾、right、threshold和featureIdx四個(gè)變量培慌。其中l(wèi)eft和right是其對應(yīng)的代號吵护,left=0馅而,right=-1瓮恭;featureIdx指的是整個(gè)分類器中使用的特征池中某個(gè)特征的ID屯蹦,比如共有108個(gè)特征绳姨,那么featureIdx就在0~107之間飘庄;threshold是上面提到的竭宰。同時(shí)可以看到這里的HOG分類器中每個(gè)弱分類器僅包含一個(gè)node切揭,也就是僅對某一個(gè)特征做判斷廓旬,而不是多個(gè)特征的集合
? ? ? ? ? ? ? ? node.left = (int)*internalNodesIter; ++internalNodesIter;
? ? ? ? ? ? ? ? node.right = (int)*internalNodesIter; ++internalNodesIter;
? ? ? ? ? ? ? ? node.featureIdx = (int)*internalNodesIter; ++internalNodesIter;
? ? ? ? ? ? ? ? if(subsetSize > 0 )
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? for(intj = 0; j < subsetSize;j++, ++internalNodesIter)
? ? ? ? ? ? ? ? ? ? ? ? subsets.push_back((int)*internalNodesIter);
? ? ? ? ? ? ? ? ? ? node.threshold = 0.f;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? node.threshold = (float)*internalNodesIter; ++internalNodesIter;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? nodes.push_back(node);//得到的node將保存在它的vector結(jié)構(gòu)nodes中
? ? ? ? ? ? }
? ? ? ? ? ? internalNodesIter=leafValues.begin(),internalNodesEnd =leafValues.end();
? ? ? ? ? ? for(; internalNodesIter != internalNodesEnd; ++internalNodesIter)// leaves
? ? ? ? ? ? ? ? leaves.push_back((float)*internalNodesIter);//leaves中保存相應(yīng)每個(gè)node的left leaf和right leaf的值,因?yàn)槊總€(gè)weak tree只有一個(gè)node也就分別只有一個(gè)left leaf和right leaf励背,這些將保存在leaves中
? ? ? ? }
? ? }
通過stage樹的建立可以看出最終是獲取stages叶眉、classifiers衅疙、nodes和leaves四個(gè)vector變量饱溢。其中的nodes和leaves共同組成一系列有序節(jié)點(diǎn)走芋,而classifiers中的變量則是在這些節(jié)點(diǎn)中查詢來構(gòu)成一個(gè)由弱分類器組翁逞,它僅僅是把這些弱分類器組合在一起熄攘,最后stages中每一個(gè)stage也就是一個(gè)強(qiáng)分類器挪圾,它在classifiers中查詢得到自己所屬的弱分類器都有哪些哲思,從而構(gòu)成一個(gè)強(qiáng)分類器的基礎(chǔ)。
4.1.2.2? features的讀取
特征的讀取最終將保留在featureEvaluator中的vector中帝簇。所以先來看一下Feature的定義,仍舊以HOG特征為例:
struct Feature
? ? {
? ? ? ? Feature();
? ? ? ? float calc( int offset )const;
? ? ? ? void updatePtrs( const vector&_hist,constMat &_normSum);
? ? ? ? bool read( const FileNode&node);
enum { CELL_NUM = 4, BIN_NUM= 9 };
Rectrect[CELL_NUM];
int featComponent; //componentindex from 0 to 35
const float* pF[4]; //for feature calculation
const float* pN[4]; //for normalization calculation
};
其中的CELL_NUM和BIN_NUM分別是HOG特征提取的過程中block內(nèi)cell個(gè)數(shù)和梯度方向劃分的區(qū)間個(gè)數(shù)芋浮。也就是說纸巷,在一個(gè)block內(nèi)將提取出CELL_NUM*BIN_NUM維度的HOG特征向量瘤旨。rect[CELL_NUM]保存的是block的四個(gè)矩形位置存哲,featComponent表明該特征是36維HOG特征中的哪一個(gè)值宏胯。而之后的pF與pN是重點(diǎn):首先我們假設(shè)featComponent=10肩袍,那就是說要提取的特征值是該rect描述的block內(nèi)提取的HOG特征的第10個(gè)值氛赐,而第一個(gè)cell中會產(chǎn)生9個(gè)值,那么第10個(gè)值就是第二個(gè)cell中的第一個(gè)值蒋川。通過原圖計(jì)算梯度和按照區(qū)間劃分的梯度積分圖之后,共產(chǎn)生9個(gè)積分圖夕冲,那么pF應(yīng)當(dāng)指向第1個(gè)積分圖內(nèi)rect描述的block內(nèi)的第二個(gè)cell矩形位置的四個(gè)點(diǎn)歹鱼。
? ? ? ? 在featureEvaluator的read中弥姻,將對所有features遍歷填充到vector中。
在下面的代碼中只是讀取了參數(shù)螺捐,并沒有更新pF和pN指針定血,因?yàn)槲覀冞€沒有獲得梯度積分圖澜沟。
bool HOGEvaluator::Feature :: read(const FileNode&node )
{
? ? FileNodernode =node[CC_RECT];//rect節(jié)點(diǎn)下包括一個(gè)矩形和一個(gè)特征類型號featComponent
? ? FileNodeIteratorit =rnode.begin();
? ? it>> rect[0].x>> rect[0].y>> rect[0].width>> rect[0].height>> featComponent;//featComponent范圍在[0,35]茫虽,36類特征中的一個(gè)
? ? rect[1].x =rect[0].x +rect[0].width;
? ? rect[1].y =rect[0].y;
? ? rect[2].x =rect[0].x;
? ? rect[2].y =rect[0].y +rect[0].height;
? ? rect[3].x =rect[0].x +rect[0].width;
? ? rect[3].y =rect[0].y +rect[0].height;
? ? rect[1].width =rect[2].width =rect[3].width =rect[0].width;
rect[1].height =rect[2].height =rect[3].height =rect[0].height;
//xml中的rect存儲的矩形信息與4個(gè)矩形之間的關(guān)系如下圖4所示
? ? return true;
}
4.2? CascadeClassifier::detectMultiScale
這里的代碼的偽碼可以簡單寫成如下:
vector results;
for( doublefactor = 1; ;factor*= scaleFact;
{
MatscaledImage(scaledImageSize,CV_8U,imageBuffer.data);
? ? resize( grayImage,scaledImage,scaledImageSize,0, 0, CV_INTER_LINEAR );
? ? ? detectSingleScale( scaledImage,results );
}
oupRectangles( results );
簡單來說号杏,多尺度檢測只是尺度縮放形成圖像金字塔然后在每個(gè)尺度上檢測之后將結(jié)果進(jìn)行合并的過程。
在detectSingleScale中斯棒,使用OpenCV中的并行計(jì)算機(jī)制盾致,以CascadeClassifierInvoker類對整圖掃描檢測。detectSingleScale的檢測過程仍以偽碼表達(dá)如下:
// detectSingleScale
featureEvaluator->setImage(image,data.origWinSize )
//CascadeClassifierInvoker
for( int y = 0; y
for(int x = 0; x
{
doublegypWeight;
int result=classifier->runAt(evaluator,Point(x, y), gypWeight);
results.push_back(R(x,y,W,H,scale));// R(x,y,W,H,scale)表示在scale尺度下檢測到的矩形(x,y,W,H)映射到原圖上時(shí)的矩形
}
可以看到上面的代碼中最重要的兩部分分別是setImage和runAt荣暮。
4.2.1? setImage
前面提到過庭惜,features的read部分僅僅把特征的參數(shù)讀取進(jìn)入vector中,并沒有對指針們初始化穗酥,這正是setImage要做的工作护赊。仍以HOG為例惠遏,setImage的偽碼如下:
vector hist;
Mat? ? norm;
integralHistogram( image,hist, norm );
for( featIdx= 0;featIdx < featCount;featIdx++ )
{
? ? featuresPtr[featIdx].updatePtrs( hist, norm );
}
integralHistogram的過程如下:首先計(jì)算image每個(gè)像素點(diǎn)的梯度幅值和梯度方向骏啰,梯度方向的區(qū)間為0~360°厨内,劃分為9個(gè)區(qū)間,按照梯度方向所屬區(qū)間統(tǒng)計(jì)每個(gè)區(qū)間內(nèi)image的梯度幅值的積分圖方仿。也就是說委粉,對于hist中的第一個(gè)Mat來說衷畦,先把所有梯度方向在0~40°之外的像素點(diǎn)的幅值置為0,然后計(jì)算梯度幅值圖的積分圖,保存為hist[0]球匕;第二個(gè)Mat對應(yīng)40~80°的區(qū)間……這樣照卦,得到一個(gè)包含9個(gè)Mat的hist瞬痘,而norm則是9個(gè)Mat對應(yīng)像素點(diǎn)的和。
接下來就是要根據(jù)hist和norm來更新每個(gè)Feature中的指針了,因?yàn)槲覀円呀?jīng)知道自己要計(jì)算的是一個(gè)在什么位置上的矩形、在那個(gè)區(qū)間上的特征,所以只要把指針更新到hist中的那個(gè)Mat上即可睁宰。注意较木,這里并沒有涉及到滑動窗口機(jī)制。
這樣在計(jì)算某個(gè)HOG特征值時(shí)虹蒋,我們只要計(jì)算下面的式子即可:
HOG(i) = (pF[0]+pF[3]-pF[1]-pF[2] )/( pN[0]+pN[3]-pN[1]-pN[2] )
4.2.2 runAt
runAt函數(shù)調(diào)用了其他方法,但它的偽碼可以如下:
? ? ? ? setWindow( hist, cvPoint(x,y) );
for(intstageIdx= 0; stageIdx
{
stage= cascadeStages[stageIdx];//當(dāng)前stage
sum= 0.0;
int ntrees = stage.ntrees;
for( int i = 0; i < ntrees; i++, nodeOfs++,leafOfs+= 2 )
{
node= cascadeNodes[nodeOfs];//當(dāng)前node
doublevalue =featureEvaluator(node.featureIdx);//計(jì)算vector中的第featureIdx個(gè)特征的值
sum+= cascadeLeaves[ value< node.threshold? leafOfs : leafOfs+ 1 ];//根據(jù)node中的threshold得到左葉子或者右葉子的值,加到該stage中的總和
}
if( sum < stage.threshold )//如果總和大于stage的threshold則通過喻粹,小于則退出查乒,并返回當(dāng)前stage的相反數(shù)
return-stageIdx;
}
setWindow是根據(jù)當(dāng)前的位置(x,y)計(jì)算Feature中的指針應(yīng)當(dāng)在積分圖上的偏移量蓖议,可以看到這里才是滑動窗口機(jī)制實(shí)現(xiàn)的真正部分修然,而不是在setImage中中贝,setImage只是給出各個(gè)特征對應(yīng)的指針相對位置老厌,而不是真實(shí)位置。
后面在stage和node中的遍歷和檢測薇溃,正是體現(xiàn)弱分類器邑时、強(qiáng)分類器和級聯(lián)分類器的概念。當(dāng)stage中有一個(gè)不滿足時(shí)铜靶,立即退出不再進(jìn)入下一級哩掺,這是級聯(lián)分類器的概念炒刁;弱分類器的判定僅僅給出一個(gè)分?jǐn)?shù)疾瓮,若干個(gè)弱分類器的分?jǐn)?shù)的和作為強(qiáng)分類器的判定依據(jù)盈匾,這是強(qiáng)弱分類器的概念启昧。
4.3 LBP的判定
這里的HOG的例子追城,與Haar很相似冠胯,只是特征計(jì)算環(huán)節(jié)有所不同,在判定環(huán)節(jié)都是根據(jù)某個(gè)閾值來判斷京髓,但是LBP除了在特征計(jì)算環(huán)節(jié)不同以外,在判定環(huán)節(jié)也大不相同蒋困。訓(xùn)練獲得的LBP分類器的node中包含8個(gè)數(shù)存儲在subset中,與node的存儲很類似乾吻。然后在判定階段按照下式
t = subset[c>>5]& (1 << (c & 31))
其中c是提取得到的LBP特征值。當(dāng)t為0時(shí)鹊奖,結(jié)果為左葉,為1時(shí),結(jié)果為右葉重贺。
4.4 groupRectangles
最終的結(jié)果由于在多尺度上獲得,因而矩形之間難免有重合踏志、重疊和包含的關(guān)系,由于縮放尺度可能相對于目標(biāo)大小比較小嗦玖,導(dǎo)致同一個(gè)目標(biāo)在多個(gè)尺度上被檢測出來绘雁,所以有必要進(jìn)行合并。OpenCV的合并規(guī)則中有按照權(quán)重合并的禽拔,也有以MeanShift方法合并的,最簡單的一種是直接按照位置和大小關(guān)系合并秋忙。首先將所有矩形按照大小位置合并成不同的類別,然后將同一類別中的矩形合并成同一個(gè)矩形报强,當(dāng)不滿足給出的閾值條件時(shí),該矩形不會被保存下來格仲。這一部分不是檢測的核心豹芯,不做詳細(xì)討論。
5.? 保留的Haar支持
之前已經(jīng)說過我碟,老版本的OpenCV中只支持Haar特征的分類器訓(xùn)練和檢測矫俺,所以有大量性能表現(xiàn)優(yōu)秀的老版本Haar分類器已經(jīng)訓(xùn)練獲得包斑,如果新版本的CascadeClassifier不能支持這些分類器,那是非常遺憾的事揭糕。所以CascadeClassifier中在做新版本分類器載入之后氢拥,如果失敗將會按照老版本的分類器再做一次載入,并且保存到odlCascade指針中锨侯。在做檢測時(shí)嫩海,如果oldCascade不為空,則按照老版本的Haar分類器做檢測囚痴。這個(gè)過程完全是以C風(fēng)格的代碼完成叁怪,是對OpenCV2.2之前版本的繼承。