人脸检测(十)--强分类器源码分析

原文:

http://blog.csdn.net/beerbuddys/article/details/40712957

 下面的内容很长,倒杯水(有茶或者咖啡更好),带上耳机,准备就绪再往下看。下面我们来看强分类器是如何训练的,该过程在CvCascadeBoost::train函数中完成,代码如下:

 

[cpp] view plain copy print?

  1. bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,  
  2.                            int _numSamples,  
  3.                            int _precalcValBufSize, int _precalcIdxBufSize,  
  4.                            const CvCascadeBoostParams& _params )  
  5. {  
  6.     bool isTrained = false;  
  7.     CV_Assert( !data );  
  8.     clear();  
  9.     // 样本的数据都存在 _featureEvaluator 里面,这里把训练相关的数据都  
  10.     // 用CvCascadeBoostTrainData类封装,内部创建了运行时需要的一些内存  
  11.     // 方便后面使用  
  12.     data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,  
  13.                                         _precalcValBufSize, _precalcIdxBufSize, _params );  
  14.     CvMemStorage *storage = cvCreateMemStorage();  
  15.     // 创建一个 CvSeq 序列,存放一个强分类器的所有弱分类器  
  16.     weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage );  
  17.     storage = 0;  
  18.   
  19.   
  20.     set_params( _params );  
  21.     if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )  
  22.     {  
  23.         // 从_featureEvaluator->cls 中拷贝样本的类别信息到 data->responses  
  24.         // 因为这两种boost方法计算式把类别从0/1该为-1/+1使用  
  25.         data->do_responses_copy();  
  26.     }  
  27.     // 设置所有样本初始权值为1/n  
  28.     update_weights( 0 );  
  29.   
  30.   
  31.     cout << "+----+---------+---------+" << endl;  
  32.     cout << "|  N |    HR   |    FA   |" << endl;  
  33.     cout << "+----+---------+---------+" << endl;  
  34.   
  35.   
  36.     do  
  37.     {  
  38.         // 训练一个弱分类器,弱分类器是棵CART树  
  39.         CvCascadeBoostTree* tree = new CvCascadeBoostTree;  
  40.         if( !tree->train( data, subsample_mask, this ) )  
  41.         {  
  42.             delete tree;  
  43.             break;  
  44.         }  
  45.         // 得到弱分类器加入序列  
  46.         cvSeqPush( weak, &tree );  
  47.         // 根据boost公式更新样本数据的权值  
  48.         update_weights( tree );  
  49.         // 根据用户输入参数,把一定比例的(0.05)权值最小的样本去掉  
  50.         trim_weights();  
  51.         // subsample_mask 保存每个样本是否参数训练的标记(值为0/1)  
  52.         // 没有可用样本了,退出训练  
  53.         if( cvCountNonZero(subsample_mask) == 0 )  
  54.             break;  
  55.     } // 如果当前强分类器达到了设置的虚警率要求或弱分类数目达到上限停止  
  56.     while( !isErrDesired() && (weak->total < params.weak_count) );  
  57.   
  58.   
  59.     if(weak->total > 0)  
  60.     {  
  61.         data->is_classifier = true;  
  62.         data->free_train_data();  
  63.         isTrained = true;  
  64.     }  
  65.     else  
  66.         clear();  
  67.   
  68.   
  69.     return isTrained;  
  70. }  

 

bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,
                           int _numSamples,
                           int _precalcValBufSize, int _precalcIdxBufSize,
                           const CvCascadeBoostParams& _params )
{
    bool isTrained = false;
    CV_Assert( !data );
    clear();
	// 样本的数据都存在 _featureEvaluator 里面,这里把训练相关的数据都
	// 用CvCascadeBoostTrainData类封装,内部创建了运行时需要的一些内存
	// 方便后面使用
    data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,
                                        _precalcValBufSize, _precalcIdxBufSize, _params );
    CvMemStorage *storage = cvCreateMemStorage();
	// 创建一个 CvSeq 序列,存放一个强分类器的所有弱分类器
    weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage );
    storage = 0;


    set_params( _params );
    if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )
	{
		// 从_featureEvaluator->cls 中拷贝样本的类别信息到 data->responses
		// 因为这两种boost方法计算式把类别从0/1该为-1/+1使用
		data->do_responses_copy();
	}
	// 设置所有样本初始权值为1/n
    update_weights( 0 );


    cout << "+----+---------+---------+" << endl;
    cout << "|  N |    HR   |    FA   |" << endl;
    cout << "+----+---------+---------+" << endl;


    do
    {
		// 训练一个弱分类器,弱分类器是棵CART树
        CvCascadeBoostTree* tree = new CvCascadeBoostTree;
        if( !tree->train( data, subsample_mask, this ) )
        {
            delete tree;
            break;
        }
		// 得到弱分类器加入序列
        cvSeqPush( weak, &tree );
		// 根据boost公式更新样本数据的权值
        update_weights( tree );
		// 根据用户输入参数,把一定比例的(0.05)权值最小的样本去掉
        trim_weights();
		// subsample_mask 保存每个样本是否参数训练的标记(值为0/1)
		// 没有可用样本了,退出训练
        if( cvCountNonZero(subsample_mask) == 0 )
            break;
    } // 如果当前强分类器达到了设置的虚警率要求或弱分类数目达到上限停止
    while( !isErrDesired() && (weak->total < params.weak_count) );


    if(weak->total > 0)
    {
        data->is_classifier = true;
        data->free_train_data();
        isTrained = true;
    }
    else
        clear();


    return isTrained;
}

 


        代码中首先把训练相关的数据用CvCascadeBoostTrainData封装,一遍后面传递给其它函数,将每个样本的权值设置为1/N,N为总样本数。此后便开始进入弱分类器训练循环。我们接着来看弱分类器的训练,代码位于CvCascadeBoostTree::train中。

 

 

[cpp] view plain copy print?

  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 );  
  10. }  

 

bool
CvBoostTree::train( CvDTreeTrainData* _train_data,
                    const CvMat* _subsample_idx, CvBoost* _ensemble )
{
    clear();
    ensemble = _ensemble;
    data = _train_data;
    data->shared = true;
    return do_train( _subsample_idx );
}


        注意这里的参数_ensemble实际是CvCascadeBoost类型的指针,转入调用CvBoostTree::do_train函数,传入的参数为参与训练的样本的索引数组,具体代码如下:

 

[cpp] view plain copy print?

  1. bool CvDTree::do_train( const CvMat* _subsample_idx )  
  2. {  
  3.     bool result = false;  
  4.   
  5.   
  6.     CV_FUNCNAME( "CvDTree::do_train" );  
  7.   
  8.   
  9.     __BEGIN__;  
  10.     // 创建CART树根节点,设置根节点是数据为输入数据集  
  11.     root = data->subsample_data( _subsample_idx );  
  12.     // 开始分割节点,向树上增加子节点,构成CART树。如果设置弱分类器  
  13.     CV_CALL( try_split_node(root));  
  14.   
  15.   
  16.     if( root->split )  
  17.     {  
  18.         CV_Assert( root->left );  
  19.         CV_Assert( root->right );  
  20.   
  21.   
  22.         if( data->params.cv_folds > 0 )  
  23.             CV_CALL( prune_cv() );  
  24.   
  25.   
  26.         if( !data->shared )  
  27.             data->free_train_data();  
  28.   
  29.   
  30.         result = true;  
  31.     }  
  32.   
  33.   
  34.     __END__;  
  35.   
  36.   
  37.     return result;  
  38. }  

 

bool CvDTree::do_train( const CvMat* _subsample_idx )
{
    bool result = false;


    CV_FUNCNAME( "CvDTree::do_train" );


    __BEGIN__;
	// 创建CART树根节点,设置根节点是数据为输入数据集
    root = data->subsample_data( _subsample_idx );
	// 开始分割节点,向树上增加子节点,构成CART树。如果设置弱分类器
    CV_CALL( try_split_node(root));


    if( root->split )
    {
        CV_Assert( root->left );
        CV_Assert( root->right );


        if( data->params.cv_folds > 0 )
            CV_CALL( prune_cv() );


        if( !data->shared )
            data->free_train_data();


        result = true;
    }


    __END__;


    return result;
}


        创建一个root节点后,对root节点进行分割,调用try_split_node函数实现,代码如下:

 

