Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/apps/traincascade/cascadeclassifier.cpp
16337 views
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
12
CvCascadeParams::CvCascadeParams() : stageType( defaultStageType ),
13
featureType( defaultFeatureType ), winSize( cvSize(24, 24) )
14
{
15
name = CC_CASCADE_PARAMS;
16
}
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
25
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
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
67
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
91
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
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
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
double time = (double)getTickCount();
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
cout << "Number of unique features given windowSize [" << _cascadeParams.winSize.width << "," << _cascadeParams.winSize.height << "] : " << featureEvaluator->getNumFeatures() << "" << endl;
194
195
int startNumStages = (int)stageClassifiers.size();
196
if ( startNumStages > 1 )
197
cout << endl << "Stages 0-" << startNumStages-1 << " are loaded" << endl;
198
else if ( startNumStages == 1)
199
cout << endl << "Stage 0 is loaded" << endl;
200
201
double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) /
202
(double)stageParams->max_depth;
203
double tempLeafFARate;
204
205
for( int i = startNumStages; i < numStages; i++ )
206
{
207
cout << endl << "===== TRAINING " << i << "-stage =====" << endl;
208
cout << "<BEGIN" << endl;
209
210
if ( !updateTrainingSet( requiredLeafFARate, tempLeafFARate ) )
211
{
212
cout << "Train dataset for temp stage can not be filled. "
213
"Branch training terminated." << endl;
214
break;
215
}
216
if( tempLeafFARate <= requiredLeafFARate )
217
{
218
cout << "Required leaf false alarm rate achieved. "
219
"Branch training terminated." << endl;
220
break;
221
}
222
if( (tempLeafFARate <= acceptanceRatioBreakValue) && (acceptanceRatioBreakValue >= 0) ){
223
cout << "The required acceptanceRatio for the model has been reached to avoid overfitting of trainingdata. "
224
"Branch training terminated." << endl;
225
break;
226
}
227
228
Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
229
bool isStageTrained = tempStage->train( featureEvaluator,
230
curNumSamples, _precalcValBufSize, _precalcIdxBufSize,
231
*stageParams );
232
cout << "END>" << endl;
233
234
if(!isStageTrained)
235
break;
236
237
stageClassifiers.push_back( tempStage );
238
239
// save params
240
if( i == 0)
241
{
242
std::string paramsFilename = dirName + CC_PARAMS_FILENAME;
243
FileStorage fs( paramsFilename, FileStorage::WRITE);
244
if ( !fs.isOpened() )
245
{
246
cout << "Parameters can not be written, because file " << paramsFilename
247
<< " can not be opened." << endl;
248
return false;
249
}
250
fs << FileStorage::getDefaultObjectName(paramsFilename) << "{";
251
writeParams( fs );
252
fs << "}";
253
}
254
// save current stage
255
char buf[10];
256
sprintf(buf, "%s%d", "stage", i );
257
string stageFilename = dirName + buf + ".xml";
258
FileStorage fs( stageFilename, FileStorage::WRITE );
259
if ( !fs.isOpened() )
260
{
261
cout << "Current stage can not be written, because file " << stageFilename
262
<< " can not be opened." << endl;
263
return false;
264
}
265
fs << FileStorage::getDefaultObjectName(stageFilename) << "{";
266
tempStage->write( fs, Mat() );
267
fs << "}";
268
269
// Output training time up till now
270
double seconds = ( (double)getTickCount() - time)/ getTickFrequency();
271
int days = int(seconds) / 60 / 60 / 24;
272
int hours = (int(seconds) / 60 / 60) % 24;
273
int minutes = (int(seconds) / 60) % 60;
274
int seconds_left = int(seconds) % 60;
275
cout << "Training until now has taken " << days << " days " << hours << " hours " << minutes << " minutes " << seconds_left <<" seconds." << endl;
276
}
277
278
if(stageClassifiers.size() == 0)
279
{
280
cout << "Cascade classifier can't be trained. Check the used training parameters." << endl;
281
return false;
282
}
283
284
save( dirName + CC_CASCADE_FILENAME, baseFormatSave );
285
286
return true;
287
}
288
289
int CvCascadeClassifier::predict( int sampleIdx )
290
{
291
CV_DbgAssert( sampleIdx < numPos + numNeg );
292
for (vector< Ptr<CvCascadeBoost> >::iterator it = stageClassifiers.begin();
293
it != stageClassifiers.end();++it )
294
{
295
if ( (*it)->predict( sampleIdx ) == 0.f )
296
return 0;
297
}
298
return 1;
299
}
300
301
bool CvCascadeClassifier::updateTrainingSet( double minimumAcceptanceRatio, double& acceptanceRatio)
302
{
303
int64 posConsumed = 0, negConsumed = 0;
304
imgReader.restart();
305
int posCount = fillPassedSamples( 0, numPos, true, 0, posConsumed );
306
if( !posCount )
307
return false;
308
cout << "POS count : consumed " << posCount << " : " << (int)posConsumed << endl;
309
310
int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos ); // apply only a fraction of negative samples. double is required since overflow is possible
311
int negCount = fillPassedSamples( posCount, proNumNeg, false, minimumAcceptanceRatio, negConsumed );
312
if ( !negCount )
313
if ( !(negConsumed > 0 && ((double)negCount+1)/(double)negConsumed <= minimumAcceptanceRatio) )
314
return false;
315
316
curNumSamples = posCount + negCount;
317
acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed );
318
cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl;
319
return true;
320
}
321
322
int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, double minimumAcceptanceRatio, int64& consumed )
323
{
324
int getcount = 0;
325
Mat img(cascadeParams.winSize, CV_8UC1);
326
for( int i = first; i < first + count; i++ )
327
{
328
for( ; ; )
329
{
330
if( consumed != 0 && ((double)getcount+1)/(double)(int64)consumed <= minimumAcceptanceRatio )
331
return getcount;
332
333
bool isGetImg = isPositive ? imgReader.getPos( img ) :
334
imgReader.getNeg( img );
335
if( !isGetImg )
336
return getcount;
337
consumed++;
338
339
featureEvaluator->setImage( img, isPositive ? 1 : 0, i );
340
if( predict( i ) == 1 )
341
{
342
getcount++;
343
printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount);
344
break;
345
}
346
}
347
}
348
return getcount;
349
}
350
351
void CvCascadeClassifier::writeParams( FileStorage &fs ) const
352
{
353
cascadeParams.write( fs );
354
fs << CC_STAGE_PARAMS << "{"; stageParams->write( fs ); fs << "}";
355
fs << CC_FEATURE_PARAMS << "{"; featureParams->write( fs ); fs << "}";
356
}
357
358
void CvCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const
359
{
360
featureEvaluator->writeFeatures( fs, featureMap );
361
}
362
363
void CvCascadeClassifier::writeStages( FileStorage &fs, const Mat& featureMap ) const
364
{
365
char cmnt[30];
366
int i = 0;
367
fs << CC_STAGES << "[";
368
for( vector< Ptr<CvCascadeBoost> >::const_iterator it = stageClassifiers.begin();
369
it != stageClassifiers.end();++it, ++i )
370
{
371
sprintf( cmnt, "stage %d", i );
372
cvWriteComment( fs.fs, cmnt, 0 );
373
fs << "{";
374
(*it)->write( fs, featureMap );
375
fs << "}";
376
}
377
fs << "]";
378
}
379
380
bool CvCascadeClassifier::readParams( const FileNode &node )
381
{
382
if ( !node.isMap() || !cascadeParams.read( node ) )
383
return false;
384
385
stageParams = makePtr<CvCascadeBoostParams>();
386
FileNode rnode = node[CC_STAGE_PARAMS];
387
if ( !stageParams->read( rnode ) )
388
return false;
389
390
featureParams = CvFeatureParams::create(cascadeParams.featureType);
391
rnode = node[CC_FEATURE_PARAMS];
392
if ( !featureParams->read( rnode ) )
393
return false;
394
return true;
395
}
396
397
bool CvCascadeClassifier::readStages( const FileNode &node)
398
{
399
FileNode rnode = node[CC_STAGES];
400
if (!rnode.empty() || !rnode.isSeq())
401
return false;
402
stageClassifiers.reserve(numStages);
403
FileNodeIterator it = rnode.begin();
404
for( int i = 0; i < min( (int)rnode.size(), numStages ); i++, it++ )
405
{
406
Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
407
if ( !tempStage->read( *it, featureEvaluator, *stageParams) )
408
return false;
409
stageClassifiers.push_back(tempStage);
410
}
411
return true;
412
}
413
414
// For old Haar Classifier file saving
415
#define ICV_HAAR_TYPE_ID "opencv-haar-classifier"
416
#define ICV_HAAR_SIZE_NAME "size"
417
#define ICV_HAAR_STAGES_NAME "stages"
418
#define ICV_HAAR_TREES_NAME "trees"
419
#define ICV_HAAR_FEATURE_NAME "feature"
420
#define ICV_HAAR_RECTS_NAME "rects"
421
#define ICV_HAAR_TILTED_NAME "tilted"
422
#define ICV_HAAR_THRESHOLD_NAME "threshold"
423
#define ICV_HAAR_LEFT_NODE_NAME "left_node"
424
#define ICV_HAAR_LEFT_VAL_NAME "left_val"
425
#define ICV_HAAR_RIGHT_NODE_NAME "right_node"
426
#define ICV_HAAR_RIGHT_VAL_NAME "right_val"
427
#define ICV_HAAR_STAGE_THRESHOLD_NAME "stage_threshold"
428
#define ICV_HAAR_PARENT_NAME "parent"
429
#define ICV_HAAR_NEXT_NAME "next"
430
431
void CvCascadeClassifier::save( const string filename, bool baseFormat )
432
{
433
FileStorage fs( filename, FileStorage::WRITE );
434
435
if ( !fs.isOpened() )
436
return;
437
438
fs << FileStorage::getDefaultObjectName(filename);
439
if ( !baseFormat )
440
{
441
Mat featureMap;
442
getUsedFeaturesIdxMap( featureMap );
443
fs << "{";
444
writeParams( fs );
445
fs << CC_STAGE_NUM << (int)stageClassifiers.size();
446
writeStages( fs, featureMap );
447
writeFeatures( fs, featureMap );
448
}
449
else
450
{
451
//char buf[256];
452
CvSeq* weak;
453
if ( cascadeParams.featureType != CvFeatureParams::HAAR )
454
CV_Error( CV_StsBadFunc, "old file format is used for Haar-like features only");
455
fs << "{:" ICV_HAAR_TYPE_ID;
456
fs << ICV_HAAR_SIZE_NAME << "[:" << cascadeParams.winSize.width <<
457
cascadeParams.winSize.height << "]";
458
fs << ICV_HAAR_STAGES_NAME << "[";
459
for( size_t si = 0; si < stageClassifiers.size(); si++ )
460
{
461
fs << "{"; //stage
462
/*sprintf( buf, "stage %d", si );
463
CV_CALL( cvWriteComment( fs, buf, 1 ) );*/
464
weak = stageClassifiers[si]->get_weak_predictors();
465
fs << ICV_HAAR_TREES_NAME << "[";
466
for( int wi = 0; wi < weak->total; wi++ )
467
{
468
int inner_node_idx = -1, total_inner_node_idx = -1;
469
queue<const CvDTreeNode*> inner_nodes_queue;
470
CvCascadeBoostTree* tree = *((CvCascadeBoostTree**) cvGetSeqElem( weak, wi ));
471
472
fs << "[";
473
/*sprintf( buf, "tree %d", wi );
474
CV_CALL( cvWriteComment( fs, buf, 1 ) );*/
475
476
const CvDTreeNode* tempNode;
477
478
inner_nodes_queue.push( tree->get_root() );
479
total_inner_node_idx++;
480
481
while (!inner_nodes_queue.empty())
482
{
483
tempNode = inner_nodes_queue.front();
484
inner_node_idx++;
485
486
fs << "{";
487
fs << ICV_HAAR_FEATURE_NAME << "{";
488
((CvHaarEvaluator*)featureEvaluator.get())->writeFeature( fs, tempNode->split->var_idx );
489
fs << "}";
490
491
fs << ICV_HAAR_THRESHOLD_NAME << tempNode->split->ord.c;
492
493
if( tempNode->left->left || tempNode->left->right )
494
{
495
inner_nodes_queue.push( tempNode->left );
496
total_inner_node_idx++;
497
fs << ICV_HAAR_LEFT_NODE_NAME << total_inner_node_idx;
498
}
499
else
500
fs << ICV_HAAR_LEFT_VAL_NAME << tempNode->left->value;
501
502
if( tempNode->right->left || tempNode->right->right )
503
{
504
inner_nodes_queue.push( tempNode->right );
505
total_inner_node_idx++;
506
fs << ICV_HAAR_RIGHT_NODE_NAME << total_inner_node_idx;
507
}
508
else
509
fs << ICV_HAAR_RIGHT_VAL_NAME << tempNode->right->value;
510
fs << "}"; // ICV_HAAR_FEATURE_NAME
511
inner_nodes_queue.pop();
512
}
513
fs << "]";
514
}
515
fs << "]"; //ICV_HAAR_TREES_NAME
516
fs << ICV_HAAR_STAGE_THRESHOLD_NAME << stageClassifiers[si]->getThreshold();
517
fs << ICV_HAAR_PARENT_NAME << (int)si-1 << ICV_HAAR_NEXT_NAME << -1;
518
fs << "}"; //stage
519
} /* for each stage */
520
fs << "]"; //ICV_HAAR_STAGES_NAME
521
}
522
fs << "}";
523
}
524
525
bool CvCascadeClassifier::load( const string cascadeDirName )
526
{
527
FileStorage fs( cascadeDirName + CC_PARAMS_FILENAME, FileStorage::READ );
528
if ( !fs.isOpened() )
529
return false;
530
FileNode node = fs.getFirstTopLevelNode();
531
if ( !readParams( node ) )
532
return false;
533
featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType);
534
featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize );
535
fs.release();
536
537
char buf[16] = {0};
538
for ( int si = 0; si < numStages; si++ )
539
{
540
sprintf( buf, "%s%d", "stage", si);
541
fs.open( cascadeDirName + buf + ".xml", FileStorage::READ );
542
node = fs.getFirstTopLevelNode();
543
if ( !fs.isOpened() )
544
break;
545
Ptr<CvCascadeBoost> tempStage = makePtr<CvCascadeBoost>();
546
547
if ( !tempStage->read( node, featureEvaluator, *stageParams ))
548
{
549
fs.release();
550
break;
551
}
552
stageClassifiers.push_back(tempStage);
553
}
554
return true;
555
}
556
557
void CvCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap )
558
{
559
int varCount = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize();
560
featureMap.create( 1, varCount, CV_32SC1 );
561
featureMap.setTo(Scalar(-1));
562
563
for( vector< Ptr<CvCascadeBoost> >::const_iterator it = stageClassifiers.begin();
564
it != stageClassifiers.end();++it )
565
(*it)->markUsedFeaturesInMap( featureMap );
566
567
for( int fi = 0, idx = 0; fi < varCount; fi++ )
568
if ( featureMap.at<int>(0, fi) >= 0 )
569
featureMap.ptr<int>(0)[fi] = idx++;
570
}
571
572