00001 /*! 00002 \file Gist/train-surfpmk.C 00003 00004 \brief Interface for training and testing GistEstimatorSurfPMK. 00005 00006 The train-bbof program in conjunction with the GistEstimatorSurfPMK 00007 class implements the following paper within the INVT framework: 00008 00009 Murillo, A. C., Guerrero, J. J., Sagues, C. 00010 SURF features for efficient robot localization with omnidirectional images 00011 ICRA, 2007. 00012 00013 Whereas the GistEstimatorSurfPMK class is only concerned with the 00014 portions of the above paper that deal with gist vector computations, 00015 this program provides the remaining structure required to implement 00016 the necessary training and image classification functionalities. 00017 00018 train-surfpmk has two modes of operation, viz., training and testing. 00019 Training mode consists of four distinct phases: SURF descriptor 00020 accumulation, hierarchical K-means clustering, training histograms 00021 collection, and SVM classifier generation. Testing mode operates in a 00022 single phase that uses the results of the hierarchical clustering, 00023 histograms collection and SVM classifier generation training phases to 00024 classify input images into appropriate categories. 00025 */ 00026 00027 // //////////////////////////////////////////////////////////////////// // 00028 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00029 // University of Southern California (USC) and the iLab at USC. // 00030 // See http://iLab.usc.edu for information about this project. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00033 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00034 // in Visual Environments, and Applications'' by Christof Koch and // 00035 // Laurent Itti, California Institute of Technology, 2001 (patent // 00036 // pending; application number 09/912,225 filed July 23, 2001; see // 00037 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00038 // //////////////////////////////////////////////////////////////////// // 00039 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00040 // // 00041 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00042 // redistribute it and/or modify it under the terms of the GNU General // 00043 // Public License as published by the Free Software Foundation; either // 00044 // version 2 of the License, or (at your option) any later version. // 00045 // // 00046 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00047 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00048 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00049 // PURPOSE. See the GNU General Public License for more details. // 00050 // // 00051 // You should have received a copy of the GNU General Public License // 00052 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00053 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00054 // Boston, MA 02111-1307 USA. // 00055 // //////////////////////////////////////////////////////////////////// // 00056 // 00057 // Primary maintainer for this file: Manu Viswanathan <mviswana at usc dot edu> 00058 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Gist/train-surfpmk.C $ 00059 // $Id: train-surfpmk.C 13889 2010-09-07 23:54:33Z mviswana $ 00060 // 00061 00062 //--------------------------- LIBRARY CHECK ----------------------------- 00063 00064 #if !defined(INVT_HAVE_LIBSURF) \ 00065 || !defined(INVT_HAVE_LIBTORCH) 00066 00067 #include "Util/log.H" 00068 00069 int main() 00070 { 00071 LERROR("Sorry, this program needs the SURF, PMK and torch libraries.") ; 00072 return 255 ; 00073 } 00074 00075 #else // the actual program in all its hideous glory 00076 00077 //------------------------------ HEADERS -------------------------------- 00078 00079 // Gist specific headers 00080 #include "Neuro/GistEstimatorSurfPMK.H" 00081 00082 // Other INVT headers 00083 #include "Neuro/StdBrain.H" 00084 #include "Neuro/NeuroOpts.H" 00085 #include "Neuro/NeuroSimEvents.H" 00086 00087 #include "Media/SimFrameSeries.H" 00088 #include "Media/MediaOpts.H" 00089 00090 #include "Simulation/SimEventQueue.H" 00091 #include "Simulation/SimEventQueueConfigurator.H" 00092 00093 #include "Channels/ChannelOpts.H" 00094 #include "Component/ModelManager.H" 00095 #include "Component/ModelOptionDef.H" 00096 00097 #include "Image/Point2D.H" 00098 00099 #include "nub/ref.h" 00100 00101 // SURF headers 00102 #include <opensurf/opensurf.hh> 00103 00104 // PMK headers 00105 00106 // Unix headers 00107 #include <glob.h> 00108 #include <unistd.h> 00109 00110 // Standard C++ headers 00111 #include <fstream> 00112 #include <sstream> 00113 #include <ios> 00114 #include <numeric> 00115 #include <algorithm> 00116 #include <functional> 00117 #include <map> 00118 #include <vector> 00119 #include <iterator> 00120 #include <stdexcept> 00121 #include <utility> 00122 #include <limits> 00123 #include <cmath> 00124 00125 // torch headers 00126 #include <torch/QCTrainer.h> 00127 #include <torch/SVMClassification.h> 00128 #include <torch/Kernel.h> 00129 #include <torch/MatDataSet.h> 00130 00131 //------------------------ TEMPLATE UTILITIES --------------------------- 00132 00133 // Convenient (but perhaps not the most efficient) helper to convert 00134 // various data types to strings. 00135 // 00136 // DEVNOTE: Works as long as type T defines an operator << that writes to 00137 // an ostream. 00138 template<typename T> 00139 std::string to_string(const T& t) 00140 { 00141 std::ostringstream str ; 00142 str << t ; 00143 return str.str() ; 00144 } 00145 00146 //----------------------- COMMAND LINE OPTIONS -------------------------- 00147 00148 /*! 00149 This program has five distinct phases/modes of operation, each one 00150 specified via a suitable non-option command line argument. 00151 Additionally, it supports several command line options to allow users 00152 to tweak various parameters such as the name of the vocabulary file, 00153 the training histograms database, and so on. 00154 */ 00155 namespace { 00156 00157 const ModelOptionCateg MOC_SURFPMK = { 00158 MOC_SORTPRI_3, 00159 "Options specific to the Surf PMK program", 00160 } ; 00161 00162 //! In the SURF descriptors accumulation phase, we collect all the 00163 //! descriptors from the training images and store them in a plain text 00164 //! file. 00165 #ifndef SPMK_DEFAULT_TRAINING_DESCRIPTORS_FILE 00166 #define SPMK_DEFAULT_TRAINING_DESCRIPTORS_FILE "surf_descriptors.txt" 00167 #endif 00168 00169 const ModelOptionDef OPT_SurfDescriptors = { 00170 MODOPT_ARG_STRING, "SurfDescriptors", & MOC_SURFPMK, OPTEXP_CORE, 00171 "This option specifies the name of the file where SURF descriptors\n" 00172 "for the training images are to be accumulated. This is a plain text\n" 00173 "file containing the descriptors that will be fed into the hierarchical\n" 00174 "K-means procedure during the second training phase.\n", 00175 "surf-descriptors", '\0', "surf-descriptors-file", 00176 SPMK_DEFAULT_TRAINING_DESCRIPTORS_FILE, 00177 } ; 00178 00179 //! In the second phase of training, we perform hierarchical K-means 00180 //! clustering on the SURF descriptors accumulated in the first phase and 00181 //! store the results in yet another plain text file. 00182 #ifndef SPMK_DEFAULT_VOCABULARY_FILE 00183 #define SPMK_DEFAULT_VOCABULARY_FILE "surf_vocabulary.txt" 00184 #endif 00185 00186 const ModelOptionDef OPT_SurfVocabulary = { 00187 MODOPT_ARG_STRING, "SurfVocabulary", & MOC_SURFPMK, OPTEXP_CORE, 00188 "This option specifies the name of the file in which the \"prototypical\"\n" 00189 "SURF descriptors are (or are to be) stored. This is a plain text\n" 00190 "file containing the centroids of the hierarchical K-means clusters,\n" 00191 "which are used during gist vector computation to create feature maps\n" 00192 "and, subsequently, the flattened multi-level histograms using the\n" 00193 "pyramid matching as described in the Murillo paper.\n", 00194 "surf-vocabulary", '\0', "surf-vocabulary-file", 00195 SPMK_DEFAULT_VOCABULARY_FILE, 00196 } ; 00197 00198 //! In the third phase of training, we compute and store the gist vectors 00199 //! for the training images. These gist vectors are used in the next 00200 //! training phase as the data points that will be used to create 00201 //! appropriate SVM classifiers for each image category. 00202 #ifndef SPMK_DEFAULT_TRAINING_HISTOGRAMS_FILE 00203 #define SPMK_DEFAULT_TRAINING_HISTOGRAMS_FILE "training_histograms.txt" 00204 #endif 00205 00206 const ModelOptionDef OPT_HistogramsFile = { 00207 MODOPT_ARG_STRING, "HistogramsFile", & MOC_SURFPMK, OPTEXP_CORE, 00208 "This option specifies the name of the training histograms database,\n" 00209 "a plain text file containing one histogram entry per line. The\n" 00210 "first field specifies the name plus number of the entry (e.g.,\n" 00211 "foo.mpg:1, bar.mpg:5, and so on). The second field specifies the ground\n" 00212 "truth for this particular image. The remaining fields are simply the\n" 00213 "numbers making up the image's flattened out multi-level histogram,\n" 00214 "which serves as its gist vector.\n", 00215 "training-histograms", '\0', "training-histograms-file", 00216 SPMK_DEFAULT_TRAINING_HISTOGRAMS_FILE, 00217 } ; 00218 00219 //! In the fourth phase of training, we create SVM classifiers for each 00220 //! of the categories and store the relevant parameters to a text file 00221 //! for later use during image classification. Each segment will have its 00222 //! own SVM classifier. Therefore, the default value of this symbol is 00223 //! not a good one to use and it should be explicitly specified on the 00224 //! command line. 00225 #ifndef SPMK_DEFAULT_SVM_CLASSIFIER_FILE 00226 #define SPMK_DEFAULT_SVM_CLASSIFIER_FILE "svm_classifier.bin" 00227 #endif 00228 00229 const ModelOptionDef OPT_SvmClassifierFile = { 00230 MODOPT_ARG_STRING, "SvmClassifierFile", & MOC_SURFPMK, OPTEXP_CORE, 00231 "This option specifies the name of the file that will hold the SVM\n" 00232 "classifier for a given segment. This file is read and written by the\n" 00233 "torch library.", 00234 "svm-classifier", '\0', "svm-classifier-file", 00235 SPMK_DEFAULT_SVM_CLASSIFIER_FILE, 00236 } ; 00237 00238 //! While creating SVM classifiers for each of the categories, we need a 00239 //! temp file to store the training histograms data in the format 00240 //! required by the torch library. Usually, it would be a good idea to 00241 //! explicitly specify this on the command line rather than relying on 00242 //! the compiled in default. 00243 #ifndef SPMK_DEFAULT_SVM_TEMP_FILE 00244 #define SPMK_DEFAULT_SVM_TEMP_FILE "/tmp/train-surfpmk-torch-dataset.txt" 00245 #endif 00246 00247 const ModelOptionDef OPT_SvmTempFile = { 00248 MODOPT_ARG_STRING, "SvmTempFile", & MOC_SURFPMK, OPTEXP_CORE, 00249 "This option specifies the name of the temp file that will hold the SVM\n" 00250 "training data in the format required by the torch library. This file is\n" 00251 "automatically deleted when it is no longer required.", 00252 "svm-temp", '\0', "svm-temp-file", 00253 SPMK_DEFAULT_SVM_TEMP_FILE, 00254 } ; 00255 00256 //! In image classification mode, we write the results to a plain text 00257 //! file. 00258 #ifndef SPMK_DEFAULT_CLASSIFICATION_RESULTS_FILE 00259 #define SPMK_DEFAULT_CLASSIFICATION_RESULTS_FILE \ 00260 "surfpmk_classifications.txt" 00261 #endif 00262 00263 const ModelOptionDef OPT_ResultsFile = { 00264 MODOPT_ARG_STRING, "ResultsFile", & MOC_SURFPMK, OPTEXP_CORE, 00265 "This option specifies the name of the classification results file,\n" 00266 "a plain text file containing one result entry per line. The first\n" 00267 "field specifies the name of the input image plus number of the entry,\n" 00268 "(e.g., foo.mpg:1, bar.mpg:5, and so on). Then comes the ground truth\n" 00269 "for this image followed by its classification result.\n", 00270 "results-file", '\0', "classification-results-file", 00271 SPMK_DEFAULT_CLASSIFICATION_RESULTS_FILE, 00272 } ; 00273 00274 //! Several of the data files output by different operational modes of 00275 //! this program require inclusion of the current image/frame name and 00276 //! number and the ground truth segment/category number. These options 00277 //! allow users to specify appropriate values for this required info. 00278 //! 00279 //! NOTE: The default values for these options are not very useful. 00280 //! They really ought to be explicitly specified on the command line. 00281 #ifndef SPMK_DEFAULT_IMAGE_NAME 00282 #define SPMK_DEFAULT_IMAGE_NAME "some_image" 00283 #endif 00284 #ifndef SPMK_DEFAULT_SEGMENT_NUMBER 00285 #define SPMK_DEFAULT_SEGMENT_NUMBER "0" 00286 #endif 00287 00288 const ModelOptionDef OPT_ImageName = { 00289 MODOPT_ARG_STRING, "ImageName", & MOC_SURFPMK, OPTEXP_CORE, 00290 "This option specifies the \"root\" name for an image. The image number\n" 00291 "will be automatically appended to this \"root\" name with a colon as the\n" 00292 "separator between name and frame number. The current input MPEG file\n" 00293 "name is a good choice for the value of this option.\n", 00294 "image-name", '\0', "input-MPEG-file-name", 00295 SPMK_DEFAULT_IMAGE_NAME, 00296 } ; 00297 00298 const ModelOptionDef OPT_SegmentNumber = { 00299 MODOPT_ARG_STRING, "SegmentNumber", & MOC_SURFPMK, OPTEXP_CORE, 00300 "This option specifies the segment number for an image in the training\n" 00301 "set. The segment number is used to specify the ground truth for the\n" 00302 "image classification.\n", 00303 "segment-number", '\0', "image-segment-number", 00304 SPMK_DEFAULT_SEGMENT_NUMBER, 00305 } ; 00306 00307 /*! 00308 The different operational modes of this program must be specified as 00309 the one and only non-option command line argument. This "action" 00310 command must be one of the following strings (case-sensitive!): 00311 00312 1. surf -- accumulate the SURF descriptors for the training images 00313 in the plain text file specified by the --surf-descriptors option. 00314 By default, the descriptors will be accumulated in 00315 ./surf_descriptors.txt. 00316 00317 Additionally, the --image-name and --segment-number options are 00318 required as this information is also recorded in the SURF 00319 descriptors file. 00320 00321 2. vocab -- compute the SURF descriptors vocabulary, i.e., the 00322 "protototypical" SURF descriptors, from the accumulated descriptors 00323 using the hierarchical K-means implementation in libpmk. 00324 00325 For this action, the --surf-descriptors option specifies the input 00326 file for the K-means while the --surf-vocabulary option specifies 00327 the output file. The defaults are to read from 00328 ./surf_descriptors.txt and write to ./surf_vocabulary.txt. 00329 00330 3. hist -- compute the flattened out multi-level histograms for the 00331 training set. The output is sent to the text file specified by the 00332 --histograms-file option. 00333 00334 The --image-name and --segment-number options are also required. 00335 00336 4. svm -- generate the SVM classifiers for each of the categories. 00337 The --svm-classifier file specifies the name of the file to which 00338 the SVM parameters will be stored. By default, this is 00339 ./svm_classifier.bin. Users should supply a file name different 00340 from the default. Otherwise, this file will get overwritten for 00341 each segment. 00342 00343 The --histograms-file can be used to specify the input data for 00344 this action. 00345 00346 In addition to the above two options, this action also needs the 00347 --svm-temp option to store the histograms data in the format 00348 required by the torch library. The default value is okay for this 00349 option. However, if several instances of this program can be 00350 executing in parallel, it would be best to supply different temp 00351 files explicitly on the command line. 00352 00353 5. classify -- uses the vocabulary and SVM classifiers produced by the 00354 vocab and svm actions to classify the input images streaming in. 00355 Classification results are written, by default, to 00356 ./classification_results.txt; but this can be changed with the 00357 --results-file option. 00358 00359 The --surf-vocabulary and --svm-classifier options can be used to 00360 specify appropriate values for the different pieces of input 00361 required by the classify action. Note that the --svm-classifier 00362 option does not point to a specific classifier, but really is a 00363 "root" name to use. This program will automatically load all the 00364 classifiers that begin with this root. For example, if the user 00365 specifies --svm-classifier="ACB_svm_classifier", this program will 00366 load all the classifiers whose file names begin with 00367 "ACB_svm_classifier." and append numbers starting at 1. 00368 */ 00369 #ifndef SPMK_SURF_CMD 00370 #define SPMK_SURF_CMD "surf" 00371 #endif 00372 #ifndef SPMK_VOCABULARY_CMD 00373 #define SPMK_VOCABULARY_CMD "vocab" 00374 #endif 00375 #ifndef SPMK_HISTOGRAM_CMD 00376 #define SPMK_HISTOGRAM_CMD "hist" 00377 #endif 00378 #ifndef SPMK_SVM_CMD 00379 #define SPMK_SVM_CMD "svm" 00380 #endif 00381 #ifndef SPMK_CLASSIFY_CMD 00382 #define SPMK_CLASSIFY_CMD "classify" 00383 #endif 00384 00385 // For printing usage info 00386 #ifndef SPMK_ACTIONS 00387 #define SPMK_ACTIONS ("{"SPMK_SURF_CMD"|"SPMK_VOCABULARY_CMD"|"\ 00388 SPMK_HISTOGRAM_CMD"|"SPMK_SVM_CMD"|"\ 00389 SPMK_CLASSIFY_CMD"}") 00390 #endif 00391 00392 } // end of local namespace encapsulating command line options section 00393 00394 //--------------------- SIMULATION ENCAPSULATION ------------------------ 00395 00396 // The following helper class wraps around the ModelManager and 00397 // associated objects, providing a neatly encapsulated API for the main 00398 // program. 00399 namespace { 00400 00401 class SPMKSimulation { 00402 ModelManager model_manager ; 00403 nub::soft_ref<SimEventQueueConfigurator> configurator ; 00404 nub::soft_ref<StdBrain> brain ; 00405 nub::ref<SimInputFrameSeries> input_frame_series ; 00406 00407 // Various command line options specific to this program 00408 OModelParam<std::string> sd_option ; // --surf-descriptors 00409 OModelParam<std::string> sv_option ; // --surf-vocabulary 00410 OModelParam<std::string> th_option ; // --training-histograms 00411 OModelParam<std::string> sc_option ; // --svm-classifier 00412 OModelParam<std::string> st_option ; // --svm-temp 00413 OModelParam<std::string> rf_option ; // --results-file 00414 OModelParam<std::string> in_option ; // --image-name (not --in!) 00415 OModelParam<std::string> sn_option ; // --segment-number 00416 00417 public : 00418 SPMKSimulation(const std::string& model_name) ; 00419 void parse_command_line(int argc, const char* argv[]) ; 00420 void run() ; 00421 ~SPMKSimulation() ; 00422 00423 private : 00424 // The different actions performed by this program 00425 typedef void (SPMKSimulation::*Action)() ; 00426 typedef std::map<std::string, Action> ActionMap ; 00427 ActionMap action_map ; 00428 00429 void accumulate_surf_descriptors() ; 00430 void compute_surf_vocabulary() ; 00431 void compute_training_histograms() ; 00432 void generate_svm_classifier() ; 00433 void classify_input_images() ; 00434 00435 // Accessors for retrieving some of the command line arguments 00436 std::string surf_descriptors_file() {return sd_option.getVal() ;} 00437 std::string surf_vocabulary_file() {return sv_option.getVal() ;} 00438 std::string histograms_file() {return th_option.getVal() ;} 00439 std::string svm_classifier_file() {return sc_option.getVal() ;} 00440 std::string svm_temp_file() {return st_option.getVal() ;} 00441 std::string results_file() {return rf_option.getVal() ;} 00442 std::string image_name() {return in_option.getVal() ;} 00443 std::string segment_number() {return sn_option.getVal() ;} 00444 } ; 00445 00446 // On instantiation, create the model manager and the simulation's 00447 // various components. 00448 SPMKSimulation::SPMKSimulation(const std::string& model_name) 00449 : model_manager(model_name), 00450 configurator(new SimEventQueueConfigurator(model_manager)), 00451 brain(new StdBrain(model_manager)), 00452 input_frame_series(new SimInputFrameSeries(model_manager)), 00453 sd_option(& OPT_SurfDescriptors, & model_manager), 00454 sv_option(& OPT_SurfVocabulary, & model_manager), 00455 th_option(& OPT_HistogramsFile, & model_manager), 00456 sc_option(& OPT_SvmClassifierFile, & model_manager), 00457 st_option(& OPT_SvmTempFile, & model_manager), 00458 rf_option(& OPT_ResultsFile, & model_manager), 00459 in_option(& OPT_ImageName, & model_manager), 00460 sn_option(& OPT_SegmentNumber, & model_manager) 00461 { 00462 model_manager.addSubComponent(configurator) ; 00463 model_manager.addSubComponent(brain) ; 00464 model_manager.addSubComponent(input_frame_series) ; 00465 00466 typedef SPMKSimulation me ; // typing shortcut 00467 action_map[SPMK_SURF_CMD] = & me::accumulate_surf_descriptors ; 00468 action_map[SPMK_VOCABULARY_CMD] = & me::compute_surf_vocabulary ; 00469 action_map[SPMK_HISTOGRAM_CMD] = & me::compute_training_histograms ; 00470 action_map[SPMK_SVM_CMD] = & me::generate_svm_classifier ; 00471 action_map[SPMK_CLASSIFY_CMD] = & me::classify_input_images ; 00472 } 00473 00474 // TODO: And how can we force the gist estimator type to be always 00475 // GistEstimatorSurfPMK? This program doesn't make sense for any other 00476 // gist estimator. 00477 void SPMKSimulation::parse_command_line(int argc, const char* argv[]) 00478 { 00479 model_manager.setOptionValString(& OPT_GistEstimatorType, "SurfPMK") ; 00480 00481 model_manager.setOptionValString(& OPT_SurfDescriptors, 00482 SPMK_DEFAULT_TRAINING_DESCRIPTORS_FILE) ; 00483 model_manager.setOptionValString(& OPT_SurfVocabulary, 00484 SPMK_DEFAULT_VOCABULARY_FILE) ; 00485 model_manager.setOptionValString(& OPT_HistogramsFile, 00486 SPMK_DEFAULT_TRAINING_HISTOGRAMS_FILE ) ; 00487 model_manager.setOptionValString(& OPT_SvmClassifierFile, 00488 SPMK_DEFAULT_SVM_CLASSIFIER_FILE ) ; 00489 model_manager.setOptionValString(& OPT_SvmTempFile, 00490 SPMK_DEFAULT_SVM_TEMP_FILE ) ; 00491 model_manager.setOptionValString(& OPT_ResultsFile, 00492 SPMK_DEFAULT_CLASSIFICATION_RESULTS_FILE) ; 00493 00494 model_manager.setOptionValString(& OPT_ImageName, 00495 SPMK_DEFAULT_IMAGE_NAME) ; 00496 model_manager.setOptionValString(& OPT_SegmentNumber, 00497 SPMK_DEFAULT_SEGMENT_NUMBER) ; 00498 00499 if (! model_manager.parseCommandLine(argc, argv, SPMK_ACTIONS, 1, 1)) 00500 throw std::runtime_error("command line parse error") ; 00501 } 00502 00503 // To run the simulation, we simply dispatch to the function 00504 // corresponding to the action (non-option) command line argument. 00505 void SPMKSimulation::run() 00506 { 00507 std::string cmd(model_manager.getExtraArg(0)) ; 00508 ActionMap::iterator action = action_map.find(cmd) ; 00509 if (action == action_map.end()) 00510 throw std::runtime_error(cmd + ": sorry, unknown action") ; 00511 (this->*(action->second))() ; 00512 } 00513 00514 // Do we really not have to delete the configurator, brain and input 00515 // frame series? If it turns out we do, this empty destructor will have 00516 // to be filled out with the necessary delete calls... 00517 SPMKSimulation::~SPMKSimulation(){} 00518 00519 // Quick helper class to start and stop model manager (useful when 00520 // exceptions are thrown because destructor automatically stops the model 00521 // manager without requiring an explicit call to the stop method prior to 00522 // throwing the exception). 00523 class ModelManagerStarter { 00524 ModelManager& mgr ; 00525 public : 00526 ModelManagerStarter(ModelManager& m) : mgr(m) {mgr.start() ;} 00527 ~ModelManagerStarter() {mgr.stop() ;} 00528 } ; 00529 00530 } // end of local namespace encapsulating simulation encapsulation section 00531 00532 //------------------------------- MAIN ---------------------------------- 00533 00534 int main(int argc, const char* argv[]) 00535 { 00536 MYLOGVERB = LOG_INFO ; // suppress debug messages 00537 try 00538 { 00539 SPMKSimulation S("train-surfpmk Model") ; 00540 S.parse_command_line(argc, argv) ; 00541 S.run() ; 00542 } 00543 catch (std::exception& e) 00544 { 00545 LFATAL("%s", e.what()) ; 00546 return 1 ; 00547 } 00548 return 0 ; 00549 } 00550 00551 //------------------- SURF DESCRIPTORS ACCUMULATION --------------------- 00552 00553 // This section contains the code for accumulating the SURF descriptors 00554 // of the training images, i.e., phase one of training. 00555 namespace { 00556 00557 // Useful shortcut 00558 typedef GistEstimatorSurfPMK::SurfKeypoints SurfKeypoints ; 00559 00560 // Quick helper for storing the SURF descriptors of the training images 00561 // to a file. 00562 class surf_descriptors_accumulator { 00563 surf_descriptors_accumulator() ; // private to disallow instantiation 00564 ~surf_descriptors_accumulator() ; 00565 public : 00566 static std::string output_file ; 00567 static std::string image_name ; 00568 static int frame_number ; 00569 static std::string segment_number ; 00570 00571 static void write(const SurfKeypoints&) ; 00572 } ; 00573 00574 // This method implements the simulation's main loop for the "surf" 00575 // action. Prior to starting the main loop though, it configures the 00576 // SurfPMK gist estimator's training callback, which is triggered at each 00577 // step of the brain's evolution. The SurfPMK gist estimator passes the 00578 // SURF descriptors for the current input image to this callback, which 00579 // then proceeds to accumulate them in the file specified by the 00580 // --surf-descriptors option. 00581 void SPMKSimulation::accumulate_surf_descriptors() 00582 { 00583 00584 LFATAL("please fix me!"); 00585 /* 00586 ModelManagerStarter M(model_manager) ; 00587 00588 nub::soft_ref<GistEstimatorSurfPMK> ge = 00589 dynCastWeak<GistEstimatorSurfPMK>(brain->getGE()) ; 00590 if (ge.isInvalid()) 00591 throw std::runtime_error("can only use GistEstimatorSurfPMK") ; 00592 00593 typedef surf_descriptors_accumulator acc ; 00594 acc::output_file = surf_descriptors_file() ; 00595 acc::image_name = image_name() ; 00596 acc::segment_number = segment_number() ; 00597 ge->setTrainingHook(acc::write) ; 00598 00599 LINFO("MVN: accumulating SURF descriptors") ; 00600 nub::ref<SimEventQueue> event_queue = configurator->getQ() ; 00601 for(;;) 00602 { 00603 try 00604 { 00605 input_frame_series->evolve(*event_queue) ; 00606 acc::frame_number = input_frame_series->frame() ; 00607 brain->evolve(*event_queue) ; // triggers training hook 00608 if (event_queue->evolve() != SIM_CONTINUE) 00609 break ; 00610 } 00611 catch (lfatal_exception&) // if we seek beyond end of frame series 00612 { 00613 break ; // prevent LFATAL induced abortion 00614 } 00615 } 00616 LINFO("MVN: done accumulating SURF descriptors") ; 00617 */ 00618 } 00619 00620 // Static data members for storing the SURF descriptors file name and 00621 // other pertinent info persistently across multiple invocations of the 00622 // GistEstimatorSurfPMK's training hook. 00623 std::string surf_descriptors_accumulator::output_file ; 00624 std::string surf_descriptors_accumulator::image_name ; 00625 int surf_descriptors_accumulator::frame_number ; 00626 std::string surf_descriptors_accumulator::segment_number ; 00627 00628 // The following function is meant to be used as the GistEstimatorSurfPMK 00629 // training hook. It simply appends the SURF descriptors passed to it 00630 // (stored as an Image<GistEstimatorSurfPMK::SurfDescriptor>) to the 00631 // output file. The format of this file is as shown below: 00632 // 00633 // MPEG-file-name:frame-number segment-number row col SURF-descriptor 00634 // 00635 // The MPEG file name should be explicitly specified with the 00636 // --image-name option. The frame number is extracted automatically from 00637 // the input frame series. The segment number represents the ground 00638 // truth for the input image's category and should be specified 00639 // explicitly on the command line with the --segment-number option. The 00640 // row and col values are the SURF grid coordinates. And, finally, the 00641 // SURF descriptor itself consists of 128 numbers. 00642 // 00643 // DEVNOTE: We could open the output file once and use that object to 00644 // avoid reopening (by using a static ostream data member rather than a 00645 // static string). However, if the program were to somehow crash halfway 00646 // through, then the SURF descriptors output file would be in an 00647 // inconsistent state and rerunning the program can result in appending 00648 // data to a possibly inconsistent dataset, which would only make things 00649 // worse. 00650 // 00651 // Thus, we choose to open and close the output file each time the 00652 // GistEstimatorSurfPMK training hook is triggered. (Of course, if the 00653 // program cashes while this function is executing, then all bets are 00654 // off; the SURF descriptors file's inconsistency will be unavoidable in 00655 // this case.) 00656 void surf_descriptors_accumulator::write(const SurfKeypoints& G) 00657 { 00658 if (output_file.empty()) 00659 throw std::runtime_error("SURF descriptors accumulator output file " 00660 "not specified") ; 00661 00662 std::ofstream ofs(output_file.c_str(), std::ios::out | std::ios::app) ; 00663 for (unsigned int i = 0; i < G.size(); ++i) 00664 ofs << image_name << ':' << frame_number << ' ' 00665 << segment_number << ' ' << i << ' ' << G[i] << '\n' ; 00666 } 00667 00668 } // end of local namespace encapsulating SURF descriptors accumulation section 00669 00670 //-------------------- SURF VOCABULARY COMPUTATION ---------------------- 00671 00672 // This section contains the code for the hierarchical K-means clustering 00673 // of the SURF descriptors of the training images (i.e., training phase 00674 // two). 00675 namespace { 00676 00677 // Useful types 00678 typedef Image<float> Vocabulary ; 00679 00680 // Forward declarations 00681 int count_lines(const std::string& file_name) ; 00682 void load_surf_descriptors(const std::string& file_name, int num_lines) ; 00683 void kmeans(int K) ; 00684 void save_vocabulary(const std::string& file_name) ; 00685 00686 // The following method implements the "vocab" action of this program 00687 // for clustering the SURF descriptors of the training images to obtain 00688 // the 200 "prototypical" SURF descriptors that form the basis of the 00689 // gist vector computation in terms of these "words" or "vis-terms". 00690 void SPMKSimulation::compute_surf_vocabulary() 00691 { 00692 LINFO("MVN: counting lines in %s", surf_descriptors_file().c_str()) ; 00693 int num_rows = count_lines(surf_descriptors_file()) ; 00694 00695 LINFO("MVN: reading %d SURF descriptors from %s", 00696 num_rows, surf_descriptors_file().c_str()) ; 00697 //OpenCVMatrix surf_descriptors = 00698 load_surf_descriptors(surf_descriptors_file(), num_rows) ; 00699 00700 //const int K = GistEstimatorSurfPMK::NUM_CHANNELS ; 00701 //LINFO("MVN: doing K-means on SURF descriptors to get %d clusters", K) ; 00702 //OpenCVMatrix vocabulary = kmeans(K, surf_descriptors) ; 00703 00704 LINFO("MVN: K-means done; saving SURF vocabulary to %s", 00705 surf_vocabulary_file().c_str()) ; 00706 save_vocabulary(surf_vocabulary_file()) ; 00707 } 00708 00709 // The following function reads the SURF descriptors for the training 00710 // images into an OpenCV matrix. It must know how many lines the SURF 00711 // descriptors file has. This quantity is the number of rows in resulting 00712 // matrix. The number of columns is simply the size of each SURF 00713 // descriptor (usually: 128 values make up a SURF descriptor). 00714 void load_surf_descriptors(const std::string& file_name, int num_rows) 00715 { 00716 int num_cols = GistEstimatorSurfPMK::SURF_DESCRIPTOR_SIZE ; 00717 00718 double d ; std::string dummy ; // for ignoring first four fields 00719 std::ifstream ifs(file_name.c_str()) ; 00720 for (int i = 0; i < num_rows; ++i) 00721 { 00722 std::string str ; 00723 std::getline(ifs, str) ; 00724 if (! ifs || str.empty()) { 00725 if (i == num_rows - 1) // okay; read all rows 00726 break ; 00727 else { // descriptors file missing data or some other error 00728 throw std::runtime_error(file_name + 00729 ": missing SURF descriptors or other read error") ; 00730 } 00731 } 00732 std::istringstream line(str) ; 00733 line >> dummy >> dummy >> dummy >> dummy ; 00734 00735 for (int j = 0; j < num_cols; ++j) { 00736 if (! line) { 00737 throw std::runtime_error(file_name + 00738 ": missing SURF descriptor values on line " + to_string(i)) ; 00739 } 00740 line >> d ; 00741 } 00742 } 00743 00744 //return M ; 00745 //*/ 00746 } 00747 00748 // This function performs K-means clustering on the supplied data matrix 00749 // and returns the cluster centers. 00750 void kmeans(int K) 00751 { 00752 00753 LINFO("MVN: computing K-means cluster assignments with libPMK") ; 00754 00755 LINFO("MVN: cluster assignments done; computing centroids...") ; 00756 //return compute_centroids(K, data, cluster_assignments) ; 00757 00758 LFATAL("hum, I did nothing here, please fix my code!"); 00759 } 00760 00761 // Write the SURF vocabulary, row by row, to a plain text file. 00762 void save_vocabulary( 00763 const std::string& file_name) 00764 { 00765 std::ofstream ofs(file_name.c_str()) ; 00766 /* 00767 for (int i = 0; i < vocabulary.num_rows(); ++i) { 00768 for (int j = 0; j < vocabulary.num_cols(); ++j) 00769 ofs << vocabulary.get<float>(i, j) << ' ' ; 00770 ofs << '\n' ; 00771 } 00772 //*/ 00773 } 00774 00775 // Read the SURF vocabulary from a plain text file into an Image<T> 00776 Vocabulary load_vocabulary(const std::string& file_name) 00777 { 00778 const int M = count_lines(file_name) ; 00779 const int N = GistEstimatorSurfPMK::SURF_DESCRIPTOR_SIZE ; 00780 Vocabulary V(N, M, ZEROS) ; 00781 00782 float f ; 00783 std::ifstream ifs(file_name.c_str()) ; 00784 for (int j = 0; j < M; ++j) 00785 for (int i = 0; i < N; ++i) { 00786 if (! ifs) 00787 throw std::runtime_error(file_name + ": out of data?!?") ; 00788 ifs >> f ; 00789 V.setVal(i, j, f) ; 00790 } 00791 00792 return V ; 00793 } 00794 00795 } // end of local namespace encapsulating SURF vocabulary computation section 00796 00797 //------------------- TRAINING HISTOGRAM PROCESSING --------------------- 00798 00799 // Training is a two step process: first, we use hierarchical K-means to 00800 // cluster the training set's SURF descriptors to create the vocabulary. 00801 // Then, we collect the histograms counting the "prototypical" SURF 00802 // descriptors in the training images. The vocabulary and training set's 00803 // histogram "database" are both used for image classification. 00804 namespace { 00805 00806 // Some useful types for dealing with texton histograms 00807 typedef Image<double> Histogram ; 00808 00809 // Forward declarations 00810 void save_histogram(const Histogram& histogram, const std::string& file_name, 00811 const std::string& image_name, int frame_number, 00812 const std::string& segment_number) ; 00813 00814 // This method implements the "hist" action of this program. Like the 00815 // accumulate action, it implements a "main loop" for the simulation, 00816 // evolving different components with each iteration. But rather than 00817 // dipping into the GistEstimatorSurfPMK's processing pipeline, it 00818 // loads the SURF vocabulary and then uses GistEstimatorSurfPMK to 00819 // obtain the flattened out multi-level histogram for each of the 00820 // training images. These histograms are saved to the training 00821 // histograms database specified by the --histograms-file option. 00822 void SPMKSimulation::compute_training_histograms() 00823 { 00824 LFATAL("please fix me!!"); 00825 /* 00826 ModelManagerStarter M(model_manager) ; 00827 00828 nub::soft_ref<GistEstimatorSurfPMK> ge = 00829 dynCastWeak<GistEstimatorSurfPMK>(brain->getGE()) ; 00830 if (ge.isInvalid()) 00831 throw std::runtime_error("can only use GistEstimatorSurfPMK") ; 00832 00833 Vocabulary V = load_vocabulary(surf_vocabulary_file()) ; 00834 ge->setVocabulary(V) ; 00835 LINFO("MVN: loaded SURF vocabulary of %d vis-terms from %s", 00836 V.getHeight(), surf_vocabulary_file().c_str()) ; 00837 00838 nub::ref<SimEventQueue> event_queue = configurator->getQ() ; 00839 for(;;) 00840 { 00841 try 00842 { 00843 input_frame_series->evolve(*event_queue) ; 00844 brain->evolve(*event_queue) ; 00845 SeC<SimEventGistOutput> gist_out = 00846 event_queue->check<SimEventGistOutput>(brain.get(), 00847 SEQ_UNMARKED | SEQ_MARK, 00848 ge.get()) ; 00849 if (gist_out) // Spmk GE has a gist vector waiting to be picked up 00850 save_histogram(ge->getGist(), histograms_file(), 00851 image_name(), input_frame_series->frame(), 00852 segment_number()) ; 00853 if (event_queue->evolve() != SIM_CONTINUE) 00854 break ; 00855 } 00856 catch (lfatal_exception&) // if we seek beyond end of frame series 00857 { 00858 return ; // prevent LFATAL induced abortion 00859 } 00860 } 00861 */ 00862 } 00863 00864 // This function appends a training image's histogram to the training 00865 // histograms database file under the supplied "entry" name. As we did in 00866 // the SURF descriptors accumulation function, in order to minimize 00867 // possible inconsistencies in this database, we choose to open and close 00868 // the training histograms file with each invocation of this helper 00869 // rather than keep a persistent ostream object around that obviates the 00870 // need for repeated file open/close operations. 00871 void save_histogram(const Histogram& histogram, const std::string& file_name, 00872 const std::string& image_name, int frame_number, 00873 const std::string& segment_number) 00874 { 00875 std::ofstream ofs(file_name.c_str(), std::ios::out | std::ios::app) ; 00876 ofs << image_name << ':' << frame_number << ' ' 00877 << segment_number << ' ' ; 00878 for (int y = 0; y < histogram.getHeight(); ++y) // should be just one row 00879 for (int x = 0; x < histogram.getWidth(); ++x) // should be 4200 columns 00880 ofs << histogram.getVal(x, y) << ' ' ; 00881 ofs << '\n' ; 00882 } 00883 00884 } // end of local namespace encapsulating training histograms section 00885 00886 //--------------------- SVM CLASSIFIER GENERATION ----------------------- 00887 00888 namespace { 00889 00890 // Forward declarations 00891 void create_torch_dataset(const std::string&, const std::string&, 00892 const std::string&) ; 00893 Torch::SVMClassification* create_torch_classifier(const std::string&) ; 00894 std::string temp_file_name() ; 00895 00896 // The following method implements this program's "svm" action. 00897 void SPMKSimulation::generate_svm_classifier() 00898 { 00899 create_torch_dataset(histograms_file(), segment_number(), svm_temp_file()) ; 00900 Torch::SVMClassification* svm = create_torch_classifier(svm_temp_file()) ; 00901 svm->save(svm_classifier_file().c_str()) ; 00902 delete svm ; 00903 unlink(svm_temp_file().c_str()) ; 00904 } 00905 00906 // Quick helper for reading and writing gist vectors from/to a file 00907 struct GistVector { 00908 std::vector<double> values ; 00909 GistVector() ; 00910 } ; 00911 00912 GistVector::GistVector() 00913 : values(GistEstimatorSurfPMK::GIST_VECTOR_SIZE) 00914 {} 00915 00916 std::istream& operator>>(std::istream& is, GistVector& g) 00917 { 00918 for (int i = 0; i < GistEstimatorSurfPMK::GIST_VECTOR_SIZE; ++i) 00919 is >> g.values[i] ; 00920 return is ; 00921 } 00922 00923 std::ostream& operator<<(std::ostream& os, const GistVector& g) 00924 { 00925 for (int i = 0; i < GistEstimatorSurfPMK::GIST_VECTOR_SIZE; ++i) 00926 os << g.values[i] << ' ' ; 00927 return os ; 00928 } 00929 00930 // The torch library needs its datasets in a particular format. 00931 // Unfortunately, this program works with some other format. The 00932 // following function reads the histograms file saved by an earlier run 00933 // of this program and creates a corresponding torch dataset for 00934 // subsequent training of an SVM classifier for the specified target 00935 // segment. 00936 void create_torch_dataset(const std::string& hist_file, 00937 const std::string& target, 00938 const std::string& torch_dataset) 00939 { 00940 const int n = count_lines(hist_file) ; 00941 00942 std::ifstream in(hist_file.c_str()) ; 00943 std::ofstream out(torch_dataset.c_str()) ; 00944 00945 std::string dummy, segment, str ; 00946 GistVector gist_vector ; 00947 out << n << ' ' << (GistEstimatorSurfPMK::GIST_VECTOR_SIZE + 1) << '\n' ; 00948 for (int i = 0; i < n; ++i) 00949 { 00950 std::getline(in, str) ; 00951 if (! in || str.empty()) { 00952 if (i == n - 1) // okay; all training histograms read successfully 00953 break ; 00954 else { 00955 out.close() ; 00956 unlink(torch_dataset.c_str()) ; 00957 throw std::runtime_error(hist_file + 00958 ": missing data or other read error") ; 00959 } 00960 } 00961 std::istringstream line(str) ; 00962 line >> dummy >> segment >> gist_vector ; 00963 out << gist_vector << ' ' << ((segment == target) ? +1 : -1) << '\n' ; 00964 } 00965 } 00966 00967 // The histogram intersection kernel for matching gist vectors of 00968 // different images. 00969 class HistIntKernel : public Torch::Kernel { 00970 //Torch::real eval(Torch::Sequence*, Torch::Sequence*) ; 00971 real eval(Torch::Sequence*, Torch::Sequence*) ; 00972 } ; 00973 00974 //Torch::real HistIntKernel::eval(Torch::Sequence* a, Torch::Sequence* b) 00975 real HistIntKernel::eval(Torch::Sequence* a, Torch::Sequence* b) 00976 { 00977 //Torch::real sum = 0 ; 00978 real sum = 0 ; 00979 for (int i = 0; i < a->frame_size; ++i) 00980 //sum += std::min(a->frames[0][i], b->frames[0][i]) ; 00981 sum += min(a->frames[0][i], b->frames[0][i]) ; 00982 return sum ; 00983 } 00984 00985 // The following function creates an SVM classifier using the histogram 00986 // intersection kernel defined above. 00987 Torch::SVMClassification* create_torch_classifier(const std::string& dataset) 00988 { 00989 HistIntKernel kernel ; 00990 Torch::SVMClassification* svm = new Torch::SVMClassification(& kernel) ; 00991 Torch::QCTrainer trainer(svm) ; 00992 Torch::MatDataSet data(dataset.c_str(), 00993 GistEstimatorSurfPMK::GIST_VECTOR_SIZE, 1) ; 00994 trainer.train(& data, 0) ; 00995 return svm ; 00996 } 00997 00998 } // end of local namespace encapsulating SVM classifier generation section 00999 01000 //----------------------- IMAGE CLASSIFICATION -------------------------- 01001 01002 namespace { 01003 01004 // Useful typedefs 01005 typedef std::vector<Torch::SVMClassification*> Classifiers ; 01006 01007 // Forward declarations 01008 Classifiers load_classifiers(std::string, HistIntKernel*) ; 01009 void classify_image(const Histogram&, const Classifiers&, 01010 const std::string&, int, const std::string&, 01011 const std::string&) ; 01012 void nuke_classifiers(Classifiers&) ; 01013 01014 // The following method implements this program's "classify" action. It 01015 // reads the SURF descriptors vocabulary and computes gist vectors for 01016 // input images using the SurfPMK gist estimator. Then, it uses the SVM 01017 // classifiers generated by the "svm" action to decide which category the 01018 // input image belongs to. 01019 void SPMKSimulation::classify_input_images() 01020 { 01021 LFATAL("please fixme too!"); 01022 /* 01023 ModelManagerStarter M(model_manager) ; 01024 01025 nub::soft_ref<GistEstimatorSurfPMK> ge = 01026 dynCastWeak<GistEstimatorSurfPMK>(brain->getGE()) ; 01027 if (ge.isInvalid()) 01028 throw std::runtime_error("can only use GistEstimatorSurfPMK") ; 01029 01030 Vocabulary V = load_vocabulary(surf_vocabulary_file()) ; 01031 ge->setVocabulary(V) ; 01032 LINFO("MVN: loaded SURF vocabulary of %d vis-terms from %s", 01033 V.getHeight(), surf_vocabulary_file().c_str()) ; 01034 01035 HistIntKernel kernel ; 01036 Classifiers svm_classifiers = 01037 load_classifiers(svm_classifier_file(), & kernel) ; 01038 01039 nub::ref<SimEventQueue> event_queue = configurator->getQ() ; 01040 for(;;) 01041 { 01042 try 01043 { 01044 input_frame_series->evolve(*event_queue) ; 01045 brain->evolve(*event_queue) ; 01046 SeC<SimEventGistOutput> gist_out = 01047 event_queue->check<SimEventGistOutput>(brain.get(), 01048 SEQ_UNMARKED | SEQ_MARK, 01049 ge.get()) ; 01050 if (gist_out) // SurfPMK GE has a gist vector waiting to be picked up 01051 classify_image(ge->getGist(), svm_classifiers, 01052 image_name(), input_frame_series->frame(), 01053 segment_number(), results_file()) ; 01054 if (event_queue->evolve() != SIM_CONTINUE) 01055 break ; 01056 } 01057 catch (lfatal_exception&) // if we seek beyond end of frame series 01058 { 01059 nuke_classifiers(svm_classifiers) ; 01060 return ; // prevent LFATAL induced abortion 01061 } 01062 } 01063 01064 nuke_classifiers(svm_classifiers) ; 01065 */ 01066 } 01067 01068 // Given an input image's gist vector and the SVM classifiers for all the 01069 // categories, this function checks which categories the input image 01070 // belongs to and writes the results to the classification results file. 01071 void classify_image(const Histogram& gist_vector, 01072 const Classifiers& classifiers, 01073 const std::string& image_name, int frame_number, 01074 const std::string& ground_truth, 01075 const std::string& results_file) 01076 { 01077 std::ofstream ofs(results_file.c_str(), std::ios::out | std::ios::app) ; 01078 ofs << image_name << ':' << frame_number << ' ' << ground_truth << ' ' ; 01079 01080 Torch::Sequence gv(1, GistEstimatorSurfPMK::GIST_VECTOR_SIZE) ; 01081 std::copy(gist_vector.begin(), gist_vector.end(), gv.frames[0]) ; 01082 01083 int n = 0 ; // num categories into which input image can be classified 01084 const int N = classifiers.size() ; 01085 for (int i = 0; i < N; ++i) { 01086 classifiers[i]->forward(& gv) ; 01087 if (classifiers[i]->outputs->frames[0][0] > 0) { 01088 ofs << (i+1) << ' ' ; 01089 ++n ; 01090 } 01091 } 01092 01093 if (! n) // input image could not be classified into any category 01094 ofs << '0' ; 01095 ofs << '\n' ; 01096 } 01097 01098 // This function loads all the SVM classifiers beginning with the 01099 // specified "root" name. Here's how this is supposed to work: 01100 // 01101 // Let's say we 9 categories. Earlier runs of this program ought to have 01102 // created 9 SVM classifiers. Usually, these would be named 01103 // "XXX_svm_classifier.1", "XXX_svm_classifier.2", "XXX_svm_classifier.3" 01104 // and so on. This function will read each of these files back into 01105 // memory using the torch library (which is what created those files in 01106 // the first place). 01107 // 01108 // The kernel for each of these SVM classifiers is the histogram 01109 // intersection kernel as described in the Grauman ICCV 2006 paper. 01110 Classifiers 01111 load_classifiers(std::string classifiers_root_name, HistIntKernel* kernel) 01112 { 01113 classifiers_root_name += ".*" ; 01114 glob_t buf ; 01115 if (glob(classifiers_root_name.c_str(), 0, 0, & buf) != 0) 01116 throw std::runtime_error("couldn't find/load the SVM classifiers") ; 01117 01118 const int N = buf.gl_pathc ; 01119 Classifiers classifiers(N) ; 01120 for (int i = 0; i < N; ++i) { 01121 classifiers[i] = new Torch::SVMClassification(kernel) ; 01122 classifiers[i]->load(buf.gl_pathv[i]) ; 01123 } 01124 01125 globfree(& buf) ; 01126 return classifiers ; 01127 } 01128 01129 // Delete all SVM classifier objects created in previous function 01130 void nuke_classifiers(Classifiers& C) 01131 { 01132 const int N = C.size() ; 01133 for (int i = 0; i < N; ++i) 01134 delete C[i] ; 01135 } 01136 01137 } // end of local namespace encapsulating image classification section 01138 01139 //-------------------------- UTILITY ROUTINES --------------------------- 01140 01141 namespace { 01142 01143 // Count the number of lines in a file (wc -l) 01144 int count_lines(const std::string& file_name) 01145 { 01146 int n = -1 ; // because EOF is read after final \n (1 extra iter. of loop) 01147 std::ifstream ifs(file_name.c_str()) ; 01148 01149 std::string dummy ; 01150 while (ifs) { 01151 getline(ifs, dummy) ; 01152 ++n ; 01153 } 01154 return n ; 01155 } 01156 01157 } // end of local namespace encapsulating utility routines section 01158 01159 //----------------------------------------------------------------------- 01160 01161 #endif // #if !defined(HAVE_OPENCV || INVT_HAVE_LIBPMK || INVT_HAVE_LIBTORCH) 01162 01163 /* So things look consistent in everyone's emacs... */ 01164 /* Local Variables: */ 01165 /* indent-tabs-mode: nil */ 01166 /* End: */