[cpp] view plain copy print?

  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.     // 计算当前节点的 value,节点的风险 node_risk  
  8.     calc_node_value( node );  
  9.     // 节点样本数目过少样本数(默认为10) 或者树深度达到设置值(默认为1),也就是一个分割节点  
  10.     if( node->sample_count <= data->params.min_sample_count ||  
  11.         node->depth >= data->params.max_depth )  
  12.         can_split = false;  
  13.     // is_classifer:false  
  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.         // 平均error值很小了,说明已经分得很好,没必要继续下去 regression_accuracy (0.01)  
  28.         if( sqrt(node->node_risk)/n < data->params.regression_accuracy )  
  29.             can_split = false;  
  30.     }  
  31.   
  32.   
  33.     if( can_split )  
  34.     {  
  35.         // 调用函数找到最优分割,弱分类器训练的重头戏  
  36.         best_split = find_best_split(node);  
  37.         // TODO: check the split quality ...  
  38.         node->split = best_split;  
  39.     }  
  40.     if( !can_split || !best_split )  
  41.     {  
  42.         data->free_node_data(node);  
  43.         return;  
  44.     }  
  45.     // ignore this  
  46.     quality_scale = calc_node_dir( node );  
  47.     // 级联参数 use_surrogates = use_1se_rule = truncate_pruned_tree = false;  
  48.     if( data->params.use_surrogates )  
  49.     {  
  50.         // find all the surrogate splits  
  51.         // and sort them by their similarity to the primary one  
  52.         for( vi = 0; vi < data->var_count; vi++ )  
  53.         {  
  54.             CvDTreeSplit* split;  
  55.             int ci = data->get_var_type(vi);  
  56.   
  57.   
  58.             if( vi == best_split->var_idx )  
  59.                 continue;  
  60.   
  61.   
  62.             if( ci >= 0 )  
  63.                 split = find_surrogate_split_cat( node, vi );  
  64.             else  
  65.                 split = find_surrogate_split_ord( node, vi );  
  66.   
  67.   
  68.             if( split )  
  69.             {  
  70.                 // insert the split  
  71.                 CvDTreeSplit* prev_split = node->split;  
  72.                 split->quality = (float)(split->quality*quality_scale);  
  73.   
  74.   
  75.                 while( prev_split->next &&  
  76.                        prev_split->next->quality > split->quality )  
  77.                     prev_split = prev_split->next;  
  78.                 split->next = prev_split->next;  
  79.                 prev_split->next = split;  
  80.             }  
  81.         }  
  82.     }  
  83.     // 创建左右子节点,把node的节点的数据分给left,right子节点  
  84. split_node_data( node );  
  85. // 递归实现子节点划分,分割左右子节点  
  86.     try_split_node( node->left );  
  87.     try_split_node( node->right );  
  88. }  

 

void CvDTree::try_split_node( CvDTreeNode* node )
{
    CvDTreeSplit* best_split = 0;
    int i, n = node->sample_count, vi;
    bool can_split = true;
    double quality_scale;
	// 计算当前节点的 value,节点的风险 node_risk
    calc_node_value( node );
	// 节点样本数目过少样本数(默认为10) 或者树深度达到设置值(默认为1),也就是一个分割节点
    if( node->sample_count <= data->params.min_sample_count ||
        node->depth >= data->params.max_depth )
        can_split = false;
	// is_classifer:false
    if( can_split && data->is_classifier )
    {
        // check if we have a "pure" node,
        // we assume that cls_count is filled by calc_node_value()
        int* cls_count = data->counts->data.i;
        int nz = 0, m = data->get_num_classes();
        for( i = 0; i < m; i++ )
            nz += cls_count[i] != 0;
        if( nz == 1 ) // there is only one class
            can_split = false;
    }
    else if( can_split )
    {
		// 平均error值很小了,说明已经分得很好,没必要继续下去 regression_accuracy (0.01)
        if( sqrt(node->node_risk)/n < data->params.regression_accuracy )
            can_split = false;
    }


    if( can_split )
    {
		// 调用函数找到最优分割,弱分类器训练的重头戏
        best_split = find_best_split(node);
        // TODO: check the split quality ...
        node->split = best_split;
    }
    if( !can_split || !best_split )
    {
        data->free_node_data(node);
        return;
    }
	// ignore this
    quality_scale = calc_node_dir( node );
	// 级联参数 use_surrogates = use_1se_rule = truncate_pruned_tree = false;
    if( data->params.use_surrogates )
    {
        // find all the surrogate splits
        // and sort them by their similarity to the primary one
        for( vi = 0; vi < data->var_count; vi++ )
        {
            CvDTreeSplit* split;
            int ci = data->get_var_type(vi);


            if( vi == best_split->var_idx )
                continue;


            if( ci >= 0 )
                split = find_surrogate_split_cat( node, vi );
            else
                split = find_surrogate_split_ord( node, vi );


            if( split )
            {
                // insert the split
                CvDTreeSplit* prev_split = node->split;
                split->quality = (float)(split->quality*quality_scale);


                while( prev_split->next &&
                       prev_split->next->quality > split->quality )
                    prev_split = prev_split->next;
                split->next = prev_split->next;
                prev_split->next = split;
            }
        }
    }
	// 创建左右子节点,把node的节点的数据分给left,right子节点
split_node_data( node );
// 递归实现子节点划分,分割左右子节点
    try_split_node( node->left );
    try_split_node( node->right );
}


        创建一个新的分割节点最为关键的就是要找到一个特征和阈值的组合,该分割能够把数据划分得最好(在误差的意义上),在find_best_split(node)中实现,我们看代码:

 

[cpp] view plain copy print?

  1. CvDTreeSplit* CvDTree::find_best_split( CvDTreeNode* node )  
  2. {  
  3.     DTreeBestSplitFinder finder( this, node );  
  4.     // 在开启TBB情况下,多核并行处理  
  5.     cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder);  
  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.   
  15.     return bestSplit;  
  16. }  

 

CvDTreeSplit* CvDTree::find_best_split( CvDTreeNode* node )
{
    DTreeBestSplitFinder finder( this, node );
	// 在开启TBB情况下,多核并行处理
    cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder);
	// 保存最优分割
    CvDTreeSplit *bestSplit = 0;
    if( finder.bestSplit->quality > 0 )
    {
        bestSplit = data->new_split_cat( 0, -1.0f );
        memcpy( bestSplit, finder.bestSplit, finder.splitSize );
    }


    return bestSplit;
}


        关键代码位于的DTreeBestSplitFinder::operator()函数中,代码遍历特征序号为range.begin()--range.end()之间的特征,调用tree->find_split_ord_reg函数对特征vi找到最优的阈值。

 

[cpp] view plain copy print?

  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.     // 遍历特征 vi  
  8.     for( vi = vi1; vi < vi2; vi++ )  
  9.     {  
  10.         CvDTreeSplit *res;  
  11.         // 取特征 数值特征为 -(vi+1) < 0,编码类型特征 vi >= 0,仅用作标识  
  12.         int ci = data->get_var_type(vi);  
  13.         if( node->get_num_valid(vi) <= 1 )  
  14.             continue;  
  15.   
  16.   
  17.         if( data->is_classifier )  
  18.         {  
  19.             if( ci >= 0 )  
  20.                 res = tree->find_split_cat_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  21.             else  
  22.                 res = tree->find_split_ord_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  23.         }  
  24.         else  
  25.         {  
  26.             if( ci >= 0 )  
  27.                 res = tree->find_split_cat_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  28.             else // 找到特征vi对应的最优分割,也就是求取最优阈值   
  29.                 res = tree->find_split_ord_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );  
  30.         }  
  31. // 更新bestSplit为quality最高的分割  
  32.         if( res && bestSplit->quality < split->quality )  
  33.                 memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)split, splitSize );  
  34.     }  
  35. }  
  36. DTreeBestSplitFinder::join函数的作用是在使用TBB库并行计算时,把不同线程的运行结果进行合并,保存最优结果,这里保留quality更好的分割节点。  
  37. void DTreeBestSplitFinder::join( DTreeBestSplitFinder& rhs )  
  38. {  
  39.     if( bestSplit->quality < rhs.bestSplit->quality )  
  40.         memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)rhs.bestSplit, splitSize );  
  41. }  

 

