0
点赞
收藏
分享

微信扫一扫

从OpenCV源码了解traincascade训练报错:Train dataset for temp stage can not be filled.

我阿霆哥 2022-03-11 阅读 112


如果你在测试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

......

这样,就可以啦!




举报

相关推荐

0 条评论