OpenCV原理解讀之HAAR+Adaboost

由於在做人臉檢測的項目,用到了OpenCV的訓練結果中的老分類器,因此將舊分類器的檢測方法進行了總結,加上了一些自己的理解,並轉載了一些較好的文章記錄下來。

文章 http://www.61ic.com/Article/DaVinci/TMS320DM646x/201310/50733.html 解決了以下函數移植到DSP上的一些問題

下面爲HAAR特徵檢測的具體流程: http://blog.csdn.net/nongfu_spring/article/details/38977555

一. 在計算每個窗口的 haar 值時,使用 CvHidHaarClassifierCascade 結構的 casecade ,因此需要以下步驟。

1. 創建 CvHidHaarClassifierCascade 結構對應的 casecade

即申請內存,並填寫 casecade 中相關的頭信息,如有多少個 stage,  每個 stage 下有多少個 tree ,每個 tree 下有多少個 node ,以及相關的閾值等信息。

該操作調用的函數是icvCreateHidHaarClassifierCascade

2. 填寫每個 haar feature 對應的積分圖中矩形框的指針。

包括 casecade 指向的積分圖的指針 sum, 更多的是相應 haar 特徵對應的矩形框指針以及權重。每個 haar 特徵對應着 2 個或者 3 個帶權重的矩形框,分別用 p0,p1,p2,p3 指向每個矩形框的四個頂點在積分圖中的相應位置。

另外,這裏 haar 特徵對應的矩形框是根據窗口大小變化的。如樣本是 20*20 ,某個 haar 特徵對應的某一個矩形框是 4*4 ,當 scanWindow 的窗口放大爲 40*40 時,該矩形框也擴大爲 8*8

所有的矩形框頂點的指針都是基於原圖的積分圖的,當窗口縮放時,其 haar 特徵對應的矩形框的頂點位置也會發生相應的縮放。

該操作調用的函數是cvSetImageForHaarClassifierCascade

二. 有了 CvHidHaarClassifierCascade 結構的 casecade ,就可以計算每個 window 對應的 stage 值了。實際上,在每一個 window 尺寸上創建好 casecade 後,就會計算該 window 大小下所有窗口的 stage 值,保存滿足條件的那些窗口。然後再創建下一個縮放 window 尺寸上的 casecade ,並替換掉上一個尺寸的 casecade ,再計算新 window 大小下所有窗口的 stage 值,繼續保存滿足條件的那些窗口。如此循環,直至檢測窗口小於樣本尺寸,或者檢測窗口大於原圖尺寸。其中計算每個固定尺寸窗口的 stage 值的過程見三中詳述。

三. 計算每一個 window 尺寸上所有窗口的 stage 值。將滿足條件的窗口保存下來。這個過程用 cvRunHaarClassifierCascadeSum 函數判斷,當 cvRunHaarClassifierCascadeSum 返回值大於 0 ,纔會保存此時檢測的窗口位置,作爲備選,參與後面的聚類過程。

cvRunHaarClassifierCascadeSum 函數調用 icvEvalHidHaarClassifier 來計算出每個樹對應節點的 haar 特徵值,然後再和該節點的閾值比較,如果小於閾值選擇左邊分支作爲當前node的結果;否則選擇右值作爲當前node的結果;直至分支的索引小於等於 0 ,此時得到的 alpha 爲該樹的計算結果。

當用 icvEvalHidHaarClassifier 計算所有樹的節點後,再判斷所有樹的累積和(所有樹的 alpha 之和)是否大於 stage 閾值,如果大於閾值則返回 1 ,否則返回負值。返回 1 ,則再進行下一個 stage 計算,直至所有的 stage 計算完畢,並且每個累積和都大於每個 stage 相應的閾值,則 cvRunHaarClassifierCascadeSum 返回 1

從上面可以看出,需要比較兩次一個是node thresh的比較,一個是stage thresh的比較,比較node thresh時選擇left_val或者right_val作爲比較的結果,比較stage thresh時將所有的node結果累加起來,若累加結果大於stage thresh則算作通過比較,標記當前窗口是有效窗口。

