当先锋百科网

首页 1 2 3 4 5 6 7

之前介绍了haartraining程序中的cvCreateMTStumpClassifier函数,这个函数的功能是计算最优弱分类器,这篇文章介绍一下自己对haartraining中关于强分类器计算的一些理解,也就是程序中的icvCreateCARTStageClassifier函数。


在给出代码之前,说几处自认为值得说说的问题:

1. 由于haartraining是基于HAAR特征进行adaboost训练,对于HAAR特征的处理比较繁琐,采用了奇数弱分类器补充针对翻转特征最优弱分类器计算的代码,所以代码看起来较为冗长。

2. 创建强分类器时,其中包含有样本权值的更新,代码中共提供了四种经典adaboost算法版本,它们是Discrete Adaboost、Real Adaboost、Logit Boost、Gentle Adaboost。每种算法的权值更新策略不同,这方面的知识建议大家下载几篇博士论文看看,也可以看看我之前发的博客。

http://blog.csdn.net/wsj998689aa/article/details/42242565

3. 代码较多地通过函数指针的形式(实际上这也是opencv一直常用的手段)对函数进行回调。

4. 小权值样本需要剔除掉,因为小权值样本对训练结果的影响微乎其微,加了它们反而要耗时不少。这边有一处需要提醒大家,当前分类器剔除掉的小权值样本,仍旧参与下一个分类器中样本权值的剔除,换句话说,每一个弱分类器,其实都要对所有的样本权值进行排序,所以会造成实际训练样本比例出现“跳变”的情况,但是总体走势还是始终下降的。如下图所示:


5. 代码中采用了较多的中间结构体变量,例如CvIntHaarClassifier结构体(用于模拟强分类器结构体CvStageHaarClassifier的父类),CvBoostTrainer结构体(用于初始化,更新样本权值等)等等,看起来比较绕。

6. 关于弱分类器的创建,事先创建的其实是CART分类器,CART分类器就是一棵树,每个节点代表一个最优Haar特征,但是一般程序中的节点个数都设置为1,所以一个CART就相当于stump了,此外,CART的创建涉及到节点的分裂,通过icvSplitIndicesCallback函数实现。

7. 在创建CART的时候,最优Haar特征其实就已经被选择好了,至于下面还有一个stumpConstructor函数,是由于Haar特征被翻转而产生了新特征,所以需要重新寻找最优弱分类器。

8. 有意思的是,函数输入了最小正检率和最大误检率,前者决定了判定样本是正类还是负类的阈值,后者决定了强分类器是否能够收敛,具体来说吧,先计算当前最优弱分类器关于每个正样本的置信度,然后对置信度进行排序,这个阈值就是基于最小正检率选择的置信度,然后在根据这个阈值来计算当前最优弱分类器的误检率(计算每个负样本的置信度),如果大于了输入的最大误检率,那么OK!!一串弱分类器所构成的强分类器诞生了。


以上说的就是icvCreateCARTStageClassifier中值得注意的几点,下面上代码,是根据自己的理解添加的注释,请各位不吝批评指正哈!

转载请注明:http://blog.csdn.net/wsj998689aa/article/details/42398235

