haartraining训练分类器方法cvCreateTreeCascadeClassifier()详解——人脸识别的尝试系列(四)

本文将介绍opencv_haartraining.exe中训练分类器的核心方法cvCreateTreeCascadeClassifier()中参数的具体含义,以及具体实现代码附加详细的注释。最后给出运行截图以作代码阅读的参考


我们还是从具体的例子出发,以一些实际的参数帮助我们理解算法。

一个示例的训练命令,这是我在训练分类器时使用的:

opencv_haartraining.exe -data xml -vecpos.vec -bg neg_image.txt -nstages 20 -nsplits 2 -minhitrate 0.999-maxfalsealarm 0.5 -npos 800 -nneg 2500 -w 24 -h 24  -mem 1024 -mode ALL

 

haartraining.cpp的main函数与createsamples.cpp类似,不再赘述。

与createsamples.cpp一样,haartraining.cpp调用了一个opencv库中的核心方法——cvCreateTreeCascadeClassifier()

首先结合我实际使用的方法参数对其进行解释

 

dirname xml  创建xml中间文件的位置

vecfilename pos.vec                    bgfilename neg_image.txt

npos 800    每一阶段训练所使用的正样本数         nneg 2500

nstages 20  阶段数

numprecalculated 1024 预留的内存 表示允许使用计算机的1280M内存

numsplits 2 弱分类器二叉决策树的分裂数   1表示使用简单stump 分类(只有一个树桩)

minhitrate 0.999  每一阶段所期望的每阶段最小击中率

maxfalsealarm 0.5  每一阶段所期望的最大错误虚警率

weightfraction 默认值0.95  权重调整参数

mode ALL(2) 表示使用haar特征集的种类既有垂直的,又有45度角旋转的  

对应表 {“BASIC”,“CORE”,“ALL”}

symmetric  默认值1 表示是否假设训练的目标为垂直对称

equalweights 默认值0 表示是否初始化所有的样本为相同的权重

winwidth 24     样本宽度              winheight 24

boosttype 应用的boost(提升)算法的种类 默认值3(GAB)   对应表{ "DAB","RAB", "LB", "GAB" }

stumperror  如果使用的提升算法是DiscreteAdaBoost,使用的错误类型

 默认值0(misclass)对应表{ "misclass", "gini", "entropy" }

maxtreesplits  默认值0  maximum number of nodes in tree. Ifmaxtreesplits < nsplits,  tree will not be built  我对这个参数的含义还不明确,但是在人脸识别的应用中,这个参数一般是默认值0,可以暂且不关心。也希望有大牛看到的话可以给我讲解

minpos  默认值500 这个也可以暂且不管

bg_vecfile 默认值false

 

下面是它的具体实现,沿用http://blog.sina.com.cn/s/blog_5f853eb10100sdgn.html这篇博文中的方法,我只给出关键部分代码,来让整个方法更加简洁,可读性更高。(还有就是这篇博文的注释非常详细,推荐大家看,我在学习时这篇文章对我帮助很大)

注:由于只给出了关键代码,所以有些方法中的参数的定义部分略去了,这并不会影响代码的理解。

