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