void DTreeBestSplitFinder::operator()(const BlockedRange& range)
{
    int vi, vi1 = range.begin(), vi2 = range.end();
    int n = node->sample_count;
    CvDTreeTrainData* data = tree->get_data();
    AutoBuffer<uchar> inn_buf(2*n*(sizeof(int) + sizeof(float)));
	// 遍历特征 vi
    for( vi = vi1; vi < vi2; vi++ )
    {
        CvDTreeSplit *res;
		// 取特征 数值特征为 -(vi+1) < 0,编码类型特征 vi >= 0,仅用作标识
        int ci = data->get_var_type(vi);
        if( node->get_num_valid(vi) <= 1 )
            continue;


        if( data->is_classifier )
        {
            if( ci >= 0 )
                res = tree->find_split_cat_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
            else
                res = tree->find_split_ord_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
        }
        else
        {
            if( ci >= 0 )
                res = tree->find_split_cat_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
            else // 找到特征vi对应的最优分割,也就是求取最优阈值 
                res = tree->find_split_ord_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
        }
// 更新bestSplit为quality最高的分割
        if( res && bestSplit->quality < split->quality )
                memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)split, splitSize );
    }
}
DTreeBestSplitFinder::join函数的作用是在使用TBB库并行计算时,把不同线程的运行结果进行合并,保存最优结果,这里保留quality更好的分割节点。
void DTreeBestSplitFinder::join( DTreeBestSplitFinder& rhs )
{
    if( bestSplit->quality < rhs.bestSplit->quality )
        memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)rhs.bestSplit, splitSize );
}


        Haar特征的值两个(或三个)矩形区域均值之差,是一个浮点数值。对所有样本求取特征值得到一个数组,一个分割就是找到一个最有的阈值把数据分成左右两部分,使得两边的总体误差最小。回顾一下原理部分讲过的例子,红色为正样本,绿色为负样本,阈值就是找一条垂直x轴的分割线。

 

 

 

 

 

 

 

Haar特征值数据

 

取阈值为1.5

 

取阈值为5.5

 

        代码首先调用get_ord_var_data计算当期节点上所有样本的第vi个haar特征值,然后按照从小到大排序返回排序后的值数组values,然后根据values找到最优分割阈值(也就是移动图中的蓝色分割线)。代码依次测试最优阈值为values[i]和values[i+1]的中值,找到最小化总体误差的一个,返回得到的split。

[cpp] view plain copy print?

  1. CvDTreeSplit*  
  2. CvBoostTree::find_split_ord_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf )  
  3. {  
  4.     const float epsilon = FLT_EPSILON*2;  
  5.     const double* weights = ensemble->get_subtree_weights()->data.db;  
  6.     int n = node->sample_count;  
  7.     int n1 = node->get_num_valid(vi);  
  8.   
  9.   
  10.     cv::AutoBuffer<uchar> inn_buf;  
  11.     if( !_ext_buf )  
  12.         inn_buf.allocate(2*n*(sizeof(int)+sizeof(float)));  
  13.     uchar* ext_buf = _ext_buf ? _ext_buf : (uchar*)inn_buf;  
  14.   
  15.   
  16.     float* values_buf = (float*)ext_buf;  
  17.     int* indices_buf = (int*)(values_buf + n);  
  18.     int* sample_indices_buf = indices_buf + n;  
  19.     const float* values = 0;  
  20.     const int* indices = 0;  
  21.     // 计算所有样本的第vi个haar特征值,values为特征值数组,已经从小到大排序  
  22.     data->get_ord_var_data( node, vi, values_buf, indices_buf, &values, &indices, sample_indices_buf );  
  23.     float* responses_buf = (float*)(indices_buf + n);  
  24.     // 取所有样本的真是响应值(+1/-1)  
  25.     const float* responses = data->get_ord_responses( node, responses_buf, sample_indices_buf );  
  26.   
  27.   
  28.     int i, best_i = -1;  
  29.     double L = 0, R = weights[n]; // R 为总权值和  
  30.     double best_val = init_quality, lsum = 0, rsum = node->value*R;  
  31.   
  32.   
  33.     // compensate for missing values  
  34.     for( i = n1; i < n; i++ )  
  35.     {  
  36.         int idx = indices[i];  
  37.         double w = weights[idx];  
  38.         rsum -= responses[idx]*w;  
  39.         R -= w;  
  40.     }  
  41.   
  42.   
  43.     // find the optimal split  
  44.     for( i = 0; i < n1 - 1; i++ )  
  45.     {  
  46.         int idx = indices[i];  
  47.         double w = weights[idx];  
  48.         double t = responses[idx]*w;  
  49.         L += w; R -= w;       // L为左边权值和,R为右边权值和  
  50.         lsum += t; rsum -= t; // lsum左边正样本权值和-负样本权值和  
  51.                               // rsum右边正样本权值和-负样本权值和  
  52.         if( values[i] + epsilon < values[i+1] )  
  53.         {  
  54.             // 计算当前分割下的error  
  55.             double val = (lsum*lsum*R + rsum*rsum*L)/(L*R);  
  56.             if( best_val < val )  
  57.             {  
  58.                 best_val = val;  
  59.                 best_i = i;  
  60.             }  
  61.         }  
  62.     }  
  63.   
  64.   
  65.     CvDTreeSplit* split = 0;  
  66.     if( best_i >= 0 )  
  67.     {  
  68.         split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f );  
  69.         split->var_idx = vi;  
  70.         // 最优阈值取前后两个特征值的中间值  
  71.         split->ord.c = (values[best_i] + values[best_i+1])*0.5f;  
  72.         split->ord.split_point = best_i; // 最优特征值序号  
  73.         split->inversed = 0;  
  74.         split->quality = (float)best_val;// 最小error  
  75.     }  
  76.     return split;  
  77. }  

 

CvDTreeSplit*
CvBoostTree::find_split_ord_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf )
{
    const float epsilon = FLT_EPSILON*2;
    const double* weights = ensemble->get_subtree_weights()->data.db;
    int n = node->sample_count;
    int n1 = node->get_num_valid(vi);


    cv::AutoBuffer<uchar> inn_buf;
    if( !_ext_buf )
        inn_buf.allocate(2*n*(sizeof(int)+sizeof(float)));
    uchar* ext_buf = _ext_buf ? _ext_buf : (uchar*)inn_buf;


    float* values_buf = (float*)ext_buf;
    int* indices_buf = (int*)(values_buf + n);
    int* sample_indices_buf = indices_buf + n;
    const float* values = 0;
    const int* indices = 0;
	// 计算所有样本的第vi个haar特征值,values为特征值数组,已经从小到大排序
    data->get_ord_var_data( node, vi, values_buf, indices_buf, &values, &indices, sample_indices_buf );
    float* responses_buf = (float*)(indices_buf + n);
	// 取所有样本的真是响应值(+1/-1)
    const float* responses = data->get_ord_responses( node, responses_buf, sample_indices_buf );


    int i, best_i = -1;
    double L = 0, R = weights[n]; // R 为总权值和
    double best_val = init_quality, lsum = 0, rsum = node->value*R;


    // compensate for missing values
    for( i = n1; i < n; i++ )
    {
        int idx = indices[i];
        double w = weights[idx];
        rsum -= responses[idx]*w;
        R -= w;
    }


    // find the optimal split
    for( i = 0; i < n1 - 1; i++ )
    {
        int idx = indices[i];
        double w = weights[idx];
        double t = responses[idx]*w;
        L += w; R -= w;       // L为左边权值和,R为右边权值和
        lsum += t; rsum -= t; // lsum左边正样本权值和-负样本权值和
		                      // rsum右边正样本权值和-负样本权值和
        if( values[i] + epsilon < values[i+1] )
        {
			// 计算当前分割下的error
            double val = (lsum*lsum*R + rsum*rsum*L)/(L*R);
            if( best_val < val )
            {
                best_val = val;
                best_i = i;
            }
        }
    }


    CvDTreeSplit* split = 0;
    if( best_i >= 0 )
    {
        split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f );
        split->var_idx = vi;
		// 最优阈值取前后两个特征值的中间值
        split->ord.c = (values[best_i] + values[best_i+1])*0.5f;
        split->ord.split_point = best_i; // 最优特征值序号
        split->inversed = 0;
        split->quality = (float)best_val;// 最小error
    }
    return split;
}


        我们再来看看如何求取haar特征的,get_ord_var_data函数代码较长,没有注释的部分大可直接略过,直接看注释部分,调用(*featureEvaluator)( vi, sampleIndices[i])计算第smapleIndices[i]个样本的第vi个特征值。计算完成后,调用icvSortIntAux按照从小到大排序。

 

