Эх сурвалжийг харах

added compilable BoW evaluation pipeline

Alexander Freytag 12 жил өмнө
parent
commit
6826e25433

+ 393 - 0
progs/evaluateCompleteBoWPipeline.cpp

@@ -0,0 +1,393 @@
+/**
+* @file evaluateCompleteBoWPipeline.cpp
+* @brief A complete BoW pipeline: feature extraction, codebook creation, vector quantization, classifier training, evaluation on separate test set
+* @author Alexander Freytag
+* @date 10-05-2013
+*/
+
+//STL
+#include <iostream>
+#include <limits>
+
+//core -- basic stuff
+#include <core/basics/Config.h>
+#include <core/basics/ResourceStatistics.h>
+#include <core/basics/Timer.h>
+#include <core/image/Convert.h>
+#include <core/vector/VectorT.h>
+
+//vislearning -- basic stuff
+#include <vislearning/baselib/Globals.h>
+#include <vislearning/baselib/ICETools.h>
+#include <vislearning/cbaselib/MultiDataset.h>
+#include <vislearning/cbaselib/Example.h>
+#include <vislearning/cbaselib/ClassificationResult.h>
+#include <vislearning/cbaselib/ClassificationResults.h>
+//
+// vislearning -- classifier
+#include <vislearning/classifier/classifierbase/VecClassifier.h>
+#include <vislearning/classifier/genericClassifierSelection.h>
+//
+// vislearning -- BoW codebooks
+#include "vislearning/features/simplefeatures/CodebookPrototypes.h"
+#include "vislearning/features/simplefeatures/BoWFeatureConverter.h"
+// 
+// vislearning -- local features
+#include <vislearning/features/localfeatures/LFonHSG.h>
+#include <vislearning/features/localfeatures/LFColorSande.h>
+#include <vislearning/features/localfeatures/LFColorWeijer.h>
+#include <vislearning/features/localfeatures/LFReadCache.h>
+#include <vislearning/features/localfeatures/LFWriteCache.h>
+#include <vislearning/features/localfeatures/GenericLocalFeatureSelection.h>
+//
+// vislearning -- clustering methods
+#include <vislearning/math/cluster/ClusterAlgorithm.h>
+#include "vislearning/math/cluster/RandomClustering.h"
+#include <vislearning/math/cluster/KMeans.h>
+#include <vislearning/math/cluster/KMedian.h>
+#include <vislearning/math/cluster/GMM.h>
+//
+
+
+//
+#include <vl/generic.h>
+#include <vl/dsift.h>
+
+using namespace std;
+using namespace NICE;
+using namespace OBJREC;
+
+
+LocalFeatureRepresentation * setFeatureExtractor( const Config * _conf )
+{  
+  LocalFeatureRepresentation * featureExtractor;
+   
+    //feature stuff
+  // which OpponentSIFT implementation to use {NICE, VANDESANDE}
+  std::string opSiftImpl;  
+  opSiftImpl = _conf->gS ( "Descriptor", "implementation", "VANDESANDE" );
+  // read features?
+  bool readfeat;
+  readfeat = _conf->gB ( "Descriptor", "read", true );
+  // write features?
+  bool writefeat;  
+  writefeat = _conf->gB ( "Descriptor", "write", true );   
+  
+  // Welche Opponentsift Implementierung soll genutzt werden ?
+  LocalFeatureRepresentation *cSIFT = NULL;
+  LocalFeatureRepresentation *writeFeats = NULL;
+  LocalFeatureRepresentation *readFeats = NULL;
+  featureExtractor = NULL;
+  if ( opSiftImpl == "NICE" )
+  {
+     cSIFT = new OBJREC::LFonHSG ( _conf, "HSG" );
+  }
+  else if ( opSiftImpl == "VANDESANDE" )
+  {
+    cSIFT = new OBJREC::LFColorSande ( _conf, "LFColorSande" );
+  }
+  else
+  {
+    fthrow ( Exception, "feattype: %s not yet supported" << opSiftImpl );
+  }
+
+  featureExtractor = cSIFT;
+  
+  if ( writefeat )
+  {
+    // write the features to a file, if there isn't any to read
+    writeFeats = new LFWriteCache ( _conf, cSIFT );
+    featureExtractor = writeFeats;
+  }
+
+  if ( readfeat )
+  {
+    // read the features from a file
+    if ( writefeat )
+    {
+      readFeats = new LFReadCache ( _conf, writeFeats, -1 );
+    }
+    else
+    {
+      readFeats = new LFReadCache ( _conf, cSIFT, -1 );
+    }
+    featureExtractor = readFeats; 
+  }  
+  
+  //only set feature stuff to NULL, deletion of the underlying object is done in the destructor
+  if ( cSIFT != NULL )
+    cSIFT = NULL;
+  if ( writeFeats != NULL )
+    writeFeats = NULL;
+  if ( readFeats != NULL )
+    readFeats  = NULL ;   
+  
+  return featureExtractor;
+}
+
+OBJREC::ClusterAlgorithm * setClusterAlgo( const Config * _conf )
+{ 
+ std::string section ( "clusteringStuff" );
+  // define the initial number of clusters our codebook shall contain
+  int initialNumberOfClusters = _conf->gI(section, "initialNumberOfClusters", 10);
+  
+  // define the clustering algorithm to be used
+  std::string clusterAlgoString = _conf->gS(section, "clusterAlgo", "kmeans");  
+  
+  OBJREC::ClusterAlgorithm * clusterAlgo;
+  
+  if (clusterAlgoString.compare("kmeans") == 0)
+  {
+    clusterAlgo = new OBJREC::KMeans(initialNumberOfClusters);
+  }
+  else if (clusterAlgoString.compare("kmedian") == 0)
+  {
+    clusterAlgo = new OBJREC::KMedian(initialNumberOfClusters);
+  }  
+  else if (clusterAlgoString.compare("GMM") == 0) 
+  {
+    clusterAlgo = new OBJREC::GMM( _conf, initialNumberOfClusters );
+  }
+  else if ( clusterAlgoString.compare("RandomClustering") == 0 )   
+  {
+    clusterAlgo = new OBJREC::RandomClustering( _conf, section );
+  }
+  else
+  {
+    std::cerr << "Unknown cluster algorithm selected, use random clustering instead" << std::endl;
+    clusterAlgo = new OBJREC::RandomClustering( _conf, section );
+  }    
+  
+  return clusterAlgo;
+}
+
+
+/**
+ a complete BoW pipeline
+ 
+ possibly, we can make use of objrec/progs/testClassifier.cpp
+*/
+int main( int argc, char **argv )
+{
+  std::set_terminate( __gnu_cxx::__verbose_terminate_handler );
+
+  Config * conf = new Config ( argc, argv );
+  
+  const bool writeClassificationResults = conf->gB( "main", "writeClassificationResults", true );
+  const std::string resultsfile = conf->gS( "main", "resultsfile", "/tmp/results.txt" );
+  
+  ResourceStatistics rs;
+  
+  // ========================================================================
+  //                            TRAINING STEP
+  // ========================================================================
+
+  MultiDataset md( conf );
+  const LabeledSet *trainFiles = md["train"];  
+   
+  //**********************************************
+  //
+  //     FEATURE EXTRACTION FOR TRAINING IMAGES
+  //
+  //**********************************************  
+  
+  LocalFeatureRepresentation * featureExtractor = setFeatureExtractor( conf );
+  
+  //collect features in a single data structure
+  NICE::VVector featuresFromAllTrainingImages;  
+  featuresFromAllTrainingImages.clear();
+  
+  //okay, this is redundant - but I see no way to do it more easy right now...
+  std::vector<NICE::VVector> featuresOfImages ( trainFiles->size() );
+  //this again is somehow redundant, but we need the labels lateron for easy access - change this to a better solution :)
+  NICE::VectorT<int> labelsTrain ( trainFiles->size(), 0 );
+  
+  //TODO replace the nasty makro by a suitable for-loop to make it omp-ready (parallelization)
+  int imgCnt ( 0 );
+  LOOP_ALL_S( *trainFiles )
+  {
+      EACH_INFO( classno, info );
+      std::string filename = info.img();  
+      
+      NICE::ColorImage img( filename );
+  
+      //compute features
+      
+      //variables to store feature information
+      NICE::VVector features;
+      NICE::VVector positions;
+  
+      Globals::setCurrentImgFN ( filename );
+      featureExtractor->extractFeatures ( img, features, positions );  
+        
+      //normalization :)
+      for ( NICE::VVector::iterator i = features.begin();
+            i != features.end();
+            i++)
+      {              
+        i->normalizeL1();
+      }  
+      
+      //collect them all in a larger data structure
+      featuresFromAllTrainingImages.append( features );
+      //and store it as well in the data struct that additionally keeps the information which features belong to which image
+      //TODO this can be made more clever!
+//       featuresOfImages.push_back( features );
+      featuresOfImages[imgCnt] = features;
+      labelsTrain[imgCnt] = classno;
+      imgCnt++;
+  }
+  
+  
+  
+  //**********************************************
+  //
+  //             CODEBOOK CREATION
+  //
+  //**********************************************    
+  
+  OBJREC::ClusterAlgorithm * clusterAlgo = setClusterAlgo( conf );
+   
+  NICE::VVector prototypes;
+  
+  std::vector<double> weights;
+  std::vector<int> assignments;
+  
+  clusterAlgo->cluster( featuresFromAllTrainingImages, prototypes, weights, assignments );
+  
+  
+  OBJREC::CodebookPrototypes * codebook = new OBJREC::CodebookPrototypes ( prototypes );
+  
+  
+  //**********************************************
+  //
+  //             VECTOR QUANTIZATION OF
+  //           FEATURES OF TRAINING IMAGES
+  //
+  //**********************************************  
+  
+  OBJREC::BoWFeatureConverter * bowConverter = new OBJREC::BoWFeatureConverter ( conf, codebook );  
+  
+  OBJREC::LabeledSetVector trainSet;
+  
+  NICE::VVector histograms ( featuresOfImages.size() /* number of vectors*/, 0 /* dimension of vectors*/ ); //the internal vectors will be resized within calcHistogram
+  NICE::VVector::iterator histogramIt = histograms.begin();
+  NICE::VectorT<int>::const_iterator labelsIt = labelsTrain.begin();
+  
+  for (std::vector<NICE::VVector>::const_iterator imgIt = featuresOfImages.begin(); imgIt != featuresOfImages.end(); imgIt++, histogramIt++, labelsIt++)
+  {
+    bowConverter->calcHistogram ( *imgIt, *histogramIt );
+    bowConverter->normalizeHistogram ( *histogramIt );
+    
+    //NOTE perhaps we should use add_reference here
+    trainSet.add( *labelsIt, *histogramIt );
+  }
+  
+  
+  //**********************************************
+  //
+  //             CLASSIFIER TRAINING
+  //
+  //**********************************************  
+  
+  std::string classifierType = conf->gS( "main", "classifierType", "GPHIK" );
+  OBJREC::VecClassifier * classifier = OBJREC::GenericClassifierSelection::selectVecClassifier( conf, classifierType );
+  
+  //TODO integrate GP-HIK-NICE into vislearning and add it into genericClassifierSelection
+  
+  //this method adds the training data to the temporary knowledge of our classifier
+  classifier->teach( trainSet );
+  //now the actual training step starts (e.g., parameter estimation, ... )
+  classifier->finishTeaching();
+  
+  
+  // ========================================================================
+  //                            TEST STEP
+  // ========================================================================
+  
+  const LabeledSet *testFiles = md["test"];
+  
+  NICE::Matrix confusionMat;
+  NICE::Timer t;
+  
+  ClassificationResults results;
+  
+  LOOP_ALL_S( *testFiles )
+  {
+      EACH_INFO( classno, info );
+      std::string filename = info.img();
+  
+      //**********************************************
+      //
+      //     FEATURE EXTRACTION FOR TEST IMAGES
+      //
+      //**********************************************  
+      
+      NICE::ColorImage img( filename );
+  
+      //compute features
+      
+      //variables to store feature information
+      NICE::VVector features;
+      NICE::VVector positions;
+  
+      Globals::setCurrentImgFN ( filename );
+      featureExtractor->extractFeatures ( img, features, positions );  
+        
+      //normalization :)
+      for ( NICE::VVector::iterator i = features.begin();
+            i != features.end();
+            i++)
+      {              
+        i->normalizeL1();
+      }        
+      
+      //**********************************************
+      //
+      //             VECTOR QUANTIZATION OF
+      //           FEATURES OF TEST IMAGES
+      //
+      //********************************************** 
+      
+      NICE::Vector histogramOfCurrentImg;
+      bowConverter->calcHistogram ( features, histogramOfCurrentImg );
+      bowConverter->normalizeHistogram ( histogramOfCurrentImg );
+      
+      //**********************************************
+      //
+      //             CLASSIFIER EVALUATION
+      //
+      //**********************************************   
+            
+      uint classno_groundtruth = classno;
+      
+      t.start();
+      ClassificationResult r = classifier->classify ( histogramOfCurrentImg );
+      t.stop();
+      uint classno_estimated = r.classno;
+
+      //if we like to store the classification results for external post processing, uncomment this
+      if ( writeClassificationResults )
+      {
+        results.push_back( r );
+      }      
+      
+      confusionMat( classno_estimated, classno_groundtruth ) += 1;
+  }
+  
+  confusionMat.normalizeColumnsL1();
+  std::cerr << confusionMat << std::endl;
+
+  std::cerr << "average recognition rate: " << confusionMat.trace()/confusionMat.rows() << std::endl;  
+  
+  if ( writeClassificationResults )
+  {
+    double avgRecogResults  ( results.getAverageRecognitionRate () );    
+    std::cerr << "average recognition rate according to classificationResults: " << avgRecogResults << std::endl;
+    results.writeWEKA ( resultsfile, 0 );
+  }     
+  
+ 
+  
+   return 0;
+}