Chinaunix首页 | 论坛 | 博客
  • 博客访问: 669127
  • 博文数量: 207
  • 博客积分: 1743
  • 博客等级: 上尉
  • 技术积分: 2044
  • 用 户 组: 普通用户
  • 注册时间: 2012-08-20 14:36
文章分类

全部博文(207)

文章存档

2016年(24)

2015年(10)

2014年(50)

2013年(45)

2012年(78)

分类: LINUX

2015-06-18 15:47:23

OpenCV 人脸检测自学(3)

”OpenCV中有两个程序可以训练级联分类器: opencv_haartraining 和opencv_traincascade 。 opencv_traincascade 是一个新程序,根据OpenCV 2.x API 用C++ 编写。这二者主要的区别是opencv_traincascade 支持 Haar[Viola2001] 和 LBP[Liao2007] (Local Binary Patterns) 两种特征,并易于增加其他的特征。与Haar特征相比,LBP特征是整数特征,因此训练和检测过程都会比Haar特征快几倍。LBP和Haar特征用于检测的准确率,是依赖训练过程中的训练数据的质量和训练参数。训练一个与基于Haar特征同样准确度的LBP的分类器是可能的。“


下面按照我觉得需要理解的部分进行分析。

1. 总流程

Traincascade.cpp中调用cvCascadeClassifier的train函数进行训练。


  1. bool CvCascadeClassifier::train( const String _cascadeDirName,//工作目录  
  2.                                 const String _posFilename,//pos.vec  
  3.                                 const String _negFilename,//neg.txt  
  4.                                 int _numPos, int _numNeg,//5000,3000  
  5.                                 int _precalcValBufSize, int _precalcIdxBufSize,  
  6.                                 int _numStages,  
  7.                                 const CvCascadeParams& _cascadeParams,//BOOST, HAAR/LBP,width,height  
  8.                                 const CvFeatureParams& _featureParams,//里面的参数好像是跟上面选的是Haar还是LBP有关,具体还不懂  
  9.                                 const CvCascadeBoostParams& _stageParams,//GAB,minHitRate,maxFalseAlarmRate,weightTrimRate,maxDepth,maxWeakCount  
  10.                                 bool baseFormatSave )  
  11. {  
  12.   
  13.     if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )//读取正负样本的信息  
  14.   
  15.     if ( !load( dirName ) )  
  16.     {  
  17.         cascadeParams = _cascadeParams;//表明cascade是不是用boost,分类器基于的特征类型是Haar还是LBP,正样本的大小是多少  
  18.         featureParams = CvFeatureParams::create(cascadeParams.featureType);//根据featureType来决定返回的是new 一个CvHaarFeatureParams还是CvLBPFeatureParams  
  19.         featureParams->init(_featureParams);  
  20.         stageParams = new CvCascadeBoostParams;  
  21.         *stageParams = _stageParams;//这里填写了APP指定的GAB,minHitRate,maxFalseAlarmRate,weightTrimRate,maxDepth,maxWeakCount,个人认为都是一个训练强分类器需要的一些参数  
  22.         featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);//根据featureType来决定返回的是new 一个CvHaarEvaluator还是CvLBPEvaluator  
  23.         featureEvaluator->init( (CvFeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize );// 把featureEvaluator的里面有关feature的指针都开辟内存,例如sum,title,cls等,然后根据image的长宽调用基类的 init和子类的generateFeatrues来计算所有的Haar/LBP的features的形状存到 vector<Feature> features中  
  24.         stageClassifiers.reserve( numStages );  
  25.     }  
  26.   
  27.     double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /  
  28.                                 (double)stageParams->max_depth;//需要达到的FA的值  
  29.     double tempLeafFARate;  
  30.   
  31.     forint i = startNumStages; i < numStages; i++ )  
  32.     {  
  33.         cout << endl << "===== TRAINING " << i << "-stage =====" << endl;  
  34.         cout << "<BEGIN" << endl;  
  35.   
  36.         if ( !updateTrainingSet( tempLeafFARate ) )// 从正负样本集合里挑选出numPos+numNeg个样本到集合CvCascadeImageReader imgReader中,个人感觉很多时间耗在 这里,因为这里对每一个样本要进行predict,就是说利用当前强分类器已构建的弱分类器进行判断是正样本还是负样本,只有被判断是正样本的情况才被加 到TrainingSet中(第一次的时候当然是默认都是正样本)。所以如果i比较大的时候,在构建负样本的TrainingSet就特别费时。并且把积 分图像等计算出放到evaluator的img中。  
  37.   
  38.         if( tempLeafFARate <= requiredLeafFARate )//check下是否可以中止训练  
  39.   
  40.   
  41.         CvCascadeBoost* tempStage = new CvCascadeBoost;  
  42.         tempStage->train( (CvFeatureEvaluator*)featureEvaluator,  
  43.                            curNumSamples, _precalcValBufSize, _precalcIdxBufSize,  
  44.                           *((CvCascadeBoostParams*)stageParams) );//训练啦啦啦啦啦,训练一个强分类器  
  45.         stageClassifiers.push_back( tempStage );//训练完了一个强分类器添加到结果中  
  46.   
  47.         cout << "END>" << endl;  
  48.   
  49.     //保存params.xml文件  
  50.   
  51.     //保存stage0.xml,stage1.xml,stage2.xml。。。文件  
  52.     }//for i [startNumStages, nStage]  
  53.     save( dirName + CC_CASCADE_FILENAME, baseFormatSave );//保存cascade.xml文件  
  54.     return true;  
  55. }  
  1. bool CvCascadeClassifier::train( const String _cascadeDirName,//工作目录  
  2.                                 const String _posFilename,//pos.vec  
  3.                                 const String _negFilename,//neg.txt  
  4.                                 int _numPos, int _numNeg,//5000,3000  
  5.                                 int _precalcValBufSize, int _precalcIdxBufSize,  
  6.                                 int _numStages,  
  7.                                 const CvCascadeParams& _cascadeParams,//BOOST, HAAR/LBP,width,height  
  8.                                 const CvFeatureParams& _featureParams,//里面的参数好像是跟上面选的是Haar还是LBP有关,具体还不懂  
  9.                                 const CvCascadeBoostParams& _stageParams,//GAB,minHitRate,maxFalseAlarmRate,weightTrimRate,maxDepth,maxWeakCount  
  10.                                 bool baseFormatSave )  
  11. {  
  12.   
  13.     if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )//读取正负样本的信息  
  14.   
  15.     if ( !load( dirName ) )  
  16.     {  
  17.         cascadeParams = _cascadeParams;//表明cascade是不是用boost,分类器基于的特征类型是Haar还是LBP,正样本的大小是多少  
  18.         featureParams = CvFeatureParams::create(cascadeParams.featureType);//根据featureType来决定返回的是new 一个CvHaarFeatureParams还是CvLBPFeatureParams  
  19.         featureParams->init(_featureParams);  
  20.         stageParams = new CvCascadeBoostParams;  
  21.         *stageParams = _stageParams;//这里填写了APP指定的GAB,minHitRate,maxFalseAlarmRate,weightTrimRate,maxDepth,maxWeakCount,个人认为都是一个训练强分类器需要的一些参数  
  22.         featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);//根据featureType来决定返回的是new 一个CvHaarEvaluator还是CvLBPEvaluator  
  23.         featureEvaluator->init( (CvFeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize );// 把featureEvaluator的里面有关feature的指针都开辟内存,例如sum,title,cls等,然后根据image的长宽调用基类的 init和子类的generateFeatrues来计算所有的Haar/LBP的features的形状存到 vector<Feature> features中  
  24.         stageClassifiers.reserve( numStages );  
  25.     }  
  26.   
  27.     double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /  
  28.                                 (double)stageParams->max_depth;//需要达到的FA的值  
  29.     double tempLeafFARate;  
  30.   
  31.     forint i = startNumStages; i < numStages; i++ )  
  32.     {  
  33.         cout << endl << "===== TRAINING " << i << "-stage =====" << endl;  
  34.         cout << "<BEGIN" << endl;  
  35.   
  36.         if ( !updateTrainingSet( tempLeafFARate ) )// 从正负样本集合里挑选出numPos+numNeg个样本到集合CvCascadeImageReader imgReader中,个人感觉很多时间耗在 这里,因为这里对每一个样本要进行predict,就是说利用当前强分类器已构建的弱分类器进行判断是正样本还是负样本,只有被判断是正样本的情况才被加 到TrainingSet中(第一次的时候当然是默认都是正样本)。所以如果i比较大的时候,在构建负样本的TrainingSet就特别费时。并且把积 分图像等计算出放到evaluator的img中。  
  37.   
  38.         if( tempLeafFARate <= requiredLeafFARate )//check下是否可以中止训练  
  39.   
  40.   
  41.         CvCascadeBoost* tempStage = new CvCascadeBoost;  
  42.         tempStage->train( (CvFeatureEvaluator*)featureEvaluator,  
  43.                            curNumSamples, _precalcValBufSize, _precalcIdxBufSize,  
  44.                           *((CvCascadeBoostParams*)stageParams) );//训练啦啦啦啦啦,训练一个强分类器  
  45.         stageClassifiers.push_back( tempStage );//训练完了一个强分类器添加到结果中  
  46.   
  47.         cout << "END>" << endl;  
  48.   
  49.     //保存params.xml文件  
  50.   
  51.     //保存stage0.xml,stage1.xml,stage2.xml。。。文件  
  52.     }//for i [startNumStages, nStage]  
  53.     save( dirName + CC_CASCADE_FILENAME, baseFormatSave );//保存cascade.xml文件  
  54.     return true;  
  55. }  