[cpp] view plain copy print?

  1. void CvCascadeBoostTrainData::get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf,  
  2.         const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf )  
  3. {  
  4.     int nodeSampleCount = n->sample_count;  
  5.     const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf);  
  6.   
  7.   
  8.     if ( vi < numPrecalcIdx )  
  9.     {  
  10.         if( !is_buf_16u )  
  11.             *sortedIndices = buf->data.i + n->buf_idx*get_length_subbuf() + vi*sample_count + n->offset;  
  12.         else  
  13.         {  
  14.             const unsigned short* shortIndices = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() +  
  15.                                                     vi*sample_count + n->offset );  
  16.             for( int i = 0; i < nodeSampleCount; i++ )  
  17.                 sortedIndicesBuf[i] = shortIndices[i];  
  18.   
  19.   
  20.             *sortedIndices = sortedIndicesBuf;  
  21.         }  
  22.   
  23.   
  24.         if( vi < numPrecalcVal )  
  25.         {  
  26.             for( int i = 0; i < nodeSampleCount; i++ )  
  27.             {  
  28.                 int idx = (*sortedIndices)[i];  
  29.                 idx = sampleIndices[idx];  
  30.                 ordValuesBuf[i] =  valCache.at<float>( vi, idx);  
  31.             }  
  32.         }  
  33.         else  
  34.         {  
  35.             for( int i = 0; i < nodeSampleCount; i++ )  
  36.             {  
  37.                 int idx = (*sortedIndices)[i];  
  38.                 idx = sampleIndices[idx];  
  39.                 ordValuesBuf[i] = (*featureEvaluator)( vi, idx);  
  40.             }  
  41.         }  
  42.     }  
  43.     else // vi >= numPrecalcIdx 特征没有计算 valCache  
  44.     {  
  45.         cv::AutoBuffer<float> abuf(nodeSampleCount);  
  46.         float* sampleValues = &abuf[0];  
  47.   
  48.   
  49.         if ( vi < numPrecalcVal )  
  50.         {  
  51.             for( int i = 0; i < nodeSampleCount; i++ )  
  52.             {  
  53.                 sortedIndicesBuf[i] = i;  
  54.                 sampleValues[i] = valCache.at<float>( vi, sampleIndices[i] );  
  55.             }  
  56.         }  
  57.         else  
  58.         {   // 计算节点样本的特征值,通过featureEvaluator.operator()得到  
  59.             // 样本索引存在sampleIndices里面  
  60.             for( int i = 0; i < nodeSampleCount; i++ )  
  61.             {  
  62.                 sortedIndicesBuf[i] = i;  
  63.                 //  调用featureEvaluator.operator()计算第vi个haar特征值  
  64.                 sampleValues[i] = (*featureEvaluator)( vi, sampleIndices[i]);  
  65.             }  
  66.         }  
  67.         // 对索引进行排序,特征值从小到大,sampleValues 里面放的是特征值  
  68.         // icvSortIntAux 根据sampleValues的值排序  
  69.         icvSortIntAux( sortedIndicesBuf, nodeSampleCount, &sampleValues[0] );  
  70.         for( int i = 0; i < nodeSampleCount; i++ )  
  71.             ordValuesBuf[i] = (&sampleValues[0])[sortedIndicesBuf[i]];  
  72.         *sortedIndices = sortedIndicesBuf;  
  73.     }  
  74.   
  75.   
  76.     // *sortedIndices  样本排序后的索引数组,指示特征值在排序中的位置  
  77.     // *ordValues      特征值排序结果,用户计算最优分割  
  78.     *ordValues = ordValuesBuf;  
  79. }  

 

void CvCascadeBoostTrainData::get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf,
        const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf )
{
    int nodeSampleCount = n->sample_count;
    const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf);


    if ( vi < numPrecalcIdx )
    {
        if( !is_buf_16u )
            *sortedIndices = buf->data.i + n->buf_idx*get_length_subbuf() + vi*sample_count + n->offset;
        else
        {
            const unsigned short* shortIndices = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() +
                                                    vi*sample_count + n->offset );
            for( int i = 0; i < nodeSampleCount; i++ )
                sortedIndicesBuf[i] = shortIndices[i];


            *sortedIndices = sortedIndicesBuf;
        }


        if( vi < numPrecalcVal )
        {
            for( int i = 0; i < nodeSampleCount; i++ )
            {
                int idx = (*sortedIndices)[i];
                idx = sampleIndices[idx];
                ordValuesBuf[i] =  valCache.at<float>( vi, idx);
            }
        }
        else
        {
            for( int i = 0; i < nodeSampleCount; i++ )
            {
                int idx = (*sortedIndices)[i];
                idx = sampleIndices[idx];
                ordValuesBuf[i] = (*featureEvaluator)( vi, idx);
            }
        }
    }
    else // vi >= numPrecalcIdx 特征没有计算 valCache
    {
        cv::AutoBuffer<float> abuf(nodeSampleCount);
        float* sampleValues = &abuf[0];


        if ( vi < numPrecalcVal )
        {
            for( int i = 0; i < nodeSampleCount; i++ )
            {
                sortedIndicesBuf[i] = i;
                sampleValues[i] = valCache.at<float>( vi, sampleIndices[i] );
            }
        }
        else
        {	// 计算节点样本的特征值,通过featureEvaluator.operator()得到
			// 样本索引存在sampleIndices里面
            for( int i = 0; i < nodeSampleCount; i++ )
            {
                sortedIndicesBuf[i] = i;
				//  调用featureEvaluator.operator()计算第vi个haar特征值
                sampleValues[i] = (*featureEvaluator)( vi, sampleIndices[i]);
            }
        }
		// 对索引进行排序,特征值从小到大,sampleValues 里面放的是特征值
		// icvSortIntAux 根据sampleValues的值排序
        icvSortIntAux( sortedIndicesBuf, nodeSampleCount, &sampleValues[0] );
        for( int i = 0; i < nodeSampleCount; i++ )
            ordValuesBuf[i] = (&sampleValues[0])[sortedIndicesBuf[i]];
        *sortedIndices = sortedIndicesBuf;
    }


	// *sortedIndices  样本排序后的索引数组,指示特征值在排序中的位置
	// *ordValues      特征值排序结果,用户计算最优分割
    *ordValues = ordValuesBuf;
}

 


计算特征值
        回顾一下haar特征,两个或三个矩形加权求和。权值的作用是为了平衡区域的面积,例如A和B两个特征,黑白面积一样,权值都为1:1,而C特征,实际是用包含黑色的大矩形去减中间的黑色矩形,因此权值为1:3,也就是黑色矩形权值为3。

 

 


        前面提到过使用积分图来加速求取一个矩形内的灰度和。积分图中每个点(x,y)保存的是从(0,0)开始的矩形的灰度和。

 


        在积分图上求一个矩形内灰度和在积分图上只需要四个点进行运算,例如矩形D的和为v[4]+v[1]-v[2]-v[3]。其中v[]表示积分图对应的值。

 


        现在来看代码的实现,调用calc函数计算矩形区域的加权和,为了加快计算,矩形不是使用起点和宽高表示,创建了fastRect结构来保存上图中1234四个点相对于图像原点的偏离量,记作p0,p1,p2,p3。
        得到加权和之后,在operator()函数中还除以了normfactor归一化系数,这一值就是图像的标准差,去除因不同对比度造成的差异。

 

