从OpenCV源码了解traincascade训练报错:Train dataset for temp stage can not be filled.
如果你在测试trainCascade时,资料目录没有放在项目目录中,很有可能触发下面的报错:
Train dataset for temp stage can not be filled.
我们很容易定位这个错误的来源,在cascadeClassifier.cpp中
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,
double acceptanceRatioBreakValue )
{
...
for( int i = startNumStages; i < numStages; i++ )
{
cout << endl << "===== TRAINING " << i << "-stage =====" << endl;
cout << "<BEGIN" << endl;
if ( !updateTrainingSet( requiredLeafFARate, tempLeafFARate ) )
{
cout << "Train dataset for temp stage can not be filled. "
"Branch training terminated." << endl;
break;
}
...
}
...
}
进一步,updateTrainingSet中是在哪里返回false的呢?
bool CvCascadeClassifier::updateTrainingSet( double minimumAcceptanceRatio, double& acceptanceRatio)
{
...
int negCount = fillPassedSamples( posCount, proNumNeg, false, minimumAcceptanceRatio, negConsumed );
if ( !negCount )
if ( !(negConsumed > 0 && ((double)negCount+1)/(double)negConsumed <= minimumAcceptanceRatio) )
return false;
...
}
可见,触发这个错误的先决条件是if(!negCount), 也就是说,没有读取你的负样本。这样理解的话,你大概也猜得出来,要么文件名,要么哪个目录有问题。
如果你想通过搜索进一步去解决问题,得到的答案五花八门。
其实最便捷的办法,还是解析源码来得快,下面我们来走一遭。
首先,负本的数据是在哪里开始读取的?
我们看一下cascadeclassfier.cpp中的fillPassedSamples函数,
int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, double minimumAcceptanceRatio, 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++;
...
}
}
return getcount;
}
其中有一个明显的读取操作:imgReader.getNeg( img ),那我们来追踪一下,
bool CvCascadeImageReader::NegReader::get( Mat& _img )
{
...
if( img.empty() )
if ( !nextImg() )
return false;
...
}
接着找一个这个nextImg()函数,如下
bool CvCascadeImageReader::NegReader::nextImg()
{
Point _offset = Point(0,0);
size_t count = imgFilenames.size();
for( size_t i = 0; i < count; i++ )
{
src = imread( imgFilenames[last++], 0 );
...
}
...
}
嗯,终于看到了熟悉的imread(...),在loadsave.cpp中
Mat imread( const String& filename, int flags )
{
...
/// load the data
imread_( filename, flags, LOAD_MAT, &img );
...
return img;
}
static void*
imread_( const String& filename, int flags, int hdrtype, Mat* mat=0 )
{
...
decoder = findDecoder( filename );
...
/// if no decoder was found, return nothing.
if( !decoder ){
return 0;
}
...
}
进一步imread_(...)调用了解码器findDecoder(...)函数,
static ImageDecoder findDecoder( const String& filename ) {
size_t i, maxlen = 0;
/// iterate through list of registered codecs
for( i = 0; i < codecs.decoders.size(); i++ )
{
size_t len = codecs.decoders[i]->signatureLength();
maxlen = std::max(maxlen, len);
}
/// Open the file
FILE* f= fopen( filename.c_str(), "rb" );
/// in the event of a failure, return an empty image decoder
if( !f )
return ImageDecoder();
。。。
}
好了,打开文件的源码就是他了:FILE* f= fopen( filename.c_str(), "rb" )。这里你可以慢慢分析到底是文件名哪个地方不对。如果你要往回追究的话,这个文件名是来源于nextImg调用的数组imgFilenames[last++]的,而这个数组是在CvCascadeImageReader::NegReader::create中初始化的,
bool CvCascadeImageReader::NegReader::create( const string _filename, Size _winSize )
{
string str;
std::ifstream file(_filename.c_str());
if ( !file.is_open() )
return false;
while( !file.eof() )
{
std::getline(file, str);
str.erase(str.find_last_not_of(" \n\r\t")+1);
if (str.empty()) break;
if (str.at(0) == '#' ) continue; /* comment */
imgFilenames.push_back(str);
}
file.close();
winSize = _winSize;
last = round = 0;
return true;
}
好了,前因后果都明白了,相信问题就简单了。
不过为了方便,我举个简单的例子,假设你的项目目录为/apps/trainCascade/opencv_traincascade.vcproj,在/apps/dataCascade/建立了你的数据和图片文件,目录文件如../dataCascade/neg.txt,其内容是
neg/001.pgm
neg/002.pgm
......
负本图片放在这里
app/dataCascade/neg/001.pgm
app/dataCascade/neg/001.pgm
......
那对不起, 找不到,为什么?
因为imgFilenames.push_back(str);这句,把目录文件中的字符串如“neg/001.pgm”保存起来,当读取的时候,这个地址就会被当成相对于项目的地址,也就是app/trainCascade/neg/001.pgm, 这当然是找不到的,因为是在项目的上一级../data中放的dataCascade目录。解决办法就是,把你的neg.txt的内容改成下面这样的形式,
../dataCascade/neg/001.pgm
../dataCascade/neg/002.pgm
......
这样,就可以啦!