OpenCV的人臉檢測主要是調用訓練好的cascade(Haar分類器)來進行模式匹配。
cvHaarDetectObjects,先將圖像灰度化,根據傳入參數判斷是否進行canny邊緣處理(默認不使用),再進行匹配。匹配後收集找出的匹配塊,過濾噪聲,計算相鄰個數如果超過了規定值(傳入的min_neighbors)就當成輸出結果,否則刪去。
匹配循環:將匹配分類器放大scale(傳入值)倍,同時原圖縮小scale倍,進行匹配,直到匹配分類器的大小大於原圖,則返回匹配結果。匹配的時候調用cvRunHaarClassifierCascade來進行匹配,將所有結果存入CvSeq* Seq (可動態增長元素序列),將結果傳給cvHaarDetectObjects。
cvRunHaarClassifierCascade函數整體是根據傳入的圖像和cascade來進行匹配。並且可以根據傳入的cascade類型不同(樹型、stump(不完整的樹)或其他的),進行不同的匹配方式。
函數 cvRunHaarClassifierCascade 用於對單幅圖片的檢測。在函數調用前首先利用 cvSetImagesForHaarClassifierCascade設定積分圖和合適的比例係數 (=> 窗口尺寸)。當分析的矩形框全部通過級聯分類器每一層的時返回正值(這是一個候選目標),否則返回0或負值。
爲了瞭解OpenCV人臉檢測中尋找匹配圖像的詳細過程,就把cvHaarDetectObjects和cvRunHaarClassifierCascade的源文件詳細看了一遍,並打上了註釋。方便大家閱讀。
附cvHaarDetectObjects代碼:
CV_IMPL CvSeq*
cvHaarDetectObjects( const CvArr* _img,
CvHaarClassifierCascade* cascade,
CvMemStorage* storage, double scale_factor,
int min_neighbors, int flags, CvSize min_size )
{
int split_stage = 2;
CvMat stub, *img = (CvMat*)_img; //CvMat多通道矩陣 *img=_img指針代換傳入圖
CvMat *temp = 0, *sum = 0, *tilted = 0, *sqsum = 0, *norm_img = 0, *sumcanny = 0, *img_small = 0;
CvSeq* seq = 0;
CvSeq* seq2 = 0; //CvSeq可動態增長元素序列
CvSeq* idx_seq = 0;
CvSeq* result_seq = 0;
CvMemStorage* temp_storage = 0;
CvAvgComp* comps = 0;
int i;
#ifdef _OPENMP
CvSeq* seq_thread[CV_MAX_THREADS] = {0};
int max_threads = 0;
#endif
CV_FUNCNAME( “cvHaarDetectObjects” );
__BEGIN__;
double factor;
int npass = 2, coi; //npass=2
int do_canny_pruning = flags & CV_HAAR_DO_CANNY_PRUNING; //true做canny邊緣處理
if( !CV_IS_HAAR_CLASSIFIER(cascade) )
CV_ERROR( !cascade ? CV_StsNullPtr : CV_StsBadArg, “Invalid classifier cascade” );
if( !storage )
CV_ERROR( CV_StsNullPtr, “Null storage pointer” );
CV_CALL( img = cvGetMat( img, &stub, &coi ));
if( coi )
CV_ERROR( CV_BadCOI, “COI is not supported” ); //一些出錯代碼
if( CV_MAT_DEPTH(img->type) != CV_8U )
CV_ERROR( CV_StsUnsupportedFormat, “Only 8-bit images are supported” );
CV_CALL( temp = cvCreateMat( img->rows, img->cols, CV_8UC1 ));
CV_CALL( sum = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ));
CV_CALL( sqsum = cvCreateMat( img->rows + 1, img->cols + 1, CV_64FC1 ));
CV_CALL( temp_storage = cvCreateChildMemStorage( storage ));
#ifdef _OPENMP
max_threads = cvGetNumThreads();
for( i = 0; i < max_threads; i++ )
{
CvMemStorage* temp_storage_thread;
CV_CALL( temp_storage_thread = cvCreateMemStorage(0)); //CV_CALL就是運行,假如出錯就報錯。
CV_CALL( seq_thread[i] = cvCreateSeq( 0, sizeof(CvSeq), //CvSeq可動態增長元素序列
sizeof(CvRect), temp_storage_thread ));
}
#endif
if( !cascade->hid_cascade )
CV_CALL( icvCreateHidHaarClassifierCascade(cascade) );
if( cascade->hid_cascade->has_tilted_features )
tilted = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //多通道矩陣 圖像長寬+1 4通道
seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvRect), temp_storage ); //創建序列seq 矩形
seq2 = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), temp_storage ); //創建序列seq2 矩形和鄰近
result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), storage ); //創建序列result_seq 矩形和鄰近
if( min_neighbors == 0 )
seq = result_seq;
if( CV_MAT_CN(img->type) > 1 )
{
cvCvtColor( img, temp, CV_BGR2GRAY ); //img轉爲灰度
img = temp;
}
if( flags & CV_HAAR_SCALE_IMAGE ) //flag && 匹配圖
{
CvSize win_size0 = cascade->orig_window_size; //CvSize win_size0爲分類器的原始大小
int use_ipp = cascade->hid_cascade->ipp_stages != 0 &&
icvApplyHaarClassifier_32s32f_C1R_p != 0; //IPP相關函數
if( use_ipp )
CV_CALL( norm_img = cvCreateMat( img->rows, img->cols, CV_32FC1 )); //圖像的矩陣化 4通道.
CV_CALL( img_small = cvCreateMat( img->rows + 1, img->cols + 1, CV_8UC1 )); //小圖矩陣化 單通道 長寬+1
for( factor = 1; ; factor *= scale_factor ) //成scale_factor倍數匹配
{
int positive = 0;
int x, y;
CvSize win_size = { cvRound(win_size0.width*factor),
cvRound(win_size0.height*factor) }; //winsize 分類器行列(擴大factor倍)
CvSize sz = { cvRound( img->cols/factor ), cvRound( img->rows/factor ) }; //sz 圖像行列(縮小factor倍) 三個Cvsize
CvSize sz1 = { sz.width – win_size0.width, sz.height – win_size0.height }; //sz1 圖像 減 分類器行列
CvRect rect1 = { icv_object_win_border, icv_object_win_border,
win_size0.width – icv_object_win_border*2, //icv_object_win_border (int) 初始值=1
win_size0.height – icv_object_win_border*2 }; //矩形框rect1
CvMat img1, sum1, sqsum1, norm1, tilted1, mask1; //多通道矩陣
CvMat* _tilted = 0;
if( sz1.width <= 0 || sz1.height <= 0 ) //圖片寬或高小於分類器–>跳出
break;
if( win_size.width < min_size.width || win_size.height < min_size.height ) //分類器高或寬小於給定的mini_size的高或寬–>繼續
continue;
//CV_8UC1見定義.
//#define CV_MAKETYPE(depth,cn) ((depth) + (((cn)-1) << CV_CN_SHIFT))
//深度+(cn-1)左移3位 depth,depth+8,depth+16,depth+24.
img1 = cvMat( sz.height, sz.width, CV_8UC1, img_small->data.ptr ); //小圖的矩陣化 img1 單通道
sum1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, sum->data.ptr ); //長寬+1 4通道8位 多通道矩陣
sqsum1 = cvMat( sz.height+1, sz.width+1, CV_64FC1, sqsum->data.ptr ); //長寬+1 4通道16位
if( tilted )
{
tilted1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, tilted->data.ptr ); //長寬+1 4通道8位
_tilted = &tilted1; //長寬+1 4通道8位
}
norm1 = cvMat( sz1.height, sz1.width, CV_32FC1, norm_img ? norm_img->data.ptr : 0 ); //norm1 圖像 減 分類器行列 4通道
mask1 = cvMat( sz1.height, sz1.width, CV_8UC1, temp->data.ptr ); //mask1 灰度圖
cvResize( img, &img1, CV_INTER_LINEAR ); //img雙線性插值 輸出到img1
cvIntegral( &img1, &sum1, &sqsum1, _tilted ); //計算積分圖像
if( use_ipp && icvRectStdDev_32s32f_C1R_p( sum1.data.i, sum1.step,
sqsum1.data.db, sqsum1.step, norm1.data.fl, norm1.step, sz1, rect1 ) < 0 )
use_ipp = 0;
if( use_ipp ) //如果ipp=true (intel視頻處理加速等的函數庫)
{
positive = mask1.cols*mask1.rows; //mask1長乘寬–>positive
cvSet( &mask1, cvScalarAll(255) ); //mask1賦值爲255
for( i = 0; i < cascade->count; i++ )
{
if( icvApplyHaarClassifier_32s32f_C1R_p(sum1.data.i, sum1.step,
norm1.data.fl, norm1.step, mask1.data.ptr, mask1.step,
sz1, &positive, cascade->hid_cascade->stage_classifier[i].threshold,
cascade->hid_cascade->ipp_stages[i]) < 0 )
{
use_ipp = 0; //ipp=false;
break;
}
if( positive <= 0 )
break;
}
}
if( !use_ipp ) //如果ipp=false
{
cvSetImagesForHaarClassifierCascade( cascade, &sum1, &sqsum1, 0, 1. );
for( y = 0, positive = 0; y < sz1.height; y++ )
for( x = 0; x < sz1.width; x++ )
{
mask1.data.ptr[mask1.step*y + x] =
cvRunHaarClassifierCascade( cascade, cvPoint(x,y), 0 ) > 0; //匹配圖像.
positive += mask1.data.ptr[mask1.step*y + x];
}
}
if( positive > 0 )
{
for( y = 0; y < sz1.height; y++ )
for( x = 0; x < sz1.width; x++ )
if( mask1.data.ptr[mask1.step*y + x] != 0 )
{
CvRect obj_rect = { cvRound(y*factor), cvRound(x*factor),
win_size.width, win_size.height };
cvSeqPush( seq, &obj_rect ); //將匹配塊放到seq中
}
}
}
}
else //!(flag && 匹配圖)
{
cvIntegral( img, sum, sqsum, tilted );
if( do_canny_pruning )
{
sumcanny = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //如果 做canny邊緣檢測
cvCanny( img, temp, 0, 50, 3 );
cvIntegral( temp, sumcanny );
}
if( (unsigned)split_stage >= (unsigned)cascade->count ||
cascade->hid_cascade->is_tree )
{
split_stage = cascade->count;
npass = 1;
}
for( factor = 1; factor*cascade->orig_window_size.width < img->cols – 10 && //匹配
factor*cascade->orig_window_size.height < img->rows – 10;
factor *= scale_factor )
{
const double ystep = MAX( 2, factor );
CvSize win_size = { cvRound( cascade->orig_window_size.width * factor ),
cvRound( cascade->orig_window_size.height * factor )};
CvRect equ_rect = { 0, 0, 0, 0 };
int *p0 = 0, *p1 = 0, *p2 = 0, *p3 = 0;
int *pq0 = 0, *pq1 = 0, *pq2 = 0, *pq3 = 0;
int pass, stage_offset = 0;
int stop_height = cvRound((img->rows – win_size.height) / ystep);
if( win_size.width < min_size.width || win_size.height < min_size.height ) //超邊跳出
continue;
cvSetImagesForHaarClassifierCascade( cascade, sum, sqsum, tilted, factor ); //匹配
cvZero( temp ); //清空temp數組
if( do_canny_pruning ) //canny邊緣檢測
{
equ_rect.x = cvRound(win_size.width*0.15);
equ_rect.y = cvRound(win_size.height*0.15);
equ_rect.width = cvRound(win_size.width*0.7);
equ_rect.height = cvRound(win_size.height*0.7);
p0 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step) + equ_rect.x;
p1 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step)
+ equ_rect.x + equ_rect.width;
p2 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step) + equ_rect.x;
p3 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step)
+ equ_rect.x + equ_rect.width;
pq0 = (int*)(sum->data.ptr + equ_rect.y*sum->step) + equ_rect.x;
pq1 = (int*)(sum->data.ptr + equ_rect.y*sum->step)
+ equ_rect.x + equ_rect.width;
pq2 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step) + equ_rect.x;
pq3 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step)
+ equ_rect.x + equ_rect.width;
}
cascade->hid_cascade->count = split_stage; //分裂級
for( pass = 0; pass < npass; pass++ )
{
#ifdef _OPENMP