黑窗口检测wamp的命令_OpenCV AdaBoost + Haar目标检测技术内幕(上)
很多使用過OpenCV的小伙伴都見過如下代碼。這段看似簡單的代碼,通過讀入一個神奇的XML文件,能夠找到圖像中所有人臉,是不是非常神奇?這其實就是一個Adaboost級聯(lián)分類器的經(jīng)典實現(xiàn);但是當你希望深入代碼學(xué)習(xí)原理時,估計會被代碼復(fù)雜度嚇到。
#include代碼結(jié)果圖0-1這樣:
圖0-1希望這篇文章能幫助你了解背后的所有原理,不再迷茫。文章很長,做好心理準備。
特別聲明一下,本文成文于2015年,講解的是OpenCV2.4.11的實現(xiàn)方式。由于OpenCV版本更新,不排除有一些細節(jié)變化,請讀者注意自行區(qū)分。
盡信書不如無書!
1 Haar特征
在OpenCV接口中,實現(xiàn)了Haar/LBP/HOG等多種特征,本文以Haar特征為例介紹。
1.1 Haar特征的生成
Haar特征最先由Paul Viola等人提出,后經(jīng)過Rainer Lienhart等擴展引入45°傾斜特征,成為現(xiàn)在OpenCV所使用的的樣子。圖1-1展示了目前OpenCV(2.4.11版本)所使用的共計14種Haar特征,包括5種Basic特征、3種Core特征和6種Titled(即45°旋轉(zhuǎn))特征。
圖1-1 OpenCV中使用的的Haar特征在實際中,Haar特征可以在檢測窗口中由放大+平移產(chǎn)生一系列子特征,但是白:黑區(qū)域面積比始終保持不變。
如圖1-2,以x3特征為例,在放大+平移過程中白:黑:白面積比始終是1:1:1。首先在紅框所示的檢測窗口中生成大小為3個像素的最小x3特征;之后分別沿著x和y平移產(chǎn)生了在檢測窗口中不同位置的大量最小3像素x3特征;然后把最小x3特征分別沿著x和y放大,再平移,又產(chǎn)生了一系列大一點x3特征;然后繼續(xù)放大+平移,重復(fù)此過程,直到放大后的x3和檢測窗口一樣大。這樣x3就產(chǎn)生了完整的x3系列特征。
圖1-2 x3特征平移+放大產(chǎn)生一系列子特征示意圖那么這些通過放大+平移的獲得的子特征到底總共有多少個?Rainer Lienhart在他的論文中給出了完美的解釋:假設(shè)檢測窗口大小為W*H,矩形特征大小為w*h,X和Y為表示矩形特征在水平和垂直方向的能放大的最大比例系數(shù):
圖1-3 特征數(shù)量計算示意圖則如圖1-3,在檢測窗口Window中,一般矩形特征(upright rectangle)的數(shù)量為:
簡單解釋一下,上述公式可以理解為:
那么豎直方向總共有
個特征。考慮到水平和豎直方向縮放是獨立的,所以能得到上述公式。對應(yīng)于之前的x3特征,當x3特征在24*24大小的檢測窗口中時(此時W=H=24,w=3,h=1,X=8,Y=24),一共能產(chǎn)生27600個子特征,除x3外其他一般矩形特征數(shù)量計算方法類似。
1.2 如何計算Haar特征值
看到這里,該明白了大量的Haar特征是如何產(chǎn)生的。當有了大量的Haar特征用于訓(xùn)練和檢測時,接下來的問題是如何計算Haar特征值。按照OpenCV代碼,Haar特征值=整個Haar區(qū)域內(nèi)像素和×權(quán)重 + 黑色區(qū)域內(nèi)像素和×權(quán)重:
這也就是其他文章中提到的所謂“白色區(qū)域像素和減去黑色區(qū)域像素和”,只不過是加權(quán)相加而已。例如:
- 對于x2特征:(黑 + 白) * 1+黑 * (-2) = 白 - 黑;
- 對于Point特征:(黑 + 白) * 1 + 黑 * (-9) = 白 - 8 * 黑。
為什么要設(shè)置這種加權(quán)相減,而不是直接相減?請仔細觀察圖2中的特征,不難發(fā)現(xiàn)x3、y3、point特征黑白面積不相等,而其他特征黑白面積相等。設(shè)置權(quán)值就是為了抵消面積不等帶來的影響,保證所有Haar特征的特征值在灰度分布絕對均勻的圖中為0。
了解了特征值如何計算之后,再來看看不同的特征值的含義是什么。我選取了MIT人臉庫中2706個大小為20*20的人臉正樣本圖像,計算如圖1-4位置的Haar特征值,結(jié)果如圖1-5。
圖1-4 Haar特征位置示意圖圖1-5 圖4的2個Haar特征在MIT人臉樣本中特征值分布圖(左邊特征結(jié)果為紅色,右邊藍色)可以看到,圖1-4中2個不同Haar特征在同一組樣本中具有不同的特征值分布,左邊特征計算出的特征值基本都大于0,而右邊特征的特征值基本均勻分布于0兩側(cè)(分布越均勻?qū)颖镜膮^(qū)分度越小)。所以,正是由于樣本中Haar特征值分布不同,導(dǎo)致了不同Haar特征分類效果不同。顯而易見,對樣本區(qū)分度越大的特征分類效果越好,即紅色曲線對應(yīng)圖1-4中的的左邊Haar特征分類效果好于右邊Haar特征。那么看到這里,應(yīng)該理解了下面2個問題:
1.3 Haar特征如何保存?
對應(yīng)的,在OpenCV XML文件中,每一個Haar特征都被保存在2~3個形如:
<x y width height weight>
的標簽中,其中x和y代表Haar矩形左上角坐標(以檢測窗口左上角為原點),width和height代表矩形的寬和高,而weight則對應(yīng)了上面說的權(quán)重值,例如圖4中的左邊x2類型的Haar特征應(yīng)該為<4 2 12 8 1.0>(整個Haar,權(quán)重1)和<4 2 12 4 -2.0>(黑色區(qū)域,權(quán)重-2)。
1.4 Haar特征值標準化
從上文圖1-5中發(fā)現(xiàn),僅僅一個12*18大小的Haar特征計算出的特征值變化范圍從-2000~+6000,跨度非常大。這種跨度大的特性不利于量化評定特征值,所以需要進行“標準化”,壓縮特征值范圍。假設(shè)當前檢測窗口中的圖像為i(x,y),當前檢測窗口為w*h大小(例如圖6中為20*20大小),OpenCV采用如下方式“標準化”:
- 計算檢測窗口中間部分(w-2)*(h-2)的圖像的灰度值和灰度值平方和:
- 計算平均值:
- 計算標準化因子:
- 標準化特征值:
具體代碼在cascadedetect.cpp中的HaarEvaluator::setImage()函數(shù)中可以看到,關(guān)鍵部分如下:
normrect與代碼對應(yīng)的,如圖中藍色為檢測窗口,紅色為標準化過程中使用到的像素。
圖1-6其實如何標準化并不重要,重要的是檢測和訓(xùn)練時的方法一定要一致,否則可能會由于標準化不同帶來的誤差導(dǎo)致模型無法工作!
1.5 積分圖
以O(shè)penCV自帶的人臉分類器haarcascade_frontalface_alt2.xml為例,其中存儲了超過1000個大小和位置都不相同的Haar特征(XML文件解釋見下節(jié))。在運算中,伴隨著檢測窗口的移動,如何快速計算Haar特征值就成了一個很重要的問題。在設(shè)計Haar+AdaBoost算法時,Paul Viola等人就提出積分圖。
對于灰度圖像中任意一點image(x,y),OpenCV定義其積分圖為sum(x,y)為:
其中第0行和第0列為0:
其中image(x,y)為點(x,y)處的原始灰度圖。這樣就定義了一張類似于數(shù)學(xué)中“積分”的積分圖。如圖1-7,如果要計算D區(qū)域內(nèi)灰度和,只需計算
其中(x1,y1)、(x2,y2)、(x3,y3)和(x4,y4)依次代表圖1中image的1 2 3 4點的圖像坐標。需要說明,在計算D區(qū)域灰度和時sum(x1,y1)深藍色區(qū)域被減去了2次,最后需要補上。顯然可以通過此方法快速計算圖像中任意位置和大小區(qū)域的灰度和,即通過積分圖只需要做有限次操作就能獲得任意位置的Haar特征值
圖1-7 積分圖計算Haar矩形框示意圖1.6 旋轉(zhuǎn)積分圖
為了提高檢測精度,Rainer Lienhart等人首先提出了45°旋轉(zhuǎn)積分圖,如圖1-8。旋轉(zhuǎn)積分圖用于快速計算圖1中的titled_x2和titled_y2等共6種旋轉(zhuǎn)Haar特征。
圖1-8 45°旋轉(zhuǎn)積分圖與一般積分圖類似,OpenCV中45°旋轉(zhuǎn)積分圖同樣采用了“擴邊”方式(即旋轉(zhuǎn)積分圖比原灰度圖多1行和1列,其中第1行和第1列元素為0),對應(yīng)的計算公式為:
其中第一行第一列為0:
有了旋轉(zhuǎn)積分圖如何計算任意位置和大小的45°傾斜長方形區(qū)域的灰度和呢?
設(shè)有如圖9紅色方框大小的灰度圖image,其計算出來的45°旋轉(zhuǎn)灰度圖為titled(第0行和第0列為0),虛線代表image中cv::Rect為<3 1 2 3>區(qū)域。顯然虛線區(qū)域的灰度和為:
應(yīng)該不難理解。
圖1-9在實際中,如果使用旋轉(zhuǎn)特征,則需要多計算一張積分圖。但是旋轉(zhuǎn)特征的效果往往不理想,得不償失,不建議使用。
PS:45°旋轉(zhuǎn)旋轉(zhuǎn)積分圖還有另外一種實現(xiàn)方式,但是OpenCV由于數(shù)據(jù)存儲方式限定,不那樣做。
圖1-102 級聯(lián)分類器結(jié)構(gòu)
了解Haar特征之后,接下來分析級聯(lián)分類器結(jié)構(gòu),主要包括以下2個內(nèi)容:
OpenCV中的Adaboost級聯(lián)分類是樹狀結(jié)構(gòu),如圖2-1,其中每一個stage都代表一級強分類器。當檢測窗口通過所有的強分類器時才被認為是目標,否則拒絕。實際上,不僅強分類器是樹狀結(jié)構(gòu),強分類器中的每一個弱分類器也是樹狀結(jié)構(gòu)。
圖2-1 強分類器和弱分類器示意圖(此圖有誤,應(yīng)是stage1,stage2,...,stageN)這篇文章將結(jié)合OpenCV-2.4.11中自帶的haarcascade_frontalface_alt2.xml文件介紹整個級聯(lián)分類器的結(jié)構(gòu)。需要說明,自從2.4.11版本后所有分類器都被替換成新式XML,所以本文介紹新式XML結(jié)構(gòu)。
2.1 XML的頭部
在了解OpenCV分類器結(jié)構(gòu)之前,先來看看存儲分類器的XML文件中有什么。圖2中注釋了分類器XML文件頭部信息,括號中的參數(shù)為opencv_traincascade.exe訓(xùn)練程序?qū)?yīng)參數(shù)。
圖2-2 分類器XML文件頭部含義其中<features>標簽存儲了所有的Haar特性,在之前1.3節(jié)有講解。
2.2 弱分類器結(jié)構(gòu)
Haar特征和弱分類器之間的關(guān)系很簡單:
一個完整的弱分類器包括:
這些元素共同構(gòu)成了弱分類器,缺一不可。haarcascade_frontalface_alt2.xml的弱分類器Depth=2,包含了2種形式,如圖2-3:
- 左邊形式包含2個Haar特征、1個leftValue、2個rightValue和2個弱分類器閾(t1和t2)
- 右邊形式包括2個Haar特征、2個leftValue、1個rightValue和2個弱分類器閾值
看圖2-3應(yīng)該明白了弱分類器的大致結(jié)構(gòu),接下來我們了解樹狀弱分類器是如何工作的。還是以圖3左邊的形式為例:
即通過上述步驟計算弱分類器輸出值,這與OpenCV的cascadedetect.hpp文件中的predictOrdered()函數(shù)代碼對應(yīng)(這里簡單解釋一下,在OpenCV中所有弱分類器的leftValue和rightValue都依次存儲在一個一維數(shù)組中,代碼中的leafOfs表示當前弱分類器中l(wèi)eftValue和rightValue在該數(shù)組中存儲位置的偏移量,idx表示在偏移量leafOfs基礎(chǔ)上的leftValue和rightValue值的索引,cascadeLeaves[leafOfs - idx]就是該弱分類器的輸出):
do即弱分類器的工作方式:通過計算出的Haar特征值與弱分類器閾值對比,從而選擇最終輸出leftValue和rightValue值中的哪一個。
那么這些Haar特征、leftValue、rightValue和弱分類器閾值t都是如何存儲在xml文件中的?不妨來看haarcascade_frontalface_alt2.xml文件中的第一級的第三個弱分類器,如圖2-4。圖2-4中的弱分類器恰好是圖3中左邊類型,包含了<internalNodes>和<leafValues>兩個標簽。其中<leafValues>標簽中的3個浮點數(shù)由左向右依次是rightValue2、leftValue和rightValue1;而<internalNodes>中有6個整數(shù)和2個浮點數(shù),其中2個浮點數(shù)依次分別是弱分類器閾值t1和t2,剩下的6個整數(shù)容我慢慢分解。
首先來看兩個浮點數(shù)前的整數(shù),即4和5。這兩個整數(shù)用于標示所屬本弱分類器Haar特征存儲在<features>標簽中的位置。比如數(shù)值4表示該弱分類器的haar1特征存儲在xml文件下面<features>標簽中第4個位置,即為:
而<internalNodes>的其他4個整數(shù)1、0和-1、-2則用于控制弱分類器樹的形狀。在運行時,OpenCV會把1賦值給當前的node.left,并把0賦值給node.right(請注意do-while代碼中的條件,只有idx<=0時才停止循環(huán),參考圖3應(yīng)該可以理解這4個整數(shù)的含義)。如此,OpenCV通過這些巧妙的數(shù)值和結(jié)構(gòu),控制了整個分類器的運行。可以看到,每個弱分類器內(nèi)部都是類似于這種樹狀的“串聯(lián)”結(jié)構(gòu),所以我稱其為“串聯(lián)組成的的弱分類器”。
圖2-4 OpenCV弱分類器運行示意圖而Depth=1(如haarcascade_frontalface_alt.xml)類型的弱分類器,結(jié)構(gòu)更加簡單且運行方式對比可知,不在贅述。
圖2-5 Depth=1的stump弱分類器示意圖2.3 強分類器結(jié)構(gòu)
在OpenCV中,強分類器是由多個弱分類器“并列”構(gòu)成,即強分類器中的弱分類器是兩兩相互獨立的。在檢測目標時,每個弱分類器獨立運行并輸出cascadeLeaves[leafOfs - idx]值,然后把當前強分類器中每一個弱分類器的輸出值相加,即:
sum 圖2-6 OpenCV強分類器運行示意圖之后與本級強分類器的stageThreshold閾值對比,當且僅當結(jié)果sum>stageThreshold時,認為當前檢測窗口通過了該級強分類器。當前檢測窗口通過所有強分類器時,才被認為是一個檢測目標。可以看出,強分類器與弱分類器結(jié)構(gòu)不同,是一種類似于“并聯(lián)”的結(jié)構(gòu),我稱其為“并聯(lián)組成的強分類器”。
2.4 如何搜索目標?
還有一個問題:檢測窗口大小固定(例如alt2是20*20像素)的級聯(lián)分類器如何遍歷圖像,以便找到在圖像中大小不同、位置不同的目標?
解決方法如下:為了找到圖像中不同位置的目標,需要逐次移動檢測窗口(窗口中的Haar特征相應(yīng)也隨著移動),這樣就可以遍歷到圖像中的每一個位置;而為了檢測到不同大小的目標,一般有兩種做法:逐步縮小圖像or逐步放大檢測窗口:
新版c++函數(shù)CascadeClassifier::detectMultiScale()只實現(xiàn)了縮小圖像檢測;舊版的c函數(shù)cvHaarDetectObject()同時實現(xiàn)了縮小圖像和放大窗口兩種檢測方式,當函數(shù)參數(shù)flag為CV_HAAR_SCALE_IMAGE時是縮小圖像檢測,默認flag=0時放檢測大窗口檢測。
#define CV_HAAR_SCALE_IMAGE 23 對檢測結(jié)果NMS
考慮這樣的情況:一個被檢測為目標的窗口,其附近窗口也應(yīng)該被檢測到。
圖3-1展示檢測一副含有人臉圖像的結(jié)果,左邊為合并檢測結(jié)果窗口之前的結(jié)果,右邊為合并之后的結(jié)果。所以有必要對重疊的檢測結(jié)果窗口進行合并,同時剔除零散分布的錯誤檢測窗口。該功能其實就是NMS(non-maximum suppression)。
圖3-1 檢測結(jié)果合并窗口前后對比圖3-1 并查集(Union-Set)
在了解如何合并窗口前,先來了解一種數(shù)據(jù)結(jié)構(gòu)——并查集。為了形象的說明并查集,首先來了解一個例子。江湖上存在各種各樣的大俠,他們沒什么正當職業(yè),整天背著劍四處游蕩,碰到其他大俠就大打出手。俗話說“雙拳難敵四手”,這些大俠都會拉幫結(jié)派壯大實力。那么為了辨識每個大俠屬于哪個幫派,就需要每個幫派都推舉一個“老大”。這些大俠只需要知道自己和其他大俠的老大是不是同一個人,就能明白自己和對方是不是一個幫派,從而決定是否動手過招。
圖3-2 江湖大俠關(guān)系圖(箭頭表示上一級)如圖3-2,現(xiàn)在武當和明教分別推舉張三豐和張無忌為老大。當幫派很大時,每個大俠記不住幫派所有人,那么他們只需要記住自己的上一級是誰,一級一級往上問就知道老大是誰了。某日,宋青書和殷梨亭在武當山門遇到,那么宋青書問宋遠橋后得知自己的老大是張三豐,而殷梨亭的老大也是張三豐,那么他倆是同門。反之宋青書遇到陳友諒時,一級一級向上詢問后發(fā)現(xiàn)老大不是一個人,就不是同門。
除此之外,在武林中還需要組建聯(lián)盟擴大幫派勢力。既然楊不悔嫁給了殷梨亭,不妨直接設(shè)張無忌的上級為張三豐,這樣就可以將明教和武當組成一個更大的聯(lián)盟(如圖3-2紅色虛線。需要說明,我們只關(guān)心數(shù)據(jù)的代表,而忽略數(shù)據(jù)內(nèi)部結(jié)構(gòu))。從此以后當宋青書再和楊不悔相遇,一級一級查詢后可以確定是同伙了。但是如果大俠們相遇都像這樣一級一級往上問,查詢路徑很長。所以這種直接連接最上級的方法不是最優(yōu)。
為了解決這個問題,需要壓縮路徑——每個人記住自己最終的老大就行(如宋青書記住自己老大是張三豐,不在去問宋遠橋),基本思路如下:
- 以武當為例,張三豐創(chuàng)建門派(明教也類似)
- 宋遠橋和殷梨亭加入武當派,上級設(shè)置為張三豐
- 宋青書通過與宋遠橋的關(guān)系加入武當派,壓縮路徑后設(shè)置上級為張三豐,同時也設(shè)置其所有原上級的上級為張三豐(由于原上級宋遠橋的上級就是張三豐,沒有變化)。
壓縮完路徑后的武當與明教狀態(tài)圖如下,其中紅色代表壓縮路徑:
圖3-3- 楊不悔通過與殷梨亭的關(guān)系也加入武當派別,壓縮路徑后設(shè)置上級為張三豐,同時設(shè)置原上級張無忌的上級是張三豐。綠色代表此次壓縮路徑。
以后每次在合并中關(guān)系到了誰,就壓縮誰的路徑,同時壓縮誰的所有上級的路徑。此后宋青書和楊不悔的查詢路徑就短了很多。
- 假如某天范右使收徒了,徒弟也要加入聯(lián)盟。在加入的時候,也需要壓縮路徑,設(shè)置徒弟的上級為張三豐;同時設(shè)置徒弟的原上級(范右使和張無忌)的上級為張三豐,如藍色箭頭。由于張無忌的上級就是張三豐,所以沒有改變。這樣,范右使的路徑也得到壓縮。
看完例子之后,一起來看看并查集定義。并查集保持一組不相交的動態(tài)集合S={S1,S2,...,Sk},每個動態(tài)集合Si通過一個代表ai來識別,代表是集合中的某個元素(ai∈Si)。在某些應(yīng)用中,哪一個元素被選為代表是無所謂的,我們只關(guān)心在不修改動態(tài)集合的前提下分別尋找某一集合的代表2次獲得的結(jié)果相同;在另外一些應(yīng)用中,如何選擇集合的代表可能存在預(yù)先說明的規(guī)則,如選擇集合的最大or最小值作為代表。總之,在并查集中,不改變動態(tài)集合S則每個集合Si的代表ai不變。
不妨設(shè)x表示每個結(jié)點,p[x]表示x的父結(jié)點(即上一級,如圖2中p[宋遠橋]==張三豐),rank[x]表示x節(jié)點的秩(即該節(jié)點最長路徑中結(jié)點個數(shù),如圖2中最長路徑為:張三豐-張無忌-楊左使-楊不悔,所以rank[張三豐]==4)。并查集偽代碼如下:
//創(chuàng)建Union-set MAKE-SET(x) 1 p[x] ← x //←號表示賦值 2 rank[x] ← 0//合并x和y,底層壓縮路徑 UNION(x, y) 1 LINK(FIND-SET(x), FIND-SET(y))LINK(x, y) 1 if rank[x] < rank[y] 2 p[x] ← y 3 else 4 p[y] ← x 5 if rank[x]==rank[y] 6 rank[x] = rank[x] + 1FIND-SET(x) 1 if x ≠ p[x] 2 p[x] ← FIND-SET(p[x]) 3 return p[x]其中,MAKE-SET函數(shù)用于在無序數(shù)據(jù)中初始化并查集數(shù)據(jù)結(jié)構(gòu),將每個結(jié)點父結(jié)點設(shè)為其本身;UNION函數(shù)通過調(diào)用LINK和FIND-SET實現(xiàn)帶壓縮路徑的并查集合并;LINK函數(shù)通過秩進行并查集合并;FIND-SET是帶壓縮路徑的尋找結(jié)點代表的函數(shù)。如果還有不明白的地方,建議查閱《算法導(dǎo)論》中的第21章:《用于不相交的數(shù)據(jù)結(jié)構(gòu)》。
3.2 利用并查集合并檢測結(jié)果窗口
為了將并查集利用到合并窗口中,首先要定義窗口相似函數(shù),即當前的兩個窗口是不是“一伙人”。在OpenCV中,圖像中的矩形窗口一般用Rect結(jié)構(gòu)體表示,其包含x,y,width,height共4個成員變量,分別代表窗口的左上角點x坐標、y坐標、寬度和高度。下面代碼定義了窗口相似函數(shù)SimilarRects::operator(),當2個窗口r1和r2位置很接近時返回TRUE,通過SimilarRects::operator()就可以將圖1那些重疊的窗口合并在“一伙人”中。
class定義好窗口相似性函數(shù)后,就可以利用并查集合并窗口函數(shù)了,大致過程如下:
這里只介紹NMS基本算法,代碼請讀者自行查閱OpenCV源碼。不過在算法描述中為了清晰簡潔,使用遞歸實現(xiàn)了整個并查集;但在實際中遞歸需要保存現(xiàn)場并進行壓棧,開銷極大,所以O(shè)penCV使用循環(huán)替代了遞歸。
檢測部分到此為止。接下來介紹訓(xùn)練部分。
白裳丶:OpenCV AdaBoost + Haar目標檢測技術(shù)內(nèi)幕(下)?zhuanlan.zhihu.com總結(jié)
以上是生活随笔為你收集整理的黑窗口检测wamp的命令_OpenCV AdaBoost + Haar目标检测技术内幕(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021最新Spring教程(精简)
- 下一篇: 最新开运网源码,运势测算网站源码