OpenCV 人脸检测自学(6)

来源:互联网 发布:android源码编译apk 编辑:程序博客网 时间:2024/05/16 06:26

opencv_traincascade如何训练强弱分类器

(本文是在别处看到了,留着以后用,那个网站老弹广告)

在(3)中把opencv_traincascade在使用LBP特征的时候的训练准备工作的代码总结了下。下面开始硬着头皮看训练里面的部分了。介于这部分实在是没怎么找到网上介绍的帖子(为啥呢???)所以总结的大部分内容是自己猜测的。以后再回头慢慢完善。


接着上次结束的部分,训练一个强分类器的代码是:


查看文本打印?
  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 );//目前暂且知道这里是调用preCalculate计算所有的特征值  
  10.     CvMemStorage *storage = cvCreateMemStorage();  
  11.     weak = cvCreateSeq( 0, sizeof(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 ) )//应该是<strong>训练</strong>一个弱<strong>分类</strong>器tree  
  28.         {  
  29.             delete tree;  
  30.             break;  
  31.         }  
  32.         cvSeqPush( weak, &tree );//把弱<strong>分类</strong>器添加到强<strong>分类</strong>器里面  
  33.         update_weights( tree );//AdaBoost里面更新weight部分。  
  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. }  
这里面主要分为

  调用preCalculate计算所有样本的所有的特征值

      训练一个弱分类

      根据AdaBoost里面的步骤把样本的weight都更新一遍

  
主要看如何训练一个弱分类器。因为在(4)中总结output的xml文件结构的时候发现每一个node里面都包含了8个很大的数,后来在predict函数里看到这8个数是属于subset,在使用xml文件的时候用里面每一个feature算出来的特征值转换为二进制后是256个bit,然后跟这8个大数的最后32个位进行比较,如果有至少一个bit都是1的话那么就输出一个值,反之另一个值。在这需要指出的是,如果是haar-like feature的话用的不是subset而是threshold,后面比较后就一样了。而且haar-like基本上是一个feature一个弱分类器,而这里subset里可以有多个1出现,所以我理解着就是指有多个feature组成的subset。

而这些subset是如何求出来的呢,我跟踪程序总结如下。(里面用到好多opencv里面ml的数据结构,到现在感觉还是没有入门啊)

boost.cpp里:

查看文本打印?
  1. bool  
  2. CvBoostTree::train( CvDTreeTrainData* _train_data,  
  3.                     const CvMat* _subsample_idx, CvBoost* _ensemble )  
  4. {  
  5.     clear();  
  6.     ensemble = _ensemble;  
  7.     data = _train_data;  
  8.     data->shared = true;  
  9.     return do_train( _subsample_idx );//去CvDTree里<strong>训练</strong>弱<strong>分类</strong>器去  
  10. }  

然后在tree.cpp里

查看文本打印?
  1. bool CvDTree::do_train( const CvMat* _subsample_idx )  
  2. {  
  3.     bool result = false;  
  4.   
  5.     CV_FUNCNAME( "CvDTree::do_train" );  
  6.   
  7.     __BEGIN__;  
  8.   
  9.     root = data->subsample_data( _subsample_idx );  
  10.   
  11.     CV_CALL( try_split_node(root));  
  12.     //在这把root的value啊,爹妈孩子都算出来的,其中跟<strong>分类</strong>器的子集(threshold)有关的split->subset就在此计算的。  
  13.   
  14.     if( root->split )  
  15.     {  
  16.         CV_Assert( root->left );  
  17.         CV_Assert( root->right );  
  18.   
  19.         if( data->params.cv_folds > 0 )  
  20.             CV_CALL( prune_cv() );  
  21.   
  22.         if( !data->shared )  
  23.             data->free_train_data();  
  24.   
  25.         result = true;  
  26.     }  
  27.   
  28.     __END__;  
  29.   
  30.     return result;  
  31. }  

在这里面主要包括:

    找root(回头再研究)

   分裂root。 try_split_node(root),就是在这把我想知道的那些数给算出来的,在opencv文档上看到这么一段话应该是对此的描述“决策树是从根结点递归构造的。用所有的训练数据(特征向量和对应的响应)来在根结点处进行分裂。在每个结点处,优化准则(比如最优分裂)是基于一些基本原则来确定的(比如ML中的“纯度purity”原则被用来进行分类,方差之和用来进行回归)。

  prune_cv() 估计是剪枝(回头再研究)


然后再研究分裂root部分。
tree.cpp中