2.关于特征,以LBP为例(Haar特征网上例子比较多了)

2.1 LBP特征的形状,位置,数目的计算,都是在CvLBPEvaluator.cpp中





2.2 LBP特征的值的计算
这部分是跟加载正负样本数据到训练集合中结合到一起的,也就是CvCascadeClassifier::updateTrainingSet函数,这个函数还返回一个这次加载的负样本的FA值,来决定是否退出训练。




  1. int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, int64& consumed )  
  2. {  
  3.     int getcount = 0;  
  4.     Mat img(cascadeParams.winSize, CV_8UC1);  
  5.     forint i = first; i < first + count; i++ )  
  6.     {  
  7.         for( ; ; )  
  8.         {  
  9.             bool isGetImg = isPositive ? imgReader.getPos( img ) :  
  10.                                            imgReader.getNeg( img );//读一个图像数据到img中,个人觉得需要注意的是这里它不是说每次调用getNeg的时候就是读下一个负样本图像,在getNeg里面它会对一个负样本进行缩放操作来扣下来目标大小的图像。  
  11.             if( !isGetImg )  
  12.                 return getcount;  
  13.             consumed++;  
  14.   
  15.             featureEvaluator->setImage( img, isPositive ? 1 : 0, i );//计算这个img的积分图等信息  
  16.             if( predict( i ) == 1.0F )//这个predict应该是利用之前已有的weak 分类器来判断是否为正样本,所以对于isPositive为False的情况下,会一直去寻找第i个负样本作为setImage  
  17.             {  
  18.                 getcount++;  
  19.                 break;  
  20.             }  
  21.         }  
  22.     }  
  23.     return getcount;  
  24. }  
  1. int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, int64& consumed )  
  2. {  
  3.     int getcount = 0;  
  4.     Mat img(cascadeParams.winSize, CV_8UC1);  
  5.     forint i = first; i < first + count; i++ )  
  6.     {  
  7.         for( ; ; )  
  8.         {  
  9.             bool isGetImg = isPositive ? imgReader.getPos( img ) :  
  10.                                            imgReader.getNeg( img );//读一个图像数据到img中,个人觉得需要注意的是这里它不是说每次调用getNeg的时候就是读下一个负样本图像,在getNeg里面它会对一个负样本进行缩放操作来扣下来目标大小的图像。  
  11.             if( !isGetImg )  
  12.                 return getcount;  
  13.             consumed++;  
  14.   
  15.             featureEvaluator->setImage( img, isPositive ? 1 : 0, i );//计算这个img的积分图等信息  
  16.             if( predict( i ) == 1.0F )//这个predict应该是利用之前已有的weak 分类器来判断是否为正样本,所以对于isPositive为False的情况下,会一直去寻找第i个负样本作为setImage  
  17.             {  
  18.                 getcount++;  
  19.                 break;  
  20.             }  
  21.         }  
  22.     }  
  23.     return getcount;  
  24. }  




