123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- #include "opencv2/core.hpp"
- #include "cascadeclassifier.h"
- #include <queue>
- using namespace std;
- using namespace cv;
- static const char* stageTypes[] = { CC_BOOST };
- static const char* featureTypes[] = { CC_HAAR, CC_LBP, CC_HOG };
- CvCascadeParams::CvCascadeParams() : stageType( defaultStageType ),
- featureType( defaultFeatureType ), winSize( cvSize(24, 24) )
- {
- name = CC_CASCADE_PARAMS;
- }
- CvCascadeParams::CvCascadeParams( int _stageType, int _featureType ) : stageType( _stageType ),
- featureType( _featureType ), winSize( cvSize(24, 24) )
- {
- name = CC_CASCADE_PARAMS;
- }
- //---------------------------- CascadeParams --------------------------------------
- void CvCascadeParams::write( FileStorage &fs ) const
- {
- string stageTypeStr = stageType == BOOST ? CC_BOOST : string();
- CV_Assert( !stageTypeStr.empty() );
- fs << CC_STAGE_TYPE << stageTypeStr;
- string featureTypeStr = featureType == CvFeatureParams::HAAR ? CC_HAAR :
- featureType == CvFeatureParams::LBP ? CC_LBP :
- featureType == CvFeatureParams::HOG ? CC_HOG :
- 0;
- CV_Assert( !stageTypeStr.empty() );
- fs << CC_FEATURE_TYPE << featureTypeStr;
- fs << CC_HEIGHT << winSize.height;
- fs << CC_WIDTH << winSize.width;
- }
- bool CvCascadeParams::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_HAAR ) ? CvFeatureParams::HAAR :
- !featureTypeStr.compare( CC_LBP ) ? CvFeatureParams::LBP :
- !featureTypeStr.compare( CC_HOG ) ? CvFeatureParams::HOG :
- -1;
- if (featureType == -1)
- return false;
- node[CC_HEIGHT] >> winSize.height;
- node[CC_WIDTH] >> winSize.width;
- return winSize.height > 0 && winSize.width > 0;
- }
- void CvCascadeParams::printDefaults() const
- {
- CvParams::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 <sampleWidth = " << winSize.width << ">]" << endl;
- cout << " [-h <sampleHeight = " << winSize.height << ">]" << endl;
- }
- void CvCascadeParams::printAttrs() const
- {
- cout << "stageType: " << stageTypes[stageType] << endl;
- cout << "featureType: " << featureTypes[featureType] << endl;
- cout << "sampleWidth: " << winSize.width << endl;
- cout << "sampleHeight: " << winSize.height << endl;
- }
- bool CvCascadeParams::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 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 )
- {
- // Start recording clock ticks for training time output
- double time = (double)getTickCount();
- if( _cascadeDirName.empty() || _posFilename.empty() || _negFilename.empty() )
- CV_Error( CV_StsBadArg, "_cascadeDirName or _bgfileName or _vecFileName is NULL" );
- string dirName;
- if (_cascadeDirName.find_last_of("/\\") == (_cascadeDirName.length() - 1) )
- dirName = _cascadeDirName;
- else
- dirName = _cascadeDirName + '/';
- numPos = _numPos;
- numNeg = _numNeg;
- numStages = _numStages;
- if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )
- {
- cout << "Image reader can not be created from -vec " << _posFilename
- << " and -bg " << _negFilename << "." << endl;
- return false;
- }
- if ( !load( dirName ) )
- {
- cascadeParams = _cascadeParams;
- featureParams = CvFeatureParams::create(cascadeParams.featureType);
- featureParams->init(_featureParams);
- stageParams = makePtr<CvCascadeBoostParams>();
- *stageParams = _stageParams;
- featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
- featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize );
- stageClassifiers.reserve( numStages );
- }else{
- // Make sure that if model parameters are preloaded, that people are aware of this,
- // even when passing other parameters to the training command
- cout << "---------------------------------------------------------------------------------" << endl;
- cout << "Training parameters are pre-loaded from the parameter file in data folder!" << endl;
- cout << "Please empty this folder if you want to use a NEW set of training parameters." << endl;
- cout << "---------------------------------------------------------------------------------" << endl;
- }
- cout << "PARAMETERS:" << endl;
- cout << "cascadeDirName: " << _cascadeDirName << endl;
- cout << "vecFileName: " << _posFilename << endl;
- cout << "bgFileName: " << _negFilename << endl;
- cout << "numPos: " << _numPos << endl;
- cout << "numNeg: " << _numNeg << endl;
- cout << "numStages: " << numStages << endl;
- cout << "precalcValBufSize[Mb] : " << _precalcValBufSize << endl;
- cout << "precalcIdxBufSize[Mb] : " << _precalcIdxBufSize << endl;
- cout << "acceptanceRatioBreakValue : " << acceptanceRatioBreakValue << endl;
- cascadeParams.printAttrs();
- stageParams->printAttrs();
- featureParams->printAttrs();
- cout << "Number of unique features given windowSize [" << _cascadeParams.winSize.width << "," << _cascadeParams.winSize.height << "] : " << featureEvaluator->getNumFeatures() << "" << endl;
- 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 << "<BEGIN" << endl;
- if ( !updateTrainingSet( requiredLeafFARate, tempLeafFARate ) )
- {
- cout << "Train dataset for temp stage can not be filled. "
- "Branch training terminated." << endl;
- break;
- }
- if( tempLeafFARate <= requiredLeafFARate )
- {
- cout << "Required leaf false alarm rate achieved. "
- "Branch training terminated." << endl;
- break;
- }
- if( (tempLeafFARate <= acceptanceRatioBreakValue) && (acceptanceRatioBreakValue >= 0) ){
- cout << "The required acceptanceRatio for the model has been reached to avoid overfitting of trainingdata. "
- "Branch training terminated." << endl;
- break;
- }
- Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
- bool isStageTrained = tempStage->train( featureEvaluator,
- curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
- *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[32];
- snprintf(buf, sizeof(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
- double seconds = ( (double)getTickCount() - time)/ getTickFrequency();
- 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 CvCascadeClassifier::predict( int sampleIdx )
- {
- CV_DbgAssert( sampleIdx < numPos + numNeg );
- for (vector< Ptr<CvCascadeBoost> >::iterator it = stageClassifiers.begin();
- it != stageClassifiers.end();++it )
- {
- if ( (*it)->predict( sampleIdx ) == 0.f )
- return 0;
- }
- return 1;
- }
- bool CvCascadeClassifier::updateTrainingSet( double minimumAcceptanceRatio, double& acceptanceRatio)
- {
- int64 posConsumed = 0, negConsumed = 0;
- imgReader.restart();
- int posCount = fillPassedSamples( 0, numPos, true, 0, 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, minimumAcceptanceRatio, negConsumed );
- if ( !negCount )
- if ( !(negConsumed > 0 && ((double)negCount+1)/(double)negConsumed <= minimumAcceptanceRatio) )
- return false;
- 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, double minimumAcceptanceRatio, int64& consumed )
- {
- int getcount = 0;
- Mat img(cascadeParams.winSize, CV_8UC1);
- for( int i = first; i < first + count; i++ )
- {
- for( ; ; )
- {
- if( consumed != 0 && ((double)getcount+1)/(double)(int64)consumed <= minimumAcceptanceRatio )
- return getcount;
- 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 )
- {
- getcount++;
- printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount);
- fflush(stdout);
- break;
- }
- }
- }
- return getcount;
- }
- void CvCascadeClassifier::writeParams( FileStorage &fs ) const
- {
- cascadeParams.write( fs );
- fs << CC_STAGE_PARAMS << "{"; stageParams->write( fs ); fs << "}";
- fs << CC_FEATURE_PARAMS << "{"; featureParams->write( fs ); fs << "}";
- }
- void CvCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
- {
- featureEvaluator->writeFeatures( fs, featureMap );
- }
- void CvCascadeClassifier::writeStages( FileStorage &fs, const Mat& featureMap ) const
- {
- char cmnt[30];
- int i = 0;
- fs << CC_STAGES << "[";
- for( vector< Ptr<CvCascadeBoost> >::const_iterator it = stageClassifiers.begin();
- it != stageClassifiers.end();++it, ++i )
- {
- snprintf( cmnt, sizeof(cmnt), "stage %d", i );
- fs.writeComment(cmnt);
- fs << "{";
- (*it)->write( fs, featureMap );
- fs << "}";
- }
- fs << "]";
- }
- bool CvCascadeClassifier::readParams( const FileNode &node )
- {
- if ( !node.isMap() || !cascadeParams.read( node ) )
- return false;
- stageParams = makePtr<CvCascadeBoostParams>();
- FileNode rnode = node[CC_STAGE_PARAMS];
- if ( !stageParams->read( rnode ) )
- return false;
- featureParams = CvFeatureParams::create(cascadeParams.featureType);
- rnode = node[CC_FEATURE_PARAMS];
- if ( !featureParams->read( rnode ) )
- return false;
- return true;
- }
- bool CvCascadeClassifier::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++ )
- {
- Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
- if ( !tempStage->read( *it, featureEvaluator, *stageParams) )
- return false;
- stageClassifiers.push_back(tempStage);
- }
- return true;
- }
- // For old Haar Classifier file saving
- #define ICV_HAAR_TYPE_ID "opencv-haar-classifier"
- #define ICV_HAAR_SIZE_NAME "size"
- #define ICV_HAAR_STAGES_NAME "stages"
- #define ICV_HAAR_TREES_NAME "trees"
- #define ICV_HAAR_FEATURE_NAME "feature"
- #define ICV_HAAR_RECTS_NAME "rects"
- #define ICV_HAAR_TILTED_NAME "tilted"
- #define ICV_HAAR_THRESHOLD_NAME "threshold"
- #define ICV_HAAR_LEFT_NODE_NAME "left_node"
- #define ICV_HAAR_LEFT_VAL_NAME "left_val"
- #define ICV_HAAR_RIGHT_NODE_NAME "right_node"
- #define ICV_HAAR_RIGHT_VAL_NAME "right_val"
- #define ICV_HAAR_STAGE_THRESHOLD_NAME "stage_threshold"
- #define ICV_HAAR_PARENT_NAME "parent"
- #define ICV_HAAR_NEXT_NAME "next"
- void CvCascadeClassifier::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 );
- fs << "{";
- writeParams( fs );
- fs << CC_STAGE_NUM << (int)stageClassifiers.size();
- writeStages( fs, featureMap );
- writeFeatures( fs, featureMap );
- }
- else
- {
- //char buf[256];
- CvSeq* weak;
- if ( cascadeParams.featureType != CvFeatureParams::HAAR )
- CV_Error( CV_StsBadFunc, "old file format is used for Haar-like features only");
- fs << "{:" ICV_HAAR_TYPE_ID;
- fs << ICV_HAAR_SIZE_NAME << "[:" << cascadeParams.winSize.width <<
- cascadeParams.winSize.height << "]";
- fs << ICV_HAAR_STAGES_NAME << "[";
- for( size_t si = 0; si < stageClassifiers.size(); si++ )
- {
- fs << "{"; //stage
- /*snprintf( buf, sizeof(buf), "stage %d", si );
- CV_CALL( cvWriteComment( fs, buf, 1 ) );*/
- weak = stageClassifiers[si]->get_weak_predictors();
- fs << ICV_HAAR_TREES_NAME << "[";
- for( int wi = 0; wi < weak->total; wi++ )
- {
- int total_inner_node_idx = -1;
- queue<const CvDTreeNode*> inner_nodes_queue;
- CvCascadeBoostTree* tree = *((CvCascadeBoostTree**) cvGetSeqElem( weak, wi ));
- fs << "[";
- /*snprintf( buf, sizeof(buf), "tree %d", wi );
- CV_CALL( cvWriteComment( fs, buf, 1 ) );*/
- const CvDTreeNode* tempNode;
- inner_nodes_queue.push( tree->get_root() );
- total_inner_node_idx++;
- while (!inner_nodes_queue.empty())
- {
- tempNode = inner_nodes_queue.front();
- fs << "{";
- fs << ICV_HAAR_FEATURE_NAME << "{";
- ((CvHaarEvaluator*)featureEvaluator.get())->writeFeature( fs, tempNode->split->var_idx );
- fs << "}";
- fs << ICV_HAAR_THRESHOLD_NAME << tempNode->split->ord.c;
- if( tempNode->left->left || tempNode->left->right )
- {
- inner_nodes_queue.push( tempNode->left );
- total_inner_node_idx++;
- fs << ICV_HAAR_LEFT_NODE_NAME << total_inner_node_idx;
- }
- else
- fs << ICV_HAAR_LEFT_VAL_NAME << tempNode->left->value;
- if( tempNode->right->left || tempNode->right->right )
- {
- inner_nodes_queue.push( tempNode->right );
- total_inner_node_idx++;
- fs << ICV_HAAR_RIGHT_NODE_NAME << total_inner_node_idx;
- }
- else
- fs << ICV_HAAR_RIGHT_VAL_NAME << tempNode->right->value;
- fs << "}"; // ICV_HAAR_FEATURE_NAME
- inner_nodes_queue.pop();
- }
- fs << "]";
- }
- fs << "]"; //ICV_HAAR_TREES_NAME
- fs << ICV_HAAR_STAGE_THRESHOLD_NAME << stageClassifiers[si]->getThreshold();
- fs << ICV_HAAR_PARENT_NAME << (int)si-1 << ICV_HAAR_NEXT_NAME << -1;
- fs << "}"; //stage
- } /* for each stage */
- fs << "]"; //ICV_HAAR_STAGES_NAME
- }
- fs << "}";
- }
- bool CvCascadeClassifier::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 = CvFeatureEvaluator::create(cascadeParams.featureType);
- featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize );
- fs.release();
- char buf[5+10+1] = {0};
- for ( int si = 0; si < numStages; si++ )
- {
- snprintf( buf, sizeof(buf), "%s%d", "stage", si);
- fs.open( cascadeDirName + buf + ".xml", FileStorage::READ );
- node = fs.getFirstTopLevelNode();
- if ( !fs.isOpened() )
- break;
- Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
- if ( !tempStage->read( node, featureEvaluator, *stageParams ))
- {
- fs.release();
- break;
- }
- stageClassifiers.push_back(tempStage);
- }
- return true;
- }
- void CvCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap )
- {
- int varCount = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize();
- featureMap.create( 1, varCount, CV_32SC1 );
- featureMap.setTo(Scalar(-1));
- for( vector< Ptr<CvCascadeBoost> >::const_iterator it = stageClassifiers.begin();
- it != stageClassifiers.end();++it )
- (*it)->markUsedFeaturesInMap( featureMap );
- for( int fi = 0, idx = 0; fi < varCount; fi++ )
- if ( featureMap.at<int>(0, fi) >= 0 )
- featureMap.ptr<int>(0)[fi] = idx++;
- }
|