00001 /*!@file SIFT/VisualObjectDB.C Visual object database */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00005 // University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/SIFT/VisualObjectDB.C $ 00035 // $Id: VisualObjectDB.C 14077 2010-09-30 06:33:02Z itti $ 00036 // 00037 00038 #include "SIFT/VisualObjectDB.H" 00039 #include "SIFT/Keypoint.H" 00040 #include "SIFT/KDTree.H" 00041 #include "Image/ColorOps.H" 00042 #include "Image/Image.H" 00043 #include "Image/Pixels.H" 00044 #include "Util/Timer.H" 00045 #include "Util/WorkThreadServer.H" 00046 00047 #include <fstream> 00048 00049 00050 // ####################################################################### 00051 VisualObjectDB::VisualObjectDB() : 00052 itsName(), itsObjects(), itsKDTree(), itsKDindices() 00053 { } 00054 00055 // ####################################################################### 00056 VisualObjectDB::~VisualObjectDB() 00057 { } 00058 00059 // ###################################################################### 00060 bool VisualObjectDB::loadFrom(const std::string& fname, bool preloadImage) 00061 { 00062 const char *fn = fname.c_str(); 00063 LINFO("Loading Visual Object database: '%s'...", fn); 00064 00065 std::ifstream inf(fn); 00066 if (inf.is_open() == false) { LERROR("Cannot open '%s' -- USING EMPTY", fn); return false; } 00067 00068 inf>>(*this); 00069 00070 inf.close(); 00071 LINFO("Done. Loaded %u VisualObjects.", numObjects()); 00072 return true; 00073 } 00074 00075 // ###################################################################### 00076 bool VisualObjectDB::saveTo(const std::string& fname) 00077 { 00078 const char *fn = fname.c_str(); 00079 LINFO("Saving database '%s'...", fn); 00080 00081 std::ofstream outf(fn); 00082 if (outf.is_open() == false) { LERROR("Cannot open %s for writing -- NOT SAVED", fn); return false; } 00083 00084 outf<<(*this); 00085 00086 outf.close(); 00087 LINFO("Done. Saved %u VisualObjects.", numObjects()); 00088 return true; 00089 } 00090 00091 // ####################################################################### 00092 bool VisualObjectDB::addObject(const rutz::shared_ptr<VisualObject>& obj, bool uniqueName) 00093 { 00094 if (uniqueName) 00095 { 00096 std::string objectName = obj->getName(); 00097 00098 std::vector< rutz::shared_ptr<VisualObject> >::const_iterator 00099 vo = itsObjects.begin(), stop = itsObjects.end(); 00100 00101 while(vo != stop) 00102 { 00103 if ((*vo)->getName().compare(objectName) == 0) return false; 00104 ++ vo; 00105 } 00106 } 00107 00108 // not found, add it to the end of our list: 00109 itsObjects.push_back(obj); 00110 00111 // any KDTree we may have had is now invalid: 00112 itsKDTree.reset(); 00113 itsKDindices.clear(); 00114 00115 return true; 00116 } 00117 00118 // ###################################################################### 00119 // functor to assist with VisualObjectMatch sorting: 00120 class moreVOM 00121 { 00122 public: 00123 moreVOM(const float kcoeff, const float acoeff) : 00124 itsKcoeff(kcoeff), itsAcoeff(acoeff) 00125 { } 00126 00127 bool operator()(const rutz::shared_ptr<VisualObjectMatch>& x, 00128 const rutz::shared_ptr<VisualObjectMatch>& y) 00129 { return ( x->getScore(itsKcoeff, itsAcoeff) > 00130 y->getScore(itsKcoeff, itsAcoeff) ); } 00131 00132 private: 00133 float itsKcoeff, itsAcoeff; 00134 }; 00135 00136 // ###################################################################### 00137 void VisualObjectDB::buildKDTree() 00138 { 00139 // if we have one, no-op: 00140 if (itsKDTree.is_valid()) return; 00141 00142 LINFO("Building KDTree for %"ZU" objects...", itsObjects.size()); 00143 00144 // go over all our objects and build a giant vector of keypoints, 00145 // remembering the mapping between indices: 00146 itsKDindices.clear(); uint objidx = 0U; 00147 std::vector< rutz::shared_ptr<Keypoint> > allkps; 00148 00149 std::vector< rutz::shared_ptr<VisualObject> >::const_iterator 00150 obj = itsObjects.begin(), stop = itsObjects.end(); 00151 00152 while (obj != stop) 00153 { 00154 const std::vector< rutz::shared_ptr<Keypoint> >& kps = (*obj)->getKeypoints(); 00155 uint kidx = 0U; 00156 std::vector< rutz::shared_ptr<Keypoint> >::const_iterator 00157 kp = kps.begin(), stopk = kps.end(); 00158 00159 while(kp != stopk) 00160 { 00161 // get the keypoint: 00162 allkps.push_back(*kp); 00163 00164 // remember its object number and index within that object: 00165 itsKDindices.push_back(std::pair<uint, uint>(objidx, kidx)); 00166 00167 ++ kp; ++kidx; 00168 } 00169 ++ obj; ++objidx; 00170 } 00171 00172 // all right, 'allkps' now has all our keypoints, and itsKDindices 00173 // their object number and indices within objects. Let's build a 00174 // KDTree from allkps: 00175 itsKDTree.reset(new KDTree(allkps)); 00176 00177 LINFO("Done. KDTree initialized with %"ZU" keypoints.", allkps.size()); 00178 } 00179 00180 // ###################################################################### 00181 uint VisualObjectDB:: 00182 getObjectMatches(const rutz::shared_ptr<VisualObject> obj, 00183 std::vector< rutz::shared_ptr<VisualObjectMatch> >& matches, 00184 const VisualObjectMatchAlgo algo, const uint maxn, 00185 const float kcoeff, const float acoeff, 00186 const float minscore, const uint mink, 00187 const uint kthresh, const bool sortbypf) 00188 { 00189 LDEBUG("Matching '%s' against database...", obj->getName().c_str()); 00190 Timer tim(1000000); 00191 matches.clear(); uint nm = 0U; 00192 00193 switch(algo) 00194 { 00195 // #################################################################### 00196 case VOMA_SIMPLE: // #################### simple match 00197 { 00198 const uint nobj = itsObjects.size(); 00199 00200 std::vector<uint> sidx; 00201 if (sortbypf) computeSortedIndices(sidx, obj); 00202 00203 for (uint i = 0; i < nobj; i ++) 00204 { 00205 // get the index: 00206 const uint index = sortbypf ? sidx[i] : i; 00207 00208 // attempt a match: 00209 rutz::shared_ptr<VisualObjectMatch> 00210 match(new VisualObjectMatch(obj, itsObjects[index], 00211 algo, kthresh)); 00212 00213 // apply some standard pruning: 00214 match->prune(std::max(25U, mink * 5U), mink); 00215 00216 // if the match is good enough, store it: 00217 if (match->size() >= mink && 00218 match->getScore(kcoeff, acoeff) >= minscore && 00219 match->checkSIFTaffine()) 00220 { 00221 matches.push_back(match); ++nm; 00222 00223 // have we found enough matches? 00224 if (nm >= maxn) break; 00225 } 00226 00227 // otherwise, match runs out of scope here and its memory is 00228 // deallocated thanks to rutz::shared_ptr 00229 } 00230 } 00231 break; 00232 00233 // #################################################################### 00234 case VOMA_KDTREE: // #################### KDTree match 00235 case VOMA_KDTREEBBF: 00236 { 00237 const uint nobj = itsObjects.size(); 00238 const uint kthresh2 = kthresh * kthresh; 00239 00240 // build a KDTree if we don't already have one: 00241 buildKDTree(); 00242 00243 // get matches between our KDTree and the test object: 00244 const uint tstnkp = obj->numKeypoints(); 00245 if (tstnkp == 0U) break; // nothing to do 00246 const int maxdsq = obj->getKeypoint(0)->maxDistSquared(); 00247 00248 // prepare a list to get our keypoint matches: first index is 00249 // object number, second keypoint match number: 00250 std::vector< std::vector<KeypointMatch> > kpm(nobj); 00251 00252 // the code here is similar to VisualObjectMatch::matchKDTree() 00253 // loop over all of our test object's keypoints: 00254 for (uint i = 0; i < tstnkp; i++) 00255 { 00256 int distsq1 = maxdsq, distsq2 = maxdsq; 00257 rutz::shared_ptr<Keypoint> tstkey = obj->getKeypoint(i); 00258 00259 // find nearest neighbor in our KDTree: 00260 uint matchIndex = (algo == VOMA_KDTREEBBF) ? 00261 itsKDTree->nearestNeighborBBF(tstkey, 40, distsq1, distsq2) : 00262 itsKDTree->nearestNeighbor(tstkey, distsq1, distsq2); 00263 00264 // Check that best distance less than 0.6 of second best distance: 00265 if (100U * distsq1 < kthresh2 * distsq2) 00266 { 00267 const uint refobjnum = itsKDindices[matchIndex].first; 00268 const uint refkpnum = itsKDindices[matchIndex].second; 00269 00270 // note how we swap the ref/test here. This is for 00271 // compatibility with the Simple matching above. In 00272 // the list of object matches we will eventually 00273 // return, 'obj' is our ref object while an object in 00274 // the database is considered test object: 00275 KeypointMatch m; 00276 m.refkp = tstkey; 00277 m.tstkp = itsObjects[refobjnum]->getKeypoint(refkpnum); 00278 m.distSq = distsq1; 00279 m.distSq2 = distsq2; 00280 kpm[refobjnum].push_back(m); 00281 } 00282 } 00283 00284 // let's see which objects accumulated enough keypoint matches: 00285 for (uint i = 0U; i < nobj; i ++) 00286 { 00287 // do we have nough keypoint matches for that object? 00288 if (kpm[i].size() >= mink) 00289 { 00290 // ok, let's make a pre-build VisualObjectMatch out of it: 00291 rutz::shared_ptr<VisualObjectMatch> 00292 match(new VisualObjectMatch(obj, itsObjects[i], kpm[i])); 00293 00294 // apply some standard pruning: 00295 match->prune(std::max(25U, mink * 5U), mink); 00296 00297 // if the match is good enough, store it: 00298 if (match->size() >= mink && 00299 match->getScore(kcoeff, acoeff) >= minscore && 00300 match->checkSIFTaffine()) 00301 { 00302 matches.push_back(match); ++nm; 00303 00304 // have we found enough matches? 00305 if (nm >= maxn) break; 00306 } 00307 } 00308 } 00309 } 00310 break; 00311 00312 // #################################################################### 00313 default: 00314 LFATAL("Unknown matching algo %d", int(algo)); 00315 } 00316 00317 // finally, sort our matches: 00318 std::sort(matches.begin(), matches.end(), moreVOM(kcoeff, acoeff)); 00319 00320 uint64 t = tim.get(); 00321 LDEBUG("Found %u database object matches for '%s' in %.3fms", 00322 nm, obj->getName().c_str(), float(t) * 0.001F); 00323 00324 return nm; 00325 } 00326 00327 // ###################################################################### 00328 class MatchJob : public JobServer::Job { 00329 public: 00330 MatchJob(const rutz::shared_ptr<VisualObject> obj_, const rutz::shared_ptr<VisualObject> obj2_, 00331 pthread_mutex_t *mut_, std::vector< rutz::shared_ptr<VisualObjectMatch> >& matches_, 00332 const float kcoeff_, const float acoeff_, const float minscore_, const uint mink_, const uint kthresh_) : 00333 JobServer::Job(), obj(obj_), obj2(obj2_), mut(mut_), matches(matches_), kcoeff(kcoeff_), 00334 acoeff(acoeff_), minscore(minscore_), mink(mink_), kthresh(kthresh_) { } 00335 00336 virtual ~MatchJob() { } 00337 00338 virtual void run() { 00339 // do the matching: 00340 rutz::shared_ptr<VisualObjectMatch> match(new VisualObjectMatch(obj, obj2, VOMA_SIMPLE, kthresh)); 00341 00342 // apply some standard pruning: 00343 match->prune(std::max(25U, mink * 5U), mink); 00344 00345 // if the match is good enough, store it: 00346 if (match->size() >= mink && match->getScore(kcoeff, acoeff) >= minscore && match->checkSIFTaffine()) 00347 { 00348 pthread_mutex_lock(mut); 00349 matches.push_back(match); 00350 pthread_mutex_unlock(mut); 00351 } 00352 } 00353 00354 virtual const char* jobType() const { return "MatchJob"; } 00355 00356 private: 00357 const rutz::shared_ptr<VisualObject> obj; 00358 const rutz::shared_ptr<VisualObject> obj2; 00359 pthread_mutex_t *mut; 00360 std::vector< rutz::shared_ptr<VisualObjectMatch> >& matches; 00361 const float kcoeff; 00362 const float acoeff; 00363 const float minscore; 00364 const uint mink; 00365 const uint kthresh; 00366 }; 00367 00368 // ###################################################################### 00369 uint VisualObjectDB:: 00370 getObjectMatchesParallel(const rutz::shared_ptr<VisualObject> obj, 00371 std::vector< rutz::shared_ptr<VisualObjectMatch> >& matches, 00372 const uint numthreads, const float kcoeff, const float acoeff, 00373 const float minscore, const uint mink, const uint kthresh, const bool sortbypf) 00374 { 00375 LDEBUG("Parallel matching '%s' against database...", obj->getName().c_str()); 00376 Timer tim(1000000); matches.clear(); 00377 00378 pthread_mutex_t mut; 00379 if (pthread_mutex_init(&mut, NULL)) PLFATAL("Error creating mutex"); 00380 WorkThreadServer wts("Match Server", numthreads); 00381 00382 const uint nobj = itsObjects.size(); 00383 00384 std::vector<uint> sidx; 00385 if (sortbypf) computeSortedIndices(sidx, obj); 00386 00387 for (uint i = 0; i < nobj; i ++) 00388 { 00389 // get the index: 00390 const uint index = sortbypf ? sidx[i] : i; 00391 00392 // Enqueue a match job: 00393 wts.enqueueJob(rutz::make_shared(new MatchJob(obj, itsObjects[index], &mut, matches, kcoeff, acoeff, 00394 minscore, mink, kthresh))); 00395 } 00396 00397 // wait until all completed: 00398 wts.flushQueue(); 00399 if (pthread_mutex_destroy(&mut)) PLERROR("Error in pthread_mutex_destroy"); 00400 00401 // finally, sort our matches: 00402 std::sort(matches.begin(), matches.end(), moreVOM(kcoeff, acoeff)); 00403 00404 uint64 t = tim.get(); 00405 LDEBUG("Found %"ZU" database object matches for '%s' in %.3fms", matches.size(), 00406 obj->getName().c_str(), float(t) * 0.001F); 00407 00408 return matches.size(); 00409 } 00410 00411 // ####################################################################### 00412 void VisualObjectDB::computeSortedIndices(std::vector<uint>& indices, 00413 const rutz::shared_ptr<VisualObject>& 00414 obj) const 00415 { 00416 // delete any old indices: 00417 indices.clear(); 00418 00419 // get a list of pairs <featuredistsq, index>: 00420 std::vector< std::pair<double, uint> > lst; 00421 00422 for(uint i = 0; i < itsObjects.size(); i++) 00423 lst.push_back(std::pair<double, uint>(itsObjects[i]-> 00424 getFeatureDistSq(obj), i)); 00425 00426 // sort the list. NOTE: operator< on std::pair is lexicographic, 00427 // i.e., here, first by our first element (the distance), then by 00428 // our second element (the index, in the unlikely event we have two 00429 // equal distances): 00430 std::sort(lst.begin(), lst.end()); 00431 00432 // pull all the indices out: 00433 std::vector< std::pair<double, uint> >::const_iterator 00434 ptr = lst.begin(), stop = lst.end(); 00435 while (ptr != stop) 00436 { indices.push_back(ptr->second); ++ptr; } 00437 } 00438 00439 // ####################################################################### 00440 std::istream& operator>>(std::istream& is, VisualObjectDB& vdb) 00441 { 00442 vdb.createVisualObjectDB(is, vdb); 00443 return is; 00444 } 00445 00446 00447 // ############### 00448 void VisualObjectDB:: 00449 createVisualObjectDB(std::istream& is, VisualObjectDB& vdb, bool preloadImage) 00450 { 00451 std::string name; 00452 std::getline(is, name); 00453 00454 vdb.setName(name); 00455 00456 uint siz; is>>siz; 00457 00458 vdb.itsObjects.clear(); vdb.itsObjects.resize(siz); 00459 00460 std::vector< rutz::shared_ptr<VisualObject> >::iterator 00461 vo = vdb.itsObjects.begin(), stop = vdb.itsObjects.end(); 00462 00463 while (vo != stop) 00464 { 00465 rutz::shared_ptr<VisualObject> newvo(new VisualObject()); 00466 is>>(*newvo); 00467 *vo++ = newvo; 00468 } 00469 } 00470 00471 // ####################################################################### 00472 std::ostream& operator<<(std::ostream& os, const VisualObjectDB& vdb) 00473 { 00474 os<<vdb.getName()<<std::endl; 00475 os<<vdb.itsObjects.size()<<std::endl; 00476 00477 std::vector< rutz::shared_ptr<VisualObject> >::const_iterator 00478 vo = vdb.itsObjects.begin(), stop = vdb.itsObjects.end(); 00479 00480 while (vo != stop) { os<<(**vo); ++ vo; } 00481 00482 return os; 00483 } 00484 00485 // ###################################################################### 00486 /* So things look consistent in everyone's emacs... */ 00487 /* Local Variables: */ 00488 /* indent-tabs-mode: nil */ 00489 /* End: */