查看文本打印?
  1. void CvDTree::try_split_node( CvDTreeNode* node )  
  2. {  
  3.     CvDTreeSplit* best_split = 0;  
  4.     int i, n = node->sample_count, vi;  
  5.     bool can_split = true;  
  6.     double quality_scale;  
  7.   
  8.     calc_node_value( node );//计算node->value  
  9.   
  10.     if( node->sample_count <= data->params.min_sample_count ||  
  11.         node->depth >= data->params.max_depth )  
  12.         can_split = false;  
  13.   
  14.     if( can_split && data->is_classifier )  
  15.     {  
  16.         // check if we have a "pure" node,  
  17.         // we assume that cls_count is filled by calc_node_value()  
  18.         int* cls_count = data->counts->data.i;  
  19.         int nz = 0, m = data->get_num_classes();  
  20.         for( i = 0; i < m; i++ )  
  21.             nz += cls_count[i] != 0;  
  22.         if( nz == 1 ) // there is only one class  
  23.             can_split = false;  
  24.     }  
  25.     else if( can_split )  
  26.     {  
  27.         if( sqrt(node->node_risk)/n < data->params.regression_accuracy )  
  28.             can_split = false;  
  29.     }  
  30.   
  31.     if( can_split )  
  32.     {  
  33.         best_split = find_best_split(node);//可能是在这计算split->subset[]  
  34.         // TODO: check the split quality ...  
  35.         node->split = best_split;  
  36.     }  
  37.     if( !can_split || !best_split )  
  38.     {  
  39.         data->free_node_data(node);  
  40.         return;  
  41.     }  
  42.   
  43.     quality_scale = calc_node_dir( node );  
  44.     if( data->params.use_surrogates )  
  45.     {  
  46.         // find all the surrogate splits  
  47.         // and sort them by their similarity to the primary one  
  48.         for( vi = 0; vi < data->var_count; vi++ )  
  49.         {  
  50.             CvDTreeSplit* split;  
  51.             int ci = data->get_var_type(vi);  
  52.   
  53.             if( vi == best_split->var_idx )  
  54.                 continue;  
  55.   
  56.             if( ci >= 0 )  
  57.                 split = find_surrogate_split_cat( node, vi );  
  58.             else  
  59.                 split = find_surrogate_split_ord( node, vi );  
  60.   
  61.             if( split )  
  62.             {  
  63.                 // insert the split  
  64.                 CvDTreeSplit* prev_split = node->split;  
  65.                 split->quality = (float)(split->quality*quality_scale);  
  66.   
  67.                 while( prev_split->next &&  
  68.                        prev_split->next->quality > split->quality )  
  69.                     prev_split = prev_split->next;  
  70.                 split->next = prev_split->next;  
  71.                 prev_split->next = split;  
  72.             }  
  73.         }  
  74.     }  
  75.     split_node_data( node );  
  76.     try_split_node( node->left );///////  
  77.     try_split_node( node->right );//////  
  78.     /* 
  79.     算法回归的继续分裂左右孩子结点。在以下情况下算法可能会在某个结点停止(i.e. stop splitting the node further): 
  80.     树的深度达到了指定的最大值 
  81.     在该结点<strong>训练</strong>样本的数目少于指定值,比如,没有统计意义上的集合来进一步进行结点分裂了。 
  82.     在该结点所有的样本属于同一类(或者,如果是回归的话,变化已经非常小了) 
  83.     跟随机选择相比,能选择到的最好的分裂已经基本没有什么有意义的改进了。 
  84.     */  
  85. }  

我要找的计算在best_split = find_best_split(node)中。

查看文本打印?
  1. CvDTreeSplit* CvDTree::find_best_split( CvDTreeNode* node )  
  2. {  
  3.     DTreeBestSplitFinder finder( this, node );  
  4.   
  5.     cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder);//调用本cpp文件中的DTreeBestSplitFinder::operator()  
  6.   
  7.     CvDTreeSplit *bestSplit = 0;  
  8.     if( finder.bestSplit->quality > 0 )  
  9.     {  
  10.         bestSplit = data->new_split_cat( 0, -1.0f );  
  11.         memcpy( bestSplit, finder.bestSplit, finder.splitSize );  
  12.     }  
  13.   
  14.     return bestSplit;  
  15. }  