读一个图像是从CvCascadeImageReader读的图像,分为读正样本和负样本,正样本是通过vec文件读的,暂且不去管了,反正是固定大小的。负样本的negReader的get为:
  1. bool CvCascadeImageReader::NegReader::get( Mat& _img )  
  2. {  
  3.     if( img.empty() )  
  4.         if ( !nextImg() )//nextImg根据neg.txt的内容读取下一个负样本图片的内容  
  5.             return false;  
  6.   
  7.     Mat mat( winSize.height, winSize.width, CV_8UC1,  
  8.         (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step );//这里是把mat指向计算的img的data的那个地方,最后一个参数指明src的宽,从而mat是相当于从src上的那个位置扣下来的width x height的大小的图片。  
  9.     mat.copyTo(_img);  
  10.   
  11.     if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols )  
  12.         point.x += (int)(stepFactor * winSize.width);  
  13.     else  
  14.     {  
  15.         point.x = offset.x;  
  16.         if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows )  
  17.             point.y += (int)(stepFactor * winSize.height);  
  18.         else  
  19.         {  
  20.             point.y = offset.y;  
  21.             scale *= scaleFactor;//这里把之前缩小后的scale(w_neg/w_pos)乘以sqrt(2)  
  22.             if( scale <= 1.0F )//然后比较下这个scale有没有达到原始图像(没缩放的时候)的大小,要是没有的话就按乘以sqrt(2)的方式放大img  
  23.                 resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) );  
  24.             else  
  25.             {  
  26.                 if ( !nextImg() )//在这把下一个负样本的image给read进来  
  27.                     return false;  
  28.             }  
  29.         }  
  30.     }  
  31.     return true;  
  32. }  
  1. bool CvCascadeImageReader::NegReader::get( Mat& _img )  
  2. {  
  3.     if( img.empty() )  
  4.         if ( !nextImg() )//nextImg根据neg.txt的内容读取下一个负样本图片的内容  
  5.             return false;  
  6.   
  7.     Mat mat( winSize.height, winSize.width, CV_8UC1,  
  8.         (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step );//这里是把mat指向计算的img的data的那个地方,最后一个参数指明src的宽,从而mat是相当于从src上的那个位置扣下来的width x height的大小的图片。  
  9.     mat.copyTo(_img);  
  10.   
  11.     if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols )  
  12.         point.x += (int)(stepFactor * winSize.width);  
  13.     else  
  14.     {  
  15.         point.x = offset.x;  
  16.         if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows )  
  17.             point.y += (int)(stepFactor * winSize.height);  
  18.         else  
  19.         {  
  20.             point.y = offset.y;  
  21.             scale *= scaleFactor;//这里把之前缩小后的scale(w_neg/w_pos)乘以sqrt(2)  
  22.             if( scale <= 1.0F )//然后比较下这个scale有没有达到原始图像(没缩放的时候)的大小,要是没有的话就按乘以sqrt(2)的方式放大img  
  23.                 resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) );  
  24.             else  
  25.             {  
  26.                 if ( !nextImg() )//在这把下一个负样本的image给read进来  
  27.                     return false;  
  28.             }  
  29.         }  
  30.     }  
  31.     return true;  
  32. }  



