diff --git a/openbr/core/_cascade.cpp b/openbr/core/_cascade.cpp new file mode 100644 index 0000000..da41b5f --- /dev/null +++ b/openbr/core/_cascade.cpp @@ -0,0 +1,577 @@ +#include "_cascade.h" +#include +#include +#include + +using namespace std; +using namespace br; +using namespace cv; + +bool CascadeImageReader::create( const vector &_posImages, const vector &_negImages, Size _winSize ) +{ + posImages = _posImages; + negImages = _negImages; + winSize = _winSize; + return posReader.create(_posImages) && negReader.create(_negImages, _winSize); +} + +CascadeImageReader::NegReader::NegReader() +{ + src.create( 0, 0 , CV_8UC1 ); + img.create( 0, 0, CV_8UC1 ); + point = offset = Point( 0, 0 ); + scale = 1.0F; + scaleFactor = 1.4142135623730950488016887242097F; + stepFactor = 0.5F; +} + +bool CascadeImageReader::NegReader::create( const vector &_negImages, Size _winSize ) +{ + negImages = _negImages; + winSize = _winSize; + last = round = 0; + return true; +} + +bool CascadeImageReader::NegReader::nextImg() +{ + Point _offset = Point(0,0); + size_t count = negImages.size(); + for( size_t i = 0; i < count; i++ ) + { + src = negImages[last++]; + if( src.empty() ) + continue; + round += last / count; + round = round % (winSize.width * winSize.height); + last %= count; + + _offset.x = std::min( (int)round % winSize.width, src.cols - winSize.width ); + _offset.y = std::min( (int)round / winSize.width, src.rows - winSize.height ); + if( !src.empty() && src.type() == CV_8UC1 + && _offset.x >= 0 && _offset.y >= 0 ) + break; + } + + if( src.empty() ) + return false; // no appropriate image + point = offset = _offset; + scale = max( ((float)winSize.width + point.x) / ((float)src.cols), + ((float)winSize.height + point.y) / ((float)src.rows) ); + + Size sz( (int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F) ); + resize( src, img, sz ); + return true; +} + +bool CascadeImageReader::NegReader::get( Mat& _img ) +{ + CV_Assert( !_img.empty() ); + CV_Assert( _img.type() == CV_8UC1 ); + CV_Assert( _img.cols == winSize.width ); + CV_Assert( _img.rows == winSize.height ); + + 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; +} + +CascadeImageReader::PosReader::PosReader() +{ + file = 0; + vec = 0; + last = 0; +} + +bool CascadeImageReader::PosReader::create( const vector &_posImages ) +{ + posImages = _posImages; + return true; +} + +bool CascadeImageReader::getPos(Mat &_img) +{ + if (last > (int)posImages.size()) + CV_Error( CV_StsBadArg, "Can not get new positive sample. vec-file is over.\n"); + _img = posImages[posIdx++]; + return true; +} + +//---------------------------- CascadeParams -------------------------------------- + +static const char* stageTypes[] = { CC_BOOST }; +static const char* featureTypes[] = { CC_LBP, CC_HAAR, CC_HOG, CC_HOGMULTI, CC_NPD }; + +CascadeParams::CascadeParams() : stageType( defaultStageType ), + featureType( defaultFeatureType ), winSize( cvSize(24, 24) ) +{ + name = CC_CASCADE_PARAMS; +} +CascadeParams::CascadeParams( int _stageType, int _featureType ) : stageType( _stageType ), + featureType( _featureType ), winSize( cvSize(24, 24) ) +{ + name = CC_CASCADE_PARAMS; +} + +void CascadeParams::write( FileStorage &fs ) const +{ + string stageTypeStr = stageType == BOOST ? CC_BOOST : string(); + CV_Assert( !stageTypeStr.empty() ); + fs << CC_STAGE_TYPE << stageTypeStr; + string featureTypeStr = featureType == FeatureParams::LBP ? CC_LBP : + 0; + CV_Assert( !stageTypeStr.empty() ); + fs << CC_FEATURE_TYPE << featureTypeStr; + fs << CC_HEIGHT << winSize.height; + fs << CC_WIDTH << winSize.width; +} + +bool CascadeParams::read( const FileNode &node ) +{ + if ( node.empty() ) + return false; + string stageTypeStr, featureTypeStr; + FileNode rnode = node[CC_STAGE_TYPE]; + if ( !rnode.isString() ) + return false; + rnode >> stageTypeStr; + stageType = !stageTypeStr.compare( CC_BOOST ) ? BOOST : -1; + if (stageType == -1) + return false; + rnode = node[CC_FEATURE_TYPE]; + if ( !rnode.isString() ) + return false; + rnode >> featureTypeStr; + featureType = !featureTypeStr.compare( CC_LBP ) ? FeatureParams::LBP : + -1; + if (featureType == -1) + return false; + node[CC_HEIGHT] >> winSize.height; + node[CC_WIDTH] >> winSize.width; + return winSize.height > 0 && winSize.width > 0; +} + +void CascadeParams::printDefaults() const +{ + Params::printDefaults(); + cout << " [-stageType <"; + for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ ) + { + cout << (i ? " | " : "") << stageTypes[i]; + if ( i == defaultStageType ) + cout << "(default)"; + } + cout << ">]" << endl; + + cout << " [-featureType <{"; + for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ ) + { + cout << (i ? ", " : "") << featureTypes[i]; + if ( i == defaultStageType ) + cout << "(default)"; + } + cout << "}>]" << endl; + cout << " [-w ]" << endl; + cout << " [-h ]" << endl; +} + +void CascadeParams::printAttrs() const +{ + cout << "stageType: " << stageTypes[stageType] << endl; + cout << "featureType: " << featureTypes[featureType] << endl; + cout << "sampleWidth: " << winSize.width << endl; + cout << "sampleHeight: " << winSize.height << endl; +} + +bool CascadeParams::scanAttr( const string prmName, const string val ) +{ + bool res = true; + if( !prmName.compare( "-stageType" ) ) + { + for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ ) + if( !val.compare( stageTypes[i] ) ) + stageType = i; + } + else if( !prmName.compare( "-featureType" ) ) + { + for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ ) + if( !val.compare( featureTypes[i] ) ) + featureType = i; + } + else if( !prmName.compare( "-w" ) ) + { + winSize.width = atoi( val.c_str() ); + } + else if( !prmName.compare( "-h" ) ) + { + winSize.height = atoi( val.c_str() ); + } + else + res = false; + return res; +} + +//---------------------------- CascadeClassifier -------------------------------------- + +bool BrCascadeClassifier::train(const string _cascadeDirName, + const vector &_posImages, + const vector &_negImages, + int _numPos, int _numNeg, + int _precalcValBufSize, int _precalcIdxBufSize, + int _numStages, + const CascadeParams& _cascadeParams, + const FeatureParams& _featureParams, + const CascadeBoostParams& _stageParams, + bool baseFormatSave ) +{ + // Start recording clock ticks for training time output + const clock_t begin_time = clock(); + + if( _cascadeDirName.empty() ) + CV_Error( CV_StsBadArg, "_cascadeDirName is NULL" ); + + string dirName; + if (_cascadeDirName.find_last_of("/\\") == (_cascadeDirName.length() - 1) ) + dirName = _cascadeDirName; + else + dirName = _cascadeDirName + '/'; + + numPos = _numPos; + numNeg = _numNeg; + numStages = _numStages; + imgReader.create( _posImages, _negImages, _cascadeParams.winSize ); + + if ( !load( dirName ) ) + { + cascadeParams = _cascadeParams; + featureParams = FeatureParams::create(cascadeParams.featureType); + featureParams->init(_featureParams); + stageParams = new CascadeBoostParams; + *stageParams = _stageParams; + featureEvaluator = FeatureEvaluator::create(cascadeParams.featureType); + featureEvaluator->init( (FeatureParams*)featureParams, numPos + numNeg, cascadeParams.winSize ); + stageClassifiers.reserve( numStages ); + } + cout << "PARAMETERS:" << endl; + cout << "cascadeDirName: " << _cascadeDirName << endl; + cout << "numPos: " << _numPos << endl; + cout << "numNeg: " << _numNeg << endl; + cout << "numStages: " << numStages << endl; + cout << "precalcValBufSize[Mb] : " << _precalcValBufSize << endl; + cout << "precalcIdxBufSize[Mb] : " << _precalcIdxBufSize << endl; + cascadeParams.printAttrs(); + stageParams->printAttrs(); + featureParams->printAttrs(); + + int startNumStages = (int)stageClassifiers.size(); + if ( startNumStages > 1 ) + cout << endl << "Stages 0-" << startNumStages-1 << " are loaded" << endl; + else if ( startNumStages == 1) + cout << endl << "Stage 0 is loaded" << endl; + + 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 << "train( (FeatureEvaluator*)featureEvaluator, + curNumSamples, _precalcValBufSize, _precalcIdxBufSize, + *((CascadeBoostParams*)stageParams) ); + cout << "END>" << endl; + + if(!isStageTrained) + break; + + stageClassifiers.push_back( tempStage ); + + // save params + if( i == 0) + { + std::string paramsFilename = dirName + CC_PARAMS_FILENAME; + FileStorage fs( paramsFilename, FileStorage::WRITE); + if ( !fs.isOpened() ) + { + cout << "Parameters can not be written, because file " << paramsFilename + << " can not be opened." << endl; + return false; + } + fs << FileStorage::getDefaultObjectName(paramsFilename) << "{"; + writeParams( fs ); + fs << "}"; + } + // save current stage + char buf[10]; + sprintf(buf, "%s%d", "stage", i ); + string stageFilename = dirName + buf + ".xml"; + FileStorage fs( stageFilename, FileStorage::WRITE ); + if ( !fs.isOpened() ) + { + cout << "Current stage can not be written, because file " << stageFilename + << " can not be opened." << endl; + return false; + } + fs << FileStorage::getDefaultObjectName(stageFilename) << "{"; + tempStage->write( fs, Mat() ); + fs << "}"; + + // Output training time up till now + float seconds = float( clock () - begin_time ) / CLOCKS_PER_SEC; + int days = int(seconds) / 60 / 60 / 24; + int hours = (int(seconds) / 60 / 60) % 24; + int minutes = (int(seconds) / 60) % 60; + int seconds_left = int(seconds) % 60; + cout << "Training until now has taken " << days << " days " << hours << " hours " << minutes << " minutes " << seconds_left <<" seconds." << endl; + } + + if(stageClassifiers.size() == 0) + { + cout << "Cascade classifier can't be trained. Check the used training parameters." << endl; + return false; + } + + save( dirName + CC_CASCADE_FILENAME, baseFormatSave ); + + return true; +} + +int BrCascadeClassifier::predict( int sampleIdx ) +{ + CV_DbgAssert( sampleIdx < numPos + numNeg ); + for (vector< Ptr >::iterator it = stageClassifiers.begin(); + it != stageClassifiers.end(); it++ ) + { + if ( (*it)->predict( sampleIdx ) == 0.f ) + return 0; + } + return 1; +} + +bool BrCascadeClassifier::updateTrainingSet( double& acceptanceRatio) +{ + int64 posConsumed = 0, negConsumed = 0; + imgReader.restart(); + + int posCount = fillPassedSamples( 0, numPos, true, posConsumed ); + if( !posCount ) + return false; + cout << "POS count : consumed " << posCount << " : " << (int)posConsumed << endl; + + int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos ); // apply only a fraction of negative samples. double is required since overflow is possible + int negCount = fillPassedSamples( posCount, proNumNeg, false, negConsumed ); + if ( !negCount ) + return false; + + curNumSamples = posCount + negCount; + acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed ); + cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl; + return true; +} + +int BrCascadeClassifier::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++; + printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount); + break; + } + } + } + return getcount; +} + +void BrCascadeClassifier::writeParams( FileStorage &fs ) const +{ + cascadeParams.write( fs ); + fs << CC_STAGE_PARAMS << "{"; stageParams->write( fs ); fs << "}"; + fs << CC_FEATURE_PARAMS << "{"; featureParams->write( fs ); fs << "}"; +} + +void BrCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + ((FeatureEvaluator*)((Ptr)featureEvaluator))->writeFeatures( fs, featureMap ); +} + +void BrCascadeClassifier::writeStages( FileStorage &fs, const Mat& featureMap ) const +{ + char cmnt[30]; + int i = 0; + fs << CC_STAGES << "["; + for( vector< Ptr >::const_iterator it = stageClassifiers.begin(); + it != stageClassifiers.end(); it++, i++ ) + { + sprintf( cmnt, "stage %d", i ); + cvWriteComment( fs.fs, cmnt, 0 ); + fs << "{"; + ((CascadeBoost*)((Ptr)*it))->write( fs, featureMap ); + fs << "}"; + } + fs << "]"; +} + +bool BrCascadeClassifier::readParams( const FileNode &node ) +{ + if ( !node.isMap() || !cascadeParams.read( node ) ) + return false; + stageParams = new CascadeBoostParams; + FileNode rnode = node[CC_STAGE_PARAMS]; + if ( !stageParams->read( rnode ) ) + return false; + featureParams = FeatureParams::create(cascadeParams.featureType); + rnode = node[CC_FEATURE_PARAMS]; + if ( !featureParams->read( rnode ) ) + return false; + return true; +} + +bool BrCascadeClassifier::readStages( const FileNode &node) +{ + FileNode rnode = node[CC_STAGES]; + if (!rnode.empty() || !rnode.isSeq()) + return false; + stageClassifiers.reserve(numStages); + FileNodeIterator it = rnode.begin(); + for( int i = 0; i < min( (int)rnode.size(), numStages ); i++, it++ ) + { + CascadeBoost* tempStage = new CascadeBoost; + if ( !tempStage->read( *it, (FeatureEvaluator *)featureEvaluator, *((CascadeBoostParams*)stageParams) ) ) + { + delete tempStage; + return false; + } + stageClassifiers.push_back(tempStage); + } + return true; +} + +void BrCascadeClassifier::save( const string filename, bool baseFormat ) +{ + FileStorage fs( filename, FileStorage::WRITE ); + + if ( !fs.isOpened() ) + return; + + fs << FileStorage::getDefaultObjectName(filename) << "{"; + if ( !baseFormat ) + { + Mat featureMap; + getUsedFeaturesIdxMap( featureMap ); + writeParams( fs ); + fs << CC_STAGE_NUM << (int)stageClassifiers.size(); + writeStages( fs, featureMap ); + writeFeatures( fs, featureMap ); + } + else + { + qFatal("Old style cascade. Not sure how you got here but it's not supported"); + } + fs << "}"; +} + +bool BrCascadeClassifier::load( const string cascadeDirName ) +{ + FileStorage fs( cascadeDirName + CC_PARAMS_FILENAME, FileStorage::READ ); + if ( !fs.isOpened() ) + return false; + + FileNode node = fs.getFirstTopLevelNode(); + if ( !readParams( node ) ) + return false; + + featureEvaluator = FeatureEvaluator::create(cascadeParams.featureType); + featureEvaluator->init( ((FeatureParams*)featureParams), numPos + numNeg, cascadeParams.winSize ); + fs.release(); + + char buf[10]; + for ( int si = 0; si < numStages; si++ ) + { + sprintf( buf, "%s%d", "stage", si); + fs.open( cascadeDirName + buf + ".xml", FileStorage::READ ); + node = fs.getFirstTopLevelNode(); + if ( !fs.isOpened() ) + break; + CascadeBoost *tempStage = new CascadeBoost; + + if ( !tempStage->read( node, (FeatureEvaluator*)featureEvaluator, *((CascadeBoostParams*)stageParams )) ) + { + delete tempStage; + fs.release(); + break; + } + stageClassifiers.push_back(tempStage); + } + return true; +} + +void BrCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap ) +{ + int varCount = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize(); + featureMap.create( 1, varCount, CV_32SC1 ); + featureMap.setTo(Scalar(-1)); + + for( vector< Ptr >::const_iterator it = stageClassifiers.begin(); + it != stageClassifiers.end(); it++ ) + ((CascadeBoost*)((Ptr)(*it)))->markUsedFeaturesInMap( featureMap ); + + for( int fi = 0, idx = 0; fi < varCount; fi++ ) + if ( featureMap.at(0, fi) >= 0 ) + featureMap.ptr(0)[fi] = idx++; +} + + diff --git a/openbr/core/_cascade.h b/openbr/core/_cascade.h new file mode 100644 index 0000000..7a3d482 --- /dev/null +++ b/openbr/core/_cascade.h @@ -0,0 +1,97 @@ +#ifndef _CASCADE_H +#define _CASCADE_H + +#include +#include +#include "features.h" +#include "boost.h" + +namespace br +{ + +class CascadeImageReader +{ +public: + bool create( const std::vector &_posImages, const std::vector &_negImages, cv::Size _winSize ); + void restart() { posIdx = 0; } + bool getNeg(cv::Mat &_img); + bool getPos(cv::Mat &_img); + +private: + std::vector posImages, negImages; + + int posIdx, negIdx; + + cv::Mat src, img; + cv::Point offset, point; + float scale; + float scaleFactor; + float stepFactor; + size_t round; + cv::Size winSize; +}; + +class CascadeParams : public Params +{ +public: + enum { BOOST = 0 }; + static const int defaultStageType = BOOST; + static const int defaultFeatureType = FeatureParams::LBP; + + CascadeParams(); + CascadeParams( int _stageType, int _featureType ); + void write( cv::FileStorage &fs ) const; + bool read( const cv::FileNode &node ); + + void printDefaults() const; + void printAttrs() const; + bool scanAttr( const std::string prmName, const std::string val ); + + int stageType; + int featureType; + cv::Size winSize; +}; + +class BrCascadeClassifier +{ +public: + bool train( const std::string _cascadeDirName, + const std::vector &_posImages, + const std::vector &_negImages, + int _numPos, int _numNeg, + int _precalcValBufSize, int _precalcIdxBufSize, + int _numStages, + const CascadeParams& _cascadeParams, + const FeatureParams& _featureParams, + const CascadeBoostParams& _stageParams, + bool baseFormatSave = false ); +private: + int predict( int sampleIdx ); + void save( const std::string cascadeDirName, bool baseFormat = false ); + bool load( const std::string cascadeDirName ); + bool updateTrainingSet( double& acceptanceRatio ); + int fillPassedSamples( int first, int count, bool isPositive, int64& consumed ); + + void writeParams( cv::FileStorage &fs ) const; + void writeStages( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + bool readParams( const cv::FileNode &node ); + bool readStages( const cv::FileNode &node ); + + void getUsedFeaturesIdxMap( cv::Mat& featureMap ); + + CascadeParams cascadeParams; + cv::Ptr featureParams; + cv::Ptr stageParams; + + cv::Ptr featureEvaluator; + std::vector< cv::Ptr > stageClassifiers; + CascadeImageReader imgReader; + int numStages, curNumSamples; + int numPos, numNeg; +}; + +} // namespace br + +#endif // _CASCADE_H + diff --git a/openbr/core/cascade.cpp b/openbr/core/cascade.cpp index 43b1265..0e24593 100644 --- a/openbr/core/cascade.cpp +++ b/openbr/core/cascade.cpp @@ -6,7 +6,7 @@ using namespace std; using namespace br; using namespace cv; - +/* bool CascadeImageReader::create( const string _posFilename, const string _negFilename, Size _winSize ) { return posReader.create(_posFilename) && negReader.create(_negFilename, _winSize); @@ -41,7 +41,7 @@ bool CascadeImageReader::NegReader::create( const string _filename, Size _winSiz { std::getline(file, str); if (str.empty()) break; - if (str.at(0) == '#' ) continue; /* comment */ + if (str.at(0) == '#' ) continue; imgFilenames.push_back(dirname + str); } file.close(); @@ -186,7 +186,7 @@ CascadeImageReader::PosReader::~PosReader() cvFree( &vec ); } -// -------------------------------------- Cascade -------------------------------------------- +//---------------------------- CascadeParams -------------------------------------- static const char* stageTypes[] = { CC_BOOST }; static const char* featureTypes[] = { CC_LBP, CC_HAAR, CC_HOG, CC_HOGMULTI, CC_NPD }; @@ -202,8 +202,6 @@ CascadeParams::CascadeParams( int _stageType, int _featureType ) : stageType( _s name = CC_CASCADE_PARAMS; } -//---------------------------- CascadeParams -------------------------------------- - void CascadeParams::write( FileStorage &fs ) const { string stageTypeStr = stageType == BOOST ? CC_BOOST : string(); @@ -304,7 +302,7 @@ bool CascadeParams::scanAttr( const string prmName, const string val ) //---------------------------- CascadeClassifier -------------------------------------- -bool BrCascadeClassifier::train( const string _cascadeDirName, +bool BrCascadeClassifier::train(const string _cascadeDirName, const string _posFilename, const string _negFilename, int _numPos, int _numNeg, @@ -598,16 +596,13 @@ void BrCascadeClassifier::save( const string filename, bool baseFormat ) bool BrCascadeClassifier::load( const string cascadeDirName ) { FileStorage fs( cascadeDirName + CC_PARAMS_FILENAME, FileStorage::READ ); - qDebug() << "1"; - if ( !fs.isOpened() ) { - qDebug() << "2"; + if ( !fs.isOpened() ) return false; - } + FileNode node = fs.getFirstTopLevelNode(); - if ( !readParams( node ) ) { - qDebug() << "3"; + if ( !readParams( node ) ) return false; - } + featureEvaluator = FeatureEvaluator::create(cascadeParams.featureType); featureEvaluator->init( ((FeatureParams*)featureParams), numPos + numNeg, cascadeParams.winSize ); fs.release(); @@ -646,5 +641,5 @@ void BrCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap ) for( int fi = 0, idx = 0; fi < varCount; fi++ ) if ( featureMap.at(0, fi) >= 0 ) featureMap.ptr(0)[fi] = idx++; -} +}*/ diff --git a/openbr/core/cascade.h b/openbr/core/cascade.h index 86f3f07..b71d919 100644 --- a/openbr/core/cascade.h +++ b/openbr/core/cascade.h @@ -8,7 +8,7 @@ namespace br { - +/* class CascadeImageReader { public: @@ -113,7 +113,7 @@ private: int numStages, curNumSamples; int numPos, numNeg; }; - +*/ } // namespace br #endif // CASCADE_H diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index b31a45e..61dff95 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -1505,7 +1505,7 @@ Transform *Transform::make(QString str, QObject *parent) Transform *Transform::clone() const { Transform *clone = Factory::make("."+description(false)); - return clone; + return clone; } static void _project(const Transform *transform, const Template *src, Template *dst) diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index 0abe6bc..493495f 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -1415,6 +1415,9 @@ public: virtual ~Classifier() {} static Classifier *make(QString str, QObject *parent); /*!< \brief Make a classifier from a string. */ + + virtual Classifier *clone() const { return Factory::make("." + description(false)); } + virtual void train(const QList &images, const QList &labels) = 0; // By convention, classify should return a value normalized such that the threshold is 0. Negative values // can be interpreted as a negative classification and positive values as a positive classification. diff --git a/openbr/plugins/classification/boost.cpp b/openbr/plugins/classification/boost.cpp new file mode 100644 index 0000000..b61ff5d --- /dev/null +++ b/openbr/plugins/classification/boost.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +using namespace cv; + +namespace br +{ + +class BoostClassifier : public Classifier +{ + Q_OBJECT + + Q_PROPERTY(float minTAR READ get_minTAR WRITE set_minTAR RESET reset_minTAR STORED false) + Q_PROPERTY(float maxFAR READ get_maxFAR WRITE set_maxFAR RESET reset_maxFAR STORED false) + Q_PROPERTY(float trimRate READ get_trimRate WRITE set_trimRate RESET reset_trimRate STORED false) + Q_PROPERTY(int maxDepth READ get_maxDepth WRITE set_maxDepth RESET reset_maxDepth STORED false) + Q_PROPERTY(int maxWeakCount READ get_maxWeakCount WRITE set_maxWeakCount RESET reset_maxWeakCount STORED false) + + BR_PROPERTY(float, minTAR, 0.995) + BR_PROPERTY(float, maxFAR, 0.5) + BR_PROPERTY(float, trimRate, 0.95) + BR_PROPERTY(int, maxDepth, 1) + BR_PROPERTY(int, maxWeakCount, 100) + + CascadeBoost *boost; + + void train(const QList &images, const QList &labels) + { + FeatureEvaluator *featureEvaluator = FeatureEvaluator::create(0); + featureEvaluator->init(new FeatureParams, images.size(), Size(24, 24)); + for (int i = 0; i < images.size(); i++) + featureEvaluator->setImage(images[i], (uchar)labels[i], i); + + CascadeBoostParams stageParams(CvBoost::GENTLE, minTAR, maxFAR, trimRate, maxDepth, maxWeakCount); + + if (boost) delete boost; + + boost = new CascadeBoost; + boost->train(featureEvaluator, images.size(), 1024, 1024, stageParams); + } + + float classify(const Mat &image) const + { + (void) image; + return 0.; + } +}; + +BR_REGISTER(Classifier, BoostClassifier) + +} // namespace br + +#include "classification/boost.moc" diff --git a/openbr/plugins/classification/cascade.cpp b/openbr/plugins/classification/cascade.cpp new file mode 100644 index 0000000..03f2066 --- /dev/null +++ b/openbr/plugins/classification/cascade.cpp @@ -0,0 +1,158 @@ +#include +#include + +using namespace cv; + +namespace br +{ + +class CascadeClassifier : public Classifier +{ + Q_OBJECT + + Q_PROPERTY(br::Classifier *stage READ get_stage WRITE set_stage RESET reset_stage STORED false) + Q_PROPERTY(int numStages READ get_numStages WRITE set_numStages RESET reset_numStages STORED false) + Q_PROPERTY(int numNegs READ get_numNegs WRITE set_numNegs RESET reset_numNegs STORED false) + Q_PROPERTY(float maxFAR READ get_maxFAR WRITE set_maxFAR RESET reset_maxFAR STORED false) + + BR_PROPERTY(br::Classifier *, stage, NULL) + BR_PROPERTY(int, numStages, 20) + BR_PROPERTY(int, numNegs, 1000) + BR_PROPERTY(float, maxFAR, pow(0.5, numStages)) + + QList stages; + + void train(const QList &images, const QList &labels) + { + QList posImages, negImages; + for (int i = 0; i < images.size(); i++) + labels[i] == 1 ? posImages.append(images[i]) : negImages.append(images[i]); + + QList trainingImages; + QList trainingLabels; + + for (int i = 0; i < numStages; i++) { + float currFAR = updateTrainingSet(posImages, negImages, trainingImages, trainingLabels); + + if (currFAR < maxFAR) { + qDebug() << "FAR is below required level! Terminating early"; + return; + } + + Classifier *next_stage = stage->clone(); + next_stage->train(trainingImages, trainingLabels); + stages.append(next_stage); + } + } + + float classify(const Mat &image) const + { + (void) image; + return 0.; + } + + float updateTrainingSet(const QList &posImages, const QList &negImages, QList &trainingImages, QList &trainingLabels) + { + trainingImages.clear(); + trainingLabels.clear(); + + foreach (const Mat &pos, posImages) { + if (classify(pos) > 0) { + trainingImages.append(pos); + trainingLabels.append(1.); + } + } + + NegFinder finder(negImages, Size(24, 24)); + int totalNegs = 0, passedNegs = 0; + while (true) { + totalNegs++; + Mat neg = finder.get(); + if (classify(neg) > 0) { + trainingImages.append(neg); + trainingLabels.append(0.); + passedNegs++; + } + + if (passedNegs >= numNegs) + return passedNegs / (float)totalNegs; + } + } + +private: + struct NegFinder + { + NegFinder(const QList &_negs, Size _winSize) + { + negs = _negs; + winSize = _winSize; + + negIdx = round = 0; + img.create( 0, 0, CV_8UC1 ); + point = offset = Point( 0, 0 ); + scale = 1.0F; + scaleFactor = 1.4142135623730950488016887242097F; + stepFactor = 0.5F; + } + + void _next() + { + src = negs[negIdx++]; + + round += negIdx / negs.size(); + round %= (winSize.width * winSize.height); + negIdx %= negs.size(); + + point = offset = Point(std::min(round % winSize.width, src.cols - winSize.width), + std::min(round / winSize.width, src.rows - winSize.height)); + + scale = max(((float)winSize.width + point.x) / ((float)src.cols), + ((float)winSize.height + point.y) / ((float)src.rows)); + + Size sz((int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F)); + resize(src, img, sz); + } + + Mat get() + { + if (img.empty()) + _next(); + + Mat neg(winSize, CV_8UC1); + Mat m(winSize.height, winSize.width, CV_8UC1, (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step); + m.copyTo(neg); + + 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 + _next(); + } + } + return neg; + } + + QList negs; + int negIdx, round; + Mat src, img; + float scale; + float scaleFactor; + float stepFactor; + Size winSize; + Point offset, point; + }; +}; + +BR_REGISTER(Classifier, CascadeClassifier) + +} // namespace br + +#include "classification/cascade.moc" diff --git a/openbr/plugins/imgproc/resizefilter.cpp b/openbr/plugins/imgproc/resizefilter.cpp new file mode 100644 index 0000000..ab3345f --- /dev/null +++ b/openbr/plugins/imgproc/resizefilter.cpp @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include + +using namespace cv; + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Resize the template depending on its metadata + * \author Jordan Cheney \cite JordanCheney + * \note Method: Area should be used for shrinking an image, Cubic for slow but accurate enlargment, Bilin for fast enlargement. + */ +class ResizeFilterTransform : public UntrainableTransform +{ + Q_OBJECT + Q_ENUMS(Method) + +public: + /*!< */ + enum Method { Near = INTER_NEAREST, + Area = INTER_AREA, + Bilin = INTER_LINEAR, + Cubic = INTER_CUBIC, + Lanczo = INTER_LANCZOS4}; + +private: + Q_PROPERTY(int rows READ get_rows WRITE set_rows RESET reset_rows STORED false) + Q_PROPERTY(int columns READ get_columns WRITE set_columns RESET reset_columns STORED false) + Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) + Q_PROPERTY(QString filterKey READ get_filterKey WRITE set_filterKey RESET reset_filterKey STORED false) + Q_PROPERTY(QString filterVal READ get_filterVal WRITE set_filterVal RESET reset_filterVal STORED false) + BR_PROPERTY(int, rows, -1) + BR_PROPERTY(int, columns, -1) + BR_PROPERTY(Method, method, Bilin) + BR_PROPERTY(QString, filterKey, "Label") + BR_PROPERTY(QString, filterVal, "1.0") + + void project(const Template &src, Template &dst) const + { + dst = src; + if (src.file.get(filterKey) == filterVal) + resize(src, dst, Size((columns == -1) ? src.m().cols*rows/src.m().rows : columns, rows), 0, 0, method); + } +}; + +BR_REGISTER(Transform, ResizeFilterTransform) + +} // namespace br + +#include "imgproc/resizefilter.moc" diff --git a/openbr/plugins/imgproc/slidingwindow.cpp b/openbr/plugins/imgproc/slidingwindow.cpp index f3538b9..214dea1 100644 --- a/openbr/plugins/imgproc/slidingwindow.cpp +++ b/openbr/plugins/imgproc/slidingwindow.cpp @@ -22,144 +22,42 @@ using namespace cv; namespace br { -// Find avg aspect ratio -static float getAspectRatio(const TemplateList &data) -{ - double tempRatio = 0; - int ratioCnt = 0; - - foreach (const Template &tmpl, data) { - QList posRects = OpenCVUtils::toRects(tmpl.file.rects()); - foreach (const Rect &posRect, posRects) { - if (posRect.x + posRect.width >= tmpl.m().cols || posRect.y + posRect.height >= tmpl.m().rows || posRect.x < 0 || posRect.y < 0) { - continue; - } - tempRatio += (float)posRect.width / (float)posRect.height; - ratioCnt += 1; - } - } - return tempRatio / (double)ratioCnt; -} - /*! * \ingroup transforms - * \brief Applies a transform to a sliding window. + * \brief Applies a classifier to a sliding window. * Discards negative detections. - * \author Austin Blanton \cite imaus10 + * \author Jordan Cheney \cite JordanCheney */ class SlidingWindowTransform : public Transform { Q_OBJECT - Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) - Q_PROPERTY(int windowWidth READ get_windowWidth WRITE set_windowWidth RESET reset_windowWidth STORED false) - Q_PROPERTY(bool takeFirst READ get_takeFirst WRITE set_takeFirst RESET reset_takeFirst STORED false) - Q_PROPERTY(float threshold READ get_threshold WRITE set_threshold RESET reset_threshold STORED false) - Q_PROPERTY(float stepFraction READ get_stepFraction WRITE set_stepFraction RESET reset_stepFraction STORED false) - Q_PROPERTY(int ignoreBorder READ get_ignoreBorder WRITE set_ignoreBorder RESET reset_ignoreBorder STORED true) - BR_PROPERTY(br::Transform *, transform, NULL) - BR_PROPERTY(int, windowWidth, 24) - BR_PROPERTY(bool, takeFirst, false) - BR_PROPERTY(float, threshold, 0) - BR_PROPERTY(float, stepFraction, 0.25) - BR_PROPERTY(int, ignoreBorder, 0) + Q_PROPERTY(br::Classifier *classifier READ get_classifier WRITE set_classifier RESET reset_classifier STORED false) + Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) + Q_PROPERTY(int maxSize READ get_maxSize WRITE set_maxSize RESET reset_maxSize STORED false) + Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) -private: - int windowHeight; - bool skipProject; + BR_PROPERTY(br::Classifier *, classifier, NULL) + BR_PROPERTY(int, minSize, 20) + BR_PROPERTY(int, maxSize, -1) + BR_PROPERTY(float, scaleFactor, 1.2) - void train(const TemplateList &data) + void train(const TemplateList &_data) { - skipProject = true; - float aspectRatio = data.first().file.get("aspectRatio", -1); - if (aspectRatio == -1) - aspectRatio = getAspectRatio(data); - windowHeight = qRound(windowWidth / aspectRatio); + QList data = _data.data(); + QList labels = File::get(_data, "Label"); - if (transform->trainable) { - TemplateList dataOut = data; - if (ignoreBorder > 0) { - for (int i = 0; i < dataOut.size(); i++) { - Template t = dataOut[i]; - Mat m = t.m(); - dataOut.replace(i,Template(t.file, Mat(m,Rect(ignoreBorder,ignoreBorder,m.cols - ignoreBorder * 2, m.rows - ignoreBorder * 2)))); - } - } - transform->train(dataOut); - } - } - - void store(QDataStream &stream) const - { - transform->store(stream); - stream << windowHeight; - } - - void load(QDataStream &stream) - { - transform->load(stream); - stream >> windowHeight; + classifier->train(data, labels); } void project(const Template &src, Template &dst) const { - float scale = src.file.get("scale", 1); - projectHelp(src, dst, windowWidth, windowHeight, scale); - } - - protected: - void projectHelp(const Template &src, Template &dst, int windowWidth, int windowHeight, float scale = 1) const - { - - dst = src; - if (skipProject) { - dst = src; - return; - } - - Template windowTemplate(src.file, src); - QList confidences = dst.file.getList("Confidences", QList()); - for (float y = 0; y + windowHeight < src.m().rows; y += windowHeight*stepFraction) { - for (float x = 0; x + windowWidth < src.m().cols; x += windowWidth*stepFraction) { - Mat windowMat(src, Rect(x + ignoreBorder, y + ignoreBorder, windowWidth - ignoreBorder * 2, windowHeight - ignoreBorder * 2)); - windowTemplate.replace(0,windowMat); - Template detect; - transform->project(windowTemplate, detect); - float conf = detect.m().at(0); - - // the result will be in the Label - if (conf > threshold) { - dst.file.appendRect(QRectF(x*scale, y*scale, windowWidth*scale, windowHeight*scale)); - confidences.append(conf); - if (takeFirst) - return; - } - } - } - dst.file.setList("Confidences", confidences); + (void) src; + (void) dst; } }; BR_REGISTER(Transform, SlidingWindowTransform) -/*! - * \ingroup transforms - * \brief Overloads SlidingWindowTransform for integral images that should be - * sampled at multiple scales. - * \author Josh Klontz \cite jklontz - */ -class IntegralSlidingWindowTransform : public SlidingWindowTransform -{ - Q_OBJECT - - private: - void project(const Template &src, Template &dst) const - { - // TODO: call SlidingWindowTransform::project on multiple scales - SlidingWindowTransform::projectHelp(src, dst, 24, 24); - } -}; - -BR_REGISTER(Transform, IntegralSlidingWindowTransform) } // namespace br diff --git a/openbr/plugins/metadata/cascade.cpp b/openbr/plugins/metadata/cascade.cpp index 74de38a..1576ce5 100644 --- a/openbr/plugins/metadata/cascade.cpp +++ b/openbr/plugins/metadata/cascade.cpp @@ -16,12 +16,13 @@ #include #include #include +#include #include #include #include #include -#include +#include using namespace cv; @@ -75,18 +76,24 @@ class CascadeTransform : public MetaTransform // Training parameters Q_PROPERTY(QString vecFile READ get_vecFile WRITE set_vecFile RESET reset_vecFile STORED false) Q_PROPERTY(QString negFile READ get_negFile WRITE set_negFile RESET reset_negFile STORED false) + Q_PROPERTY(int winWidth READ get_winWidth WRITE set_winWidth RESET reset_winWidth STORED false) + Q_PROPERTY(int winHeight READ get_winHeight WRITE set_winHeight RESET reset_winHeight STORED false) Q_PROPERTY(int numPos READ get_numPos WRITE set_numPos RESET reset_numPos STORED false) Q_PROPERTY(int numNeg READ get_numNeg WRITE set_numNeg RESET reset_numNeg STORED false) + Q_PROPERTY(int numStages READ get_numStages WRITE set_numStages RESET reset_numStages STORED false) BR_PROPERTY(QString, model, "FrontalFace") BR_PROPERTY(int, minSize, 64) BR_PROPERTY(int, minNeighbors, 5) BR_PROPERTY(bool, ROCMode, false) - BR_PROPERTY(QString, vecFile, "data.vec") + BR_PROPERTY(QString, vecFile, "vec.vec") BR_PROPERTY(QString, negFile, "neg.txt") + BR_PROPERTY(int, winWidth, 24) + BR_PROPERTY(int, winHeight, 24) BR_PROPERTY(int, numPos, 1000) BR_PROPERTY(int, numNeg, 1000) + BR_PROPERTY(int, numStages, 20) Resource cascadeResource; @@ -97,21 +104,100 @@ class CascadeTransform : public MetaTransform this->trainable = false; } + QList getPos() + { + FILE *file = fopen(vecFile.toStdString().c_str(), "rb"); + if ( !file ) + qFatal("Couldn't open the file"); + + short* vec = 0; + int count, vecSize, last, base; + + short tmp = 0; + if( fread( &count, sizeof( count ), 1, file ) != 1 || + fread( &vecSize, sizeof( vecSize ), 1, file ) != 1 || + fread( &tmp, sizeof( tmp ), 1, file ) != 1 || + fread( &tmp, sizeof( tmp ), 1, file ) != 1 ) + CV_Error_( CV_StsParseError, ("wrong file format for %s\n", qPrintable(vecFile)) ); + base = sizeof( count ) + sizeof( vecSize ) + 2*sizeof( tmp ); + + last = 0; + vec = (short*) cvAlloc( sizeof( *vec ) * vecSize ); + CV_Assert( vec ); + + QList posImages; + for (int i = 0; i < 35770; i++) { + Mat pos(winHeight, winWidth, CV_8UC1); + + CV_Assert( pos.rows * pos.cols == vecSize ); + uchar tmp = 0; + size_t elements_read = fread( &tmp, sizeof( tmp ), 1, file ); + if( elements_read != 1 ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. The most possible reason is " + "insufficient count of samples in given vec-file.\n"); + elements_read = fread( vec, sizeof( vec[0] ), vecSize, file ); + if( elements_read != (size_t)(vecSize) ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. Seems that vec-file has incorrect structure.\n"); + + if( feof( file ) || last++ >= count ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. vec-file is over.\n"); + + for( int r = 0; r < pos.rows; r++ ) + for( int c = 0; c < pos.cols; c++ ) + pos.ptr(r)[c] = (uchar)vec[r * pos.cols + c]; + posImages.append(pos); + } + return posImages; + } + + QList getNeg() + { + QList negs; + + std::string dirname, str; + std::ifstream file(negFile.toStdString().c_str()); + + size_t pos = negFile.toStdString().rfind('\\'); + char dlmrt = '\\'; + if (pos == string::npos) + { + pos = negFile.toStdString().rfind('/'); + dlmrt = '/'; + } + dirname = pos == string::npos ? "" : negFile.toStdString().substr(0, pos) + dlmrt; + while( !file.eof() ) + { + std::getline(file, str); + if (str.empty()) break; + if (str.at(0) == '#' ) continue; + negs.append(imread(dirname + str, CV_LOAD_IMAGE_GRAYSCALE)); + } + file.close(); + + return negs; + } + // Train transform void train(const TemplateList& data) { - (void)data; + (void) data; + + QList posImages = getPos(); + QList negImages = getNeg(); BrCascadeClassifier classifier; CascadeParams cascadeParams(CascadeParams::BOOST, FeatureParams::LBP); + cascadeParams.winSize = Size(winWidth, winHeight); + CascadeBoostParams stageParams(CvBoost::GENTLE, 0.999, 0.5, 0.95, 1, 200); LBPFeatureParams featureParams; QString cascadeDir = Globals->sdkPath + "/share/openbr/models/openbrcascades/" + model; classifier.train(cascadeDir.toStdString(), - vecFile.toStdString(), negFile.toStdString(), - numPos, numNeg, 1024, 1024, 17, + posImages.toVector().toStdVector(), + negImages.toVector().toStdVector(), + numPos, numNeg, 1024, 1024, numStages, cascadeParams, featureParams, stageParams); }