查看文本打印?
  1. void DTreeBestSplitFinder::operator()(const BlockedRange& range)  
  2. {  
  3.     int vi, vi1 = range.begin(), vi2 = range.end();  
  4.     int n = node->sample_count;  
  5.     CvDTreeTrainData* data = tree->get_data();  
  6.     AutoBuffer<uchar> inn_buf(2*n*(sizeof(int) + sizeof(float)));  
  7.   
  8.     for( vi = vi1; vi < vi2; vi++ )//{0,8464},就是一共有多少个feature,这个属于外面的循环,对于每一个feature  
  9.     {  
  10.         CvDTreeSplit *res;  
  11.         int ci = data->get_var_type(vi);  
  12.         if( node->get_num_valid(vi) <= 1 )  
  13.             continue;  
  14.   
  15.         if( data->is_classifier )  
  16.         {  
  17.             if( ci >= 0 )  
  18.                 res = tree->find_split_cat_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  19.             else  
  20.                 res = tree->find_split_ord_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  21.         }  
  22.         else  
  23.         {  
  24.             if( ci >= 0 )  
  25.                 res = tree->find_split_cat_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );//计算split 调用的是CvDTreeSplit* CvBoostTree::find_split_cat_reg  
  26.             else  
  27.                 res = tree->find_split_ord_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  28.         }  
  29.   
  30.         if( res && bestSplit->quality < split->quality )  
  31.                 memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)split, splitSize );//更新bestSplit选取那个最好的feature  
  32.     }  
  33. }  

LBP是属于category的,所以调用res = tree->find_split_cat_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf )

boost.cpp中:
查看文本打印?
  1. CvDTreeSplit*  
  2. CvBoostTree::find_split_cat_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf )//cat好像是categary的意思,reg是regression的意思。  
  3. {  
  4.     const double* weights = ensemble->get_subtree_weights()->data.db;  
  5.     int ci = data->get_var_type(vi);  
  6.     int n = node->sample_count;//正负样本个数  
  7.     int mi = data->cat_count->data.i[ci];  
  8.     int base_size = (2*mi+3)*sizeof(double) + mi*sizeof(double*);  
  9.     cv::AutoBuffer<uchar> inn_buf(base_size);  
  10.     if( !_ext_buf )  
  11.         inn_buf.allocate(base_size + n*(2*sizeof(int) + sizeof(float)));  
  12.     uchar* base_buf = (uchar*)inn_buf;  
  13.     uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size;  
  14.   
  15.     int* cat_labels_buf = (int*)ext_buf;  
  16.     const int* cat_labels = data->get_cat_var_data(node, vi, cat_labels_buf);//返回的是1 x n的矩阵,里面是所有样本的第vi个feature的特征值  
  17.     float* responses_buf = (float*)(cat_labels_buf + n);  
  18.     int* sample_indices_buf = (int*)(responses_buf + n);  
  19.     const float* responses = data->get_ord_responses(node, responses_buf, sample_indices_buf);//response就是类别{1,-1}  
  20.   
  21.     double* sum = (double*)cv::alignPtr(base_buf,sizeof(double)) + 1;  
  22.     double* counts = sum + mi + 1;  
  23.     double** sum_ptr = (double**)(counts + mi);  
  24.     double L = 0, R = 0, best_val = init_quality, lsum = 0, rsum = 0;  
  25.     int i, best_subset = -1, subset_i;  
  26.   
  27.     for( i = -1; i < mi; i++ )//mi 256,LBP一共256个category  
  28.         sum[i] = counts[i] = 0;  
  29.   
  30.     // calculate sum response and weight of each category of the input var  
  31.     for( i = 0; i < n; i++ )//对于所有正负样本  
  32.     {  
  33.         int idx = ((cat_labels[i] == 65535) && data->is_buf_16u) ? -1 : cat_labels[i];//对于LBP来说,这个idx都属于[0,255]  
  34.         double w = weights[i];  
  35.         double s = sum[idx] + responses[i]*w;//把weight给乘以(+1)或(-1)求和  
  36.         double nc = counts[idx] + w;//纯粹weight求和  
  37.         sum[idx] = s;  
  38.         counts[idx] = nc;  
  39.     }  
  40.   
  41.     // calculate average response in each category  
  42.     for( i = 0; i < mi; i++ )//对于LBP的256个category  
  43.     {  
  44.         R += counts[i];  
  45.         rsum += sum[i];  
  46.         sum[i] = fabs(counts[i]) > DBL_EPSILON ? sum[i]/counts[i] : 0;//求category里的平均值,要是这个category不太重要的话那么这个值估计会比较小(极端点考虑如果一半是正样本,一半是负样本那正好为0)  
  47.         sum_ptr[i] = sum + i;  
  48.     }  
  49.   
  50.     icvSortDblPtr( sum_ptr, mi, 0 );//把256个值进行升序排序,注意sum_ptr里存的是sum[i]的地址  
  51.   
  52.     // revert back to unnormalized sums  
  53.     // (there should be a very little loss in accuracy)  
  54.     for( i = 0; i < mi; i++ )  
  55.         sum[i] *= counts[i];  
  56.   
  57.     for( subset_i = 0; subset_i < mi-1; subset_i++ )  
  58.     {  
  59.         int idx = (int)(sum_ptr[subset_i] - sum);//表示排序之后的第subset_i个在排序前是第idx个  
  60.         double ni = counts[idx];  
  61.   
  62.         if( ni > FLT_EPSILON )  
  63.         {  
  64.             double s = sum[idx];  
  65.             lsum += s; L += ni;  
  66.             rsum -= s; R -= ni;  
  67.   
  68.             if( L > FLT_EPSILON && R > FLT_EPSILON )  
  69.             {  
  70.                 double val = (lsum*lsum*R + rsum*rsum*L)/(L*R);//要赋值给下面的quality,然后外层的每个feature的循环里还要用到  
  71.                 if( best_val < val )  
  72.                 {  
  73.                     best_val = val;  
  74.                     best_subset = subset_i;  
  75.                 }  
  76.             }  
  77.         }  
  78.     }  
  79.   
  80.     CvDTreeSplit* split = 0;  
  81.     if( best_subset >= 0 )  
  82.     {  
  83.         split = _split ? _split : data->new_split_cat( 0, -1.0f);  
  84.         split->var_idx = vi;//表示这是利用的第几个feature  
  85.         split->quality = (float)best_val;//上层的每个feature的循环里要用来比较好选择哪个feature  
  86.         memset( split->subset, 0, (data->max_c_count + 31)/32 * sizeof(int));  
  87.         for( i = 0; i <= best_subset; i++ )  
  88.         {  
  89.             int idx = (int)(sum_ptr[i] - sum);  
  90.             split->subset[idx >> 5] |= 1 << (idx & 31);  
  91.             //在这里把256个bin放到8个subset中(8 x 32 = 256),每个subset里面包含32位信息来表示这32个情况是存在与否,  
  92.             //但是我的问题是为啥subset定义的大小是2,根本不够使的啊。  
  93.         }  
  94.     }  
  95.     return split;  
  96. }  

