-
从
Opencv教程上才发现下面的话。要是早点看到就好了,就不用看haartraining了,不过话说haartraining的网上的资料还是有不少
的,但是traincascade就比较少了,所以只能自己硬着头皮看代码了。在程序的使用上跟haartraining差不多,代码流程部分在这记录下
以后慢慢补充。
”OpenCV中有两个程序可以训练级联分类器: opencv_haartraining 和opencv_traincascade
。 opencv_traincascade 是一个新程序,根据OpenCV 2.x API 用C++ 编写。这二者主要的区别是opencv_traincascade 支持 Haar[Viola2001] 和
LBP[Liao2007] (Local
Binary Patterns) 两种特征,并易于增加其他的特征。与Haar特征相比,LBP特征是整数特征,因此训练和检测过程都会比Haar特征快几倍。LBP和Haar特征用于检测的准确率,是依赖训练过程中的训练数据的质量和训练参数。训练一个与基于Haar特征同样准确度的LBP的分类器是可能的。“
下面按照我觉得需要理解的部分进行分析。
1. 总流程
Traincascade.cpp中调用cvCascadeClassifier的train函数进行训练。
-
bool CvCascadeClassifier::train( const String _cascadeDirName,
-
const String _posFilename,
-
const String _negFilename,
-
int _numPos, int _numNeg,
-
int _precalcValBufSize, int _precalcIdxBufSize,
-
int _numStages,
-
const CvCascadeParams& _cascadeParams,
-
const CvFeatureParams& _featureParams,
-
const CvCascadeBoostParams& _stageParams,
-
bool baseFormatSave )
-
{
-
-
if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )
-
-
if ( !load( dirName ) )
-
{
-
cascadeParams = _cascadeParams;
-
featureParams = CvFeatureParams::create(cascadeParams.featureType);
-
featureParams->init(_featureParams);
-
stageParams = new CvCascadeBoostParams;
-
*stageParams = _stageParams;
-
featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
-
featureEvaluator->init( (CvFeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize );
-
stageClassifiers.reserve( numStages );
-
}
-
-
double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /
-
(double)stageParams->max_depth;
-
double tempLeafFARate;
-
-
for( int i = startNumStages; i < numStages; i++ )
-
{
-
cout << endl << "===== TRAINING " << i << "-stage =====" << endl;
-
cout << "<BEGIN" << endl;
-
-
if ( !updateTrainingSet( tempLeafFARate ) )
-
-
if( tempLeafFARate <= requiredLeafFARate )
-
-
-
CvCascadeBoost* tempStage = new CvCascadeBoost;
-
tempStage->train( (CvFeatureEvaluator*)featureEvaluator,
-
curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
-
*((CvCascadeBoostParams*)stageParams) );
-
stageClassifiers.push_back( tempStage );
-
-
cout << "END>" << endl;
-
-
-
-
-
}
-
save( dirName + CC_CASCADE_FILENAME, baseFormatSave );
-
return true;
-
}
-
bool CvCascadeClassifier::train( const String _cascadeDirName,
-
const String _posFilename,
-
const String _negFilename,
-
int _numPos, int _numNeg,
-
int _precalcValBufSize, int _precalcIdxBufSize,
-
int _numStages,
-
const CvCascadeParams& _cascadeParams,
-
const CvFeatureParams& _featureParams,
-
const CvCascadeBoostParams& _stageParams,
-
bool baseFormatSave )
-
{
-
-
if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )
-
-
if ( !load( dirName ) )
-
{
-
cascadeParams = _cascadeParams;
-
featureParams = CvFeatureParams::create(cascadeParams.featureType);
-
featureParams->init(_featureParams);
-
stageParams = new CvCascadeBoostParams;
-
*stageParams = _stageParams;
-
featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
-
featureEvaluator->init( (CvFeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize );
-
stageClassifiers.reserve( numStages );
-
}
-
-
double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /
-
(double)stageParams->max_depth;
-
double tempLeafFARate;
-
-
for( int i = startNumStages; i < numStages; i++ )
-
{
-
cout << endl << "===== TRAINING " << i << "-stage =====" << endl;
-
cout << "<BEGIN" << endl;
-
-
if ( !updateTrainingSet( tempLeafFARate ) )
-
-
if( tempLeafFARate <= requiredLeafFARate )
-
-
-
CvCascadeBoost* tempStage = new CvCascadeBoost;
-
tempStage->train( (CvFeatureEvaluator*)featureEvaluator,
-
curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
-
*((CvCascadeBoostParams*)stageParams) );
-
stageClassifiers.push_back( tempStage );
-
-
cout << "END>" << endl;
-
-
-
-
-
}
-
save( dirName + CC_CASCADE_FILENAME, baseFormatSave );
-
return true;
-
}
2.关于特征,以LBP为例(Haar特征网上例子比较多了)
2.1 LBP特征的形状,位置,数目的计算,都是在CvLBPEvaluator.cpp中
-
void CvLBPEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize)
-
{
-
CV_Assert( _maxSampleCount > 0);
-
sum.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1);
-
CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );
-
}
-
void CvLBPEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize)
-
{
-
CV_Assert( _maxSampleCount > 0);
-
sum.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1);
-
CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );
-
}
-
void CvFeatureEvaluator::init(const CvFeatureParams *_featureParams,
-
int _maxSampleCount, Size _winSize )
-
{
-
CV_Assert(_maxSampleCount > 0);
-
featureParams = (CvFeatureParams *)_featureParams;
-
winSize = _winSize;
-
numFeatures = 0;
-
cls.create( (int)_maxSampleCount, 1, CV_32FC1 );
-
generateFeatures();
-
}
-
void CvFeatureEvaluator::init(const CvFeatureParams *_featureParams,
-
int _maxSampleCount, Size _winSize )
-
{
-
CV_Assert(_maxSampleCount > 0);
-
featureParams = (CvFeatureParams *)_featureParams;
-
winSize = _winSize;
-
numFeatures = 0;
-
cls.create( (int)_maxSampleCount, 1, CV_32FC1 );
-
generateFeatures();
-
}
-
void CvLBPEvaluator::generateFeatures()
-
{
-
int offset = winSize.width + 1;
-
for( int x = 0; x < winSize.width; x++ )
-
for( int y = 0; y < winSize.height; y++ )
-
for( int w = 1; w <= winSize.width / 3; w++ )
-
for( int h = 1; h <= winSize.height / 3; h++ )
-
if ( (x+3*w <= winSize.width) && (y+3*h <= winSize.height) )
-
features.push_back( Feature(offset, x, y, w, h ) );
-
numFeatures = (int)features.size();
-
}
-
void CvLBPEvaluator::generateFeatures()
-
{
-
int offset = winSize.width + 1;
-
for( int x = 0; x < winSize.width; x++ )
-
for( int y = 0; y < winSize.height; y++ )
-
for( int w = 1; w <= winSize.width / 3; w++ )
-
for( int h = 1; h <= winSize.height / 3; h++ )
-
if ( (x+3*w <= winSize.width) && (y+3*h <= winSize.height) )
-
features.push_back( Feature(offset, x, y, w, h ) );
-
numFeatures = (int)features.size();
-
}
-
-
/*
-
个人理解这里LBP的Feature是指的是liao的文章的Fig.1(b)。是9x9MB-LBP operator
-
CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight )
-
{
-
Rect tr = rect = cvRect(x, y, _blockWidth, _blockHeight);
-
CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset )
-
tr.x += 2*rect.width;
-
CV_SUM_OFFSETS( p[2], p[3], p[6], p[7], tr, offset )
-
tr.y +=2*rect.height;
-
CV_SUM_OFFSETS( p[10], p[11], p[14], p[15], tr, offset )
-
tr.x -= 2*rect.width;
-
CV_SUM_OFFSETS( p[8], p[9], p[12], p[13], tr, offset )
-
}
-
-
/*
-
个人理解这里LBP的Feature是指的是liao的文章的Fig.1(b)。是9x9MB-LBP operator
-
CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight )
-
{
-
Rect tr = rect = cvRect(x, y, _blockWidth, _blockHeight);
-
CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset )
-
tr.x += 2*rect.width;
-
CV_SUM_OFFSETS( p[2], p[3], p[6], p[7], tr, offset )
-
tr.y +=2*rect.height;
-
CV_SUM_OFFSETS( p[10], p[11], p[14], p[15], tr, offset )
-
tr.x -= 2*rect.width;
-
CV_SUM_OFFSETS( p[8], p[9], p[12], p[13], tr, offset )
-
}
2.2 LBP特征的值的计算
这部分是跟加载正负样本数据到训练集合中结合到一起的,也就是CvCascadeClassifier::updateTrainingSet函数,这个函数还返回一个这次加载的负样本的FA值,来决定是否退出训练。
-
bool CvCascadeClassifier::updateTrainingSet( double& acceptanceRatio)
-
{
-
int64 posConsumed = 0, negConsumed = 0;
-
imgReader.restart();
-
int posCount = fillPassedSamples( 0, numPos, true, posConsumed );
-
cout << "POS count : consumed " << posCount << " : " << (int)posConsumed << endl;
-
-
int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos );
-
int negCount = fillPassedSamples( posCount, proNumNeg, false, negConsumed );
-
curNumSamples = posCount + negCount;
-
acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed );
-
cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl;
-
return true;
-
}
-
bool CvCascadeClassifier::updateTrainingSet( double& acceptanceRatio)
-
{
-
int64 posConsumed = 0, negConsumed = 0;
-
imgReader.restart();
-
int posCount = fillPassedSamples( 0, numPos, true, posConsumed );
-
cout << "POS count : consumed " << posCount << " : " << (int)posConsumed << endl;
-
-
int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos );
-
int negCount = fillPassedSamples( posCount, proNumNeg, false, negConsumed );
-
curNumSamples = posCount + negCount;
-
acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed );
-
cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl;
-
return true;
-
}
-
int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, int64& consumed )
-
{
-
int getcount = 0;
-
Mat img(cascadeParams.winSize, CV_8UC1);
-
for( int i = first; i < first + count; i++ )
-
{
-
for( ; ; )
-
{
-
bool isGetImg = isPositive ? imgReader.getPos( img ) :
-
imgReader.getNeg( img );
-
if( !isGetImg )
-
return getcount;
-
consumed++;
-
-
featureEvaluator->setImage( img, isPositive ? 1 : 0, i );
-
if( predict( i ) == 1.0F )
-
{
-
getcount++;
-
break;
-
}
-
}
-
}
-
return getcount;
-
}
-
int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, int64& consumed )
-
{
-
int getcount = 0;
-
Mat img(cascadeParams.winSize, CV_8UC1);
-
for( int i = first; i < first + count; i++ )
-
{
-
for( ; ; )
-
{
-
bool isGetImg = isPositive ? imgReader.getPos( img ) :
-
imgReader.getNeg( img );
-
if( !isGetImg )
-
return getcount;
-
consumed++;
-
-
featureEvaluator->setImage( img, isPositive ? 1 : 0, i );
-
if( predict( i ) == 1.0F )
-
{
-
getcount++;
-
break;
-
}
-
}
-
}
-
return getcount;
-
}
-
bool getNeg(Mat &_img) { return negReader.get( _img ); }
-
bool getPos(Mat &_img) { return posReader.get( _img ); }
-
bool getNeg(Mat &_img) { return negReader.get( _img ); }
-
bool getPos(Mat &_img) { return posReader.get( _img ); }
读一个图像是从CvCascadeImageReader读的图像,分为读正样本和负样本,正样本是通过vec文件读的,暂且不去管了,反正是固定大小的。负样本的negReader的get为:
-
bool CvCascadeImageReader::NegReader::get( Mat& _img )
-
{
-
if( img.empty() )
-
if ( !nextImg() )
-
return false;
-
-
Mat mat( winSize.height, winSize.width, CV_8UC1,
-
(void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step );
-
mat.copyTo(_img);
-
-
if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols )
-
point.x += (int)(stepFactor * winSize.width);
-
else
-
{
-
point.x = offset.x;
-
if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows )
-
point.y += (int)(stepFactor * winSize.height);
-
else
-
{
-
point.y = offset.y;
-
scale *= scaleFactor;
-
if( scale <= 1.0F )
-
resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) );
-
else
-
{
-
if ( !nextImg() )
-
return false;
-
}
-
}
-
}
-
return true;
-
}
-
bool CvCascadeImageReader::NegReader::get( Mat& _img )
-
{
-
if( img.empty() )
-
if ( !nextImg() )
-
return false;
-
-
Mat mat( winSize.height, winSize.width, CV_8UC1,
-
(void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step );
-
mat.copyTo(_img);
-
-
if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols )
-
point.x += (int)(stepFactor * winSize.width);
-
else
-
{
-
point.x = offset.x;
-
if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows )
-
point.y += (int)(stepFactor * winSize.height);
-
else
-
{
-
point.y = offset.y;
-
scale *= scaleFactor;
-
if( scale <= 1.0F )
-
resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) );
-
else
-
{
-
if ( !nextImg() )
-
return false;
-
}
-
}
-
}
-
return true;
-
}
-
void CvLBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx)
-
{
-
CV_DbgAssert( !sum.empty() );
-
CvFeatureEvaluator::setImage( img, clsLabel, idx );
-
Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr<int>((int)idx));
-
integral( img, innSum );
-
}
-
void CvLBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx)
-
{
-
CV_DbgAssert( !sum.empty() );
-
CvFeatureEvaluator::setImage( img, clsLabel, idx );
-
Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr<int>((int)idx));
-
integral( img, innSum );
-
}
3. 训练
-
bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,
-
int _numSamples,
-
int _precalcValBufSize, int _precalcIdxBufSize,
-
const CvCascadeBoostParams& _params )
-
{
-
CV_Assert( !data );
-
clear();
-
data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,
-
_precalcValBufSize, _precalcIdxBufSize, _params );
-
CvMemStorage *storage = cvCreateMemStorage();
-
weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage );
-
storage = 0;
-
-
set_params( _params );
-
if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )
-
data->do_responses_copy();
-
-
update_weights( 0 );
-
-
cout << "+----+---------+---------+" << endl;
-
cout << "| N | HR | FA |" << endl;
-
cout << "+----+---------+---------+" << endl;
-
-
do
-
{
-
CvCascadeBoostTree* tree = new CvCascadeBoostTree;
-
if( !tree->train( data, subsample_mask, this ) )
-
{
-
delete tree;
-
break;
-
}
-
cvSeqPush( weak, &tree );
-
update_weights( tree );
-
trim_weights();
-
if( cvCountNonZero(subsample_mask) == 0 )
-
break;
-
}
-
while( !isErrDesired() && (weak->total < params.weak_count) );
-
-
data->is_classifier = true;
-
data->free_train_data();
-
return true;
-
}
-
bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,
-
int _numSamples,
-
int _precalcValBufSize, int _precalcIdxBufSize,
-
const CvCascadeBoostParams& _params )
-
{
-
CV_Assert( !data );
-
clear();
-
data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,
-
_precalcValBufSize, _precalcIdxBufSize, _params );
-
CvMemStorage *storage = cvCreateMemStorage();
-
weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage );
-
storage = 0;
-
-
set_params( _params );
-
if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )
-
data->do_responses_copy();
-
-
update_weights( 0 );
-
-
cout << "+----+---------+---------+" << endl;
-
cout << "| N | HR | FA |" << endl;
-
cout << "+----+---------+---------+" << endl;
-
-
do
-
{
-
CvCascadeBoostTree* tree = new CvCascadeBoostTree;
-
if( !tree->train( data, subsample_mask, this ) )
-
{
-
delete tree;
-
break;
-
}
-
cvSeqPush( weak, &tree );
-
update_weights( tree );
-
trim_weights();
-
if( cvCountNonZero(subsample_mask) == 0 )
-
break;
-
}
-
while( !isErrDesired() && (weak->total < params.weak_count) );
-
-
data->is_classifier = true;
-
data->free_train_data();
-
return true;
-
}
阅读(1720) | 评论(0) | 转发(0) |