cvCreateMTStumpClassifier函数出自opencv中的haartraining程序,在adaboost(cvCreateTreeCascadeClassifier)的强分类器(icvCreateCARTStageClassifier)中被两次调用,该函数用于寻找最优弱分类器,或者说成计算最优haar特征。功能很明确,但是大家都知道的,opencv的代码绝大部分写的让人真心看不懂,这个函数算是haartraining中比较难以看懂的函数,局部变量达到20个之多,童鞋我也是不甘心,不甘心被这小小的函数所击溃,于是擦干泪水,仔细研读,终于恍然大悟,大彻大悟的同时,不忘回报CSDN博客,与朋友们分享。
最优弱分类器的计算过程,网上到处都有介绍,其实就是个穷举的过程,对每个特征多对应的训练样本的特征值进行排序,然后遍历每个特征值作为阈值,根据一定的公式(1.misclass 2.gini 3.entropy 4.least sum of squares)确定最优阈值,进一步确定最优特征,也就是最优弱分类器了。
但是opencv写的比较通用,所以有点让人摸不清头脑,它是这么干的:我先预计算一些haar特征(基本上为600个)的训练样本升序矩阵(trainData),然后先寻找这600个特征中的最优特征,但是总共的特征可能有1万多个,对于新特征那就只能在重新计算训练样本升序矩阵(mat),继续寻找最优特征。可能是程序一直跃跃欲试,想用并行的方法处理,导致了程序的局部变量增多(例如portion的引用)。这是值得大家注意的地方。
另外,上面说到cvCreateMTStumpClassifier函数被两次调用,一次是在cvCreateCARTClassifier中,一次是在icvCreateCARTStageClassifier中,其中,cvCreateCARTClassifier中trainData对应的是一个矩阵,而后者对应的是一个行向量。
注意上面两处,应该就不会被弄晕了,我直接上代码,并且做了比较详细的注释,这样子更加实在一些,希望能够对童鞋们有所帮助!
// 函数功能:计算最优弱分类器 CV_BOOST_IMPL CvClassifier* cvCreateMTStumpClassifier( CvMat* trainData, // 训练样本特征值 int flags, // 1.按行排列,0.按列排列 CvMat* trainClasses, // weakTrainVals(行向量) 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; float right; 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:<span style="white-space:pre"> </span>// 这里重复度很高,只注释一个分支,剩下的都一个道理 // 从一组特征(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 ); } 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; }
其实,我现在一直认为,寻找弱分类器是一个很easy的过程,根本不需要这么多行代码,这么多局部变量,但是仔细阅读之后发现,opencv还是很牛的,这段代码的通用性比较强大,兼顾了并行操作可能性。可以应对多特征弱分类器,代码结构也是比较爽快的,尤其是其在条件宏、函数指针方面的应用,令人羡慕异常。今后还要继续研读opencv代码,对编程素养的提高,绝对有很大帮助。
如果有啥问题,还请不吝赐教,私聊,评论,哪怕直接骂都可以!!!!