這時一個窗口計算完畢,保存此時檢測的窗口位置,作爲備選,參與後面的聚類過程。

然後平移窗口,重複上述步驟,直至窗口移動到圖像的右下邊界。

四.當所有滿足尺寸要求的窗口遍歷完畢,並將滿足條件的窗口保存完畢後,再對保存的窗口進行聚類,和最小鄰域過濾。

cvLoadHaarClassifierCascade

從文件中裝載訓練好的級聯分類器或者從OpenCV中嵌入的分類器數據庫中導入

CvHaarClassifierCascade* cvLoadHaarClassifierCascade(
                         const char* directory,
                         CvSize orig_window_size );

directory: 訓練好的級聯分類器的路徑

orig_window_size: 級聯分類器訓練中採用的檢測目標的尺寸。因爲這個信息沒有在級聯分類器中存儲,所以要單獨指出,但是在haarcascade_frontalface_alt.xml中有指出size的大小。

函數 cvLoadHaarClassifierCascade 用於從文件中裝載訓練好的利用哈爾特徵的級聯分類器,或者從OpenCV中嵌入的分類器數據庫中導入。分類器的訓練可以應用函數haartraining(詳細察看opencv/apps/haartraining),orig_window_size是在訓練分類器時就確定好的,修改它並不能改變檢測的範圍或精度。

需要注意的是,這個函數已經過時了。現在的目標檢測分類器通常存儲在 XML 或 YAML 文件中,而不是通過路徑導入。 從文件中導入分類器,可以使用函數 cvLoad 

cvReleaseHaarClassifierCascade

釋放haar classifier cascade。

void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade );

cascade: 雙指針類型指針指向要釋放的cascade. 指針由函數聲明。

函數 cvReleaseHaarClassifierCascade 釋放cascade的動態內存,其中cascade的動態內存或者是手工創建,或者通過函數 cvLoadHaarClassifierCascade 或 cvLoad分配。

cvHaarDetectObjects

檢測圖像中的目標

typedef struct CvAvgComp
{
  CvRect rect; /* bounding rectangle for the object (average rectangle of a group) */
  int neighbors; /* number of neighbor rectangles in the group */
}
CvAvgComp;

CvSeq* cvHaarDetectObjects( const CvArr* image, CvHaarClassifierCascade* cascade,
              CvMemStorage* storage, double scale_factor=1.1,
              int min_neighbors=3, int flags=0,
              CvSize min_size=cvSize(0,0) );

image: 被檢圖像

cascade: harr 分類器級聯的內部標識形式

storage: 用來存儲檢測到的一序列候選目標矩形框的內存區域。

scale_factor: 在前後兩次相繼的掃描中,搜索窗口的比例係數。例如1.1指將搜索窗口依次擴大10%。

min_neighbors: 構成檢測目標的相鄰矩形的最小個數(缺省-1)。如果組成檢測目標的小矩形的個數和小於min_neighbors-1 都會被排除。如果min_neighbors 爲 0, 則函數不做任何操作就返回所有的被檢候選矩形框,這種設定值一般用在用戶自定義對檢測結果的組合程序上。

flags: 操作方式。當前可以定義的操作方式是 CV_HAAR_DO_CANNY_PRUNING(CANNY邊緣檢測)、CV_HAAR_SCALE_IMAGE(縮放圖像檢測)、CV_HAAR_FIND_BIGGEST_OBJECT(尋找最大的目標)、CV_HAAR_DO_ROUGH_SEARCH(做粗略搜索)

如果 CV_HAAR_DO_CANNY_PRUNING 被設定,函數利用Canny邊緣檢測器來排除一些邊緣很少或者很多的圖像區域,因爲這樣的區域一般不含被檢目標。人臉檢測中通過設定閾值使用了這種方法,並因此提高了檢測速度。當然該標記是在沒有定義 CV_HAAR_SCALE_IMAGE下使用的,也就是說使用縮放檢測窗口的形式定義的

如果 CV_HAAR_SCALE_IMAGE被設定則在每一個scale上縮放圖像檢測,如果沒有定義則縮放檢測窗口進行檢測,當縮放檢測窗口檢測的時候是不能返回rejectLevels和levelWeights的。