[cpp] view plain copy print?

  1. inline float CvHaarEvaluator::operator()(int featureIdx, int sampleIdx) const  
  2. {  
  3.     float nf = normfactor.at<float>(0, sampleIdx);  
  4.     return !nf ? 0.0f : (features[featureIdx].calc( sum, tilted, sampleIdx)/nf);  
  5. }  
  6.   
  7.   
  8. inline float CvHaarEvaluator::Feature::calc( const cv::Mat &_sum, const cv::Mat &_tilted, size_t y) const  
  9. {  
  10.     const int* img = tilted ? _tilted.ptr<int>((int)y) : _sum.ptr<int>((int)y);  
  11.     float ret = rect[0].weight * (img[fastRect[0].p0] - img[fastRect[0].p1] - img[fastRect[0].p2] + img[fastRect[0].p3] ) +  
  12.         rect[1].weight * (img[fastRect[1].p0] - img[fastRect[1].p1] - img[fastRect[1].p2] + img[fastRect[1].p3] );  
  13.     if( rect[2].weight != 0.0f )  
  14.         ret += rect[2].weight * (img[fastRect[2].p0] - img[fastRect[2].p1] - img[fastRect[2].p2] + img[fastRect[2].p3] );  
  15.     return ret;  
  16. }  

 

inline float CvHaarEvaluator::operator()(int featureIdx, int sampleIdx) const
{
    float nf = normfactor.at<float>(0, sampleIdx);
    return !nf ? 0.0f : (features[featureIdx].calc( sum, tilted, sampleIdx)/nf);
}


inline float CvHaarEvaluator::Feature::calc( const cv::Mat &_sum, const cv::Mat &_tilted, size_t y) const
{
    const int* img = tilted ? _tilted.ptr<int>((int)y) : _sum.ptr<int>((int)y);
    float ret = rect[0].weight * (img[fastRect[0].p0] - img[fastRect[0].p1] - img[fastRect[0].p2] + img[fastRect[0].p3] ) +
        rect[1].weight * (img[fastRect[1].p0] - img[fastRect[1].p1] - img[fastRect[1].p2] + img[fastRect[1].p3] );
    if( rect[2].weight != 0.0f )
        ret += rect[2].weight * (img[fastRect[2].p0] - img[fastRect[2].p1] - img[fastRect[2].p2] + img[fastRect[2].p3] );
    return ret;
}


代码走到这里,一个弱分类器的流程就全部走完了。我们得到了一个弱分类器。我们继续回到强分类器训练流程中看看接下来做了什么工作。

 

[cpp] view plain copy print?

  1. bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,  
  2.                            int _numSamples,  
  3.                            int _precalcValBufSize, int _precalcIdxBufSize,  
  4.                            const CvCascadeBoostParams& _params )  
  5. {  
  6.     bool isTrained = false;  
  7.     // ...此前代码略过  
  8.     // 设置所有样本初始权值为1/n  
  9.     update_weights( 0 );  
  10.   
  11.   
  12.     cout << "+----+---------+---------+" << endl;  
  13.     cout << "|  N |    HR   |    FA   |" << endl;  
  14.     cout << "+----+---------+---------+" << endl;  
  15.   
  16.   
  17.     do  
  18.     {  
  19.         // 训练一个弱分类器,弱分类器是棵CART树  
  20.         CvCascadeBoostTree* tree = new CvCascadeBoostTree;  
  21.         if( !tree->train( data, subsample_mask, this ) )  
  22.         {  
  23.             delete tree;  
  24.             break;  
  25.         }  
  26.         // 得到弱分类器加入序列  
  27.         cvSeqPush( weak, &tree );  
  28.         // 根据boost公式更新样本数据的权值  
  29.         update_weights( tree );  
  30.         // 根据用户输入参数,把一定比例的(0.05)权值最小的样本去掉  
  31.         trim_weights();  
  32.         // subsample_mask 保存每个样本是否参数训练的标记(值为0/1)  
  33.         // 没有可用样本了,退出训练  
  34.         if( cvCountNonZero(subsample_mask) == 0 )  
  35.             break;  
  36.     } // 如果当前强分类器达到了设置的虚警率要求或弱分类数目达到上限停止  
  37.     while( !isErrDesired() && (weak->total < params.weak_count) );  
  38.   
  39.   
  40.     if(weak->total > 0)  
  41.     {  
  42.         data->is_classifier = true;  
  43.         data->free_train_data();  
  44.         isTrained = true;  
  45.     }  
  46.     else  
  47.         clear();  
  48.   
  49.   
  50.     return isTrained;  
  51. }  

 

bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,
                           int _numSamples,
                           int _precalcValBufSize, int _precalcIdxBufSize,
                           const CvCascadeBoostParams& _params )
{
    bool isTrained = false;
    // ...此前代码略过
	// 设置所有样本初始权值为1/n
    update_weights( 0 );


    cout << "+----+---------+---------+" << endl;
    cout << "|  N |    HR   |    FA   |" << endl;
    cout << "+----+---------+---------+" << endl;


    do
    {
		// 训练一个弱分类器,弱分类器是棵CART树
        CvCascadeBoostTree* tree = new CvCascadeBoostTree;
        if( !tree->train( data, subsample_mask, this ) )
        {
            delete tree;
            break;
        }
		// 得到弱分类器加入序列
        cvSeqPush( weak, &tree );
		// 根据boost公式更新样本数据的权值
        update_weights( tree );
		// 根据用户输入参数,把一定比例的(0.05)权值最小的样本去掉
        trim_weights();
		// subsample_mask 保存每个样本是否参数训练的标记(值为0/1)
		// 没有可用样本了,退出训练
        if( cvCountNonZero(subsample_mask) == 0 )
            break;
    } // 如果当前强分类器达到了设置的虚警率要求或弱分类数目达到上限停止
    while( !isErrDesired() && (weak->total < params.weak_count) );


    if(weak->total > 0)
    {
        data->is_classifier = true;
        data->free_train_data();
        isTrained = true;
    }
    else
        clear();


    return isTrained;
}

 


        首先将得到的弱分类器加入序列weak,然后根据Adaboost公式(我们采用的Gentle adaboost)更新所有样本的权值,为下一轮弱分类器训练作准备。更新权值调用trim_weights函数,根据的设置,将一定比例的权值很小的样本从训练集中去除(能够提高训练的速度,对性能有一定的影响)。
我们来看权值更新是如何进行的。当传入tree为0时,设置每个样本权值为1/n。得到一个弱分类器后,传入上一个弱分类器tree,样本i更新后的为:w_i *= exp(-y_i*f(x_i)),其中y_i为样本的真实响应(+1/-1值)f(x_i)表示输入的弱分类器tree对样本i的响应值。最后将调整后的样本总权值调整为1。

 

 

