icvCreateCARTStageClassifier函数的详细分析见我博文中的另外一篇文章http://blog.csdn.net/ding977921830/article/details/46442805,这篇文章也是转载的迷雾forest博客中的一篇文章http://blog.csdn.net/wsj998689aa/article/details/42398235,在这里我要感谢迷雾forest的无私分享和付出,能让我对该源代码能有更深理解。 icvCreateCARTStageClassifier的源代码我研究了很多遍,用了一周多的时间,下面是我对该源代码的总结,以便把icvCreateCARTStageClassifier的代码的框架给梳理出来。
温馨提示:
(1)本文的代码不全,只是提出其代码框架,如要研究详细代码,请参考我上面提到的两篇文章;
(2)本文研究的adaboost算法是只有discrete adaboost算法;
(3)在研究该函数前我建议把代码的前面参数地定义和初始化部分打印出来看,因为,后面的代码要反反复复使用这些变量。在opencv的源代码用到大量的结构体、宏和函数指针,并且相互嵌套,所以容易忘记其含义,打印出来研究相对方便的多。
代码框架如下:
/*
*icvCreateCARTStageClassifier
*作用:建立分类回归树强分类器
*/
static
CvIntHaarClassifier* icvCreateCARTStageClassifier( CvHaarTrainingData* data, // 全部训练样本,包含训练样本的数量、大小,权重,积分图和类别等信息
CvMat* sampleIdx, // 实际训练样本序列
CvIntHaarFeatures* haarFeatures, // 全部HAAR特征
float minhitrate, // 最小击中率
float maxfalsealarm, // 最大虚警率
int symmetric, // HAAR特征是否对称
float weightfraction, // 样本剔除比例(用于剔除小权值样本)
int numsplits, // 每个弱分类器特征个数(一般为1)
CvBoostType boosttype, // adaboost类型,共有四种,一般常用的是Discrete AdaBoost
CvStumpError stumperror, // Discrete AdaBoost中的阈值计算方式
int maxsplits )
{
/******************************************************************************************************************\
第一步:参数的定义和初始化
\*******************************************************************************************************************/
CvStageHaarClassifier* stage = NULL; // 强分类器
CvBoostTrainer* trainer; // 临时训练器,用于更新样本权值
CvCARTClassifier* cart = NULL; // 弱分类器
CvCARTTrainParams trainParams; // 训练参数
CvMTStumpTrainParams stumpTrainParams; // 弱分类器参数
//CvMat* trainData = NULL;
//CvMat* sortedIdx = NULL;
CvMat eval; // 临时矩阵
int n = 0; // 特征总数
int m = 0; // 总样本个数
int numpos = 0; // 正样本个数
int numneg = 0; // 负样本个数
int numfalse = 0; // 误检样本个数
float sum_stage = 0.0F; // 置信度累积和
float threshold = 0.0F; // 强分类器阈值
float falsealarm = 0.0F; // 误检率
//CvMat* sampleIdx = NULL;
CvMat* trimmedIdx; // 剔除小权值之后的样本序列
//float* idxdata = NULL;
//float* tempweights = NULL;
//int idxcount = 0;
CvUserdata userdata; // 训练数据
int i = 0;
int j = 0;
int idx;
int numsamples; // 实际样本个数
int numtrimmed; // 剔除小权值之后的样本个数
CvCARTHaarClassifier* classifier; // 弱分类器
CvSeq* seq = NULL;
CvMemStorage* storage = NULL;
CvMat* weakTrainVals; // 样本类别,只有logitboost才会用到
float alpha;
float sumalpha;
int num_splits; // 弱分类器个数
n = haarFeatures->count;
m = data->sum.rows;
numsamples = (sampleIdx) ? MAX( sampleIdx->rows, sampleIdx->cols ) : m;
// 样本与HAAR特征
userdata = cvUserdata( data, haarFeatures );
//1.1 多阈值最优弱分类器的参数配置
stumpTrainParams.type = ( boosttype == CV_DABCLASS )
? CV_CLASSIFICATION_CLASS : CV_REGRESSION; // 分类或者回归
stumpTrainParams.error = ( boosttype == CV_LBCLASS || boosttype == CV_GABCLASS )
? CV_SQUARE : stumperror; // 弱分类器阈值计算方式
stumpTrainParams.portion = CV_STUMP_TRAIN_PORTION; // 每组特征个数
stumpTrainParams.getTrainData = icvGetTrainingDataCallback; // 计算样本的haar值
stumpTrainParams.numcomp = n; // 特征个数
stumpTrainParams.userdata = &userdata;
stumpTrainParams.sortedIdx = data->idxcache; // 特征-样本序号矩阵(排序之后)
//1.2分类回归树参数设置
trainParams.count = numsplits; // 弱分类器特征树
trainParams.stumpTrainParams = (CvClassifierTrainParams*) &stumpTrainParams;/*弱分类参数,这里又把上述多阈值若分类器的参数包含进来,所以要先进行
上述多阈值若分类器参数的初始化,然后再进行分类回归树参数设置 */
trainParams.stumpConstructor = cvCreateMTStumpClassifier; // 筛选最优弱分类器
trainParams.splitIdx = icvSplitIndicesCallback; // CART节点分裂函数
trainParams.userdata = &userdata;
/******************************************************************************************************************\
* 第二步:使用cvBoostStartTraining开始训练
*确定训练的类型boosttype,样本数量,通过映射函数y*=2y-1,使得类别标签由{0,1}变为{-1,1}
\*******************************************************************************************************************/
trainer = cvBoostStartTraining( &data->cls, weakTrainVals, &data->weights, sampleIdx, boosttype );
/******************************************************************************************************************\
* 第三步:使用do-while循环训练,直到收敛条件
*收敛条件为:<span><span class="comment">两种收敛方式,一种是误检率小于规定阈值,另一种是弱分类器个数小于规定阈值
\*******************************************************************************************************************/
do {
//3.1 使用cvTrimWeights剔除小权值样本
trimmedIdx = cvTrimWeights( &data->weights, sampleIdx, weightfraction );
// 实际样本总数
numtrimmed = (trimmedIdx) ? MAX( trimmedIdx->rows, trimmedIdx->cols ) : m;
//3.2 创建分类回归树分类器
cart = (CvCARTClassifier*) cvCreateCARTClassifier( data->valcache,
flags,
weakTrainVals, 0, 0, 0, trimmedIdx,
&(data->weights),
(CvClassifierTrainParams*) &trainParams );
// 创建弱分类器
classifier = (CvCARTHaarClassifier*) icvCreateCARTHaarClassifier( numsplits );
// 将CART树转化为分类回归树haar弱分类器
icvInitCARTHaarClassifier( classifier, cart, haarFeatures );
num_splits += classifier->count;
cart->release( (CvClassifier**) &cart );
// 为何一定要在奇数个弱分类器处计算?
if( symmetric && (seq->total % 2) )
{
float normfactor = 0.0F;
CvStumpClassifier* stump;
/* 翻转HAAR特征 */
for( i = 0; i < classifier->count; i++ )
{
if( classifier->feature[i].desc[0] == 'h' ) //对垂直haar特征进行翻转
{
for( j = 0; j < CV_HAAR_FEATURE_MAX &&
classifier->feature[i].rect[j].weight != 0.0F; j++ )
{
classifier->feature[i].rect[j].r.x = data->winsize.width -
classifier->feature[i].rect[j].r.x -
classifier->feature[i].rect[j].r.width;
}
}
else
{ //对旋转haar特征进行翻转
int tmp = 0;
/* (x,y) -> (24-x,y) */
/* w -> h; h -> w */
for( j = 0; j < CV_HAAR_FEATURE_MAX &&
classifier->feature[i].rect[j].weight != 0.0F; j++ )
{
classifier->feature[i].rect[j].r.x = data->winsize.width -
classifier->feature[i].rect[j].r.x;
CV_SWAP( classifier->feature[i].rect[j].r.width,
classifier->feature[i].rect[j].r.height, tmp );
}
}
}
// 转化为基于积分图计算的特征
icvConvertToFastHaarFeature( classifier->feature,
classifier->fastfeature,
classifier->count, data->winsize.width + 1 );
// 为了验证最新翻转特征是否为最优特征
stumpTrainParams.getTrainData = NULL;
stumpTrainParams.numcomp = 1;
stumpTrainParams.userdata = NULL;
stumpTrainParams.sortedIdx = NULL;
// 验证是否新生成的特征可作为最优弱分类器
for( i = 0; i < classifier->count; i++ )
{
for( j = 0; j < numtrimmed; j++ )
{
// 获取训练样本
idx = icvGetIdxAt( trimmedIdx, j );
// 对每个训练样本计算Haar特征
eval.data.fl[idx] = cvEvalFastHaarFeature( &classifier->fastfeature[i],
(sum_type*) (data->sum.data.ptr + idx * data->sum.step),
(sum_type*) (data->tilted.data.ptr + idx * data->tilted.step) );
// 归一化因子
normfactor = data->normfactor.data.fl[idx];
// 对Haar特征归一化
eval.data.fl[idx] = ( normfactor == 0.0F )
? 0.0F : (eval.data.fl[idx] / normfactor);
}
//3.3 创建最优弱分类器
// 计算最优弱分类器 stump = (CvStumpClassifier*) trainParams.stumpConstructor( &eval, CV_COL_SAMPLE, weakTrainVals,
0, 0, 0, trimmedIdx, &(data->weights), trainParams.stumpTrainParams );
classifier->threshold[i] = stump->threshold; // 阈值
if( classifier->left[i] <= 0 )
{ classifier->val[-classifier->left[i]] = stump->left; // 左分支输出置信度 }
if( classifier->right[i] <= 0 )
{ classifier->val[-classifier->right[i]] = stump->right; // 右分支输出置信度
} stump->release( (CvClassifier**) &stump );
} // 调用icvEvalCARTHaarClassifier函数,计算每个样本的当前最优弱分类器置信度
for( i = 0; i < numsamples; i++ )
{ idx = icvGetIdxAt( sampleIdx, i );
eval.data.fl[idx] = classifier->eval( (CvIntHaarClassifier*) classifier, (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
(sum_type*) (data->tilted.data.ptr + idx * data->tilted.step), data->normfactor.data.fl[idx] );
}
//3.4 cvBoostNextWeakClassifier
//作用:根据上一个求出来的弱分类器计算错误率err,并根据错误率对所有训练样本更新权重,为求下一个弱分类器做准备
alpha = cvBoostNextWeakClassifier( &eval, &data->cls, weakTrainVals, &data->weights, trainer );
/*********************3.5遍历sampleIdx中所有样本并计算每个正样本的所有弱分类器置信度和************************/
for( i = 0; i < numsamples; i++ ) { // 获得样本序号 idx = icvGetIdxAt( sampleIdx, i ); // 如果样本为正样本
if( data->cls.data.fl[idx] == 1.0F )
{ // 初始化置信度值 eval.data.fl[numpos] = 0.0F; // 遍历seq中所有弱分类器
for( j = 0; j < seq->total; j++ )
{
// 获取弱分类器
classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j )); // 累积当前正样本的弱分类器置信度,eval.data.fl中的值相当于是每个正样本对于每个强分类器的特征值
eval.data.fl[numpos] += classifier->eval( (CvIntHaarClassifier*) classifier,
(sum_type*) (data->sum.data.ptr + idx * data->sum.step),
(sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
data->normfactor.data.fl[idx] );
} /* eval.data.fl[numpos] = 2.0F * eval.data.fl[numpos] - seq->total; */ numpos++; } } // 对弱分类器输出置信度和进行排序
icvSort_32f( eval.data.fl, numpos, 0 ); // 计算阈值,应该是大于threshold则为正类,小于threshold则为负类
threshold = eval.data.fl[(int) ((1.0F - minhitrate) * numpos)]; numneg = 0; numfalse = 0;
/****************3.6遍历sampleIdx中所有样本,计算每个负样本的对所有弱分类器置信度和,根据该值计算分错的负样本总数**********/
for( i = 0; i < numsamples; i++ )
{
idx = icvGetIdxAt( sampleIdx, i );
// 如果样本为负样本
if( data->cls.data.fl[idx] == 0.0F )
{
numneg++;
sum_stage = 0.0F;
// 遍历seq中所有弱分类器
for( j = 0; j < seq->total; j++ )
{
classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
// 累积当前负样本的分类器输出结果
sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
(sum_type*) (data->sum.data.ptr + idx * data->sum.step),
(sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
data->normfactor.data.fl[idx] );
}
/* sum_stage = 2.0F * sum_stage - seq->total; */
// 因为小于threshold为负类,所以下面是分类错误的情况
if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
{
numfalse++;
}
}
}
// 计算虚警率
falsealarm = ((float) numfalse) / ((float) numneg);
/******************************************3.7输出屏幕的内容********************************************/
#ifdef CV_VERBOSE
{
// 正样本检出率
float v_hitrate = 0.0F;
// 负样本误检率
float v_falsealarm = 0.0F;
/* expected error of stage classifier regardless threshold */
// 这是什么?
float v_experr = 0.0F;
// 遍历所有样本
for( i = 0; i < numsamples; i++ )
{
idx = icvGetIdxAt( sampleIdx, i );
sum_stage = 0.0F;
// 遍历seq中所有弱分类器
for( j = 0; j < seq->total; j++ )
{
classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
(sum_type*) (data->sum.data.ptr + idx * data->sum.step),
(sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
data->normfactor.data.fl[idx] );
}
/* sum_stage = 2.0F * sum_stage - seq->total; */
// 只需要判断单一分支即可
if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
{
if( data->cls.data.fl[idx] == 1.0F )
{
v_hitrate += 1.0F;
}
else
{
v_falsealarm += 1.0F;
}
}
// 正类样本的sum_stage必须大于0
if( ( sum_stage >= 0.0F ) != (data->cls.data.fl[idx] == 1.0F) )
{
v_experr += 1.0F;
}
}
v_experr /= numsamples;
printf( "|%4d|%3d%%|%c|%9f|%9f|%9f|%9f|\n",
seq->total, v_wt, ( (v_flipped) ? '+' : '-' ),
threshold, v_hitrate / numpos, v_falsealarm / numneg,
v_experr );
printf( "+----+----+-+---------+---------+---------+---------+\n" );
fflush( stdout );
}
} while( falsealarm > maxfalsealarm && (!maxsplits || (num_splits < maxsplits) ) );
/******************************************************************************************************************\
* 第四步:cvBoostEndTraining结束训练
\*******************************************************************************************************************/
cvBoostEndTraining( &trainer );
/******************************************************************************************************************\
* 第五步:根据虚警率来判断该训练好的强分类器是否保留
\*******************************************************************************************************************/
if( falsealarm > maxfalsealarm )
{
// 如果弱分类器达到上限而收敛,则放弃当前强分类器
stage = NULL;
}
else
{
// 创建当前强分类器
stage = (CvStageHaarClassifier*) icvCreateStageHaarClassifier( seq->total,
threshold );
// 保存当前强分类器
cvCvtSeqToArray( seq, (CvArr*) stage->classifier );
}
/* CLEANUP */
cvReleaseMemStorage( &storage );
cvReleaseMat( &weakTrainVals );
cvFree( &(eval.data.ptr) );
return (CvIntHaarClassifier*) stage;
}