static
CvIntHaarClassifier* icvCreateCARTStageClassifier( CvHaarTrainingData* data,        // 全部训练样本
                                                   CvMat* sampleIdx,                // 实际训练样本序列
                                                   CvIntHaarFeatures* haarFeatures, // 全部HAAR特征
                                                   float minhitrate,    // 最小正检率(用于确定强分类器阈值)
                                                   float maxfalsealarm, // 最大误检率(用于确定是否收敛)            
                                                   int   symmetric,     // HAAR是否对称
                                                   float weightfraction,    // 样本剔除比例(用于剔除小权值样本)
                                                   int numsplits,           // 每个弱分类器特征个数(一般为1)
                                                   CvBoostType boosttype,   // adaboost类型
                                                   CvStumpError stumperror, // Discrete AdaBoost中的阈值计算方式
                                                   int maxsplits )          // 弱分类器最大个数
{

#ifdef CV_COL_ARRANGEMENT
    int flags = CV_COL_SAMPLE;
#else
    int flags = CV_ROW_SAMPLE;
#endif

    CvStageHaarClassifier* stage = NULL;                    // 强分类器
    CvBoostTrainer* trainer;                                // 临时训练器,用于更新样本权值
    CvCARTClassifier* cart = NULL;                          // 弱分类器
    CvCARTTrainParams trainParams;                          // 训练参数
    CvMTStumpTrainParams stumpTrainParams;                  // 弱分类器参数
    //CvMat* trainData = NULL;
    //CvMat* sortedIdx = NULL;
    CvMat eval;                                             // 临时矩阵
    int n = 0;                                              // 特征总数
    int m = 0;                                              // 总样本个数
    int numpos = 0;                                         // 正样本个数
    int numneg = 0;                                         // 负样本个数
    int numfalse = 0;                                       // 误检样本个数
    float sum_stage = 0.0F;                                 // 置信度累积和                              
    float threshold = 0.0F;                                 // 强分类器阈值
    float falsealarm = 0.0F;                                // 误检率
    
    //CvMat* sampleIdx = NULL;
    CvMat* trimmedIdx;                                      // 剔除小权值之后的样本序列
    //float* idxdata = NULL;
    //float* tempweights = NULL;
    //int    idxcount = 0;
    CvUserdata userdata;                                    // 训练数据

    int i = 0;
    int j = 0;
    int idx;
    int numsamples;                                         // 实际样本个数
    int numtrimmed;                                         // 剔除小权值之后的样本个数
    
    CvCARTHaarClassifier* classifier;                       // 弱分类器
    CvSeq* seq = NULL;
    CvMemStorage* storage = NULL;
    CvMat* weakTrainVals;                                   // 样本类别,只有logitboost才会用到
    float alpha;
    float sumalpha;
    int num_splits;                                         // 弱分类器个数                                    

#ifdef CV_VERBOSE
    printf( "+----+----+-+---------+---------+---------+---------+\n" );
    printf( "|  N |%%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|\n" );
    printf( "+----+----+-+---------+---------+---------+---------+\n" );
#endif /* CV_VERBOSE */
    
    n = haarFeatures->count;
    m = data->sum.rows;
    numsamples = (sampleIdx) ? MAX( sampleIdx->rows, sampleIdx->cols ) : m;

    // 样本与HAAR特征
    userdata = cvUserdata( data, haarFeatures );


    /* 弱分类参数设置 */
    stumpTrainParams.type = ( boosttype == CV_DABCLASS )
        ? CV_CLASSIFICATION_CLASS : CV_REGRESSION;                              // 分类或者回归
    stumpTrainParams.error = ( boosttype == CV_LBCLASS || boosttype == CV_GABCLASS )
        ? CV_SQUARE : stumperror;                                               // 弱分类器阈值计算方式
    stumpTrainParams.portion = CV_STUMP_TRAIN_PORTION;                          // 每组特征个数
    stumpTrainParams.getTrainData = icvGetTrainingDataCallback;                 // 计算样本的haar值
    stumpTrainParams.numcomp = n;                                               // 特征个数            
    stumpTrainParams.userdata = &userdata; 
    stumpTrainParams.sortedIdx = data->idxcache;                                // 特征-样本序号矩阵(排序之后)


    // 由于参数众多,所以创建参数结构体
    trainParams.count = numsplits;                                              // 弱分类器特征树
    trainParams.stumpTrainParams = (CvClassifierTrainParams*) &stumpTrainParams;// 弱分类参数
    trainParams.stumpConstructor = cvCreateMTStumpClassifier;                   // 筛选最优弱分类器
    trainParams.splitIdx = icvSplitIndicesCallback;                             // CART节点分裂函数
    trainParams.userdata = &userdata;                                               

    // 临时向量,用于存放样本haar特征值
    eval = cvMat( 1, m, CV_32FC1, cvAlloc( sizeof( float ) * m ) );
    
    storage = cvCreateMemStorage();

    // 最优弱分类器存储序列
    seq = cvCreateSeq( 0, sizeof( *seq ), sizeof( classifier ), storage );

    // 样本类别,只有logitboost才会用到
    weakTrainVals = cvCreateMat( 1, m, CV_32FC1 );

    // 初始化样本类别与权重,weakTrainVals为{-1, 1},权重都一样
    trainer = cvBoostStartTraining( &data->cls, weakTrainVals, &data->weights,
                                    sampleIdx, boosttype );
    num_splits = 0;
    sumalpha = 0.0F;
    do
    {     

#ifdef CV_VERBOSE
        int v_wt = 0;
        int v_flipped = 0;
#endif /* CV_VERBOSE */

        // 剔除小权值样本
        trimmedIdx = cvTrimWeights( &data->weights, sampleIdx, weightfraction );

        // 实际样本总数
        numtrimmed = (trimmedIdx) ? MAX( trimmedIdx->rows, trimmedIdx->cols ) : m;

#ifdef CV_VERBOSE
        v_wt = 100 * numtrimmed / numsamples;
        v_flipped = 0;

#endif /* CV_VERBOSE */

        // 重要函数,创建CART树的同时,当前最优弱分类器出炉,一般只有根节点
        cart = (CvCARTClassifier*) cvCreateCARTClassifier( data->valcache,
                        flags,
                        weakTrainVals, 0, 0, 0, trimmedIdx,
                        &(data->weights),
                        (CvClassifierTrainParams*) &trainParams );

        // 创建弱分类器
        classifier = (CvCARTHaarClassifier*) icvCreateCARTHaarClassifier( numsplits );

        // 将CART树转化为弱分类器
        icvInitCARTHaarClassifier( classifier, cart, haarFeatures );

        num_splits += classifier->count;

        cart->release( (CvClassifier**) &cart );
        
        // 为何一定要在奇数个弱分类器处计算?
        if( symmetric && (seq->total % 2) )
        {
            float normfactor = 0.0F;
            CvStumpClassifier* stump;
            
            /* 翻转HAAR特征 */
            for( i = 0; i < classifier->count; i++ )
            {
                if( classifier->feature[i].desc[0] == 'h' )
                {
                    for( j = 0; j < CV_HAAR_FEATURE_MAX &&
                                    classifier->feature[i].rect[j].weight != 0.0F; j++ )
                    {
                        classifier->feature[i].rect[j].r.x = data->winsize.width - 
                            classifier->feature[i].rect[j].r.x -
                            classifier->feature[i].rect[j].r.width;                
                    }
                }
                else
                {
                    int tmp = 0;

                    /* (x,y) -> (24-x,y) */
                    /* w -> h; h -> w    */
                    for( j = 0; j < CV_HAAR_FEATURE_MAX &&
                                    classifier->feature[i].rect[j].weight != 0.0F; j++ )
                    {
                        classifier->feature[i].rect[j].r.x = data->winsize.width - 
                            classifier->feature[i].rect[j].r.x;
                        CV_SWAP( classifier->feature[i].rect[j].r.width,
                                 classifier->feature[i].rect[j].r.height, tmp );
                    }
                }
            }

            // 转化为基于积分图计算的特征
            icvConvertToFastHaarFeature( classifier->feature,
                                         classifier->fastfeature,
                                         classifier->count, data->winsize.width + 1 );

            // 为了验证最新翻转特征是否为最优特征
            stumpTrainParams.getTrainData = NULL;
            stumpTrainParams.numcomp = 1;
            stumpTrainParams.userdata = NULL;
            stumpTrainParams.sortedIdx = NULL;

            // 验证是否新生成的特征可作为最优弱分类器
            for( i = 0; i < classifier->count; i++ )
            {
                for( j = 0; j < numtrimmed; j++ )
                {
                    // 获取训练样本
                    idx = icvGetIdxAt( trimmedIdx, j );

                    // 对每个训练样本计算Haar特征
                    eval.data.fl[idx] = cvEvalFastHaarFeature( &classifier->fastfeature[i],
                                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step) ); 

                    // 归一化因子
                    normfactor = data->normfactor.data.fl[idx];

                    // 对Haar特征归一化
                    eval.data.fl[idx] = ( normfactor == 0.0F )
                        ? 0.0F : (eval.data.fl[idx] / normfactor);
                }

                // 计算最优弱分类器
                stump = (CvStumpClassifier*) trainParams.stumpConstructor( &eval,
                    CV_COL_SAMPLE,
                    weakTrainVals, 0, 0, 0, trimmedIdx,
                    &(data->weights),
                    trainParams.stumpTrainParams );
            
                classifier->threshold[i] = stump->threshold;                // 阈值
                if( classifier->left[i] <= 0 )
                {
                    classifier->val[-classifier->left[i]] = stump->left;    // 左分支输出置信度
                }
                if( classifier->right[i] <= 0 )
                {
                    classifier->val[-classifier->right[i]] = stump->right;  // 右分支输出置信度
                }

                stump->release( (CvClassifier**) &stump );        
                
            }

            // 还原参数,参数支持cvCreateCARTClassifier函数
            stumpTrainParams.getTrainData = icvGetTrainingDataCallback;
            stumpTrainParams.numcomp = n;
            stumpTrainParams.userdata = &userdata;
            stumpTrainParams.sortedIdx = data->idxcache;

#ifdef CV_VERBOSE
            v_flipped = 1;
#endif /* CV_VERBOSE */

        } /* if symmetric */
        if( trimmedIdx != sampleIdx )
        {
            cvReleaseMat( &trimmedIdx );
            trimmedIdx = NULL;
        }
        
        // 调用icvEvalCARTHaarClassifier函数,计算每个样本的当前最优弱分类器置信度
        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            eval.data.fl[idx] = classifier->eval( (CvIntHaarClassifier*) classifier,
                (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                data->normfactor.data.fl[idx] );
        }

        // 更新样本权重,如果是LogitBoost,也会更新weakTrainVals,函数返回的是弱分类器权重
        alpha = cvBoostNextWeakClassifier( &eval, &data->cls, weakTrainVals,
                                           &data->weights, trainer );
        
        // 这个变量没什么用
        sumalpha += alpha;
        
        for( i = 0; i <= classifier->count; i++ )
        {
            if( boosttype == CV_RABCLASS ) 
            {
                classifier->val[i] = cvLogRatio( classifier->val[i] );
            }
            classifier->val[i] *= alpha;
        }

        // 添加弱分类器
        cvSeqPush( seq, (void*) &classifier );

        // 正样本个数
        numpos = 0;

        // 遍历sampleIdx中所有样本,计算每个样本的弱分类器置信度和
        for( i = 0; i < numsamples; i++ )
        {
            // 获得样本序号
            idx = icvGetIdxAt( sampleIdx, i );

            // 如果样本为正样本
            if( data->cls.data.fl[idx] == 1.0F )
            {
                // 初始化置信度值
                eval.data.fl[numpos] = 0.0F;

                // 遍历seq中所有弱分类器
                for( j = 0; j < seq->total; j++ )
                {
                    // 获取弱分类器
                    classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));

                    // 累积当前正样本的弱分类器置信度和
                    eval.data.fl[numpos] += classifier->eval( 
                        (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }
                /* eval.data.fl[numpos] = 2.0F * eval.data.fl[numpos] - seq->total; */
                numpos++;
            }
        }

        // 对弱分类器输出置信度和进行排序
        icvSort_32f( eval.data.fl, numpos, 0 );

        // 计算阈值,应该是大于threshold则为正类,小于threshold则为负类
        threshold = eval.data.fl[(int) ((1.0F - minhitrate) * numpos)];

        numneg = 0;
        numfalse = 0;

        // 遍历所有样本,统计错分负样本个数
        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            // 如果样本为负样本
            if( data->cls.data.fl[idx] == 0.0F )
            {
                numneg++;
                sum_stage = 0.0F;

                // 遍历seq中所有弱分类器
                for( j = 0; j < seq->total; j++ )
                {
                   classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));

                   // 累积当前负样本的分类器输出结果
                   sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }
                /* sum_stage = 2.0F * sum_stage - seq->total; */

                // 因为小于threshold为负类,所以下面是分类错误的情况
                if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
                {
                    numfalse++;
                }
            }
        }

        // 计算虚警率
        falsealarm = ((float) numfalse) / ((float) numneg);