如果 CV_HAAR_FIND_BIGGEST_OBJECT被設定,如果沒有設定 CV_HAAR_SCALE_IMAGE,保存當前檢測窗口中面積最大的矩形,不管設定沒有設定 CV_HAAR_SCALE_IMAGE,最後都輸出一個面積最大的矩形(如果檢測結果不爲空的話),詳細分析可以參考 cvHaarDetectObjectsForROC

如果 CV_HAAR_DO_ROUGH_SEARCH設定了,則最小的縮放比例爲0.6,否則爲0.4,僅在縮放檢測窗口中有效

if( findBiggestObject )

flags &= ~(CV_HAAR_SCALE_IMAGE|CV_HAAR_DO_CANNY_PRUNING);

從以上代碼可以看出,尋找最大目標優先於縮放圖像和邊緣檢測,也就是說如果同時定義了以上三項,則以尋找最大目標爲準

其次從代碼結構上縮放圖像在前縮放檢測窗口在後,因此如果定義了縮放圖像,則不進行邊緣檢測

min_size: 檢測窗口的最小尺寸。缺省的情況下被設爲分類器訓練時採用的樣本尺寸(人臉檢測中缺省大小是~20×20)。

函數 cvHaarDetectObjects 使用針對某目標物體訓練的級聯分類器在圖像中找到包含目標物體的矩形區域,並且將這些區域作爲一序列的矩形框返回。函數以不同比例大小的掃描窗口對圖像進行幾次搜索(察看cvSetImagesForHaarClassifierCascade)。 每次都要對圖像中的這些重疊區域利用cvRunHaarClassifierCascade進行檢測。 有時候也會利用某些繼承(heuristics)技術以減少分析的候選區域,例如利用 Canny 裁減 (prunning)方法。 函數在處理和收集到候選的方框(全部通過級聯分類器各層的區域)之後,接着對這些區域進行組合並且返回一系列各個足夠大的組合中的平均矩形。調節程序中的缺省參數(scale_factor=1.1, min_neighbors=3, flags=0)用於對目標進行更精確同時也是耗時較長的進一步檢測。爲了能對視頻圖像進行更快的實時檢測,參數設置通常是:scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING, min_size=<minimum possible face size> (例如, 對於視頻會議的圖像區域).

程序內部調用cvHaarDetectObjectsForROC實現目標檢測 ,返回結果存儲在返回值CvSeq中,使用CvAvgComp結構體可以實現CvSeq到Rect的轉換

vector<CvAvgComp> vecAvgComp;

Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);

objects.resize(vecAvgComp.size());

std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());

也可以使用cvGetSeqElem實現數據的讀取

CvRect face_rect = *(CvRect*)cvGetSeqElem( _objects, i, 0 ); cvHaarDetectObjectsForROC

執行目標檢測,可以返回rejectLevels和levelWeights

CvSeq*  cvHaarDetectObjectsForROC( const CvArr* _img,

CvHaarClassifierCascade* cascade, CvMemStorage* storage,

std::vector<int>& rejectLevels, std::vector<double>& levelWeights,

double scaleFactor, int minNeighbors, int flags,

CvSize minSize, CvSize maxSize, bool outputRejectLevels );

在該函數 cvHaarDetectObjectsForROC 的內部調用了以下函數進行目標的檢測,需要注意的是如果定義了多尺度檢測也就是參數flags中設置了 CV_HAAR_SCALE_IMAGE,則在後續檢測中在每一個scale上使用 cvSetImagesForHaarClassifierCascade設置圖像(scale=1.0),使用 HaarDetectObjects_ScaleImage_Invoker進行並行運算(可以返回rejectLevels和levelWeights),而如果沒有設置多尺度檢測則只在 當前尺度 上使用 cvSetImagesForHaarClassifierCascade設置圖像(scale*=scaleFactor,需要注意的是當設置了 CV_HAAR_FIND_BIGGEST_OBJECT則scaleFactor=1/scaleFactor,並且scale從最大開始不斷減小到1,否則scale從1開始不斷增大到最大 ),使用 HaarDetectObjects_ScaleCascade_Invoker進行並行運算 (不可以返回rejectLevels和levelWeights)

