您的位置:首页 > 大数据 > 人工智能

Opencv研读笔记:haartraining程序之cvCreateMTStumpClassifier函数详解(弱分类器创建)~

2016-11-08 10:12 357 查看
cvCreateMTStumpClassifier函数出自OpenCV中的haartraining程序,在Haartraining中强分类器创建函数icvCreateCARTStageClassifier中被两次调用,该函数用于寻找最优弱分类器,或者说成计算最优Haar特征。功能很明确,但是大家都知道的,opencv的代码绝大部分写的让人真心看不懂,这个函数算是haartraining中比较难以看懂的函数,局部变量达到20个之多,童鞋我也是不甘心,不甘心被这小小的函数所击溃,于是擦干泪水,仔细研读,终于恍然大悟,大彻大悟的同时,不忘回报CSDN博客,与朋友们分享。

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)

[cpp] view
plain copy

// 函数功能:计算最优弱分类器

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;

}

其实,我现在一直认为,寻找弱分类器是一个很easy的过程,根本不需要这么多行代码,这么多局部变量,但是仔细阅读之后发现,opencv还是很牛的,这段代码的通用性比较强大,兼顾了并行操作可能性。可以应对多特征弱分类器,代码结构也是比较爽快的,尤其是其在条件宏、函数指针方面的应用,令人羡慕异常。今后还要继续研读opencv代码,对编程素养的提高,绝对有很大帮助。

如果有啥问题,还请不吝赐教哦!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