就是在这里最后把split->subset[]给写入数据的。

个人感觉sum[i] = fabs(counts[i]) > DBL_EPSILON ? sum[i]/counts[i] : 0;是[1] 里的Eq.4,但是double val = (lsum*lsum*R + rsum*rsum*L)/(L*R);这个公式从哪来的不明白,[1]里对应的应该是Eq.2才对啊。


在xml文件里除了subset还有threshold和output值,关于threshold的思路跟haartraing差不多,关于output值(即left->value 和 right->value)是在boost.cpp的calc_node_value(CvDTreeNode *n)中的。主要代码是:

查看文本打印?
  1. {  
  2.         // in case of regression tree:  
  3.         //  * node value is 1/n*sum_i(Y_i), where Y_i is i-th response,  
  4.         //    n is the number of samples in the node.  
  5.         //  * node risk is the sum of squared errors: sum_i((Y_i - <node_value>)^2)  
  6.         double sum = 0, sum2 = 0, iw;  
  7.         float* values_buf = (float*)(labels_buf + n);  
  8.         int* sample_indices_buf = (int*)(values_buf + n);  
  9.         const float* values = data->get_ord_responses(node, values_buf, sample_indices_buf);  
  10.   
  11.         for( i = 0; i < n; i++ )  
  12.         {  
  13.             int idx = labels[i];  
  14.             double w = weights[idx]/*priors[values[i] > 0]*/;  
  15.             double t = values[i];//{-1,1}  
  16.             rcw[0] += w;  
  17.             subtree_weights[i] = w;  
  18.             sum += t*w;  
  19.             sum2 += t*t*w;  
  20.         }  
  21.   
  22.         iw = 1./rcw[0];  
  23.         node->value = sum*iw;  
  24.         node->node_risk = sum2 - (sum*iw)*sum;  
  25.   
  26.         // renormalize the risk, as in try_split_node the unweighted formula  
  27.         // sqrt(risk)/n is used, rather than sqrt(risk)/sum(weights_i)  
  28.         node->node_risk *= n*iw*n*iw;  
  29.     }  

这部分代码对应的数学公式是出自哪里啊?有知道的朋友帮忙告诉一声。



【1】 Face Detection Based on Multi-Block LBP Representation, L.Zhang


0 0
原创粉丝点击