不管是使用縮放檢測圖像,還是使用縮放檢測窗口,檢測完畢後對窗口進行合併

if( minNeighbors != 0 || findBiggestObject )

{

if( outputRejectLevels )

groupRectangles(rectList, rejectLevels, levelWeights, minNeighbors, GROUP_EPS ); // 關於group的方法參考《OpenCV函數解讀之 groupRectangles

else

groupRectangles(rectList, rweights, std::max(minNeighbors, 1), GROUP_EPS);

}

else

rweights.resize(rectList.size(),0);

從上面的代碼可以看出如果定義了CV_HAAR_FIND_BIGGEST_OBJECT,並且輸出outputRejectLevels爲true的話是肯定沒有結果的,因爲如果定義了CV_HAAR_FIND_BIGGEST_OBJECT,是不能進行縮放圖像檢測的( CV_HAAR_SCALE_IMAGE被置爲0 ),不進行縮放圖像檢測也就不能返回 rejectLevels和levelWeights,因此 如果想返回 rejectLevels和levelWeights,則必須不能定義 CV_HAAR_FIND_BIGGEST_OBJECT並且必須定義 CV_HAAR_SCALE_IMAGE ,同時 outputRejectLevels必須爲true 。當然如果定義不恰當很可能返回值中存在object但是不存在 rejectLevels和levelWeights。

icvCreateHidHaarClassifierCascade

創建hid haar級聯分類器

/* create more efficient internal representation of haar classifier cascade */

static CvHidHaarClassifierCascade*  icvCreateHidHaarClassifierCascade(CvHaarClassifierCascade* cascade);

創建隱形HAAR級聯分類器的原因是提高HAAR級聯分類器內部處理的效率, CvHidHaarClassifierCascade CvHaarClassifierCascade結構是類似的,只不過使用指針將stage聯繫起來,參考《OpenCV結構解讀之 CvHaarClassifierCascade等

icvCreateHidHaarClassifierCascade函數中,分爲以下幾步將cascade中的stage_classifier 與hid_cascade聯繫起來:

1) 遍歷stage_classifier,統計stage、tree以及node總數,查看是否存在傾斜特徵,檢查每個rect是否超出檢測窗口界限

2) 通過上述得到的數據初始化CvHidHaarClassifierCascade 指針對象,其數據結構可以參考 《OpenCV結構解讀之 CvHaarClassifierCascade等

疑問:爲什麼拷貝了所有的聯繫,但是沒有拷貝rect呢?

原因:針對不同的檢測窗口如24*24,48*48,檢測窗口的尺寸可能是不同的,所以rect的縮放比例也是不同的,所以每次運算的時候需要根據當前檢測窗口大小來尋找不同的的rect的位置    

for( l = 0; l < node_count; l++ )

{

CvHidHaarTreeNode* node = hid_classifier->node + l;

CvHaarFeature* feature = classifier->haar_feature + l;

memset( node, -1, sizeof(*node) );

node->threshold = classifier->threshold[l];

node->left = classifier->left[l];

node->right = classifier->right[l];

// 如果第2個rect不存在的話則將隱形的feature置0,否則將變量tree_rects置爲0

if( fabs(feature->rect[2].weight) < DBL_EPSILON ||

feature->rect[2].r.width == 0 ||

feature->rect[2].r.height == 0 )

memset( &(node->feature.rect[2]), 0, sizeof(node->feature.rect[2]) );

else

hid_stage_classifier->two_rects = 0;

}

從上面的代碼可以看出,將node中的所有數據賦值爲-1,當只存在兩個rect的時候,將node的第三個rect賦值爲0,

cvSetImagesForHaarClassifierCascade

通過 縮放比例 爲隱藏的cascade(hidden cascade)指定 積分圖像、平方和圖像與傾斜和圖像、特徵矩形

void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                          const CvArr* sum, const CvArr* sqsum,
                                          const CvArr* tilted_sum, double scale );

cascade: Harr 分類器級聯(Haar classifier cascade)

