cvCreateMTStumpClassifier函数出自OpenCV中的haartraining程序,在Haartraining中强分类器创建函数icvCreateCARTStageClassifier中被两次调用,该函数用于寻找最优弱分类器,或者说成计算最优Haar特征。功能很明确,但是大家都知道的,opencv的代码绝大部分写的让人真心看不懂,这个函数算是haartraining中比较难以看懂的函数,局部变量达到20个之多,童鞋我也是不甘心,不甘心被这小小的函数所击溃,于是擦干泪水,仔细研读,终于恍然大悟,大彻大悟的同时,不忘回报优快云博客,与朋友们分享。
1. 最优弱分类器的计算过程,网上到处都有介绍,其实就是个穷举的过程,先对每个特征所对应的训练样本的特征值进行排序,然后遍历每个特征值作为阈值,根据特定的方法(1.misclass 2.gini 3.entropy 4.least sum of squares)确定最优阈值,进一步确定最优特征,也就是最优弱分类器了。
2. opencv写的比较通用,所以有点让人摸不清头脑,它是这么干的:先预计算“Haar特征-训练样本”矩阵(trainData),一般Haar特征有600个,然后先寻找这600个特征中的最优特征,但是总共的Haar特征可能有1万多个,对于新特征那就只能在重新计算训练样本升序矩阵(mat),继续寻找最优特征。
3. 由于程序一直跃跃欲试,想用并行的方法处理,导致了程序的局部变量增多(例如portion的引用),这是值得大家注意的地方。
4. 另外,上面说到cvCreateMTStumpClassifier函数被两次调用,一次是在cvCreateCARTClassifier中,一次是在icvCreateCARTStageClassifier中,其中,前者中trainData对应的是一个矩阵,而后者trainData对应的是一个行向量。
注意上面几处,再看源码,应该就不会被弄晕了,我直接上代码,并且做了比较详细的注释,这样子更加实在一些,希望能够对童鞋们有所帮助!
(转载请注明:http://blog.csdn.NET/wsj998689aa/article/details/42294703,作者:迷雾forest)
- // 函数功能:计算最优弱分类器
- CV_BOOST_IMPL
- CvClassifier* cvCreateMTStumpClassifier( CvMat* trainData, // 训练样本HAAR特征值矩阵
- int flags, // 1.按行排列,0.按列排列
- CvMat* trainClasses, // 样本类别{-1,1}
- CvMat* /*typeMask*/, // 为了便于回调函数统一格式
- CvMat* missedMeasurementsMask, // 未知,很少用到
- CvMat* compIdx, // 特征序列(必须为NULL)(行向量)
- CvMat* sampleIdx, // 实际训练样本序列(行向量)
- CvMat* weights, // 实际训练样本样本权重(行向量)
- CvClassifierTrainParams* trainParams ) // 其它数据&参数
- {
- CvStumpClassifier* stump = NULL; // 弱分类器(桩)
- int m = 0; // 样本总数
- int n = 0; // 所有特征个数
- uchar* data = NULL; // trainData数据指针
- size_t cstep = 0; // trainData一行字节数
- size_t sstep = 0; // trainData元素字节数
- int datan = 0; // 预计算特征个数
- uchar* ydata = NULL; // trainClasses数据指针
- size_t ystep = 0; // trainClasses元素字节数
- uchar* idxdata = NULL; // sampleIdx数据指针
- size_t idxstep = 0; // sampleIdx单个元素字节数
- int l = 0; // 实际训练样本个数
- uchar* wdata = NULL; // weights数据指针
- size_t wstep = 0; // weights元素字节数
- /* sortedIdx为事先计算好的特征值-样本矩阵,包含有预计算的所有HAAR特征对应于所有样本的特征值(按大小排列) */
- uchar* sorteddata = NULL; // sortedIdx数据指针
- int sortedtype = 0; // sortedIdx元素类型
- size_t sortedcstep = 0; // sortedIdx一行字节数
- size_t sortedsstep = 0; // sortedIdx元素字节数
- int sortedn = 0; // sortedIdx行数(预计算特征个数)
- int sortedm = 0; // sortedIdx列数(实际训练样本个数)
- char* filter = NULL; // 样本存在标示(行向量),如果样本存在则为1,否则为0
- int i = 0;
- int compidx = 0; // 每组特征的起始序号
- int stumperror; // 计算阈值方法:1.misclass 2.gini 3.entropy 4.least sum of squares
- int portion; // 每组特征个数,对所有特征n进行分组处理,每组portion个
- /* private variables */
- CvMat mat; // 补充特征-样本矩阵
- CvValArray va;
- float lerror; // 阈值左侧误差
- float rerror; // 阈值右侧误差
- float left;<span style="white-space:pre"> </span> // 置信度(左分支)
- float right;<span style="white-space:pre"> </span> // 置信度(右分支)
- float threshold; // 阈值
- int optcompidx; // 最优特征
- float sumw;
- float sumwy;
- float sumwyy;
- /*临时变量,循环用*/
- int t_compidx;
- int t_n;
- int ti;
- int tj;
- int tk;
- uchar* t_data; // 指向data
- size_t t_cstep; // cstep
- size_t t_sstep; // sstep
- size_t matcstep; // mat一行字节数
- size_t matsstep; // mat元素字节数
- int* t_idx; // 样本序列
- /* end private variables */
- CV_Assert( trainParams != NULL );
- CV_Assert( trainClasses != NULL );
- CV_Assert( CV_MAT_TYPE( trainClasses->type ) == CV_32FC1 );
- CV_Assert( missedMeasurementsMask == NULL );
- CV_Assert( compIdx == NULL );
- // 计算阈值方法:1.misclass 2.gini 3.entropy 4.least sum of squares
- stumperror = (int) ((CvMTStumpTrainParams*) trainParams)->error;
- // 样本类别
- ydata = trainClasses->data.ptr;
- if( trainClasses->rows == 1 )
- {
- m = trainClasses->cols;
- ystep = CV_ELEM_SIZE( trainClasses->type );
- }
- else
- {
- m = trainClasses->rows;
- ystep = trainClasses->step;
- }
- // 样本权重
- wdata = weights->data.ptr;
- if( weights->rows == 1 )
- {
- CV_Assert( weights->cols == m );
- wstep = CV_ELEM_SIZE( weights->type );
- }
- else
- {
- CV_Assert( weights->rows == m );
- wstep = weights->step;
- }
- // sortedIdx为空,trainData为行向量(1*m);sortedIdx不为空,trainData为矩阵(m*datan);
- if( ((CvMTStumpTrainParams*) trainParams)->sortedIdx != NULL )
- {
- sortedtype =
- CV_MAT_TYPE( ((CvMTStumpTrainParams*) trainParams)->sortedIdx->type );
- assert( sortedtype == CV_16SC1 || sortedtype == CV_32SC1
- || sortedtype == CV_32FC1 );
- sorteddata = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->data.ptr;
- sortedsstep = CV_ELEM_SIZE( sortedtype );
- sortedcstep = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->step;
- sortedn = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->rows;
- sortedm = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->cols;
- }
- if( trainData == NULL ) // 为空的情况没有遇到
- {
- assert( ((CvMTStumpTrainParams*) trainParams)->getTrainData != NULL );
- n = ((CvMTStumpTrainParams*) trainParams)->numcomp;
- assert( n > 0 );
- }
- else
- {
- assert( CV_MAT_TYPE( trainData->type ) == CV_32FC1 );
- data = trainData->data.ptr;
- if( CV_IS_ROW_SAMPLE( flags ) ) // trainData为矩阵
- {
- cstep = CV_ELEM_SIZE( trainData->type );
- sstep = trainData->step;
- assert( m == trainData->rows );
- datan = n = trainData->cols;
- }
- else // trainData为向量
- {
- sstep = CV_ELEM_SIZE( trainData->type );
- cstep = trainData->step;
- assert( m == trainData->cols );
- datan = n = trainData->rows;
- }
- // trainData为矩阵,当trainData为向量时,datan = n = 1
- if( ((CvMTStumpTrainParams*) trainParams)->getTrainData != NULL )
- {
- n = ((CvMTStumpTrainParams*) trainParams)->numcomp; // 总特征个数
- }
- }
- // 预计算特征个数一定要小于特征总数
- assert( datan <= n );
- if( sampleIdx != NULL ) // 已经剔除小权值样本
- {
- assert( CV_MAT_TYPE( sampleIdx->type ) == CV_32FC1 );
- idxdata = sampleIdx->data.ptr;
- idxstep = ( sampleIdx->rows == 1 )
- ? CV_ELEM_SIZE( sampleIdx->type ) : sampleIdx->step;
- l = ( sampleIdx->rows == 1 ) ? sampleIdx->cols : sampleIdx->rows;
- // sorteddata中存放的是所有训练样本,需要筛选出实际训练样本
- if( sorteddata != NULL )
- {
- filter = (char*) cvAlloc( sizeof( char ) * m );
- memset( (void*) filter, 0, sizeof( char ) * m );
- for( i = 0; i < l; i++ )
- {
- filter[(int) *((float*) (idxdata + i * idxstep))] = (char) 1; // 存在则为1,不存在则为0
- }
- }
- }
- else // 未剔除小权值样本
- {
- l = m;
- }
- // 桩
- stump = (CvStumpClassifier*) cvAlloc( sizeof( CvStumpClassifier) );
- memset( (void*) stump, 0, sizeof( CvStumpClassifier ) );
- // 每组特征个数
- portion = ((CvMTStumpTrainParams*)trainParams)->portion;
- if( portion < 1 )
- {
- /* auto portion */
- portion = n;
- #ifdef _OPENMP
- portion /= omp_get_max_threads();
- #endif /* _OPENMP */
- }
- stump->eval = cvEvalStumpClassifier;
- stump->tune = NULL;
- stump->save = NULL;
- stump->release = cvReleaseStumpClassifier;
- stump->lerror = FLT_MAX;
- stump->rerror = FLT_MAX;
- stump->left = 0.0F;
- stump->right = 0.0F;
- compidx = 0;
- // 并行计算,默认为关闭的
- #ifdef _OPENMP
- #pragma omp parallel private(mat, va, lerror, rerror, left, right, threshold, \
- optcompidx, sumw, sumwy, sumwyy, t_compidx, t_n, \
- ti, tj, tk, t_data, t_cstep, t_sstep, matcstep, \
- matsstep, t_idx)
- #endif /* _OPENMP */
- {
- lerror = FLT_MAX;
- rerror = FLT_MAX;
- left = 0.0F;
- right = 0.0F;
- threshold = 0.0F;
- optcompidx = 0;
- sumw = FLT_MAX;
- sumwy = FLT_MAX;
- sumwyy = FLT_MAX;
- t_compidx = 0;
- t_n = 0;
- ti = 0;
- tj = 0;
- tk = 0;
- t_data = NULL;
- t_cstep = 0;
- t_sstep = 0;
- matcstep = 0;
- matsstep = 0;
- t_idx = NULL;
- mat.data.ptr = NULL;
- // 预计算特征个数小于特征总数,则说明存在新特征,用于计算样本的新特征,存放在mat中
- if( datan < n )
- {
- if( CV_IS_ROW_SAMPLE( flags ) )
- {
- mat = cvMat( m, portion, CV_32FC1, 0 );
- matcstep = CV_ELEM_SIZE( mat.type );
- matsstep = mat.step;
- }
- else
- {
- mat = cvMat( portion, m, CV_32FC1, 0 );
- matcstep = mat.step;
- matsstep = CV_ELEM_SIZE( mat.type );
- }
- mat.data.ptr = (uchar*) cvAlloc( sizeof( float ) * mat.rows * mat.cols );
- }
- // 将实际训练样本序列存放进t_idx
- if( filter != NULL || sortedn < n )
- {
- t_idx = (int*) cvAlloc( sizeof( int ) * m );
- if( sortedn == 0 || filter == NULL )
- {
- if( idxdata != NULL )
- {
- for( ti = 0; ti < l; ti++ )
- {
- t_idx[ti] = (int) *((float*) (idxdata + ti * idxstep));
- }
- }
- else
- {
- for( ti = 0; ti < l; ti++ )
- {
- t_idx[ti] = ti;
- }
- }
- }
- }
- #ifdef _OPENMP
- #pragma omp critical(c_compidx)
- #endif /* _OPENMP */
- // 初始化计算特征范围
- {
- t_compidx = compidx;
- compidx += portion;
- }
- // 寻找最优弱分类器
- while( t_compidx < n )
- {
- t_n = portion; // 每组特征个数
- if( t_compidx < datan ) // 已经计算过的特征
- {
- t_n = ( t_n < (datan - t_compidx) ) ? t_n : (datan - t_compidx);
- t_data = data;
- t_cstep = cstep;
- t_sstep = sstep;
- }
- else // 新特征
- {
- t_n = ( t_n < (n - t_compidx) ) ? t_n : (n - t_compidx);
- t_cstep = matcstep;
- t_sstep = matsstep;
- t_data = mat.data.ptr - t_compidx * ((size_t) t_cstep );
- // 计算每个新特征对应于每个训练样本的特征值
- ((CvMTStumpTrainParams*)trainParams)->getTrainData( &mat,
- sampleIdx, compIdx, t_compidx, t_n,
- ((CvMTStumpTrainParams*)trainParams)->userdata );
- }
- /* 预计算特征部分,直接寻找最优特征,也就是传说中的最优弱分类器 */
- if( sorteddata != NULL )
- {
- if( filter != NULL ) // 需要提取实际训练样本
- {
- switch( sortedtype )
- {
- case CV_16SC1: // 这里重复度很高,只注释一个分支,剩下的都一个道理
- // 从一组特征(datan个预计算特征)中寻找最优特征
- for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
- {
- tk = 0;
- // 提取实际训练样本
- for( tj = 0; tj < sortedm; tj++ )
- {
- int curidx = (int) ( *((short*) (sorteddata
- + ti * sortedcstep + tj * sortedsstep)) );
- if( filter[curidx] != 0 )
- {
- t_idx[tk++] = curidx;
- }
- }
- // 如果findStumpThreshold_32s返回值为1, 则更新最优特征
- if( findStumpThreshold_32s[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- (uchar*) t_idx, sizeof( int ), tk,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- break;
- case CV_32SC1:
- for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
- {
- tk = 0;
- for( tj = 0; tj < sortedm; tj++ )
- {
- int curidx = (int) ( *((int*) (sorteddata
- + ti * sortedcstep + tj * sortedsstep)) );
- if( filter[curidx] != 0 )
- {
- t_idx[tk++] = curidx;
- }
- }
- if( findStumpThreshold_32s[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- (uchar*) t_idx, sizeof( int ), tk,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- break;
- case CV_32FC1:
- for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
- {
- tk = 0;
- for( tj = 0; tj < sortedm; tj++ )
- {
- int curidx = (int) ( *((float*) (sorteddata
- + ti * sortedcstep + tj * sortedsstep)) );
- if( filter[curidx] != 0 )
- {
- t_idx[tk++] = curidx;
- }
- }
- if( findStumpThreshold_32s[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- (uchar*) t_idx, sizeof( int ), tk,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- break;
- default:
- assert( 0 );
- break;
- }
- }
- else // 所有训练样本均参与计算
- {
- switch( sortedtype )
- {
- case CV_16SC1:
- for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
- {
- if( findStumpThreshold_16s[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- sorteddata + ti * sortedcstep, sortedsstep, sortedm,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- break;
- case CV_32SC1:
- for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
- {
- if( findStumpThreshold_32s[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- sorteddata + ti * sortedcstep, sortedsstep, sortedm,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- break;
- case CV_32FC1:
- for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
- {
- if( findStumpThreshold_32f[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- sorteddata + ti * sortedcstep, sortedsstep, sortedm,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- break;
- default:
- assert( 0 );
- break;
- }
- }
- }
- /* 新特征部分,要对样本特征值进行排序,然后再寻找最优特征 */
- ti = MAX( t_compidx, MIN( sortedn, t_compidx + t_n ) );
- for( ; ti < t_compidx + t_n; ti++ )
- {
- va.data = t_data + ti * t_cstep;
- va.step = t_sstep;
- // 对样本特征值进行排序
- icvSortIndexedValArray_32s( t_idx, l, &va );
- // 继续寻找最优特征
- if( findStumpThreshold_32s[stumperror](
- t_data + ti * t_cstep, t_sstep,
- wdata, wstep, ydata, ystep,
- (uchar*)t_idx, sizeof( int ), l,
- &lerror, &rerror,
- &threshold, &left, &right,
- &sumw, &sumwy, &sumwyy ) )
- {
- optcompidx = ti;
- }
- }
- #ifdef _OPENMP
- #pragma omp critical(c_compidx)
- #endif /* _OPENMP */
- // 更新特征计算范围
- {
- t_compidx = compidx;
- compidx += portion;
- }
- }
- #ifdef _OPENMP
- #pragma omp critical(c_beststump)
- #endif /* _OPENMP */
- // 设置最优弱分类器
- {
- if( lerror + rerror < stump->lerror + stump->rerror )
- {
- stump->lerror = lerror;
- stump->rerror = rerror;
- stump->compidx = optcompidx;
- stump->threshold = threshold;
- stump->left = left;
- stump->right = right;
- }
- }
- /* free allocated memory */
- if( mat.data.ptr != NULL )
- {
- cvFree( &(mat.data.ptr) );
- }
- if( t_idx != NULL )
- {
- cvFree( &t_idx );
- }
- } /* end of parallel region */
- /* END */
- /* free allocated memory */
- if( filter != NULL )
- {
- cvFree( &filter );
- }
- // 如果设置为离散型,置信度应为1或者-1
- if( ((CvMTStumpTrainParams*) trainParams)->type == CV_CLASSIFICATION_CLASS )
- {
- stump->left = 2.0F * (stump->left >= 0.5F) - 1.0F;
- stump->right = 2.0F * (stump->right >= 0.5F) - 1.0F;
- }
- return (CvClassifier*) stump;
- }
如果有啥问题,还请不吝赐教哦!!!