evaluateCompleteBoWPipeline.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /**
  2. * @file evaluateCompleteBoWPipeline.cpp
  3. * @brief A complete BoW pipeline: feature extraction, codebook creation, vector quantization, classifier training, evaluation on separate test set
  4. * @author Alexander Freytag
  5. * @date 10-05-2013
  6. */
  7. //STL
  8. #include <iostream>
  9. #include <limits>
  10. //core -- basic stuff
  11. #include <core/basics/Config.h>
  12. #include <core/basics/ResourceStatistics.h>
  13. #include <core/basics/Timer.h>
  14. #include <core/image/Convert.h>
  15. #include <core/vector/VectorT.h>
  16. //vislearning -- basic stuff
  17. #include <vislearning/baselib/Globals.h>
  18. #include <vislearning/baselib/ICETools.h>
  19. #include <vislearning/cbaselib/MultiDataset.h>
  20. #include <vislearning/cbaselib/Example.h>
  21. #include <vislearning/cbaselib/ClassificationResult.h>
  22. #include <vislearning/cbaselib/ClassificationResults.h>
  23. //
  24. // vislearning -- classifier
  25. #include <vislearning/classifier/classifierbase/VecClassifier.h>
  26. #include <vislearning/classifier/genericClassifierSelection.h>
  27. //
  28. // vislearning -- BoW codebooks
  29. #include "vislearning/features/simplefeatures/CodebookPrototypes.h"
  30. #include "vislearning/features/simplefeatures/BoWFeatureConverter.h"
  31. //
  32. // vislearning -- local features
  33. #include <vislearning/features/localfeatures/LFonHSG.h>
  34. #include <vislearning/features/localfeatures/LFColorSande.h>
  35. #include <vislearning/features/localfeatures/LFColorWeijer.h>
  36. #include <vislearning/features/localfeatures/LFReadCache.h>
  37. #include <vislearning/features/localfeatures/LFWriteCache.h>
  38. #include <vislearning/features/localfeatures/GenericLFSelection.h>
  39. //
  40. // vislearning -- clustering methods
  41. #include <vislearning/math/cluster/ClusterAlgorithm.h>
  42. #include "vislearning/math/cluster/RandomClustering.h"
  43. #include <vislearning/math/cluster/KMeans.h>
  44. #include <vislearning/math/cluster/KMedian.h>
  45. #include <vislearning/math/cluster/GMM.h>
  46. //
  47. using namespace std;
  48. using namespace NICE;
  49. using namespace OBJREC;
  50. LocalFeatureRepresentation * setFeatureExtractor( const Config * _conf )
  51. {
  52. LocalFeatureRepresentation * featureExtractor;
  53. //feature stuff
  54. // which OpponentSIFT implementation to use {NICE, VANDESANDE}
  55. std::string opSiftImpl;
  56. opSiftImpl = _conf->gS ( "Descriptor", "implementation", "VANDESANDE" );
  57. // read features?
  58. bool readfeat;
  59. readfeat = _conf->gB ( "Descriptor", "read", true );
  60. // write features?
  61. bool writefeat;
  62. writefeat = _conf->gB ( "Descriptor", "write", true );
  63. // Welche Opponentsift Implementierung soll genutzt werden ?
  64. LocalFeatureRepresentation *cSIFT = NULL;
  65. LocalFeatureRepresentation *writeFeats = NULL;
  66. LocalFeatureRepresentation *readFeats = NULL;
  67. featureExtractor = NULL;
  68. if ( opSiftImpl == "NICE" )
  69. {
  70. cSIFT = new OBJREC::LFonHSG ( _conf, "HSG" );
  71. }
  72. else if ( opSiftImpl == "VANDESANDE" )
  73. {
  74. cSIFT = new OBJREC::LFColorSande ( _conf, "LFColorSande" );
  75. }
  76. else
  77. {
  78. fthrow ( Exception, "feattype: %s not yet supported" << opSiftImpl );
  79. }
  80. featureExtractor = cSIFT;
  81. if ( writefeat )
  82. {
  83. // write the features to a file, if there isn't any to read
  84. writeFeats = new LFWriteCache ( _conf, cSIFT );
  85. featureExtractor = writeFeats;
  86. }
  87. if ( readfeat )
  88. {
  89. // read the features from a file
  90. if ( writefeat )
  91. {
  92. readFeats = new LFReadCache ( _conf, writeFeats, -1 );
  93. }
  94. else
  95. {
  96. readFeats = new LFReadCache ( _conf, cSIFT, -1 );
  97. }
  98. featureExtractor = readFeats;
  99. }
  100. //only set feature stuff to NULL, deletion of the underlying object is done in the destructor
  101. if ( cSIFT != NULL )
  102. cSIFT = NULL;
  103. if ( writeFeats != NULL )
  104. writeFeats = NULL;
  105. if ( readFeats != NULL )
  106. readFeats = NULL ;
  107. return featureExtractor;
  108. }
  109. OBJREC::ClusterAlgorithm * setClusterAlgo( const Config * _conf )
  110. {
  111. std::string section ( "clusteringStuff" );
  112. // define the initial number of clusters our codebook shall contain
  113. int initialNumberOfClusters = _conf->gI(section, "initialNumberOfClusters", 10);
  114. // define the clustering algorithm to be used
  115. std::string clusterAlgoString = _conf->gS(section, "clusterAlgo", "kmeans");
  116. OBJREC::ClusterAlgorithm * clusterAlgo;
  117. if (clusterAlgoString.compare("kmeans") == 0)
  118. {
  119. clusterAlgo = new OBJREC::KMeans(initialNumberOfClusters);
  120. }
  121. else if (clusterAlgoString.compare("kmedian") == 0)
  122. {
  123. clusterAlgo = new OBJREC::KMedian(initialNumberOfClusters);
  124. }
  125. else if (clusterAlgoString.compare("GMM") == 0)
  126. {
  127. clusterAlgo = new OBJREC::GMM( _conf, initialNumberOfClusters );
  128. }
  129. else if ( clusterAlgoString.compare("RandomClustering") == 0 )
  130. {
  131. clusterAlgo = new OBJREC::RandomClustering( _conf, section );
  132. }
  133. else
  134. {
  135. std::cerr << "Unknown cluster algorithm selected, use random clustering instead" << std::endl;
  136. clusterAlgo = new OBJREC::RandomClustering( _conf, section );
  137. }
  138. return clusterAlgo;
  139. }
  140. /**
  141. a complete BoW pipeline
  142. possibly, we can make use of objrec/progs/testClassifier.cpp
  143. */
  144. int main( int argc, char **argv )
  145. {
  146. std::set_terminate( __gnu_cxx::__verbose_terminate_handler );
  147. Config * conf = new Config ( argc, argv );
  148. const bool writeClassificationResults = conf->gB( "main", "writeClassificationResults", true );
  149. const std::string resultsfile = conf->gS( "main", "resultsfile", "/tmp/results.txt" );
  150. ResourceStatistics rs;
  151. // ========================================================================
  152. // TRAINING STEP
  153. // ========================================================================
  154. MultiDataset md( conf );
  155. const LabeledSet *trainFiles = md["train"];
  156. //**********************************************
  157. //
  158. // FEATURE EXTRACTION FOR TRAINING IMAGES
  159. //
  160. //**********************************************
  161. std::cerr << "FEATURE EXTRACTION FOR TRAINING IMAGES" << std::endl;
  162. OBJREC::LocalFeatureRepresentation * featureExtractor = OBJREC::GenericLFSelection::selectLocalFeatureRep ( conf, "features", OBJREC::GenericLFSelection::TRAINING );
  163. // LocalFeatureRepresentation * featureExtractor = setFeatureExtractor( conf );
  164. //collect features in a single data structure
  165. NICE::VVector featuresFromAllTrainingImages;
  166. featuresFromAllTrainingImages.clear();
  167. //okay, this is redundant - but I see no way to do it more easy right now...
  168. std::vector<NICE::VVector> featuresOfImages ( trainFiles->size() );
  169. //this again is somehow redundant, but we need the labels lateron for easy access - change this to a better solution :)
  170. NICE::VectorT<int> labelsTrain ( trainFiles->size(), 0 );
  171. //TODO replace the nasty makro by a suitable for-loop to make it omp-ready (parallelization)
  172. int imgCnt ( 0 );
  173. LOOP_ALL_S( *trainFiles )
  174. {
  175. EACH_INFO( classno, info );
  176. std::string filename = info.img();
  177. NICE::ColorImage img( filename );
  178. //compute features
  179. //variables to store feature information
  180. NICE::VVector features;
  181. NICE::VVector positions;
  182. Globals::setCurrentImgFN ( filename );
  183. featureExtractor->extractFeatures ( img, features, positions );
  184. //normalization :)
  185. for ( NICE::VVector::iterator i = features.begin();
  186. i != features.end();
  187. i++)
  188. {
  189. i->normalizeL1();
  190. }
  191. //collect them all in a larger data structure
  192. featuresFromAllTrainingImages.append( features );
  193. //and store it as well in the data struct that additionally keeps the information which features belong to which image
  194. //TODO this can be made more clever!
  195. // featuresOfImages.push_back( features );
  196. featuresOfImages[imgCnt] = features;
  197. labelsTrain[imgCnt] = classno;
  198. imgCnt++;
  199. }
  200. //**********************************************
  201. //
  202. // CODEBOOK CREATION
  203. //
  204. //**********************************************
  205. std::cerr << "CODEBOOK CREATION" << std::endl;
  206. OBJREC::ClusterAlgorithm * clusterAlgo = setClusterAlgo( conf );
  207. NICE::VVector prototypes;
  208. std::vector<double> weights;
  209. std::vector<int> assignments;
  210. clusterAlgo->cluster( featuresFromAllTrainingImages, prototypes, weights, assignments );
  211. OBJREC::CodebookPrototypes * codebook = new OBJREC::CodebookPrototypes ( prototypes );
  212. //**********************************************
  213. //
  214. // VECTOR QUANTIZATION OF
  215. // FEATURES OF TRAINING IMAGES
  216. //
  217. //**********************************************
  218. OBJREC::BoWFeatureConverter * bowConverter = new OBJREC::BoWFeatureConverter ( conf, codebook );
  219. OBJREC::LabeledSetVector trainSet;
  220. NICE::VVector histograms ( featuresOfImages.size() /* number of vectors*/, 0 /* dimension of vectors*/ ); //the internal vectors will be resized within calcHistogram
  221. NICE::VVector::iterator histogramIt = histograms.begin();
  222. NICE::VectorT<int>::const_iterator labelsIt = labelsTrain.begin();
  223. for (std::vector<NICE::VVector>::const_iterator imgIt = featuresOfImages.begin(); imgIt != featuresOfImages.end(); imgIt++, histogramIt++, labelsIt++)
  224. {
  225. bowConverter->calcHistogram ( *imgIt, *histogramIt );
  226. bowConverter->normalizeHistogram ( *histogramIt );
  227. //NOTE perhaps we should use add_reference here
  228. trainSet.add( *labelsIt, *histogramIt );
  229. }
  230. //**********************************************
  231. //
  232. // CLASSIFIER TRAINING
  233. //
  234. //**********************************************
  235. std::string classifierType = conf->gS( "main", "classifierType", "GPHIK" );
  236. OBJREC::VecClassifier * classifier = OBJREC::GenericClassifierSelection::selectVecClassifier( conf, classifierType );
  237. //TODO integrate GP-HIK-NICE into vislearning and add it into genericClassifierSelection
  238. //this method adds the training data to the temporary knowledge of our classifier
  239. classifier->teach( trainSet );
  240. //now the actual training step starts (e.g., parameter estimation, ... )
  241. classifier->finishTeaching();
  242. // ========================================================================
  243. // TEST STEP
  244. // ========================================================================
  245. const LabeledSet *testFiles = md["test"];
  246. NICE::Matrix confusionMat;
  247. NICE::Timer t;
  248. ClassificationResults results;
  249. LOOP_ALL_S( *testFiles )
  250. {
  251. EACH_INFO( classno, info );
  252. std::string filename = info.img();
  253. //**********************************************
  254. //
  255. // FEATURE EXTRACTION FOR TEST IMAGES
  256. //
  257. //**********************************************
  258. NICE::ColorImage img( filename );
  259. //compute features
  260. //variables to store feature information
  261. NICE::VVector features;
  262. NICE::VVector positions;
  263. Globals::setCurrentImgFN ( filename );
  264. featureExtractor->extractFeatures ( img, features, positions );
  265. //normalization :)
  266. for ( NICE::VVector::iterator i = features.begin();
  267. i != features.end();
  268. i++)
  269. {
  270. i->normalizeL1();
  271. }
  272. //**********************************************
  273. //
  274. // VECTOR QUANTIZATION OF
  275. // FEATURES OF TEST IMAGES
  276. //
  277. //**********************************************
  278. NICE::Vector histogramOfCurrentImg;
  279. bowConverter->calcHistogram ( features, histogramOfCurrentImg );
  280. bowConverter->normalizeHistogram ( histogramOfCurrentImg );
  281. //**********************************************
  282. //
  283. // CLASSIFIER EVALUATION
  284. //
  285. //**********************************************
  286. uint classno_groundtruth = classno;
  287. t.start();
  288. ClassificationResult r = classifier->classify ( histogramOfCurrentImg );
  289. t.stop();
  290. uint classno_estimated = r.classno;
  291. //if we like to store the classification results for external post processing, uncomment this
  292. if ( writeClassificationResults )
  293. {
  294. results.push_back( r );
  295. }
  296. confusionMat( classno_estimated, classno_groundtruth ) += 1;
  297. }
  298. confusionMat.normalizeColumnsL1();
  299. std::cerr << confusionMat << std::endl;
  300. std::cerr << "average recognition rate: " << confusionMat.trace()/confusionMat.rows() << std::endl;
  301. if ( writeClassificationResults )
  302. {
  303. double avgRecogResults ( results.getAverageRecognitionRate () );
  304. std::cerr << "average recognition rate according to classificationResults: " << avgRecogResults << std::endl;
  305. results.writeWEKA ( resultsfile, 0 );
  306. }
  307. return 0;
  308. }