// 输出内容
#ifdef CV_VERBOSE
        {
            // 正样本检出率
            float v_hitrate    = 0.0F;

            // 负样本误检率
            float v_falsealarm = 0.0F;
            /* expected error of stage classifier regardless threshold */

            // 这是什么?
            float v_experr = 0.0F;

            // 遍历所有样本
            for( i = 0; i < numsamples; i++ )
            {
                idx = icvGetIdxAt( sampleIdx, i );

                sum_stage = 0.0F;

                // 遍历seq中所有弱分类器
                for( j = 0; j < seq->total; j++ )
                {
                    classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
                    sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }
                /* sum_stage = 2.0F * sum_stage - seq->total; */

                // 只需要判断单一分支即可
                if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
                {
                    if( data->cls.data.fl[idx] == 1.0F )
                    {
                        v_hitrate += 1.0F;
                    }
                    else
                    {
                        v_falsealarm += 1.0F;
                    }
                }

                // 正类样本的sum_stage必须大于0
                if( ( sum_stage >= 0.0F ) != (data->cls.data.fl[idx] == 1.0F) )
                {
                    v_experr += 1.0F;
                }
            }
            v_experr /= numsamples;
            printf( "|%4d|%3d%%|%c|%9f|%9f|%9f|%9f|\n",
                seq->total, v_wt, ( (v_flipped) ? '+' : '-' ),
                threshold, v_hitrate / numpos, v_falsealarm / numneg,
                v_experr );
            printf( "+----+----+-+---------+---------+---------+---------+\n" );
            fflush( stdout );
        }
#endif /* CV_VERBOSE */
        
    // 两种收敛方式,一种是误检率小于规定阈值,另一种是弱分类器个数小于规定阈值
    } while( falsealarm > maxfalsealarm && (!maxsplits || (num_splits < maxsplits) ) );
    cvBoostEndTraining( &trainer );

    if( falsealarm > maxfalsealarm )        
    {
        // 如果弱分类器达到上限而收敛,则放弃当前强分类器
        stage = NULL;
    }
    else
    {
        // 创建当前强分类器
        stage = (CvStageHaarClassifier*) icvCreateStageHaarClassifier( seq->total,
                                                                       threshold );
        // 保存当前强分类器
        cvCvtSeqToArray( seq, (CvArr*) stage->classifier );
    }
    
    /* CLEANUP */
    cvReleaseMemStorage( &storage );
    cvReleaseMat( &weakTrainVals );
    cvFree( &(eval.data.ptr) );
    
    return (CvIntHaarClassifier*) stage;
}