1 #include "opencv2/core.hpp"
2 
3 #include "cascadeclassifier.h"
4 #include <queue>
5 
6 using namespace std;
7 using namespace cv;
8 
9 static const char* stageTypes[] = { CC_BOOST };
10 static const char* featureTypes[] = { CC_HAAR, CC_LBP, CC_HOG };
11 
CvCascadeParams()12 CvCascadeParams::CvCascadeParams() : stageType( defaultStageType ),
13     featureType( defaultFeatureType ), winSize( cvSize(24, 24) )
14 {
15     name = CC_CASCADE_PARAMS;
16 }
CvCascadeParams(int _stageType,int _featureType)17 CvCascadeParams::CvCascadeParams( int _stageType, int _featureType ) : stageType( _stageType ),
18     featureType( _featureType ), winSize( cvSize(24, 24) )
19 {
20     name = CC_CASCADE_PARAMS;
21 }
22 
23 //---------------------------- CascadeParams --------------------------------------
24 
write(FileStorage & fs) const25 void CvCascadeParams::write( FileStorage &fs ) const
26 {
27     string stageTypeStr = stageType == BOOST ? CC_BOOST : string();
28     CV_Assert( !stageTypeStr.empty() );
29     fs << CC_STAGE_TYPE << stageTypeStr;
30     string featureTypeStr = featureType == CvFeatureParams::HAAR ? CC_HAAR :
31                             featureType == CvFeatureParams::LBP ? CC_LBP :
32                             featureType == CvFeatureParams::HOG ? CC_HOG :
33                             0;
34     CV_Assert( !stageTypeStr.empty() );
35     fs << CC_FEATURE_TYPE << featureTypeStr;
36     fs << CC_HEIGHT << winSize.height;
37     fs << CC_WIDTH << winSize.width;
38 }
39 
read(const FileNode & node)40 bool CvCascadeParams::read( const FileNode &node )
41 {
42     if ( node.empty() )
43         return false;
44     string stageTypeStr, featureTypeStr;
45     FileNode rnode = node[CC_STAGE_TYPE];
46     if ( !rnode.isString() )
47         return false;
48     rnode >> stageTypeStr;
49     stageType = !stageTypeStr.compare( CC_BOOST ) ? BOOST : -1;
50     if (stageType == -1)
51         return false;
52     rnode = node[CC_FEATURE_TYPE];
53     if ( !rnode.isString() )
54         return false;
55     rnode >> featureTypeStr;
56     featureType = !featureTypeStr.compare( CC_HAAR ) ? CvFeatureParams::HAAR :
57                   !featureTypeStr.compare( CC_LBP ) ? CvFeatureParams::LBP :
58                   !featureTypeStr.compare( CC_HOG ) ? CvFeatureParams::HOG :
59                   -1;
60     if (featureType == -1)
61         return false;
62     node[CC_HEIGHT] >> winSize.height;
63     node[CC_WIDTH] >> winSize.width;
64     return winSize.height > 0 && winSize.width > 0;
65 }
66 
printDefaults() const67 void CvCascadeParams::printDefaults() const
68 {
69     CvParams::printDefaults();
70     cout << "  [-stageType <";
71     for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ )
72     {
73         cout << (i ? " | " : "") << stageTypes[i];
74         if ( i == defaultStageType )
75             cout << "(default)";
76     }
77     cout << ">]" << endl;
78 
79     cout << "  [-featureType <{";
80     for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ )
81     {
82         cout << (i ? ", " : "") << featureTypes[i];
83         if ( i == defaultStageType )
84             cout << "(default)";
85     }
86     cout << "}>]" << endl;
87     cout << "  [-w <sampleWidth = " << winSize.width << ">]" << endl;
88     cout << "  [-h <sampleHeight = " << winSize.height << ">]" << endl;
89 }
90 
printAttrs() const91 void CvCascadeParams::printAttrs() const
92 {
93     cout << "stageType: " << stageTypes[stageType] << endl;
94     cout << "featureType: " << featureTypes[featureType] << endl;
95     cout << "sampleWidth: " << winSize.width << endl;
96     cout << "sampleHeight: " << winSize.height << endl;
97 }
98 
scanAttr(const string prmName,const string val)99 bool CvCascadeParams::scanAttr( const string prmName, const string val )
100 {
101     bool res = true;
102     if( !prmName.compare( "-stageType" ) )
103     {
104         for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ )
105             if( !val.compare( stageTypes[i] ) )
106                 stageType = i;
107     }
108     else if( !prmName.compare( "-featureType" ) )
109     {
110         for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ )
111             if( !val.compare( featureTypes[i] ) )
112                 featureType = i;
113     }
114     else if( !prmName.compare( "-w" ) )
115     {
116         winSize.width = atoi( val.c_str() );
117     }
118     else if( !prmName.compare( "-h" ) )
119     {
120         winSize.height = atoi( val.c_str() );
121     }
122     else
123         res = false;
124     return res;
125 }
126 
127 //---------------------------- CascadeClassifier --------------------------------------
128 
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)129 bool CvCascadeClassifier::train( const string _cascadeDirName,
130                                 const string _posFilename,
131                                 const string _negFilename,
132                                 int _numPos, int _numNeg,
133                                 int _precalcValBufSize, int _precalcIdxBufSize,
134                                 int _numStages,
135                                 const CvCascadeParams& _cascadeParams,
136                                 const CvFeatureParams& _featureParams,
137                                 const CvCascadeBoostParams& _stageParams,
138                                 bool baseFormatSave,
139                                 double acceptanceRatioBreakValue )
140 {
141     // Start recording clock ticks for training time output
142     const clock_t begin_time = clock();
143 
144     if( _cascadeDirName.empty() || _posFilename.empty() || _negFilename.empty() )
145         CV_Error( CV_StsBadArg, "_cascadeDirName or _bgfileName or _vecFileName is NULL" );
146 
147     string dirName;
148     if (_cascadeDirName.find_last_of("/\\") == (_cascadeDirName.length() - 1) )
149         dirName = _cascadeDirName;
150     else
151         dirName = _cascadeDirName + '/';
152 
153     numPos = _numPos;
154     numNeg = _numNeg;
155     numStages = _numStages;
156     if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) )
157     {
158         cout << "Image reader can not be created from -vec " << _posFilename
159                 << " and -bg " << _negFilename << "." << endl;
160         return false;
161     }
162     if ( !load( dirName ) )
163     {
164         cascadeParams = _cascadeParams;
165         featureParams = CvFeatureParams::create(cascadeParams.featureType);
166         featureParams->init(_featureParams);
167         stageParams = makePtr<CvCascadeBoostParams>();
168         *stageParams = _stageParams;
169         featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
170         featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize );
171         stageClassifiers.reserve( numStages );
172     }else{
173         // Make sure that if model parameters are preloaded, that people are aware of this,
174         // even when passing other parameters to the training command
175         cout << "---------------------------------------------------------------------------------" << endl;
176         cout << "Training parameters are pre-loaded from the parameter file in data folder!" << endl;
177         cout << "Please empty this folder if you want to use a NEW set of training parameters." << endl;
178         cout << "---------------------------------------------------------------------------------" << endl;
179     }
180     cout << "PARAMETERS:" << endl;
181     cout << "cascadeDirName: " << _cascadeDirName << endl;
182     cout << "vecFileName: " << _posFilename << endl;
183     cout << "bgFileName: " << _negFilename << endl;
184     cout << "numPos: " << _numPos << endl;
185     cout << "numNeg: " << _numNeg << endl;
186     cout << "numStages: " << numStages << endl;
187     cout << "precalcValBufSize[Mb] : " << _precalcValBufSize << endl;
188     cout << "precalcIdxBufSize[Mb] : " << _precalcIdxBufSize << endl;
189     cout << "acceptanceRatioBreakValue : " << acceptanceRatioBreakValue << endl;
190     cascadeParams.printAttrs();
191     stageParams->printAttrs();
192     featureParams->printAttrs();
193 
194     int startNumStages = (int)stageClassifiers.size();
195     if ( startNumStages > 1 )
196         cout << endl << "Stages 0-" << startNumStages-1 << " are loaded" << endl;
197     else if ( startNumStages == 1)
198         cout << endl << "Stage 0 is loaded" << endl;
199 
200     double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /
201                                 (double)stageParams->max_depth;
202     double tempLeafFARate;
203 
204     for( int i = startNumStages; i < numStages; i++ )
205     {
206         cout << endl << "===== TRAINING " << i << "-stage =====" << endl;
207         cout << "<BEGIN" << endl;
208 
209         if ( !updateTrainingSet( tempLeafFARate ) )
210         {
211             cout << "Train dataset for temp stage can not be filled. "
212                     "Branch training terminated." << endl;
213             break;
214         }
215         if( tempLeafFARate <= requiredLeafFARate )
216         {
217             cout << "Required leaf false alarm rate achieved. "
218                     "Branch training terminated." << endl;
219             break;
220         }
221         if( (tempLeafFARate <= acceptanceRatioBreakValue) && (acceptanceRatioBreakValue >= 0) ){
222             cout << "The required acceptanceRatio for the model has been reached to avoid overfitting of trainingdata. "
223                     "Branch training terminated." << endl;
224             break;
225         }
226 
227         Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
228         bool isStageTrained = tempStage->train( featureEvaluator,
229                                                 curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
230                                                 *stageParams );
231         cout << "END>" << endl;
232 
233         if(!isStageTrained)
234             break;
235 
236         stageClassifiers.push_back( tempStage );
237 
238         // save params
239         if( i == 0)
240         {
241             std::string paramsFilename = dirName + CC_PARAMS_FILENAME;
242             FileStorage fs( paramsFilename, FileStorage::WRITE);
243             if ( !fs.isOpened() )
244             {
245                 cout << "Parameters can not be written, because file " << paramsFilename
246                         << " can not be opened." << endl;
247                 return false;
248             }
249             fs << FileStorage::getDefaultObjectName(paramsFilename) << "{";
250             writeParams( fs );
251             fs << "}";
252         }
253         // save current stage
254         char buf[10];
255         sprintf(buf, "%s%d", "stage", i );
256         string stageFilename = dirName + buf + ".xml";
257         FileStorage fs( stageFilename, FileStorage::WRITE );
258         if ( !fs.isOpened() )
259         {
260             cout << "Current stage can not be written, because file " << stageFilename
261                     << " can not be opened." << endl;
262             return false;
263         }
264         fs << FileStorage::getDefaultObjectName(stageFilename) << "{";
265         tempStage->write( fs, Mat() );
266         fs << "}";
267 
268         // Output training time up till now
269         float seconds = float( clock () - begin_time ) / CLOCKS_PER_SEC;
270         int days = int(seconds) / 60 / 60 / 24;
271         int hours = (int(seconds) / 60 / 60) % 24;
272         int minutes = (int(seconds) / 60) % 60;
273         int seconds_left = int(seconds) % 60;
274         cout << "Training until now has taken " << days << " days " << hours << " hours " << minutes << " minutes " << seconds_left <<" seconds." << endl;
275     }
276 
277     if(stageClassifiers.size() == 0)
278     {
279         cout << "Cascade classifier can't be trained. Check the used training parameters." << endl;
280         return false;
281     }
282 
283     save( dirName + CC_CASCADE_FILENAME, baseFormatSave );
284 
285     return true;
286 }
287 
predict(int sampleIdx)288 int CvCascadeClassifier::predict( int sampleIdx )
289 {
290     CV_DbgAssert( sampleIdx < numPos + numNeg );
291     for (vector< Ptr<CvCascadeBoost> >::iterator it = stageClassifiers.begin();
292         it != stageClassifiers.end(); it++ )
293     {
294         if ( (*it)->predict( sampleIdx ) == 0.f )
295             return 0;
296     }
297     return 1;
298 }
299 
updateTrainingSet(double & acceptanceRatio)300 bool CvCascadeClassifier::updateTrainingSet( double& acceptanceRatio)
301 {
302     int64 posConsumed = 0, negConsumed = 0;
303     imgReader.restart();
304     int posCount = fillPassedSamples( 0, numPos, true, posConsumed );
305     if( !posCount )
306         return false;
307     cout << "POS count : consumed   " << posCount << " : " << (int)posConsumed << endl;
308 
309     int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos ); // apply only a fraction of negative samples. double is required since overflow is possible
310     int negCount = fillPassedSamples( posCount, proNumNeg, false, negConsumed );
311     if ( !negCount )
312         return false;
313 
314     curNumSamples = posCount + negCount;
315     acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed );
316     cout << "NEG count : acceptanceRatio    " << negCount << " : " << acceptanceRatio << endl;
317     return true;
318 }
319 
fillPassedSamples(int first,int count,bool isPositive,int64 & consumed)320 int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, int64& consumed )
321 {
322     int getcount = 0;
323     Mat img(cascadeParams.winSize, CV_8UC1);
324     for( int i = first; i < first + count; i++ )
325     {
326         for( ; ; )
327         {
328             bool isGetImg = isPositive ? imgReader.getPos( img ) :
329                                            imgReader.getNeg( img );
330             if( !isGetImg )
331                 return getcount;
332             consumed++;
333 
334             featureEvaluator->setImage( img, isPositive ? 1 : 0, i );
335             if( predict( i ) == 1.0F )
336             {
337                 getcount++;
338                 printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount);
339                 break;
340             }
341         }
342     }
343     return getcount;
344 }
345 
writeParams(FileStorage & fs) const346 void CvCascadeClassifier::writeParams( FileStorage &fs ) const
347 {
348     cascadeParams.write( fs );
349     fs << CC_STAGE_PARAMS << "{"; stageParams->write( fs ); fs << "}";
350     fs << CC_FEATURE_PARAMS << "{"; featureParams->write( fs ); fs << "}";
351 }
352 
writeFeatures(FileStorage & fs,const Mat & featureMap) const353 void CvCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
354 {
355     featureEvaluator->writeFeatures( fs, featureMap );
356 }
357 
writeStages(FileStorage & fs,const Mat & featureMap) const358 void CvCascadeClassifier::writeStages( FileStorage &fs, const Mat& featureMap ) const
359 {
360     char cmnt[30];
361     int i = 0;
362     fs << CC_STAGES << "[";
363     for( vector< Ptr<CvCascadeBoost> >::const_iterator it = stageClassifiers.begin();
364         it != stageClassifiers.end(); it++, i++ )
365     {
366         sprintf( cmnt, "stage %d", i );
367         cvWriteComment( fs.fs, cmnt, 0 );
368         fs << "{";
369         (*it)->write( fs, featureMap );
370         fs << "}";
371     }
372     fs << "]";
373 }
374 
readParams(const FileNode & node)375 bool CvCascadeClassifier::readParams( const FileNode &node )
376 {
377     if ( !node.isMap() || !cascadeParams.read( node ) )
378         return false;
379 
380     stageParams = makePtr<CvCascadeBoostParams>();
381     FileNode rnode = node[CC_STAGE_PARAMS];
382     if ( !stageParams->read( rnode ) )
383         return false;
384 
385     featureParams = CvFeatureParams::create(cascadeParams.featureType);
386     rnode = node[CC_FEATURE_PARAMS];
387     if ( !featureParams->read( rnode ) )
388         return false;
389     return true;
390 }
391 
readStages(const FileNode & node)392 bool CvCascadeClassifier::readStages( const FileNode &node)
393 {
394     FileNode rnode = node[CC_STAGES];
395     if (!rnode.empty() || !rnode.isSeq())
396         return false;
397     stageClassifiers.reserve(numStages);
398     FileNodeIterator it = rnode.begin();
399     for( int i = 0; i < min( (int)rnode.size(), numStages ); i++, it++ )
400     {
401         Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
402         if ( !tempStage->read( *it, featureEvaluator, *stageParams) )
403             return false;
404         stageClassifiers.push_back(tempStage);
405     }
406     return true;
407 }
408 
409 // For old Haar Classifier file saving
410 #define ICV_HAAR_SIZE_NAME            "size"
411 #define ICV_HAAR_STAGES_NAME          "stages"
412 #define ICV_HAAR_TREES_NAME             "trees"
413 #define ICV_HAAR_FEATURE_NAME             "feature"
414 #define ICV_HAAR_RECTS_NAME                 "rects"
415 #define ICV_HAAR_TILTED_NAME                "tilted"
416 #define ICV_HAAR_THRESHOLD_NAME           "threshold"
417 #define ICV_HAAR_LEFT_NODE_NAME           "left_node"
418 #define ICV_HAAR_LEFT_VAL_NAME            "left_val"
419 #define ICV_HAAR_RIGHT_NODE_NAME          "right_node"
420 #define ICV_HAAR_RIGHT_VAL_NAME           "right_val"
421 #define ICV_HAAR_STAGE_THRESHOLD_NAME   "stage_threshold"
422 #define ICV_HAAR_PARENT_NAME            "parent"
423 #define ICV_HAAR_NEXT_NAME              "next"
424 
save(const string filename,bool baseFormat)425 void CvCascadeClassifier::save( const string filename, bool baseFormat )
426 {
427     FileStorage fs( filename, FileStorage::WRITE );
428 
429     if ( !fs.isOpened() )
430         return;
431 
432     fs << FileStorage::getDefaultObjectName(filename) << "{";
433     if ( !baseFormat )
434     {
435         Mat featureMap;
436         getUsedFeaturesIdxMap( featureMap );
437         writeParams( fs );
438         fs << CC_STAGE_NUM << (int)stageClassifiers.size();
439         writeStages( fs, featureMap );
440         writeFeatures( fs, featureMap );
441     }
442     else
443     {
444         //char buf[256];
445         CvSeq* weak;
446         if ( cascadeParams.featureType != CvFeatureParams::HAAR )
447             CV_Error( CV_StsBadFunc, "old file format is used for Haar-like features only");
448         fs << ICV_HAAR_SIZE_NAME << "[:" << cascadeParams.winSize.width <<
449             cascadeParams.winSize.height << "]";
450         fs << ICV_HAAR_STAGES_NAME << "[";
451         for( size_t si = 0; si < stageClassifiers.size(); si++ )
452         {
453             fs << "{"; //stage
454             /*sprintf( buf, "stage %d", si );
455             CV_CALL( cvWriteComment( fs, buf, 1 ) );*/
456             weak = stageClassifiers[si]->get_weak_predictors();
457             fs << ICV_HAAR_TREES_NAME << "[";
458             for( int wi = 0; wi < weak->total; wi++ )
459             {
460                 int inner_node_idx = -1, total_inner_node_idx = -1;
461                 queue<const CvDTreeNode*> inner_nodes_queue;
462                 CvCascadeBoostTree* tree = *((CvCascadeBoostTree**) cvGetSeqElem( weak, wi ));
463 
464                 fs << "[";
465                 /*sprintf( buf, "tree %d", wi );
466                 CV_CALL( cvWriteComment( fs, buf, 1 ) );*/
467 
468                 const CvDTreeNode* tempNode;
469 
470                 inner_nodes_queue.push( tree->get_root() );
471                 total_inner_node_idx++;
472 
473                 while (!inner_nodes_queue.empty())
474                 {
475                     tempNode = inner_nodes_queue.front();
476                     inner_node_idx++;
477 
478                     fs << "{";
479                     fs << ICV_HAAR_FEATURE_NAME << "{";
480                     ((CvHaarEvaluator*)featureEvaluator.get())->writeFeature( fs, tempNode->split->var_idx );
481                     fs << "}";
482 
483                     fs << ICV_HAAR_THRESHOLD_NAME << tempNode->split->ord.c;
484 
485                     if( tempNode->left->left || tempNode->left->right )
486                     {
487                         inner_nodes_queue.push( tempNode->left );
488                         total_inner_node_idx++;
489                         fs << ICV_HAAR_LEFT_NODE_NAME << total_inner_node_idx;
490                     }
491                     else
492                         fs << ICV_HAAR_LEFT_VAL_NAME << tempNode->left->value;
493 
494                     if( tempNode->right->left || tempNode->right->right )
495                     {
496                         inner_nodes_queue.push( tempNode->right );
497                         total_inner_node_idx++;
498                         fs << ICV_HAAR_RIGHT_NODE_NAME << total_inner_node_idx;
499                     }
500                     else
501                         fs << ICV_HAAR_RIGHT_VAL_NAME << tempNode->right->value;
502                     fs << "}"; // ICV_HAAR_FEATURE_NAME
503                     inner_nodes_queue.pop();
504                 }
505                 fs << "]";
506             }
507             fs << "]"; //ICV_HAAR_TREES_NAME
508             fs << ICV_HAAR_STAGE_THRESHOLD_NAME << stageClassifiers[si]->getThreshold();
509             fs << ICV_HAAR_PARENT_NAME << (int)si-1 << ICV_HAAR_NEXT_NAME << -1;
510             fs << "}"; //stage
511         } /* for each stage */
512         fs << "]"; //ICV_HAAR_STAGES_NAME
513     }
514     fs << "}";
515 }
516 
load(const string cascadeDirName)517 bool CvCascadeClassifier::load( const string cascadeDirName )
518 {
519     FileStorage fs( cascadeDirName + CC_PARAMS_FILENAME, FileStorage::READ );
520     if ( !fs.isOpened() )
521         return false;
522     FileNode node = fs.getFirstTopLevelNode();
523     if ( !readParams( node ) )
524         return false;
525     featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
526     featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize );
527     fs.release();
528 
529     char buf[10];
530     for ( int si = 0; si < numStages; si++ )
531     {
532         sprintf( buf, "%s%d", "stage", si);
533         fs.open( cascadeDirName + buf + ".xml", FileStorage::READ );
534         node = fs.getFirstTopLevelNode();
535         if ( !fs.isOpened() )
536             break;
537         Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
538 
539         if ( !tempStage->read( node, featureEvaluator, *stageParams ))
540         {
541             fs.release();
542             break;
543         }
544         stageClassifiers.push_back(tempStage);
545     }
546     return true;
547 }
548 
getUsedFeaturesIdxMap(Mat & featureMap)549 void CvCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap )
550 {
551     int varCount = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize();
552     featureMap.create( 1, varCount, CV_32SC1 );
553     featureMap.setTo(Scalar(-1));
554 
555     for( vector< Ptr<CvCascadeBoost> >::const_iterator it = stageClassifiers.begin();
556         it != stageClassifiers.end(); it++ )
557         (*it)->markUsedFeaturesInMap( featureMap );
558 
559     for( int fi = 0, idx = 0; fi < varCount; fi++ )
560         if ( featureMap.at<int>(0, fi) >= 0 )
561             featureMap.ptr<int>(0)[fi] = idx++;
562 }
563