3. 训练
  1. bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,//包含了sum,tilted,特征的位置等信息  
  2.                            int _numSamples,  
  3.                            int _precalcValBufSize, int _precalcIdxBufSize,  
  4.                            const CvCascadeBoostParams& _params )  
  5. {  
  6.     CV_Assert( !data );  
  7.     clear();  
  8.     data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,  
  9.                                         _precalcValBufSize, _precalcIdxBufSize, _params );  
  10.     CvMemStorage *storage = cvCreateMemStorage();  
  11.     weak = cvCreateSeq( 0sizeof(CvSeq), sizeof(CvBoostTree*), storage );  
  12.     storage = 0;  
  13.   
  14.     set_params( _params );  
  15.     if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )  
  16.         data->do_responses_copy();  
  17.   
  18.     update_weights( 0 );  
  19.   
  20.     cout << "+----+---------+---------+" << endl;  
  21.     cout << "|  N |    HR   |    FA   |" << endl;  
  22.     cout << "+----+---------+---------+" << endl;  
  23.   
  24.     do  
  25.     {  
  26.         CvCascadeBoostTree* tree = new CvCascadeBoostTree;  
  27.         if( !tree->train( data, subsample_mask, this ) )  
  28.         {  
  29.             delete tree;  
  30.             break;  
  31.         }  
  32.         cvSeqPush( weak, &tree );  
  33.         update_weights( tree );  
  34.         trim_weights();  
  35.         if( cvCountNonZero(subsample_mask) == 0 )  
  36.             break;  
  37.     }  
  38.     while( !isErrDesired() && (weak->total < params.weak_count) );  
  39.   
  40.     data->is_classifier = true;  
  41.     data->free_train_data();  
  42.     return true;  
  43. }  
  1. bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,//包含了sum,tilted,特征的位置等信息  
  2.                            int _numSamples,  
  3.                            int _precalcValBufSize, int _precalcIdxBufSize,  
  4.                            const CvCascadeBoostParams& _params )  
  5. {  
  6.     CV_Assert( !data );  
  7.     clear();  
  8.     data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,  
  9.                                         _precalcValBufSize, _precalcIdxBufSize, _params );  
  10.     CvMemStorage *storage = cvCreateMemStorage();  
  11.     weak = cvCreateSeq( 0sizeof(CvSeq), sizeof(CvBoostTree*), storage );  
  12.     storage = 0;  
  13.   
  14.     set_params( _params );  
  15.     if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )  
  16.         data->do_responses_copy();  
  17.   
  18.     update_weights( 0 );  
  19.   
  20.     cout << "+----+---------+---------+" << endl;  
  21.     cout << "|  N |    HR   |    FA   |" << endl;  
  22.     cout << "+----+---------+---------+" << endl;  
  23.   
  24.     do  
  25.     {  
  26.         CvCascadeBoostTree* tree = new CvCascadeBoostTree;  
  27.         if( !tree->train( data, subsample_mask, this ) )  
  28.         {  
  29.             delete tree;  
  30.             break;  
  31.         }  
  32.         cvSeqPush( weak, &tree );  
  33.         update_weights( tree );  
  34.         trim_weights();  
  35.         if( cvCountNonZero(subsample_mask) == 0 )  
  36.             break;  
  37.     }  
  38.     while( !isErrDesired() && (weak->total < params.weak_count) );  
  39.   
  40.     data->is_classifier = true;  
  41.     data->free_train_data();  
  42.     return true;  
  43. }  






阅读(1676) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~