VisualObjectMatch.C

Go to the documentation of this file.
00001 /*!@file SIFT/VisualObjectMatch.C Visual Object matches */
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: Philip Williams <plw@usc.edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/SIFT/VisualObjectMatch.C $
00035 // $Id: VisualObjectMatch.C 13084 2010-03-30 02:42:00Z kai $
00036 //
00037 
00038 #include "SIFT/VisualObjectMatch.H"
00039 #include "SIFT/VisualObject.H"
00040 #include "SIFT/KDTree.H"
00041 #include "SIFT/SIFThough.H"
00042 #include "Image/MatrixOps.H"
00043 #include "Image/CutPaste.H"
00044 #include "Image/DrawOps.H"
00045 
00046 // ######################################################################
00047 VisualObjectMatch::VisualObjectMatch(const rutz::shared_ptr<VisualObject>& voref,
00048                                      const rutz::shared_ptr<VisualObject>& votest,
00049                                      const VisualObjectMatchAlgo algo,
00050                                      const uint thresh) :
00051   itsVoRef(voref), itsVoTest(votest), itsMatches(), itsKDTree(),
00052   itsHasAff(false), itsAff(),
00053   itsHasKpAvgDist(false), itsHasAfAvgDist(false)
00054 {
00055   uint nm = 0U;
00056 
00057   switch (algo)
00058     {
00059     case VOMA_SIMPLE: nm = matchSimple(thresh); break;
00060     case VOMA_KDTREE: nm = matchKDTree(thresh, 0); break;
00061     case VOMA_KDTREEBBF: nm = matchKDTree(thresh, 40); break;
00062     }
00063   LDEBUG("Got %u KP matches (th=%d) btw %s and %s",
00064          nm, thresh, voref->getName().c_str(), votest->getName().c_str());
00065 }
00066 
00067 // ######################################################################
00068 VisualObjectMatch::VisualObjectMatch(const rutz::shared_ptr<KDTree>& kdref,
00069                                      const rutz::shared_ptr<VisualObject>& votest,
00070                                      const VisualObjectMatchAlgo algo,
00071                                      const uint thresh) :
00072   itsVoRef(new VisualObject("KDref")), itsVoTest(votest), itsMatches(),
00073   itsKDTree(kdref), itsHasAff(false), itsAff(),
00074   itsHasKpAvgDist(false), itsHasAfAvgDist(false)
00075 {
00076   uint nm = 0U;
00077 
00078   switch (algo)
00079     {
00080     case VOMA_SIMPLE:
00081       LFATAL("Can't use Simple match when constructing from a KDTree"); break;
00082     case VOMA_KDTREE: nm = matchKDTree(thresh, 0); break;
00083     case VOMA_KDTREEBBF: nm = matchKDTree(thresh, 40); break;
00084     }
00085   LDEBUG("Got %u KP matches (th=%d) btw %s and %s",
00086          nm, thresh, itsVoRef->getName().c_str(), votest->getName().c_str());
00087 }
00088 
00089 // ######################################################################
00090 VisualObjectMatch::VisualObjectMatch(const rutz::shared_ptr<VisualObject>& voref,
00091                                      const rutz::shared_ptr<VisualObject>& votest,
00092                                      const std::vector<KeypointMatch>& kpm) :
00093   itsVoRef(voref), itsVoTest(votest), itsMatches(kpm),
00094   itsKDTree(), itsHasAff(false), itsAff(),
00095   itsHasKpAvgDist(false), itsHasAfAvgDist(false)
00096 {
00097   LDEBUG("Got %"ZU" KP matches btw %s and %s",
00098          itsMatches.size(), itsVoRef->getName().c_str(),
00099          itsVoTest->getName().c_str());
00100 }
00101 
00102 // ######################################################################
00103 VisualObjectMatch::~VisualObjectMatch()
00104 { }
00105 
00106 // ######################################################################
00107 uint VisualObjectMatch::prune(const uint maxn, const uint minn)
00108 {
00109   // do not go below a min number of matches:
00110   if (itsMatches.size() <= minn) return 0U;
00111   uint ndel = 0U;
00112 
00113   // if we have lots of matches, start by cutting some of them easily
00114   // by enforcing a better best-to-second-best keypoint match
00115   // ratio. But don't go too far in this direction, as sometimes
00116   // poorer matches may be the correct ones:
00117   const uint targetn1 = maxn * 2U; uint distthresh = 9U;
00118   while (itsMatches.size() > targetn1 && distthresh > 5)
00119     ndel += pruneByDist(distthresh--, targetn1);
00120   LDEBUG("After pruning by distance: total %d outliers pruned.", ndel);
00121 
00122   // let's now do a Hough-based pruning:
00123   const uint targetn2 = (maxn + minn) / 2; uint iter = 2U;
00124   while (itsMatches.size() > targetn2 && iter > 0)
00125     { ndel += pruneByHough(0.6F, targetn2); --iter; }
00126   LDEBUG("After pruning by Hough: total %d outliers pruned.", ndel);
00127 
00128   // finally a few passes of pruning by inconsistency with the full
00129   // affine transform. Hopefully by now gross outliers have been
00130   // eliminated (especially by the Hough transform) and this will work:
00131   const uint targetn3 = minn; float dist = 5.0F; iter = 3U;
00132   while (itsMatches.size() > targetn3 && iter > 0)
00133     { ndel += pruneByAff(dist, targetn3); dist *= 0.75F; --iter; }
00134   LDEBUG("After pruning by affine: total %d outliers pruned.", ndel);
00135 
00136   return ndel;
00137 }
00138 
00139 // ######################################################################
00140 uint VisualObjectMatch::pruneByDist(const uint thresh, const uint minn)
00141 {
00142   // do not go below a min number of matches:
00143   if (itsMatches.size() <= minn) return 0U;
00144   std::vector<KeypointMatch>::iterator itr = itsMatches.begin();
00145   const uint t2 = thresh * thresh; uint ndel = 0U;
00146 
00147   while (itr < itsMatches.end())
00148     {
00149       if (100U * itr->distSq >= t2 * itr->distSq2)
00150         {
00151           itr = itsMatches.erase(itr); ++ ndel;
00152           if (itsMatches.size() <= minn) return ndel;
00153         }
00154       else ++ itr;
00155       // note: the behavior of vector::erase() guarantees this code works...
00156     }
00157  return ndel;
00158 }
00159 
00160 // ######################################################################
00161 uint VisualObjectMatch::pruneByHough(const float rangefac, const uint minn)
00162 {
00163   // do not go below a min number of matches:
00164   if (itsMatches.size() <= minn) return 0U;
00165   std::vector<KeypointMatch>::iterator
00166     itr = itsMatches.begin(), stop = itsMatches.end();
00167 
00168   // do a first pass over the matches to determine the range:
00169   float dxmi = 1.0e30F, dxma = -1.0e30F; // difference in X position
00170   float dymi = 1.0e30F, dyma = -1.0e30F; // difference in Y position
00171   float domi = 1.0e30F, doma = -1.0e30F; // difference in orientation
00172   float dsmi = 1.0e30F, dsma = -1.0e30F; // difference in scale
00173   while (itr < stop)
00174     {
00175       // get the keypoint differences: dx is the difference between X
00176       // of the test keypoint and X of the ref keypoint, etc:
00177       float dx, dy, doo, ds;
00178       getKdiff(*itr, dx, dy, doo, ds);
00179 
00180       if (dx < dxmi) dxmi = dx; else if (dx > dxma) dxma = dx;
00181       if (dy < dymi) dymi = dy; else if (dy > dyma) dyma = dy;
00182       if (ds < dsmi) dsmi = ds; else if (ds > dsma) dsma = ds;
00183       if (doo < domi) domi = doo; else if (doo > doma) doma = doo;
00184 
00185       ++ itr;
00186     }
00187   //LINFO("dx = [%f .. %f]", dxmi, dxma);
00188   //LINFO("dy = [%f .. %f]", dymi, dyma);
00189   //LINFO("do = [%f .. %f]", domi, doma);
00190   //LINFO("ds = [%f .. %f]", dsmi, dsma);
00191 
00192   // make sure our ranges are not empty:
00193   if (dxma - dxmi < 1.0F) dxma = dxmi + 1.0F;
00194   if (dyma - dymi < 1.0F) dyma = dymi + 1.0F;
00195   if (doma - domi < 1.0e-3F) doma = domi + 1.0e-3F;
00196   if (dsma - dsmi < 1.0e-3F) dsma = dsmi + 1.0e-3F;
00197 
00198   // we are going to divide each range into eight bins:
00199   const float facx = 8.0F / (dxma - dxmi);
00200   const float facy = 8.0F / (dyma - dymi);
00201   const float faco = 8.0F / (doma - domi);
00202   const float facs = 8.0F / (dsma - dsmi);
00203 
00204   // let's populate a SIFThough:
00205   SIFThough h;
00206   itr = itsMatches.begin();
00207   while (itr < stop)
00208     {
00209       // get the keypoint differences:
00210       float dx, dy, doo, ds;
00211       getKdiff(*itr, dx, dy, doo, ds);
00212 
00213       // add to our Hough accumulator:
00214       h.addValue((dx - dxmi) * facx, (dy - dymi) * facy,
00215                  (doo - domi) * faco, (ds - dsmi) * facs, 1.0F);
00216 
00217       ++ itr;
00218     }
00219 
00220   // all right, let's get the peak out:
00221   float peakx, peaky, peako, peaks;
00222   h.getPeak(peakx, peaky, peako, peaks);
00223 
00224   // convert back from bin to real coordinates:
00225   peakx = peakx / facx + dxmi;
00226   peaky = peaky / facy + dymi;
00227   peako = peako / faco + domi;
00228   peaks = peaks / facs + dsmi;
00229   //LINFO("Peak at dx=%f, dy=%f, do=%f, ds=%f", peakx, peaky, peako, peaks);
00230 
00231   // compute the acceptable range:
00232   const float rxmi = peakx - rangefac * (dxma - dxmi);
00233   const float rxma = peakx + rangefac * (dxma - dxmi);
00234   const float rymi = peaky - rangefac * (dyma - dymi);
00235   const float ryma = peaky + rangefac * (dyma - dymi);
00236   const float romi = peako - rangefac * (doma - domi);
00237   const float roma = peako + rangefac * (doma - domi);
00238   const float rsmi = peaks - rangefac * (dsma - dsmi);
00239   const float rsma = peaks + rangefac * (dsma - dsmi);
00240 
00241   // let's prune away matches that are more than some fraction of the
00242   // range from the peak:
00243   uint ndel = 0U; itr = itsMatches.begin();
00244   while (itr < itsMatches.end())  // do not use 'stop' as size will shrink
00245     {
00246       // get the keypoint differences:
00247       float dx, dy, doo, ds;
00248       getKdiff(*itr, dx, dy, doo, ds);
00249 
00250       // prune that outlier?
00251       if (dx < rxmi || dx > rxma ||
00252           dy < rymi || dy > ryma ||
00253           doo < romi || doo > roma ||
00254           ds < rsmi || ds > rsma)
00255         {
00256           itr = itsMatches.erase(itr); ++ ndel;
00257           if (itsMatches.size() <= minn) return ndel;
00258         }
00259       else
00260         ++ itr;
00261     }
00262   return ndel;
00263 }
00264 
00265 // ######################################################################
00266 uint VisualObjectMatch::pruneByAff(const float dist, const uint minn)
00267 {
00268   // do not go below a min number of matches:
00269   if (itsMatches.size() <= minn) return 0U;
00270   uint ndel = 0U; const float dist2 = dist * dist;
00271 
00272   // get our affine transform given our current matches:
00273   computeAffine();
00274 
00275   // loop over our matches and find the outliers:
00276   std::vector<KeypointMatch>::iterator itr = itsMatches.begin();
00277   while (itr < itsMatches.end())
00278     {
00279       // get residual distance between affine-transformed ref
00280       // keypoint and test keypoint:
00281       const float d = itsAff.getResidualDistSq(*itr);
00282 
00283       if (d > dist2)
00284         {
00285           // delete that outlier match:
00286           itr = itsMatches.erase(itr); ++ndel;
00287 
00288           // do not go below a min number of remaining matches:
00289           if (itsMatches.size() <= minn) return ndel;
00290         }
00291       else
00292         ++ itr;
00293     }
00294   return ndel;
00295 }
00296 
00297 // ######################################################################
00298 void VisualObjectMatch::computeAffine()
00299 {
00300   const uint nmatches = itsMatches.size();
00301 
00302   // we require at least 3 matches for this to work:
00303   if (nmatches < 3)
00304     {
00305       LDEBUG("Too few matches (%u) -- RETURNING IDENTITY", nmatches);
00306       itsAff = SIFTaffine();  // default constructor is identity
00307       return;
00308     }
00309 
00310   // we are going to solve the linear system Ax=b in the least-squares sense
00311   Image<float> A(3, nmatches, NO_INIT);
00312   Image<float> b(2, nmatches, NO_INIT);
00313 
00314   for (uint i = 0; i < nmatches; i ++)
00315     {
00316       rutz::shared_ptr<Keypoint> refkp = itsMatches[i].refkp;
00317       rutz::shared_ptr<Keypoint> tstkp = itsMatches[i].tstkp;
00318 
00319       A.setVal(0, i, refkp->getX());
00320       A.setVal(1, i, refkp->getY());
00321       A.setVal(2, i, 1.0f);
00322 
00323       b.setVal(0, i, tstkp->getX());
00324       b.setVal(1, i, tstkp->getY());
00325     }
00326 
00327   try
00328     {
00329       // the solution to Ax=b is x = [A^t A]^-1 A^t b:
00330       Image<float> At = transpose(A);
00331 
00332       Image<float> x =
00333         matrixMult(matrixMult(matrixInv(matrixMult(At, A)), At), b);
00334 
00335       // store into our SIFTaffine:
00336       itsAff.m1 = x.getVal(0, 0); itsAff.m3 = x.getVal(1, 0);
00337       itsAff.m2 = x.getVal(0, 1); itsAff.m4 = x.getVal(1, 1);
00338       itsAff.tx = x.getVal(0, 2); itsAff.ty = x.getVal(1, 2);
00339 
00340       // ok, we have it:
00341       itsHasAff = true;
00342     }
00343   catch (SingularMatrixException& e)
00344     {
00345       LDEBUG("Couldn't invert matrix -- RETURNING IDENTITY");
00346       itsAff = SIFTaffine(); // default constructor is identity
00347       itsHasAff = false;
00348     }
00349 }
00350 
00351 // ######################################################################
00352 bool VisualObjectMatch::checkSIFTaffine(const float maxrot,
00353                                         const float maxscale,
00354                                         const float maxshear)
00355 {
00356   if (itsHasAff == false) computeAffine();
00357   if (itsAff.isInversible() == false) return false;
00358 
00359   float theta, sx, sy, str;
00360   itsAff.decompose(theta, sx, sy, str);
00361 
00362   LDEBUG("theta=%fdeg sx=%f sy=%f shx=%f shy=%f",
00363          theta * 180.0F / M_PI, sx, sy, str/sx, str/sy);
00364 
00365   // check the rotation:
00366   if (fabsf(theta) > maxrot) return false;
00367 
00368   // check the scaling:
00369   if (fabsf(sx) > maxscale || fabsf(sx) < 1.0F / maxscale) return false;
00370   if (fabsf(sy) > maxscale || fabsf(sy) < 1.0F / maxscale) return false;
00371 
00372   // check the shearing. Note: from the previous check, we are
00373   // guaranteed that sx and sy are non-zero:
00374   if (fabsf(str/sx) > maxshear) return false;
00375   if (fabsf(str/sy) > maxshear) return false;
00376 
00377   // if we get here, the affine is not weird:
00378   return true;
00379 }
00380 
00381 // ######################################################################
00382 float VisualObjectMatch::getScore(const float kcoeff,
00383                                   const float acoeff)
00384 {
00385   if(!itsHasKpAvgDist) getKeypointAvgDist();
00386   if(!itsHasAfAvgDist) getAffineAvgDist();
00387 
00388   // keypoint average distance
00389   // FIX: figure out a good ratio with the affine
00390   //      cap out at .05 for now
00391   float kp = itsKpAvgDist; if(kp < .05) kp = .05;
00392 
00393   // affine average distance: capped out at .05
00394   // object that close in affine transform is probably a good match
00395   // no need to insert in a lower value
00396   // otherwise it will saturates the other factor
00397   float af = itsAfAvgDist; if(af < .05) af = .05;
00398 
00399   // number of keypoint matches score
00400   // cap at 20 matches: .05 * 20 = 1.0
00401   // any bigger should not add more weight; they're all good matches
00402   float nm =  0.05F * float(itsMatches.size()); if(nm > 1.0) nm = 1.0F;
00403 
00404   // matching score:
00405   // max value: 21.0: .5/.05 + .5/.05 + 1.0 = 10.0 + 10.0 + 1.0
00406   float score = kcoeff/kp + acoeff/af + nm;
00407 
00408   return score;
00409 }
00410 
00411 // ######################################################################
00412 float VisualObjectMatch::getSalScore(const float wcoeff,
00413                                      const float hcoeff )
00414 {
00415   // score = feature similarity * distance
00416   float sscore = getSalDiff();
00417   if(sscore == -1.0F) return sscore;
00418   float sdist = getSalDist();
00419   if(sdist == -1.0F) return sscore;
00420 
00421   float maxDist = 0.0;
00422   rutz::shared_ptr<VisualObject> obj1 = getVoRef();
00423   rutz::shared_ptr<VisualObject> obj2 = getVoTest();
00424   if(wcoeff == 0.0F && hcoeff == 0.0F)
00425     {
00426       uint w1 = obj1->getImage().getWidth();
00427       uint h1 = obj1->getImage().getHeight();
00428       float dist1 = sqrt(w1*w1 + h1*h1);
00429       uint w2 = obj2->getImage().getWidth();
00430       uint h2 = obj2->getImage().getHeight();
00431       float dist2 = sqrt(w2*w2 + h2*h2);
00432       maxDist = dist1 + dist2;
00433     }
00434   else
00435     {
00436       maxDist = sqrt(wcoeff * wcoeff + hcoeff * hcoeff);
00437     }
00438   float dscore = 1.0 - sdist/maxDist;
00439   LINFO("dist score : 1.0 - %f/%f = %f", sdist, maxDist, dscore);
00440 
00441   float score = dscore * sscore;
00442   LINFO("dist * sim:  %f * %f = %f", dscore, sscore, score);
00443 
00444   return score;
00445 }
00446 
00447 // ######################################################################
00448 float VisualObjectMatch::getSalDiff()
00449 {
00450   // check if both has salient points and feature vectors
00451   rutz::shared_ptr<VisualObject> obj1 = getVoRef();
00452   rutz::shared_ptr<VisualObject> obj2 = getVoTest();
00453   const std::vector<float>& feat1 = obj1->getFeatures();
00454   const std::vector<float>& feat2 = obj2->getFeatures();
00455   bool compfeat = ((feat1.size() > 0) && (feat1.size() == feat2.size()));
00456   if (!compfeat) return -1.0F;
00457 
00458   // feature similarity  [ 0.0 ... 1.0 ]:
00459   float cval = 0.0;
00460   for(uint i = 0; i < feat1.size(); i++)
00461     {
00462       const float val = feat1[i] - feat2[i];
00463       cval += val * val;
00464     }
00465   cval = sqrtf(cval / feat1.size());
00466   float sscore =  1.0F - cval;
00467   LDEBUG("cval: %f: score: %f", cval, sscore);
00468   return sscore;
00469 }
00470 
00471 // ######################################################################
00472 float VisualObjectMatch::getSalDist()
00473 {
00474   // check if both has salient points and feature vectors
00475   rutz::shared_ptr<VisualObject> obj1 = getVoRef();
00476   rutz::shared_ptr<VisualObject> obj2 = getVoTest();
00477   Point2D<int> salpt1 = obj1->getSalPoint();
00478   Point2D<int> salpt2 = obj2->getSalPoint();
00479   bool compfeat = ((salpt1.i != -1) && (salpt2.i != -1));
00480   if(!compfeat) return -1.0F;
00481 
00482   // forward affine transform [A * ref -> tst ]
00483   SIFTaffine aff = getSIFTaffine();
00484   float u, v; aff.transform(salpt1.i, salpt1.j, u, v);
00485   float dist = salpt2.distance(Point2D<int>(int(u+0.5F),int(v+0.5F)));
00486   LDEBUG("pos1: (%d,%d) -> (%f,%f) & pos2: (%d,%d): dist: %f",
00487          salpt1.i, salpt1.j, u, v, salpt2.i, salpt2.j, dist);
00488   return dist;
00489 }
00490 
00491 // ######################################################################
00492 float VisualObjectMatch::getKeypointAvgDist()
00493 {
00494   if(itsHasKpAvgDist) return itsKpAvgDist;
00495   if (itsMatches.size() == 0U) return 1.0e30F;
00496 
00497   float d = 0.0F;
00498   std::vector<KeypointMatch>::const_iterator
00499     itr = itsMatches.begin(), stop = itsMatches.end();
00500   float fac = 1.0F; if (itr != stop) fac /= float(itr->refkp->getFVlength());
00501 
00502   while(itr != stop)
00503     {
00504       d += sqrtf(float(itr->refkp->distSquared(itr->tstkp)) * fac); ++itr;
00505     }
00506 
00507   itsKpAvgDist = 0.1F * d / float(itsMatches.size());
00508   itsHasKpAvgDist = true;
00509 
00510   return itsKpAvgDist;
00511 }
00512 
00513 // ######################################################################
00514 float VisualObjectMatch::getAffineAvgDist()
00515 {
00516   if(itsHasAfAvgDist) return itsAfAvgDist;
00517   if (itsMatches.size() < 3U) return 1.0e30F;
00518   if (itsHasAff == false) computeAffine();
00519 
00520   float d = 0.0F;
00521   std::vector<KeypointMatch>::const_iterator
00522     itr = itsMatches.begin(), stop = itsMatches.end();
00523 
00524   while(itr != stop)
00525     { d += sqrtf(itsAff.getResidualDistSq(*itr)); ++itr; }
00526 
00527   itsAfAvgDist = d / float(itsMatches.size());
00528   itsHasAfAvgDist = true;
00529 
00530   return itsAfAvgDist;
00531 }
00532 
00533 // ######################################################################
00534 uint VisualObjectMatch::matchSimple(const uint thresh)
00535 {
00536   const uint refnkp = itsVoRef->numKeypoints();
00537   const uint tstnkp = itsVoTest->numKeypoints();
00538   if (refnkp == 0 || tstnkp == 0) return 0U;
00539 
00540   const int maxdsq = itsVoRef->getKeypoint(0)->maxDistSquared();
00541   uint nmatches = 0; uint thresh2 = thresh * thresh;
00542 
00543   // loop over all of the test object's keypoints:
00544   for (uint i = 0; i < tstnkp; i++)
00545     {
00546       int distsq1 = maxdsq, distsq2 = maxdsq;
00547       rutz::shared_ptr<Keypoint> tstkey = itsVoTest->getKeypoint(i);
00548       rutz::shared_ptr<Keypoint> refkey;
00549 
00550       // loop over all of the ref object's keypoints:
00551       for (uint j = 0; j < refnkp; j ++)
00552         {
00553           rutz::shared_ptr<Keypoint> rkey = itsVoRef->getKeypoint(j);
00554           const int distsq = rkey->distSquared(tstkey);
00555 
00556           // is this better than our best one?
00557           if (distsq < distsq1)
00558             {
00559               distsq2 = distsq1; // old best becomes second best
00560               distsq1 = distsq;  // we got a new best
00561               refkey = rkey;     // remember the best keypoint
00562             }
00563           else if (distsq < distsq2)  // maybe between best and second best?
00564             distsq2 = distsq;
00565         }
00566 
00567       // Check that best distance less than thresh of second best distance:
00568       if (100U * distsq1 < thresh2 * distsq2)
00569         {
00570           KeypointMatch m;
00571           m.refkp = refkey; m.tstkp = tstkey;
00572           m.distSq = distsq1; m.distSq2 = distsq2;
00573           itsMatches.push_back(m);
00574           ++ nmatches;
00575         }
00576     }
00577 
00578   // return number of matches:
00579   return nmatches;
00580 }
00581 
00582 // ######################################################################
00583 uint VisualObjectMatch::matchKDTree(const uint thresh, const int bbf)
00584 {
00585   const uint refnkp = itsVoRef->numKeypoints();
00586   const uint tstnkp = itsVoTest->numKeypoints();
00587   if (refnkp == 0 || tstnkp == 0) return 0U;
00588   const int maxdsq = itsVoRef->getKeypoint(0)->maxDistSquared();
00589   uint nmatches = 0; uint thresh2 = thresh * thresh;
00590 
00591   // do we already have a valid KDTree for our ref object? Otherwise
00592   // let's build one:
00593   if (itsKDTree.is_invalid())
00594     {
00595       LINFO("Building KDTree for VisualObject '%s'...",
00596             itsVoRef->getName().c_str());
00597       itsKDTree.reset(new KDTree(itsVoRef->getKeypoints()));
00598       LINFO("KDTree for VisualObject '%s' complete.",
00599             itsVoRef->getName().c_str());
00600     }
00601 
00602   // loop over all of our test object's keypoints:
00603   for (uint i = 0; i < tstnkp; i++)
00604     {
00605       int distsq1 = maxdsq, distsq2 = maxdsq;
00606       rutz::shared_ptr<Keypoint> tstkey = itsVoTest->getKeypoint(i);
00607 
00608       // find nearest neighbor in our KDTree:
00609       uint matchIndex = bbf > 0 ?
00610         itsKDTree->nearestNeighborBBF(tstkey, bbf, distsq1, distsq2) :
00611         itsKDTree->nearestNeighbor(tstkey, distsq1, distsq2);
00612 
00613       // Check that best distance less than 0.6 of second best distance:
00614       if (100U * distsq1 < thresh2 * distsq2)
00615         {
00616           KeypointMatch m;
00617           m.refkp = itsVoRef->getKeypoint(matchIndex); m.tstkp = tstkey;
00618           m.distSq = distsq1; m.distSq2 = distsq2;
00619           itsMatches.push_back(m);
00620           ++ nmatches;
00621         }
00622     }
00623 
00624   // return number of matches:
00625   return nmatches;
00626 }
00627 
00628 // ######################################################################
00629 Image< PixRGB<byte> >
00630 VisualObjectMatch::getMatchImage(const float scale) const
00631 {
00632   // get a keypoint image (without vectors) for both objects:
00633   Image< PixRGB<byte> > refimg = itsVoRef->getKeypointImage(scale, 0.0F);
00634   Image< PixRGB<byte> > tstimg = itsVoTest->getKeypointImage(scale, 0.0F);
00635   LDEBUG("r[%d %d] t[%d %d]", 
00636          refimg.getWidth(), refimg.getHeight(),
00637          tstimg.getWidth(), tstimg.getHeight());
00638 
00639   // put ref image on top of the other:
00640   int refdx, tstdx;
00641   const int w = std::max(refimg.getWidth(), tstimg.getWidth());
00642   const int dy = refimg.getHeight();
00643   const PixRGB<byte> greycol(128), linkcol(255, 200, 100);
00644   Image< PixRGB<byte> > combo(w, dy + tstimg.getHeight(), ZEROS);
00645 
00646   if (refimg.getWidth() > tstimg.getWidth())
00647     { refdx = 0; tstdx = (w - tstimg.getWidth()) / 2; }
00648   else
00649     { refdx = (w - refimg.getWidth()) / 2; tstdx = 0; }
00650 
00651   if (refimg.coordsOk(itsVoRef->getSalPoint()))
00652     drawDisk(refimg, itsVoRef->getSalPoint(),  3, PixRGB<byte>(255,255,0));
00653 
00654   if (tstimg.coordsOk(itsVoTest->getSalPoint()))
00655     drawDisk(tstimg, itsVoTest->getSalPoint(), 3, PixRGB<byte>(255,255,0));
00656 
00657   inplacePaste(combo, refimg, Point2D<int>(refdx, 0));
00658   inplacePaste(combo, tstimg, Point2D<int>(tstdx, dy));
00659 
00660   drawLine(combo, Point2D<int>(0, dy-1), Point2D<int>(w-1, dy-1), greycol);
00661   drawLine(combo, Point2D<int>(0, dy), Point2D<int>(w-1, dy), greycol);
00662 
00663   // let's link the matched keypoints:
00664   const uint nm = itsMatches.size();
00665   for (uint i = 0; i < nm; i ++)
00666     {
00667       rutz::shared_ptr<Keypoint> refk = itsMatches[i].refkp;
00668       rutz::shared_ptr<Keypoint> tstk = itsMatches[i].tstkp;
00669 
00670       drawLine(combo,
00671                Point2D<int>(int(refk->getX() * scale + 0.5F) + refdx,
00672                             int(refk->getY() * scale + 0.5F)),
00673                Point2D<int>(int(tstk->getX() * scale + 0.5F) + tstdx,
00674                             int(tstk->getY() * scale + 0.5F) + dy),
00675                linkcol);
00676     }
00677 
00678   return combo;
00679 }
00680 
00681 // ######################################################################
00682 Image< PixRGB<byte> >
00683 VisualObjectMatch::getMatchImage(Dims frameSize,
00684                                  Point2D<int> refOffset, Point2D<int> testOffset,
00685                                  const float scale) const
00686 {
00687   int w = frameSize.w();
00688   int h = frameSize.h();
00689 
00690   // get a keypoint image (without vectors) for both objects:
00691   Image< PixRGB<byte> > refimg =
00692     getVoRef()->getKeypointImage(scale, 0.0F);
00693   Image< PixRGB<byte> > tstimg =
00694     getVoTest()->getKeypointImage(scale, 0.0F);
00695 
00696   // draw the salient point locations
00697   drawDisk(refimg, getVoRef()->getSalPoint(), 3, PixRGB<byte>(255,255,0));
00698   drawDisk(tstimg, getVoTest()->getSalPoint(), 3, PixRGB<byte>(255,255,0));
00699 
00700   const PixRGB<byte> greycol(128), linkcol(255, 200, 100);
00701   Image< PixRGB<byte> > combo(w, 2*h, ZEROS);
00702 
00703   // put ref image on top of the other:
00704   inplacePaste(combo, refimg, Point2D<int>(0, 0)+ refOffset);
00705   inplacePaste(combo, tstimg, Point2D<int>(0, h)+ testOffset);
00706 
00707   drawLine(combo, Point2D<int>(0, h-1), Point2D<int>(w-1, h-1), greycol);
00708 
00709   // let's link the matched keypoints:
00710   std::vector<KeypointMatch> matches = getKeypointMatches();
00711 
00712   const uint nm = matches.size();
00713   for (uint i = 0; i < nm; i ++)
00714     {
00715       rutz::shared_ptr<Keypoint> refk = matches[i].refkp;
00716       rutz::shared_ptr<Keypoint> tstk = matches[i].tstkp;
00717 
00718       drawLine
00719         (combo,
00720          Point2D<int>(int(refk->getX() * scale + 0.5F),
00721                  int(refk->getY() * scale + 0.5F))
00722          + refOffset,
00723          Point2D<int>(int(tstk->getX() * scale + 0.5F),
00724                  int(tstk->getY() * scale + 0.5F))
00725          + testOffset + Point2D<int>(0,h),
00726          linkcol);
00727     }
00728 
00729   return combo;
00730 }
00731 
00732 // ######################################################################
00733 Image< PixRGB<byte> > VisualObjectMatch::
00734 getTransfTestImage(const Image< PixRGB<byte> >& im)
00735 {
00736   SIFTaffine aff = getSIFTaffine();
00737 
00738   // we loop over all pixel locations in the ref image, transform the
00739   // coordinates using the forward affine transform, get the pixel
00740   // value in the test image, and plot it:
00741   Image< PixRGB<byte> > result(im);
00742   if (result.initialized() == false)
00743     result.resize(itsVoRef->getImage().getDims(), true);
00744   Image< PixRGB<byte> > tsti = itsVoTest->getImage();
00745 
00746   uint w = result.getWidth(), h = result.getHeight();
00747   Image< PixRGB<byte> >::iterator dptr = result.beginw();
00748 
00749   for (uint j = 0; j < h; j ++)
00750     for (uint i = 0; i < w; i ++)
00751       {
00752         float u, v;
00753         aff.transform(float(i), float(j), u, v);
00754 
00755         if (tsti.coordsOk(u, v))
00756           *dptr++ = tsti.getValInterp(u, v);
00757         else
00758           ++dptr;
00759       }
00760   return result;
00761 }
00762 
00763 // ######################################################################
00764 void VisualObjectMatch::
00765 getTransfTestOutline(Point2D<int>& tl, Point2D<int>& tr, Point2D<int>& br, Point2D<int>& bl)
00766 {
00767   SIFTaffine a = getSIFTaffine();
00768   SIFTaffine aff = a.inverse();
00769 
00770   // transform the four corners of the test image using the inverse affine:
00771 
00772   const Dims objSize = itsVoTest->getObjectSize();
00773   const uint w = objSize.w();
00774   const uint h = objSize.h();
00775   float u, v;
00776 
00777   aff.transform(0.0F, 0.0F, u, v);
00778   tl.i = int(u + 0.5F); tl.j = int(v + 0.5F);
00779 
00780   aff.transform(float(w-1), 0.0F, u, v);
00781   tr.i = int(u + 0.5F); tr.j = int(v + 0.5F);
00782 
00783   aff.transform(float(w-1), float(h-1), u, v);
00784   br.i = int(u + 0.5F); br.j = int(v + 0.5F);
00785 
00786   aff.transform(0.0F, float(h-1), u, v);
00787   bl.i = int(u + 0.5F); bl.j = int(v + 0.5F);
00788 }
00789 
00790 // ######################################################################
00791 Image< PixRGB<byte> > VisualObjectMatch::getFusedImage(const float mix)
00792 {
00793   SIFTaffine aff = getSIFTaffine();
00794 
00795   // we loop over all pixel locations in the ref image, transform the
00796   // coordinates using the forward affine transform, get the pixel
00797   // value in the test image, and mix:
00798   Image< PixRGB<byte> > refi = itsVoRef->getImage();
00799   Image< PixRGB<byte> > tsti = itsVoTest->getImage();
00800 
00801   uint w = refi.getWidth(), h = refi.getHeight();
00802   Image< PixRGB<byte> > result(w, h, NO_INIT);
00803   Image< PixRGB<byte> >::const_iterator rptr = refi.begin();
00804   Image< PixRGB<byte> >::iterator dptr = result.beginw();
00805 
00806   for (uint j = 0; j < h; j ++)
00807     for (uint i = 0; i < w; i ++)
00808       {
00809         float u, v;
00810         aff.transform(float(i), float(j), u, v);
00811         PixRGB<byte> rval = *rptr++;
00812 
00813         if (tsti.coordsOk(u, v))
00814           {
00815             PixRGB<byte> tval = tsti.getValInterp(u, v);
00816             PixRGB<byte> mval = PixRGB<byte>(rval * mix + tval * (1.0F - mix));
00817             *dptr++ = mval;
00818           }
00819         else
00820           *dptr++ = PixRGB<byte>(rval * mix);
00821       }
00822   return result;
00823 }
00824 
00825 // ######################################################################
00826 Point2D<int> VisualObjectMatch::getSpatialDist
00827 ( Point2D<int> offset1, Point2D<int> offset2)
00828 {
00829   // get the matches
00830   // translation is: t = Ar + b
00831   //   [testX]   [ a.m1 a.m2 ] [refX]   [ a.tx ]
00832   //   [testY] = [ a.m3 a.m4 ] [refY] + [ a.ty ]
00833   SIFTaffine a = getSIFTaffine();
00834 
00835   // get the needed matrix
00836   Image<double> A(2,2, ZEROS);
00837   A.setVal(0, 0, a.m1);    A.setVal(1, 0, a.m2);
00838   A.setVal(0, 1, a.m3);    A.setVal(1, 1, a.m4);
00839 
00840   Image<double> b(1,2, ZEROS);
00841   b.setVal(0, 0, a.tx);
00842   b.setVal(0, 1, a.ty);
00843 
00844   Image<double>  r(1,2,ZEROS);
00845   r.setVal(0,0, -offset1.i);
00846   r.setVal(0,1, -offset1.j);
00847 
00848   LINFO("[ %7.3f %7.3f ][ %7.3f ]   [ %7.3f ]",
00849         A.getVal(0,0),  A.getVal(1,0),  r.getVal(0,0), b.getVal(0,0));
00850   LINFO("[ %7.3f %7.3f ][ %7.3f ] + [ %7.3f ]",
00851         A.getVal(0,1),  A.getVal(1,1),  r.getVal(0,1), b.getVal(0,1));
00852 
00853   Image<double> diff = matrixMult(A,r) + b;
00854   Point2D<int> diffPt = Point2D<int>(int(diff.getVal(0,0)),
00855                            int(diff.getVal(0,1)));
00856   Point2D<int> res = diffPt + offset2;
00857 
00858   LINFO("diff:[%d %d] + offset:[%d %d] = [%d %d]",
00859         diffPt.i, diffPt.j, offset2.i, offset2.j, res.i, res.j);
00860   return res;
00861 }
00862 
00863 // ######################################################################
00864 Rectangle VisualObjectMatch::getOverlapRect()
00865 {
00866   // NOTE: overlap assume only translational transformation
00867 
00868   SIFTaffine aff = getSIFTaffine();
00869 
00870   // we loop over all pixel locations in the ref image, transform the
00871   // coordinates using the forward affine transform [A * ref -> tst ]
00872   // get the pixel value in the ref image and estimate
00873 
00874   Image< PixRGB<byte> > refi = getVoRef()->getImage();
00875   Image< PixRGB<byte> > tsti = getVoTest()->getImage();
00876 
00877   uint wr = refi.getWidth(), hr = refi.getHeight();
00878   uint wt = tsti.getWidth(), ht = tsti.getHeight();
00879   LDEBUG("r[%d,%d]  t[%d,%d]", wr, hr, wt, ht);
00880 
00881   // get the affine transforms of the test image corners
00882   // top-left corner of refI
00883   float u, v;
00884   aff.transform(float(0), float(0), u, v);
00885   float t = v, l = u, b = v, r = u;
00886   LDEBUG("t-l: [%f,%f]: [%f,%f,%f,%f]", u, v, t, l, b, r);
00887 
00888   // top-right corner of refI
00889   aff.transform(float(wr-1), float(0), u, v);
00890   if(u < l) l = u;  if(u > r) r = u;
00891   if(v < t) t = v;  if(v > b) b = v;
00892   LDEBUG("t-r: [%f,%f]: [%f,%f,%f,%f]", u, v, t, l, b, r);
00893 
00894   // bottom-left corner of refI
00895   aff.transform(float(0), float(hr-1), u, v);
00896   if(u < l) l = u;  if(u > r) r = u;
00897   if(v < t) t = v;  if(v > b) b = v;
00898   LDEBUG("b-l: [%f,%f]: [%f,%f,%f,%f]", u, v, t, l, b, r);
00899 
00900   // bottom-right corner of refI
00901   aff.transform(float(wr-1), float(hr-1), u, v);
00902   if(u < l) l = u;  if(u > r) r = u;
00903   if(v < t) t = v;  if(v > b) b = v;
00904   LDEBUG("b-r: [%f,%f]: [%f,%f,%f,%f]", u, v, t, l, b, r);
00905 
00906   const Rectangle refr = Rectangle::tlbrI(int(t+0.5F), int(l+0.5F),
00907                                          int(b+0.5F), int(r+0.5F));
00908   const Rectangle tstr = tsti.getBounds();
00909   const Rectangle ovl = refr.getOverlap(tstr);
00910   LINFO("overlap at: [%d, %d, %d, %d], %d, %d",
00911         ovl.left(), ovl.top(), ovl.rightI(), ovl.bottomI(),
00912         ovl.width(),ovl.height());
00913 
00914   return ovl;
00915 }
00916 
00917 // ######################################################################
00918 bool VisualObjectMatch::isOverlapping()
00919 {
00920   Image< PixRGB<byte> > refi = getVoRef()->getImage();
00921   Image< PixRGB<byte> > tsti = getVoTest()->getImage();
00922 
00923   const Rectangle a = refi.getBounds();
00924   const Rectangle b = tsti.getBounds();
00925 
00926   // print the rectangles
00927   const Rectangle ovl = getOverlapRect();
00928 
00929   LINFO("o:[%d, %d, %d, %d], [%d,%d] dbo:[%d, %d, %d, %d], [%d,%d]",
00930         a.left(), a.top(), a.rightI(), a.bottomI(), a.width(), a.height(),
00931         b.left(), b.top(), b.rightI(), b.bottomI(), b.width(), b.height() );
00932   LINFO("overlap at: [%d, %d, %d, %d], %d, %d",
00933         ovl.left(), ovl.top(), ovl.rightI(), ovl.bottomI(),
00934         ovl.width(),ovl.height());
00935 
00936   // check percentage of overlap 50%
00937   if(!ovl.isValid()) { LINFO("do not overlap"); return false; }
00938   float ovlA = (ovl.width() * ovl.height())/(a.width() * a.height()+0.0);
00939   float ovlB = (ovl.width() * ovl.height())/(b.width() * b.height()+0.0);
00940   LINFO("ovl: a=  %f, b=  %f", ovlA, ovlB);
00941   if((ovlA > .5 && ovlB > .5) || (ovlA > .8) || (ovlB > .8))
00942     { LINFO("the objects overlap"); return true; }
00943   else
00944     { LINFO("objects do not significantly overlap"); return false; }
00945       // maybe later check which one is the smaller one
00946 }
00947 
00948 // ######################################################################
00949 bool VisualObjectMatch::isOverlapping2()
00950 {
00951   rutz::shared_ptr<VisualObject> obj1 = getVoRef();
00952   rutz::shared_ptr<VisualObject> obj2 = getVoTest();
00953   LINFO("obj1 size %d, obj2 size: %d match size: %d",
00954         obj1->numKeypoints(), obj2->numKeypoints(), size());
00955 
00956   // get object 1 borders - from keypoints
00957   float t1 = -1.0f, b1 = -1.0f, l1 = -1.0f, r1 = -1.0f;
00958   if(obj1->numKeypoints() > 0)
00959     {
00960       float x = obj1->getKeypoint(0)->getX();
00961       float y = obj1->getKeypoint(0)->getY();
00962       t1 = y, b1 = y, l1 = x, r1 = x;
00963       //LINFO("[%f %f]",x,y);
00964     }
00965   for(uint i = 1; i < obj1->numKeypoints(); i++)
00966     {
00967       float x = obj1->getKeypoint(i)->getX();
00968       float y = obj1->getKeypoint(i)->getY();
00969 
00970       if(t1 > y) t1 = y; else if(b1 < y) b1 = y;
00971       if(l1 > x) l1 = x; else if(r1 < x) r1 = x;
00972       //LINFO("[%f %f]",x,y);
00973     }
00974   float area1 = (b1 - t1)*(r1 - l1);
00975   LINFO("obj1[%d]: t1: %f, b1: %f, l1: %f, r1: %f; A: %f",
00976         obj1->numKeypoints(), t1, b1, l1, r1, area1);
00977 
00978   // get object 1 borders - from keypoints
00979   float t2 = -1.0f, b2 = -1.0f, l2 = -1.0f, r2 = -1.0f;
00980   if(obj2->numKeypoints() > 0)
00981     {
00982       float x = obj2->getKeypoint(0)->getX();
00983       float y = obj2->getKeypoint(0)->getY();
00984       t2 = y, b2 = y, l2 = x, r2 = x;
00985       //LINFO("[%f %f]",x,y);
00986     }
00987   for(uint i = 1; i < obj2->numKeypoints(); i++)
00988     {
00989       float x = obj2->getKeypoint(i)->getX();
00990       float y = obj2->getKeypoint(i)->getY();
00991 
00992       if(t2 > y) t2 = y; else if(b2 < y) b2 = y;
00993       if(l2 > x) l2 = x; else if(r2 < x) r2 = x;
00994       //LINFO("[%f %f]",x,y);
00995     }
00996   float area2 = (b2 - t2)*(r2 - l2);
00997   LINFO("obj2[%d]: t2: %f, b2: %f, l2: %f, r2: %f; A: %f",
00998         obj2->numKeypoints(), t2, b2, l2, r2, area2);
00999 
01000   // go through all the matches from the ref side
01001   float tm1 = -1.0f, bm1 = -1.0f, lm1 = -1.0f, rm1 = -1.0f;
01002   if(size() > 0)
01003     {
01004       float x = itsMatches[0].refkp->getX();
01005       float y = itsMatches[0].refkp->getY();
01006       tm1 = y, bm1 = y, lm1 = x, rm1 = x;
01007       //LINFO("[%f %f]",x,y);
01008     }
01009   for(uint i = 1; i < size(); i++)
01010     {
01011       float x = itsMatches[i].refkp->getX();
01012       float y = itsMatches[i].refkp->getY();
01013 
01014       if(tm1 > y) tm1 = y; else if(bm1 < y) bm1 = y;
01015       if(lm1 > x) lm1 = x; else if(rm1 < x) rm1 = x;
01016       //LINFO("[%f %f]",x,y);
01017     }
01018   float aream1 = (bm1 - tm1)*(rm1 - lm1);
01019   LINFO("m1[%d]: tm1: %f, bm1: %f, lm1: %f, rm1: %f; Am: %f",
01020         size(), tm1, bm1, lm1, rm1, aream1);
01021 
01022   // go through all the matches from the tst side
01023   float tm2 = -1.0f, bm2 = -1.0f, lm2 = -1.0f, rm2 = -1.0f;
01024   if(size() > 0)
01025     {
01026       float x = itsMatches[0].tstkp->getX();
01027       float y = itsMatches[0].tstkp->getY();
01028       tm2 = y, bm2 = y, lm2 = x, rm2 = x;
01029       //LINFO("[%f %f]",x,y);
01030     }
01031   for(uint i = 1; i < size(); i++)
01032     {
01033       float x = itsMatches[i].tstkp->getX();
01034       float y = itsMatches[i].tstkp->getY();
01035 
01036       if(tm2 > y) tm2 = y; else if(bm2 < y) bm2 = y;
01037       if(lm2 > x) lm2 = x; else if(rm2 < x) rm2 = x;
01038       //LINFO("[%f %f]",x,y);
01039     }
01040   float aream2 = (bm2 - tm2)*(rm2 - lm2);
01041   LINFO("m2[%d]: tm2: %f, bm2: %f, lm2: %f, rm2: %f; Am: %f",
01042         size(), tm2, bm2, lm2, rm2, aream2);
01043 
01044   // if the incoming object overlaps by less than 20%
01045   // of its middle quarter
01046   float lmid = l2 + (r2-l2)/4;  float rmid = r2 - (r2-l2)/4;
01047   float lo = lm2; if(lo < lmid) lo = lmid;
01048   float ro = rm2; if(ro > rmid) ro = rmid;
01049   float wo = 0.0; if(ro > lo) wo = ro - lo;
01050 
01051   float tmid = t2 + (b2-t2)/4;  float bmid = b2 - (b2-t2)/4;
01052   float to = tm2; if(to < tmid) to = tmid;
01053   float bo = bm2; if(bo > bmid) bo = bmid;
01054   float ho = 0.0; if(bo > to) ho = bo - to;
01055   float ao = ho * wo;
01056 
01057   bool ret = true; if(ao/(area2/4.0) < .3) ret = false;
01058   LINFO("wo: %f ho: %f ao/(a2/4) = %f/%f = %f < .4 -> ret = %d",
01059         wo,ho, ao, area2/4.0, ao/(area2/4.0), ret);
01060 
01061   bool ret2 = true; if(ao/aream2 < .4) ret2 = false;
01062   LINFO("wo: %f ho: %f ao/(am2) = %f/%f = %f < .5 -> ret = %d",
01063         wo,ho, ao, aream2, ao/aream2, ret2);
01064   return ret || ret2;
01065 }
01066 
01067 // ######################################################################
01068 /* So things look consistent in everyone's emacs... */
01069 /* Local Variables: */
01070 /* indent-tabs-mode: nil */
01071 /* End: */
Generated on Sun May 8 08:42:17 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3