【转载】浅谈OpenCV人脸检测以及两个函数cvHaarDetectObjects和cvRunHaarClassifierCascade

来源:互联网 发布:python cartopy 编辑:程序博客网 时间:2024/05/17 07:00

本文转自:http://2000liuzhenxing.blog.163.com/blog/static/51677475200981952828662/和http://2000liuzhenxing.blog.163.com/blog/static/5167747520098195217494/。

第一部分  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__;

第二部分  cvRunHaarClassifierCascade

#ifdef _OPENMPfor( i = 0; i < max_threads; i++ ){if( seq_thread[i] )            cvReleaseMemStorage( &seq_thread[i]->storage );                //如果使用了OpenMP就释放使用的seq_thread}#endif     cvReleaseMemStorage( &temp_storage );    cvReleaseMat( &sum );    cvReleaseMat( &sqsum );    cvReleaseMat( &tilted );                                                                //释放使用的空间    cvReleaseMat( &temp );    cvReleaseMat( &sumcanny );    cvReleaseMat( &norm_img );    cvReleaseMat( &img_small );    cvFree( &comps );     return result_seq;                                                                                //返回结果} 下面是cvRunHaarClassifierCascade的:CV_IMPL intcvRunHaarClassifierCascade( CvHaarClassifierCascade* _cascade,                            CvPoint pt, int start_stage ){    int result = -1;    CV_FUNCNAME(”cvRunHaarClassifierCascade”);    __BEGIN__;    int p_offset, pq_offset;    int i, j;    double mean, variance_norm_factor;    CvHidHaarClassifierCascade* cascade;    if( !CV_IS_HAAR_CLASSIFIER(_cascade) )        CV_ERROR( !_cascade ? CV_StsNullPtr : CV_StsBadArg, “Invalid cascade pointer” );    cascade = _cascade->hid_cascade;    if( !cascade )        CV_ERROR( CV_StsNullPtr, “Hidden cascade has not been created.\n”            “Use cvSetImagesForHaarClassifierCascade” );    if( pt.x < 0 || pt.y < 0 ||        pt.x + _cascade->real_window_size.width >= cascade->sum.width-2 ||        pt.y + _cascade->real_window_size.height >= cascade->sum.height-2 )                   //超边退出        EXIT;    p_offset = pt.y * (cascade->sum.step/sizeof(sumtype)) + pt.x;    pq_offset = pt.y * (cascade->sqsum.step/sizeof(sqsumtype)) + pt.x;    mean = calc_sum(*cascade,p_offset)*cascade->inv_window_area;    variance_norm_factor = cascade->pq0[pq_offset] – cascade->pq1[pq_offset] -                //左上+右下-右上-左下                           cascade->pq2[pq_offset] + cascade->pq3[pq_offset];    variance_norm_factor = variance_norm_factor*cascade->inv_window_area – mean*mean;    if( variance_norm_factor >= 0. )        variance_norm_factor = sqrt(variance_norm_factor);    else        variance_norm_factor = 1.;    if( cascade->is_tree )           //是树形的分类器,就按照层来匹配.    {        CvHidHaarStageClassifier* ptr;        assert( start_stage == 0 );                      //start_stage==0继续        result = 1;        ptr = cascade->stage_classifier;        while( ptr )        {            double stage_sum = 0;            for( j = 0; j < ptr->count; j++ )            {                stage_sum += icvEvalHidHaarClassifier( ptr->classifier + j,     //层判断                    variance_norm_factor, p_offset );            }            if( stage_sum >= ptr->threshold )            {                ptr = ptr->child;               //层判断通过,到下一层.            }            else            {                while( ptr && ptr->next == NULL ) ptr = ptr->parent;              //未通过,且当前子分类器没有同层分类器,没有返回上层                if( ptr == NULL )              //如果刚才已经是最顶层了.                {                    result = 0;                  //返回0,退出.                    EXIT;                }                ptr = ptr->next;               //指向下一个分类器.            }        }    }    else if( cascade->is_stump_based )              //如果是stump类的分类器    {        for( i = start_stage; i < cascade->count; i++ )        {            double stage_sum = 0;            if( cascade->stage_classifier[i].two_rects )            {                for( j = 0; j < cascade->stage_classifier[i].count; j++ )                {                    CvHidHaarClassifier* classifier = cascade->stage_classifier[i].classifier + j;                    CvHidHaarTreeNode* node = classifier->node;                    double sum, t = node->threshold*variance_norm_factor, a, b;                    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;                    a = classifier->alpha[0];                    b = classifier->alpha[1];                    stage_sum += sum < t ? a : b;                }            }            else            {                for( j = 0; j < cascade->stage_classifier[i].count; j++ )                {                    CvHidHaarClassifier* classifier = cascade->stage_classifier[i].classifier + j;                    CvHidHaarTreeNode* node = classifier->node;                    double sum, t = node->threshold*variance_norm_factor, a, b;                    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;                    a = classifier->alpha[0];                    b = classifier->alpha[1];                    stage_sum += sum < t ? a : b;                }            }            if( stage_sum < cascade->stage_classifier[i].threshold )            {                   //没通过.则返回负的没通过的分类器数.                result = -i;                EXIT;            }        }    }    else                    //如果不是那两种强分类器    {        for( i = start_stage; i < cascade->count; i++ )        {            double stage_sum = 0;            for( j = 0; j < cascade->stage_classifier[i].count; j++ )            {                stage_sum += icvEvalHidHaarClassifier(                    cascade->stage_classifier[i].classifier + j,                    variance_norm_factor, p_offset );            }            if( stage_sum < cascade->stage_classifier[i].threshold )            {                result = -i;                EXIT;            }        }    }    result = 1;    __END__;    return result;     //返回结果}


0 0
原创粉丝点击