[cpp] view plain copy print?

  1. void CvCascadeBoost::update_weights( CvBoostTree* tree )  
  2. {  
  3.     int n = data->sample_count;  
  4.     double sumW = 0.;  
  5.     int step = 0;  
  6.     float* fdata = 0;  
  7.     int *sampleIdxBuf;  
  8.     const int* sampleIdx = 0;  
  9.     int inn_buf_size = ((params.boost_type == LOGIT) || (params.boost_type == GENTLE) ? n*sizeof(int) : 0) +  
  10.                        ( !tree ? n*sizeof(int) : 0 );  
  11.     cv::AutoBuffer<uchar> inn_buf(inn_buf_size);  
  12.     uchar* cur_inn_buf_pos = (uchar*)inn_buf;  
  13.     if ( (params.boost_type == LOGIT) || (params.boost_type == GENTLE) )  
  14.     {  
  15.         step = CV_IS_MAT_CONT(data->responses_copy->type) ?  
  16.             1 : data->responses_copy->step / CV_ELEM_SIZE(data->responses_copy->type);  
  17.         // data->responses_copy = data->responses 为样本的真实相应(正样本+1,负样本-1)  
  18.         fdata = data->responses_copy->data.fl;  
  19.         sampleIdxBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(sampleIdxBuf + n);  
  20.         sampleIdx = data->get_sample_indices( data->data_root, sampleIdxBuf );  
  21.     }  
  22.     CvMat* buf = data->buf;  
  23.     size_t length_buf_row = data->get_length_subbuf();  
  24.     if( !tree ) // before training the first tree, initialize weights and other parameters  
  25.     {  
  26.         int* classLabelsBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(classLabelsBuf + n);  
  27.         // 实际上也是取data->responses数据指针,此时正样本为1,负样本为0  
  28.         const int* classLabels = data->get_class_labels(data->data_root, classLabelsBuf);  
  29.         // in case of logitboost and gentle adaboost each weak tree is a regression tree,  
  30.         // so we need to convert class labels to floating-point values  
  31.         double w0 = 1./n;  
  32.         double p[2] = { 1, 1 };  
  33.   
  34.   
  35.         cvReleaseMat( &orig_response );  
  36.         cvReleaseMat( &sum_response );  
  37.         cvReleaseMat( &weak_eval );  
  38.         cvReleaseMat( &subsample_mask );  
  39.         cvReleaseMat( &weights );  
  40.   
  41.   
  42.         orig_response = cvCreateMat( 1, n, CV_32S );  
  43.         weak_eval = cvCreateMat( 1, n, CV_64F );  
  44.         subsample_mask = cvCreateMat( 1, n, CV_8U );  
  45.         weights = cvCreateMat( 1, n, CV_64F );  
  46.         subtree_weights = cvCreateMat( 1, n + 2, CV_64F );  
  47.   
  48.   
  49.         if (data->is_buf_16u)  
  50.         {  
  51.             unsigned short* labels = (unsigned short*)(buf->data.s + data->data_root->buf_idx*length_buf_row +  
  52.                 data->data_root->offset + (data->work_var_count-1)*data->sample_count);  
  53.             for( int i = 0; i < n; i++ )  
  54.             {  
  55.                 // save original categorical responses {0,1}, convert them to {-1,1}  
  56.                 // 将样本标签{0,1}转到{-1,+1}  
  57.                 orig_response->data.i[i] = classLabels[i]*2 - 1;  
  58.                 // make all the samples active at start.  
  59.                 // later, in trim_weights() deactivate/reactive again some, if need  
  60.                 // subsample_mask标识每个样本是否使用,为1表示参与训练  
  61.                 subsample_mask->data.ptr[i] = (uchar)1;  
  62.                 // make all the initial weights the same.  
  63.                 // 设置样本的初始权值为1/n,每个样本权值一样  
  64.                 weights->data.db[i] = w0*p[classLabels[i]];  
  65.                 // set the labels to find (from within weak tree learning proc)  
  66.                 // the particular sample weight, and where to store the response.  
  67.                 labels[i] = (unsigned short)i;  
  68.             }  
  69.         }  
  70.         else  
  71.         {  
  72.             int* labels = buf->data.i + data->data_root->buf_idx*length_buf_row +  
  73.                 data->data_root->offset + (data->work_var_count-1)*data->sample_count;  
  74.   
  75.   
  76.             for( int i = 0; i < n; i++ )  
  77.             {  
  78.                 // save original categorical responses {0,1}, convert them to {-1,1}  
  79.                 orig_response->data.i[i] = classLabels[i]*2 - 1;  
  80.                 subsample_mask->data.ptr[i] = (uchar)1;  
  81.                 weights->data.db[i] = w0*p[classLabels[i]];  
  82.                 labels[i] = i;  
  83.             }  
  84.         }  
  85.   
  86.   
  87.         if( params.boost_type == LOGIT )  
  88.         {  
  89.             sum_response = cvCreateMat( 1, n, CV_64F );  
  90.   
  91.   
  92.             for( int i = 0; i < n; i++ )  
  93.             {  
  94.                 sum_response->data.db[i] = 0;  
  95.                 fdata[sampleIdx[i]*step] = orig_response->data.i[i] > 0 ? 2.f : -2.f;  
  96.             }  
  97.   
  98.   
  99.             // in case of logitboost each weak tree is a regression tree.  
  100.             // the target function values are recalculated for each of the trees  
  101.             data->is_classifier = false;  
  102.         }  
  103.         else if( params.boost_type == GENTLE )  
  104.         {  
  105.             // 设置 data->reponse 为{-1,+1}  
  106.             for( int i = 0; i < n; i++ )  
  107.                 fdata[sampleIdx[i]*step] = (float)orig_response->data.i[i];  
  108.   
  109.   
  110.             data->is_classifier = false;  
  111.         }  
  112.     }  
  113.     else  
  114.     {  
  115.         // at this moment, for all the samples that participated in the training of the most  
  116.         // recent weak classifier we know the responses. For other samples we need to compute them  
  117.         if( have_subsample )  
  118.         {  
  119.             // invert the subsample mask  
  120.             cvXorS( subsample_mask, cvScalar(1.), subsample_mask );  
  121.   
  122.   
  123.             // run tree through all the non-processed samples  
  124.             for( int i = 0; i < n; i++ )  
  125.                 if( subsample_mask->data.ptr[i] )  
  126.                 {  
  127.                     weak_eval->data.db[i] = ((CvCascadeBoostTree*)tree)->predict( i )->value;  
  128.                 }  
  129.         }  
  130.   
  131.   
  132.         // ... 其他boost方式处理,忽略  
  133.         {  
  134.             // Gentle AdaBoost:  
  135.             //   weak_eval[i] = f(x_i) in [-1,1]  
  136.             //   w_i *= exp(-y_i*f(x_i))  
  137.             assert( params.boost_type == GENTLE );  
  138.   
  139.   
  140.             for( int i = 0; i < n; i++ )  
  141.                 weak_eval->data.db[i] *= -orig_response->data.i[i];  
  142.   
  143.   
  144.             cvExp( weak_eval, weak_eval );  
  145.   
  146.   
  147.             for( int i = 0; i < n; i++ )  
  148.             {  
  149.                 double w = weights->data.db[i] * weak_eval->data.db[i];  
  150.                 weights->data.db[i] = w;  
  151.                 sumW += w;  
  152.             }  
  153.         }  
  154.     }  
  155.   
  156.   
  157.     // renormalize weights  
  158.     if( sumW > FLT_EPSILON )  
  159.     {  
  160.         sumW = 1./sumW;  
  161.         for( int i = 0; i < n; ++i )  
  162.             weights->data.db[i] *= sumW;  
  163.     }  
  164. }  

 