sum: 32-比特,單通道圖像的積分圖像(Integral (sum) 單通道 image of 32-比特 integer format). 這幅圖像以及隨後的兩幅用於對快速特徵的評價和亮度/對比度的歸一化。 它們都可以利用函數 cvIntegral從8-比特或浮點數 單通道的輸入圖像中得到。

sqsum: 單通道64比特圖像的平方和圖像

tilted_sum: 單通道32比特整數格式的圖像的傾斜和圖像(Tilted sum),如果存在傾斜特徵該參數有不爲NULL

以上三個參數:積分圖像,平方和圖像以及傾斜和是通過cvIntergral函數計算得到的,每一個縮放比例下計算一次。

scale: cascade的窗口比例. 如果 scale=1, 就只用原始窗口尺寸檢測 (只檢測同樣尺寸大小的目標物體) - 原始窗口尺寸在函數cvLoadHaarClassifierCascade中定義 (在 "<default_face_cascade>"中缺省爲24x24), 當對圖像縮放時scale設置爲1.0,當對檢測窗口縮放時設置爲scale*scaleFactor。

cvRunHaarClassifierCascade

在給定位置的圖像中運行cascade of boosted classifier,在 HaarDetectObjects_ScaleCascade_Invoker中調用,也就是在縮放檢測窗口檢測時調用該函數

該函數內部調用了函數 cvRunHaarClassifierCascadeSum

int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                CvPoint pt, int start_stage=0 );
cvRunHaarClassifierCascadeSum

在給定位置的圖像中運行 cascade of boosted classifier,在 HaarDetectObjects_ScaleImage_Invoker中調用,也就是在縮放檢測圖像檢測時調用該函數

static int

cvRunHaarClassifierCascadeSum( const CvHaarClassifierCascade* _cascade,

CvPoint pt, double& stage_sum, int start_stage );

cascade:Haar 級聯分類器

pt:待檢測區域的左上角座標。待檢測區域大小爲原始窗口尺寸乘以當前設定的比例係數。當前窗口尺寸可以通過cvGetHaarClassifierCascadeWindowSize重新得到

stage_sum: 權重之和

start_stage:級聯層的初始下標值(從0開始計數)。函數假定前面所有每層的分類器都已通過。這個特徵通過函數cvHaarDetectObjects內部調用,用於更好的處理器高速緩衝存儲器。

函數 cvRunHaarClassifierCascade 用於對單幅圖片的檢測。在函數調用前首先利用 cvSetImagesForHaarClassifierCascade設定積分圖和合適的比例係數 (=> 窗口尺寸)。當分析的矩形框全部通過級聯分類器從start_stage開始的每一層時(當前stage的alpha總和大於當前stage的threshold),返回正值(1)(這是一個候選目標),否則返回0或負值。

函數中根據當前分類器是否爲tree、是否只有一個node來分爲三種不同的計算方法。

icvEvalHidHaarClassifier

CV_INLINE

double icvEvalHidHaarClassifier( CvHidHaarClassifier* classifier,

double variance_norm_factor,

size_t p_offset );

icvEvalHidHaarClassifier函數在計算指定窗口中分類器對應的haar值,使用了積分圖。即使用該window尺寸下casecade中所有haar特徵的p0,p1,p2,p3。每移動一次窗口,p0,p1,p2,p3指針移動相應的位置,再計算(*p0+*p3-*p1-*p2),其值爲圖像中矩形框位置的灰度和,將其乘以相應的權重,即可得到haar特徵值。下面爲相應的代碼:

do

{

CvHidHaarTreeNode* node = classifier->node + idx;

double t = node->threshold * variance_norm_factor;

double sum = calc_sum(node->feature.rect[0],p_offset) * node->feature.rect[0].weight;

sum += calc_sum(node->feature.rect[1],p_offset) * node->feature.rect[1].weight;

if( node->feature.rect[2].p0 )

sum += calc_sum(node->feature.rect[2],p_offset) * node->feature.rect[2].weight;

idx = sum < t ? node->left : node->right;

} while( idx > 0 );

發佈了48 篇原創文章 · 獲贊 8 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章