void cvCreateTreeCascadeClassifier( const char* dirname,
                                    const char* vecfilename,
                                    const char* bgfilename,
                                    int npos, int nneg, int nstages,
                                    int numprecalculated,
                                    int numsplits,
                                    float minhitrate, float maxfalsealarm,
                                    float weightfraction,
                                    int mode, int symmetric,
                                    int equalweights,
                                    int winwidth, int winheight,
                                    int boosttype, int stumperror,
                                    int maxtreesplits, int minpos, bool bg_vecfile )
{
	//几个关键的指针
    CvTreeCascadeClassifier* tcc = NULL;        //最终得分类器
    CvIntHaarFeatures* haar_features = NULL;    //记录所有的haar特征
    CvHaarTrainingData* training_data = NULL;   //训练样本数据内存区
	
    CV_FUNCNAME( "cvCreateTreeCascadeClassifier" );
//opencv宏: CV_FUNCNAME 定义变量 cvFuncName存放函数名,用于出错时可以报告出错的函数
    
__BEGIN__;
// opencv宏:  __BEGIN__ 和__END__配套使用,当出现error时,EXIT
	
    int i, k;
    CvTreeCascadeNode* leaves;           //代表上一次迭代后的分类器中最后一个强分类器
    int best_num, cur_num;
    CvSize winsize;    //样本大小
    char stage_name[PATH_MAX];
    int total_splits;
    int poscount;
    int negcount;
    int consumed;
    double false_alarm;
    double proctime;
    int nleaves;   //叶子结点个数
    double required_leaf_fa_rate; //叶子虚警率
    float neg_ratio;
    int max_clusters;

    max_clusters = CV_MAX_CLUSTERS;  //值为3
    neg_ratio = (float) nneg / npos; //负样本比重

    nleaves = 1 + MAX( 0, maxtreesplits );
required_leaf_fa_rate = pow( (double) maxfalsealarm, (double) nstages ) / nleaves;
//最大虚警率的nstages次方,再除以叶子总数即为叶子虚警率

    printf( "Required leaf false alarm rate: %g\n", required_leaf_fa_rate );

// CV_CALL和OPENCV_CALL代替了调用函数和检查状态两个步骤
    CV_CALL( tcc = (CvTreeCascadeClassifier*)
        icvLoadTreeCascadeClassifier( dirname, winwidth + 1, &total_splits ) );
// icvLoadTreeCascadeClassifier方法从中间文件dirname中读取已经存在的分类器,一般情况//下我们要训练分类器,并没有已经存在的分类器,所以读到的为null
    
//获取最深的叶子及最后一个训练出的强分类器,因为下一个新的强分类器要接在这个节点的后//面,所以要有一个对当前最深的叶子节点的引用
CV_CALL( leaves = icvFindDeepestLeaves( tcc ) );
	
	//将当前分类器的情况打印出来
    CV_CALL( icvPrintTreeCascade( tcc->root ) );
	
	//创建haar特征,构造出所有满足指定参数要求的Haar特征,并分配所需的存储空间
haar_features = icvCreateIntHaarFeatures( winsize, mode, symmetric );
    printf( "Number of features used : %d\n", haar_features->count );

    training_data = icvCreateHaarTrainingData( winsize, npos + nneg );
	/*
创建训练样本数据,分配用于训练的缓冲区
包括正负样本的矩形积分图和倾斜积分图
为赋值
*/

    if( nstages > 0 )
    {
        /* width-first search in the tree */
        do  //第一层循环,分类器训练到指定阶数或满足最小击中率最大虚警率要求后退出
        {
            CvTreeCascadeNode* parent;
            CvTreeCascadeNode* cur_node;
            CvTreeCascadeNode* last_node;
            parent = leaves;
            leaves = NULL;
            do     //第二层循环,循环直至训练出满足要求的强分类器
            {
                float posweight, negweight;
                double leaf_fa_rate;

                if( parent ) sprintf( buf, "%d", parent->idx );
                else sprintf( buf, "NULL" );
                printf( "\nParent node: %s\n\n", buf );
                printf( "*** 1 cluster ***\n" );
				
			   /*这里是为了后面样本的过滤方法icvGetHaarTrainingDataFromVec的使用。用来过滤出能够通过前面各个stage强分类器的正负样本。*/
                tcc->eval = icvEvalTreeCascadeClassifierFilter;
                
/* find path from the root to the node <parent> 
设置从根节点到叶子节点的路径*/
                icvSetLeafNode( tcc, parent );

                /* load samples 
读取正样本,并用tcc->eval计算通过所有前面的stage的正样本数量,用来计算出检测率。并通过前面训练出的分类器过滤掉少量正采样(被认为是非人脸的正样本,即漏识的正样本,因为几率非常小)然后计算积分图
Consume是遍寻过的正样本数,所以poscount/consume就是当前人脸识别率
*/
                consumed = 0;
                poscount = icvGetHaarTrainingDataFromVec( training_data, 0, npos,
                    (CvIntHaarClassifier*) tcc, vecfilename, &consumed );
                printf( "POS: %d %d %f\n", poscount, consumed, ((double) poscount)/consumed );


                proctime = -TIME( 0 );
			/*
从文件中将负样本读出,使用前面训练出的分类器进行过滤获得若干被错误划分为正样本的负采样。如果得到的数量达不到neg,则会重复提取这些负样本,以获得足够负样本。因此,当被错分的负样本为0,就会出现死循环,此时训练认为已经收敛,可以退出。
*/
                nneg = (int) (neg_ratio * poscount);
                negcount = icvGetHaarTrainingDataFromBG( training_data, poscount, nneg,
                    (CvIntHaarClassifier*) tcc, &false_alarm, bg_vecfile ? bgfilename : NULL );
                printf( "NEG: %d %g\n", negcount, false_alarm );
                printf( "BACKGROUND PROCESSING TIME: %.2f\n", (proctime + TIME( 0 )) );

                if( negcount <= 0 )
                    CV_ERROR( CV_StsError, "Unable to obtain negative samples" );

                leaf_fa_rate = false_alarm;
                if( leaf_fa_rate <= required_leaf_fa_rate )  //小于最低虚警率,退出
                {
                    printf( "Required leaf false alarm rate achieved. "
                            "Branch training terminated.\n" );
                }
   else if( nleaves == 1 && tcc->next_idx == nstages )                        {
//达到设定的stages退出
                    printf( "Required number of stages achieved. "
                            "Branch training terminated.\n" );
                }
                else
                {
				  //需要训练新一个强分类器
				  
                    CvTreeCascadeNode* single_cluster;
                    CvTreeCascadeNode* multiple_clusters;
                    int single_num;
				  
				  
                    icvSetNumSamples( training_data, poscount + negcount );
                    posweight = (equalweights) ? 1.0F / (poscount + negcount) : (0.5F/poscount);
                    negweight = (equalweights) ? 1.0F / (poscount + negcount) : (0.5F/negcount);
				  //设置每个样本的权重和学习信息(用0,1代表)
                    icvSetWeightsAndClasses( training_data,
                        poscount, posweight, 1.0F, negcount, negweight, 0.0F );

                    /* precalculate feature values 
预先计算每个样本的所有特征的特征值,对每一特征,内部调用cvGetSortedIndices,将所有正负样本按特征值升序排序,idx和特征值分别放在training_data的valcache和idxcache中。*/
                    proctime = -TIME( 0 );
                    icvPrecalculate( training_data, haar_features, numprecalculated );
                    printf( "Precalculation time: %.2f\n", (proctime + TIME( 0 )) );

                    /* train stage classifier using all positive samples 
训练由多个弱分类器级联的强分类器*/
                    CV_CALL( single_cluster = icvCreateTreeCascadeNode() );
                    fflush( stdout );
                    proctime = -TIME( 0 );
                    single_cluster->stage =
                        (CvStageHaarClassifier*) icvCreateCARTStageClassifier(
                            training_data, NULL, haar_features,
                            minhitrate, maxfalsealarm, symmetric,
                            weightfraction, numsplits, (CvBoostType) boosttype,
                            (CvStumpError) stumperror, 0 );
                    printf( "Stage training time: %.2f\n", (proctime + TIME( 0 )) );

                    single_num = icvNumSplits( single_cluster->stage );
                    best_num = single_num;
                    best_clusters = 1;
                    multiple_clusters = NULL;

                    printf( "Number of used features: %d\n", single_num );

                    if( maxtreesplits >= 0 )
                    {
                        max_clusters = MIN( max_clusters, maxtreesplits - total_splits + 1 );
                    }

                    /* try clustering */
                    vals = NULL;
                    for( k = 2; k <= max_clusters; k++ )
                    {
   	 //这段循环中包含大段代码,但在训练无分支的级联分类器中并不需要//用到,为了方便理解就略去了
                    } /* try different number of clusters */
                    cvReleaseMat( &vals );
}
//…
            } while( parent );
         //…   
        } while( leaves );
        /* save the cascade to xml file …*/
}

最后想补充的是,对于这个方法,我最开始看了好久还是一知半解,后来发现一个小技巧。利用代码中的printf输出的内容以及实际执行的结果,先把代码运行的流程捋一捋,思路将会清晰许多。所以后面附上我程序运行时的截图,方便大家使用


其中有个表格的参数要解释下:

BACKGROUNGPROCESSING TIME 是负样本切割时间,一般会占用很长的时间

N 为训练层数

%SMP样本占总样本个数

ST.THR阈值,

HR 击中率,

FA 虚警,只有当每一层训练的FA低于你的命令中声明的maxfalsealarm数值才会进入下一层训练

EXP.ERR经验错误率


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值