void CvCascadeBoost::update_weights( CvBoostTree* tree )
{
    int n = data->sample_count;
    double sumW = 0.;
    int step = 0;
    float* fdata = 0;
    int *sampleIdxBuf;
    const int* sampleIdx = 0;
    int inn_buf_size = ((params.boost_type == LOGIT) || (params.boost_type == GENTLE) ? n*sizeof(int) : 0) +
                       ( !tree ? n*sizeof(int) : 0 );
    cv::AutoBuffer<uchar> inn_buf(inn_buf_size);
    uchar* cur_inn_buf_pos = (uchar*)inn_buf;
    if ( (params.boost_type == LOGIT) || (params.boost_type == GENTLE) )
    {
        step = CV_IS_MAT_CONT(data->responses_copy->type) ?
            1 : data->responses_copy->step / CV_ELEM_SIZE(data->responses_copy->type);
		// data->responses_copy = data->responses 为样本的真实相应(正样本+1,负样本-1)
        fdata = data->responses_copy->data.fl;
        sampleIdxBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(sampleIdxBuf + n);
        sampleIdx = data->get_sample_indices( data->data_root, sampleIdxBuf );
    }
    CvMat* buf = data->buf;
    size_t length_buf_row = data->get_length_subbuf();
    if( !tree ) // before training the first tree, initialize weights and other parameters
    {
        int* classLabelsBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(classLabelsBuf + n);
        // 实际上也是取data->responses数据指针,此时正样本为1,负样本为0
		const int* classLabels = data->get_class_labels(data->data_root, classLabelsBuf);
        // in case of logitboost and gentle adaboost each weak tree is a regression tree,
        // so we need to convert class labels to floating-point values
        double w0 = 1./n;
        double p[2] = { 1, 1 };


        cvReleaseMat( &orig_response );
        cvReleaseMat( &sum_response );
        cvReleaseMat( &weak_eval );
        cvReleaseMat( &subsample_mask );
        cvReleaseMat( &weights );


        orig_response = cvCreateMat( 1, n, CV_32S );
        weak_eval = cvCreateMat( 1, n, CV_64F );
        subsample_mask = cvCreateMat( 1, n, CV_8U );
        weights = cvCreateMat( 1, n, CV_64F );
        subtree_weights = cvCreateMat( 1, n + 2, CV_64F );


        if (data->is_buf_16u)
        {
            unsigned short* labels = (unsigned short*)(buf->data.s + data->data_root->buf_idx*length_buf_row +
                data->data_root->offset + (data->work_var_count-1)*data->sample_count);
            for( int i = 0; i < n; i++ )
            {
                // save original categorical responses {0,1}, convert them to {-1,1}
				// 将样本标签{0,1}转到{-1,+1}
                orig_response->data.i[i] = classLabels[i]*2 - 1;
                // make all the samples active at start.
                // later, in trim_weights() deactivate/reactive again some, if need
				// subsample_mask标识每个样本是否使用,为1表示参与训练
                subsample_mask->data.ptr[i] = (uchar)1;
                // make all the initial weights the same.
				// 设置样本的初始权值为1/n,每个样本权值一样
                weights->data.db[i] = w0*p[classLabels[i]];
                // set the labels to find (from within weak tree learning proc)
                // the particular sample weight, and where to store the response.
                labels[i] = (unsigned short)i;
            }
        }
        else
        {
            int* labels = buf->data.i + data->data_root->buf_idx*length_buf_row +
                data->data_root->offset + (data->work_var_count-1)*data->sample_count;


            for( int i = 0; i < n; i++ )
            {
                // save original categorical responses {0,1}, convert them to {-1,1}
                orig_response->data.i[i] = classLabels[i]*2 - 1;
                subsample_mask->data.ptr[i] = (uchar)1;
                weights->data.db[i] = w0*p[classLabels[i]];
                labels[i] = i;
            }
        }


        if( params.boost_type == LOGIT )
        {
            sum_response = cvCreateMat( 1, n, CV_64F );


            for( int i = 0; i < n; i++ )
            {
                sum_response->data.db[i] = 0;
                fdata[sampleIdx[i]*step] = orig_response->data.i[i] > 0 ? 2.f : -2.f;
            }


            // in case of logitboost each weak tree is a regression tree.
            // the target function values are recalculated for each of the trees
            data->is_classifier = false;
        }
        else if( params.boost_type == GENTLE )
        {
			// 设置 data->reponse 为{-1,+1}
            for( int i = 0; i < n; i++ )
                fdata[sampleIdx[i]*step] = (float)orig_response->data.i[i];


            data->is_classifier = false;
        }
    }
    else
    {
        // at this moment, for all the samples that participated in the training of the most
        // recent weak classifier we know the responses. For other samples we need to compute them
        if( have_subsample )
        {
            // invert the subsample mask
            cvXorS( subsample_mask, cvScalar(1.), subsample_mask );


            // run tree through all the non-processed samples
            for( int i = 0; i < n; i++ )
                if( subsample_mask->data.ptr[i] )
                {
                    weak_eval->data.db[i] = ((CvCascadeBoostTree*)tree)->predict( i )->value;
                }
        }


        // ... 其他boost方式处理,忽略
        {
            // Gentle AdaBoost:
            //   weak_eval[i] = f(x_i) in [-1,1]
            //   w_i *= exp(-y_i*f(x_i))
            assert( params.boost_type == GENTLE );


            for( int i = 0; i < n; i++ )
                weak_eval->data.db[i] *= -orig_response->data.i[i];


            cvExp( weak_eval, weak_eval );


            for( int i = 0; i < n; i++ )
            {
                double w = weights->data.db[i] * weak_eval->data.db[i];
                weights->data.db[i] = w;
                sumW += w;
            }
        }
    }


    // renormalize weights
    if( sumW > FLT_EPSILON )
    {
        sumW = 1./sumW;
        for( int i = 0; i < n; ++i )
            weights->data.db[i] *= sumW;
    }
}


        没增加一个弱分类器都是对当前强分类器的增强,我们需要检查当前的强分类器是否已经足够强,也就是它能否满足设置的性能指标,命中率(也就是recall)和虚警率达到要求。在isErrorDisired函数中进行,我们来看个究竟。

 

[cpp] view plain copy print?

  1. bool CvCascadeBoost::isErrDesired()  
  2. {  
  3.     int sCount = data->sample_count,  
  4.         numPos = 0, numNeg = 0, numFalse = 0, numPosTrue = 0;  
  5.     vector<float> eval(sCount);  
  6.     // 计算每个正样本的弱分类器输出之和,predict函数中完成  
  7.     for( int i = 0; i < sCount; i++ )  
  8.         if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 1.0F )  
  9.             eval[numPos++] = predict( i, true );  
  10.     // 所有正样本的该值进行从小到大排序  
  11.     icvSortFlt( &eval[0], numPos, 0 );  
  12.     // 因为我们要求正样本通过强分类器的比例为minHitRate  
  13.     // 因此阈值应该取从小到大排序数组中的(1.0F - minHitRate)处的值  
  14.     int thresholdIdx = (int)((1.0F - minHitRate) * numPos);  
  15.     threshold = eval[ thresholdIdx ];  
  16.     numPosTrue = numPos - thresholdIdx;  
  17.     for( int i = thresholdIdx - 1; i >= 0; i--)  
  18.         if ( abs( eval[i] - threshold) < FLT_EPSILON )  
  19.             numPosTrue++;  
  20.     float hitRate = ((float) numPosTrue) / ((float) numPos);  
  21.     // 确定强分类器的阈值threshold之后,还需要计算虚警率,也就是负样本通过该  
  22.     // 阈值的比例。同样的调用predict  
  23.     for( int i = 0; i < sCount; i++ )  
  24.     {  
  25.         if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 0.0F )  
  26.         {  
  27.             numNeg++;  
  28.             // 返回1表示通过,也就是弱分类器和大于设置阈值,此处表示负样本通过强分类器  
  29.             if( predict( i ) )  
  30.                 numFalse++;  
  31.         }  
  32.     }  
  33.     // 虚警率 = 通过的负样本/总负样本数  
  34.     float falseAlarm = ((float) numFalse) / ((float) numNeg);  
  35.   
  36.   
  37.     cout << "|"; cout.width(4); cout << right << weak->total;  
  38.     cout << "|"; cout.width(9); cout << right << hitRate;  
  39.     cout << "|"; cout.width(9); cout << right << falseAlarm;  
  40.     cout << "|" << endl;  
  41.     cout << "+----+---------+---------+" << endl;  
  42.   
  43.   
  44.     return falseAlarm <= maxFalseAlarm;  
  45. }  

 

bool CvCascadeBoost::isErrDesired()
{
    int sCount = data->sample_count,
        numPos = 0, numNeg = 0, numFalse = 0, numPosTrue = 0;
    vector<float> eval(sCount);
	// 计算每个正样本的弱分类器输出之和,predict函数中完成
    for( int i = 0; i < sCount; i++ )
        if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 1.0F )
            eval[numPos++] = predict( i, true );
	// 所有正样本的该值进行从小到大排序
    icvSortFlt( &eval[0], numPos, 0 );
	// 因为我们要求正样本通过强分类器的比例为minHitRate
	// 因此阈值应该取从小到大排序数组中的(1.0F - minHitRate)处的值
    int thresholdIdx = (int)((1.0F - minHitRate) * numPos);
    threshold = eval[ thresholdIdx ];
    numPosTrue = numPos - thresholdIdx;
    for( int i = thresholdIdx - 1; i >= 0; i--)
        if ( abs( eval[i] - threshold) < FLT_EPSILON )
            numPosTrue++;
    float hitRate = ((float) numPosTrue) / ((float) numPos);
	// 确定强分类器的阈值threshold之后,还需要计算虚警率,也就是负样本通过该
	// 阈值的比例。同样的调用predict
    for( int i = 0; i < sCount; i++ )
    {
        if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 0.0F )
        {
            numNeg++;
			// 返回1表示通过,也就是弱分类器和大于设置阈值,此处表示负样本通过强分类器
            if( predict( i ) )
                numFalse++;
        }
    }
	// 虚警率 = 通过的负样本/总负样本数
    float falseAlarm = ((float) numFalse) / ((float) numNeg);


    cout << "|"; cout.width(4); cout << right << weak->total;
    cout << "|"; cout.width(9); cout << right << hitRate;
    cout << "|"; cout.width(9); cout << right << falseAlarm;
    cout << "|" << endl;
    cout << "+----+---------+---------+" << endl;


    return falseAlarm <= maxFalseAlarm;
}


        来看一下强分类器是如何预测的,在predict函数中,依次调用每个弱分类器的predict来给出第当前样本的输出,统计输出值sum。如果设置返回是否通过,则将sum与强分类器阈值threshold比较的结果。否则直接返回sum值。

 

