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时,EXITint 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;  //值为3neg_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经验错误率


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常用知识总结——模板Template

1. 模板的概念。 我们已经学过重载(Overloading)&#xff0c;对重载函数而言,C的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如&#xff0c;为求两个数的最大值&#xff0c;我们定义MAX()函数需要对不同的数据类型分别定义不同重载(Overload)版本。 /…

opencv视频读写和视频等间隔采样

今天学习了opencv的HighGUI的内容 总结了两个视频读写demo以备以后进行视频处理和识别用 demo1 视频的读取和写入 按顺序读取视频的每一帧。对于读取的每一帧图像&#xff0c;显示在窗口中&#xff0c;然后转化为灰度图像输出到指定的文件中。 运行期间可以按ESC键退出。 还…

opencv感兴趣通道COI的使用

opencv中设置和获取感兴趣通道COI的函数如下&#xff1a; SetImageCOI 设置感兴趣通道 void cvSetImageCOI( IplImage* image, int coi ); image 图像头. coi 感兴趣通道. 函数 cvSetImageCOI 基于给定的值设置感兴趣的通道。值 0 意味着所有的通道都被选定, 1 意味着第…

Socket通用TCP通信协议设计及实现(防止粘包,可移植,可靠)

Socket通用TCP通信协议设计及实现&#xff08;防止粘包&#xff0c;可移植&#xff0c;可靠&#xff09; 引文 我们接收Socket字节流数据一般都会定义一个数据包协议。我们每次开发一个软件的通信模块时&#xff0c;尽管具体的数据内容是不尽相同的&#xff0c;但是大体上的框…

浅谈 Adaboost 算法

注&#xff1a;本文全文引用自http://blog.csdn.net/carson2005/article/details/41444289 当然作者也是转载的&#xff0c;原文是http://blog.csdn.net/haidao2009/article/details/7514787 写的很好所以转载过来以便之后再次翻阅。 一 Boosting 算法的起源 boost 算法系列的起…

如何理解离散傅里叶变换(一)实数形式傅里叶变换

如何理解离散傅里叶变换&#xff08;一&#xff09; ——实数形式傅里叶变换 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文…

C#复数类Complex的封装

C#复数类Complex的封装----------------------------------------------------------------------------------------------------------------------------------------------------------本文作者&#xff1a;随煜而安时间&#xff1a; 二零一五年七月二十日--------------…

快速傅里叶变换(FFT)

快速傅里叶变换&#xff08;FFT&#xff09; ------------------------------------------------------------------------------------------------------------------- 作者&#xff1a;随煜而安 时间&#xff1a;2015/7/21 注&#xff1a;本文为作者原创文章&#xff0c…

快速傅里叶变换(FFT)的C#实现及详细注释

快速傅里叶变换&#xff08;FFT&#xff09;的C#实现及详细注释 ------------------------------------------------------------------------------------------------------------------- 作者&#xff1a;随煜而安 时间&#xff1a;2015/7/21 注&#xff1a;本文为作者原创文…

风机桨叶故障诊断(一) 样本的获取

风机桨叶故障诊断&#xff08;一&#xff09; 样本的获取今天团队接了个新项目&#xff0c;做一个风机桨叶故障诊断系统。虽然马上就是准备考研的关键期了&#xff0c;可是一想到这是我学习了机器学习后遇到的第一个实际项目&#xff0c;我觉得参与进来&#xff0c;也帮导师分担…

风机桨叶故障诊断(二) 获取图像几何主方向

风机桨叶故障诊断&#xff08;二&#xff09; 获取图像几何主方向 昨天&#xff0c;我将视频资源按帧抽取并筛选得到了可以用来提取样本的图像库。今天还是进行项目的准备工作。当我们拿到一张图片&#xff0c;我们的软件要做的大致可以分为三个步骤&#xff1a;从原图中识别桨…

风机桨叶故障诊断(三) 识别桨叶——初步构建BP神经网络

风机桨叶故障诊断&#xff08;三&#xff09; 识别桨叶——初步构建BP神经网络 新的一天&#xff0c;希望有好的运气。今天开始着手系统的第一个模块&#xff0c;从一幅图像中寻找到桨叶所在的位置。第一直觉我们的识别任务属于难度比较大&#xff0c;干扰因素多的了&#xff…

风机桨叶故障诊断(四) 正负样本准备——从图像中随机扣图

风机桨叶故障诊断&#xff08;四&#xff09; 正负样本准备——从图像中随机扣图 在之前的工作中&#xff0c;我们已经训练了一个400252的三层BP神经网络&#xff0c;通过这个基础的神经网络进行了误差分析&#xff0c;对我们的问题有了更深刻的认识。现在我们要开始不断完善我…

风机桨叶故障诊断(五) 修改隐含层神经元个数的尝试

风机桨叶故障诊断&#xff08;五&#xff09; 修改隐含层神经元个数的尝试 我们已经为训练一个更为稳健的神经网络做好了样本的准备工作&#xff0c;那么我们开始下一步的工作吧&#xff01; 我们已经有了样本集&#xff0c;目前我筛选出来了247个正样本&#xff0c;652个负样本…

风机桨叶故障诊断(六) 利用自编码器进行特征学习

风机桨叶故障诊断&#xff08;六&#xff09; 利用自编码器进行特征学习 在之前的工作中&#xff0c;我已经初步构建了三层的BP神经网络&#xff0c;并已经从样本集的选取&#xff0c;模型的选择&#xff08;隐含层神经元个数&#xff09;&#xff0c;和输出层神经元阈值选择这…