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 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 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 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 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 346 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 353 void CvCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const 354 { 355 featureEvaluator->writeFeatures( fs, featureMap ); 356 } 357 358 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 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 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 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 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 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