[cpp] view plain copy print?

  1. float CvCascadeBoost::predict( int sampleIdx, bool returnSum ) const  
  2. {  
  3.     CV_Assert( weak );  
  4.     double sum = 0;  
  5.     CvSeqReader reader;  
  6.     cvStartReadSeq( weak, &reader );  
  7.     cvSetSeqReaderPos( &reader, 0 );  
  8.     // 遍历当前所有的弱分类器  
  9.     for( int i = 0; i < weak->total; i++ )  
  10.     {  
  11.         CvBoostTree* wtree;  
  12.         CV_READ_SEQ_ELEM( wtree, reader );  
  13.         // 累加第i个弱分类器的输出  
  14.         sum += ((CvCascadeBoostTree*)wtree)->predict(sampleIdx)->value;  
  15.     }  
  16.     // 如果设置不返回和sum,返回sum是否通过强分类器的阈值threshold  
  17.     if( !returnSum )  
  18.         sum = sum < threshold - CV_THRESHOLD_EPS ? 0.0 : 1.0;  
  19.     return (float)sum;  
  20. }  

 

float CvCascadeBoost::predict( int sampleIdx, bool returnSum ) const
{
    CV_Assert( weak );
    double sum = 0;
    CvSeqReader reader;
    cvStartReadSeq( weak, &reader );
    cvSetSeqReaderPos( &reader, 0 );
	// 遍历当前所有的弱分类器
    for( int i = 0; i < weak->total; i++ )
    {
        CvBoostTree* wtree;
        CV_READ_SEQ_ELEM( wtree, reader );
		// 累加第i个弱分类器的输出
        sum += ((CvCascadeBoostTree*)wtree)->predict(sampleIdx)->value;
    }
	// 如果设置不返回和sum,返回sum是否通过强分类器的阈值threshold
    if( !returnSum )
        sum = sum < threshold - CV_THRESHOLD_EPS ? 0.0 : 1.0;
    return (float)sum;
}


        最后我们来看上面的弱分类器是如何预测的,predict函数中实现,已经做了注释。

 

[cpp] view plain copy print?

  1. CvDTreeNode* CvCascadeBoostTree::predict( int sampleIdx ) const  
  2. {  
  3.     CvDTreeNode* node = root;  
  4.     if( !node )  
  5.         CV_Error( CV_StsError, "The tree has not been trained yet" );  
  6.   
  7.   
  8.     if ( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount() == 0 ) // ordered  
  9.     {  
  10.         // 我们说过一个弱分类是一个CART树,结构上是一个二叉树,一个节点最多有左右孩子两个子节点  
  11.         // CART树上的分割节点总是有两个孩子,而叶子节点没有孩子。因此此处就是判断node为split节点  
  12.         // 也就是要走到叶子节点才会退出,结束树的遍历  
  13.         while( node->left )  
  14.         {  
  15.             CvDTreeSplit* split = node->split;  
  16.             // 取split节点的特征序号var_idx,计算第sampleIndex个样本的第var_idx个特征值  
  17.             float val = ((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx );  
  18.             // 与split节点的阈值ord.c比较,如果小于,转向左孩子,否则转右孩子  
  19.             node = val <= split->ord.c ? node->left : node->right;  
  20.         }  
  21.     }  
  22.     else // categorical  
  23.     {  
  24.         while( node->left )  
  25.         {  
  26.             CvDTreeSplit* split = node->split;  
  27.             int c = (int)((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx );  
  28.             node = CV_DTREE_CAT_DIR(c, split->subset) < 0 ? node->left : node->right;  
  29.         }  
  30.     }  
  31.     // 返回样本落入的叶节点  
  32.     return node;  
  33. }  

 

CvDTreeNode* CvCascadeBoostTree::predict( int sampleIdx ) const
{
    CvDTreeNode* node = root;
    if( !node )
        CV_Error( CV_StsError, "The tree has not been trained yet" );


    if ( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount() == 0 ) // ordered
    {
		// 我们说过一个弱分类是一个CART树,结构上是一个二叉树,一个节点最多有左右孩子两个子节点
		// CART树上的分割节点总是有两个孩子,而叶子节点没有孩子。因此此处就是判断node为split节点
		// 也就是要走到叶子节点才会退出,结束树的遍历
        while( node->left )
        {
            CvDTreeSplit* split = node->split;
			// 取split节点的特征序号var_idx,计算第sampleIndex个样本的第var_idx个特征值
            float val = ((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx );
			// 与split节点的阈值ord.c比较,如果小于,转向左孩子,否则转右孩子
            node = val <= split->ord.c ? node->left : node->right;
        }
    }
    else // categorical
    {
        while( node->left )
        {
            CvDTreeSplit* split = node->split;
            int c = (int)((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx );
            node = CV_DTREE_CAT_DIR(c, split->subset) < 0 ? node->left : node->right;
        }
    }
	// 返回样本落入的叶节点
    return node;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenCV是一个开源计算机视觉库,提供了许多图像处理和计算机视觉相关的函数和算法。其中,人脸检测是OpenCV库中最常用的功能之一。OpenCV提供了一个基于Haar特征的人脸检测分类器,可以通过训练得到。 Haar特征是一种基于图像的局部特征描述方法,可以用于检测物体。Haar特征可以用来描述图像中的区域,比如边缘、角、线和矩形等。人脸检测分类器基于Haar特征通过AdaBoost算法进行训练,最终得到一个可以检测人脸的分类器。 在OpenCV中,人脸检测分类器可以通过CascadeClassifier类实现。CascadeClassifier类是一个封装了Haar分类器的类,可以简单地调用该类的detectMultiScale函数实现人脸检测。detectMultiScale函数会对输入的图像进行多尺度检测,返回检测到的人脸区域的坐标。 使用OpenCV进行人脸检测的步骤如下: 1. 加载人脸检测分类器:使用CascadeClassifier类加载Haar分类器。可以使用OpenCV提供的已经训练好的分类器文件,也可以通过训练自己的分类器文件。 2. 读入图像:使用imread函数读入待检测的图像。 3. 调用detectMultiScale函数进行人脸检测:使用detectMultiScale函数对图像进行人脸检测,返回检测到的人脸区域的坐标。 4. 绘制人脸框:使用rectangle函数对检测到的人脸区域进行绘制。 5. 显示结果:使用imshow函数显示检测结果。 下面是一个简单的示例代码: ``` import cv2 # 加载人脸检测分类器 face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') # 读入图像 img = cv2.imread('test.jpg') # 调用detectMultiScale函数进行人脸检测 faces = face_cascade.detectMultiScale(img, scaleFactor=1.1, minNeighbors=5) # 绘制人脸框 for (x,y,w,h) in faces: cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2) # 显示结果 cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在这个例子中,我们使用了一个已经训练好的分类器文件haarcascade_frontalface_default.xml,这个文件可以在OpenCV库中的data文件夹中找到。我们读入了一张名为test.jpg的图像,然后使用detectMultiScale函数进行人脸检测,并将检测到的人脸区域用绿色框标出。最后使用imshow函数显示结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值