1. 程式人生 > >cvHaarDetectObjects函式與人臉識別等應用

cvHaarDetectObjects函式與人臉識別等應用

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
    #pragma omp parallel for num_threads(max_threads), schedule(dynamic)
#endif
                for( int _iy = 0; _iy < stop_height; _iy++ )
                {
                    int iy = cvRound(_iy*ystep);
                    int _ix, _xstep = 1;
                    int stop_width = cvRound((img->cols – win_size.width) / ystep);
                    uchar* mask_row = temp->data.ptr + temp->step * iy;
                    for( _ix = 0; _ix < stop_width; _ix += _xstep )
                    {
                        int ix = cvRound(_ix*ystep); // it really should be ystep
                  
                        if( pass == 0 )                                                   //第一次迴圈 做
                        {
                            int result;
                            _xstep = 2;
                            if( do_canny_pruning )                                                        //canny邊緣檢測
                            {
                                int offset;
                                int s, sq;
                      
                                offset = iy*(sum->step/sizeof(p0[0])) + ix;
                                s = p0[offset] – p1[offset] – p2[offset] + p3[offset];
                                sq = pq0[offset] – pq1[offset] – pq2[offset] + pq3[offset];
                                if( s < 100 || sq < 20 )
                                    continue;
                            }
                            result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy), 0 );                //匹配結果存到result裡
                            if( result > 0 )
                            {
                                if( pass < npass – 1 )
                                    mask_row[ix] = 1;
                                else
                                {
                                    CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
#ifndef _OPENMP                                                                                                        //如果用OpenMP
                                    cvSeqPush( seq, &rect );                                                        //result 放到seq中
#else                                                                                                                        //如果不用OpenMP
                                    cvSeqPush( seq_thread[omp_get_thread_num()], &rect );                        //result放到seq_thread裡
#endif
                                }
                            }
                            if( result < 0 )
                                _xstep = 1;
                        }
                        else if( mask_row[ix] )                     //不是第一次
                        {
                            int result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy),
                                                                     stage_offset );
                            if( result > 0 )
                            {
                                if( pass == npass – 1 )          //如果是最後一次
                                {
                                    CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
#ifndef _OPENMP
                                    cvSeqPush( seq, &rect );
#else
                                    cvSeqPush( seq_thread[omp_get_thread_num()], &rect );
#endif
                                }
                            }
                            else
                                mask_row[ix] = 0;
                        }
                    }
                }
                stage_offset = cascade->hid_cascade->count;
                cascade->hid_cascade->count = cascade->count;
            }
        }
    }
#ifdef _OPENMP
// gather the results                                               //收集結果
for( i = 0; i < max_threads; i++ )
{
CvSeq* s = seq_thread[i];
        int j, total = s->total;
        CvSeqBlock* b = s->first;
        for( j = 0; j < total; j += b->count, b = b->next )
            cvSeqPushMulti( seq, b->data, b->count );                  //結果輸出到seq
}
#endif
    if( min_neighbors != 0 )
    {
        // group retrieved rectangles in order to filter out noise         收集找出的匹配塊,過濾噪聲
        int ncomp = cvSeqPartition( seq, 0, &idx_seq, is_equal, 0 );
        CV_CALL( comps = (CvAvgComp*)cvAlloc( (ncomp+1)*sizeof(comps[0])));
        memset( comps, 0, (ncomp+1)*sizeof(comps[0]));
        // count number of neighbors                                   計算相鄰個數
        for( i = 0; i < seq->total; i++ )
        {
            CvRect r1 = *(CvRect*)cvGetSeqElem( seq, i );
            int idx = *(int*)cvGetSeqElem( idx_seq, i );
            assert( (unsigned)idx < (unsigned)ncomp );
            comps[idx].neighbors++;
           
            comps[idx].rect.x += r1.x;
            comps[idx].rect.y += r1.y;
            comps[idx].rect.width += r1.width;
            comps[idx].rect.height += r1.height;
        }
        // calculate average bounding box                                    計算重心
        for( i = 0; i < ncomp; i++ )
        {
            int n = comps[i].neighbors;
            if( n >= min_neighbors )
            {
                CvAvgComp comp;
                comp.rect.x = (comps[i].rect.x*2 + n)/(2*n);
                comp.rect.y = (comps[i].rect.y*2 + n)/(2*n);
                comp.rect.width = (comps[i].rect.width*2 + n)/(2*n);
                comp.rect.height = (comps[i].rect.height*2 + n)/(2*n);
                comp.neighbors = comps[i].neighbors;
                cvSeqPush( seq2, &comp );                                   //結果輸入到seq2
            }
        }
        // filter out small face rectangles inside large face rectangles                在大的面塊中找出小的面塊
        for( i = 0; i < seq2->total; i++ )                                                                                //在seq2中尋找
        {
            CvAvgComp r1 = *(CvAvgComp*)cvGetSeqElem( seq2, i );                //r1指向結果
            int j, flag = 1;
            for( j = 0; j < seq2->total; j++ )
            {
                CvAvgComp r2 = *(CvAvgComp*)cvGetSeqElem( seq2, j );
                int distance = cvRound( r2.rect.width * 0.2 );
          
                if( i != j &&
                    r1.rect.x >= r2.rect.x – distance &&
                    r1.rect.y >= r2.rect.y – distance &&
                    r1.rect.x + r1.rect.width <= r2.rect.x + r2.rect.width + distance &&
                    r1.rect.y + r1.rect.height <= r2.rect.y + r2.rect.height + distance &&
                    (r2.neighbors > MAX( 3, r1.neighbors ) || r1.neighbors < 3) )
                {
                    flag = 0;
                    break;
                }
            }
            if( flag )
            {
                cvSeqPush( result_seq, &r1 );      //新增r1到返回結果.
                /* cvSeqPush( result_seq, &r1.rect ); */
            }
        }
    }
    __END__;