GaborSnake.C

Go to the documentation of this file.
00001 /** @file Psycho/GaborSnake.C sequences of contour-aligned gabor elements */
00002 
00003 // //////////////////////////////////////////////////////////////////// //
00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005   //
00005 // by the 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: Rob Peters <rjpeters at usc dot edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Psycho/GaborSnake.C $
00035 // $Id: GaborSnake.C 9078 2007-12-11 21:11:45Z rjpeters $
00036 //
00037 
00038 // Code herein is derived from GroovX, also licensed under the GPL
00039 // Copyright (c) 2002-2004 California Institute of Technology
00040 // Copyright (c) 2004-2007 University of Southern California
00041 // [http://ilab.usc.edu/rjpeters/groovx/]
00042 
00043 #ifndef PSYCHO_GABORSNAKE_C_DEFINED
00044 #define PSYCHO_GABORSNAKE_C_DEFINED
00045 
00046 #include "Psycho/GaborSnake.H"
00047 
00048 #include "Image/geom.h"
00049 #include "Image/vec2.h"
00050 #include "Util/Assert.H"
00051 #include "Util/log.H"
00052 #include "rutz/rand.h"
00053 
00054 #include <algorithm>
00055 #include <cmath>
00056 
00057 using geom::vec2d;
00058 
00059 namespace
00060 {
00061   const double MAX_DELTA         = M_PI/4.0; // == 45 degrees
00062   const double TEMPERATURE       = 0.05;
00063   const double INITIAL_INCREMENT = 0.1;
00064 
00065   struct Tuple4
00066   {
00067     Tuple4() {}
00068 
00069     Tuple4(double a0, double a1, double a2, double a3)
00070     { arr[0] = a0; arr[1] = a1; arr[2] = a2; arr[3] = a3; }
00071 
00072     double  operator[](int i) const { return arr[i]; }
00073     double& operator[](int i)       { return arr[i]; }
00074 
00075     double arr[4];
00076   };
00077 
00078   void pickRandom4(int length, int i[], rutz::urand& urand)
00079   {
00080     i[0] = i[1] = i[2] = i[3] = urand.idraw(length);
00081 
00082     while (i[1] == i[0])
00083     {
00084       i[1] = urand.idraw(length);
00085     }
00086 
00087     while (i[2] == i[0] || i[2] == i[1])
00088     {
00089       i[2] = urand.idraw(length);
00090     }
00091 
00092     while (i[3] == i[0] || i[3] == i[1] || i[3] == i[2])
00093     {
00094       i[3] = urand.idraw(length);
00095     }
00096 
00097     std::sort(i, i+4);
00098   }
00099 
00100   Tuple4 getEdgeLengths(const vec2d no[4])
00101   {
00102     return Tuple4(no[0].distance_to(no[1]),
00103                   no[1].distance_to(no[2]),
00104                   no[2].distance_to(no[3]),
00105                   no[3].distance_to(no[0]));
00106   }
00107 
00108   Tuple4 getThetas(const vec2d no[4])
00109   {
00110     return Tuple4(no[0].angle_to(no[1]),
00111                   no[1].angle_to(no[2]),
00112                   no[2].angle_to(no[3]),
00113                   no[3].angle_to(no[0]));
00114   }
00115 
00116   Tuple4 getAlphas(const Tuple4& theta)
00117   {
00118     return Tuple4(geom::rad_0_2pi(M_PI - theta[0] + theta[3]),
00119                   geom::rad_0_2pi(M_PI - theta[1] + theta[0]),
00120                   geom::rad_0_2pi(M_PI - theta[2] + theta[1]),
00121                   geom::rad_0_2pi(M_PI - theta[3] + theta[2]));
00122   }
00123 
00124   // Must return "true" in order to proceed with new nodes in jiggle().
00125   bool squashQuadrangle(const vec2d& old_0, const vec2d& old_1,
00126                         const vec2d& old_2, const vec2d& old_3,
00127                         vec2d* new_0, vec2d* new_1,
00128                         vec2d* new_2, vec2d* new_3,
00129                         double new_theta)
00130   {
00131     {
00132       // Here we adjust the position of node-1 by jiggling its angle with
00133       // respect to node-0:
00134 
00135       const double d = old_0.distance_to(old_1);
00136 
00137       new_1->x() = old_0.x() + d * cos(new_theta);
00138       new_1->y() = old_0.y() + d * sin(new_theta);
00139     }
00140 
00141     /*                                                     */
00142     /*                             . (2)                   */
00143     /*                           ./|                       */
00144     /*                  b      ./  `.                      */
00145     /*                       ./     |                      */
00146     /*                     ./       `.                     */
00147     /*                   ./          |                     */
00148     /*                 ./            `.  c                 */
00149     /*       (NEW 1) ./_________      |                    */
00150     /*                `--.  ang       `.                   */
00151     /*                    `--.         |                   */
00152     /*                        `--.     `.                  */
00153     /*                      a     `--.  |                  */
00154     /*                                `--. (3)             */
00155     /*                                                     */
00156 
00157 
00158     /*
00159 
00160     OK, we've picked a new node-1... now we want to hold node-3 fixed and
00161     try to find a node-2 that will keep the other distances constant. In
00162     trigonometric terms, we're trying to solve for the missing vertex of a
00163     triangle, given (1) the positions of the other two vertices, and (2)
00164     the lengths of all three sides.
00165 
00166     We do this in two steps:
00167 
00168       (1) assume node-1 is at (0,0), the origin
00169              and node-3 is at (a,0)
00170 
00171           then find node-2 in this unrotated coord system
00172 
00173       (2) now take the rotation angle (ang) required to put node-3 back in
00174           its proper position, and rotate node-2 by that angle as well
00175 
00176     From http://mathworld.wolfram.com/Triangle.html:
00177 
00178     A triangle with sides a, b, and c can be constructed by selecting
00179     vertices (0,0), (a,0), and (x,y), then solving:
00180 
00181         x^2   + y^2  =  b^2
00182       (x-a)^2 + y^2  =  c^2
00183 
00184     to obtain:
00185 
00186            a^2 + b^2 - c^2
00187        x = ---------------
00188                  2a
00189 
00190                     ,------------------------------
00191                    /          (a^2 + b^2 - c^2)^2
00192        y = +/- _  /  b^2  -  ---------------------
00193                 \/                   4a^2
00194 
00195                    ,-----------------------------------------------
00196                 -\/  2(a^2b^2 + b^2c^2 + a^2c^2) - a^4 - b^4 - c^4
00197          = +/- -----------------------------------------------------
00198                                        2a
00199 
00200     (note that the website has an incorrect formula for the solved y!)
00201 
00202     */
00203 
00204     const double a = old_3.distance_to(*new_1);
00205     const double b = old_1.distance_to(old_2);
00206     const double c = old_2.distance_to(old_3);
00207 
00208     const double cos_ang = (old_3.x() - new_1->x()) / a;  // adjac / hypot
00209     const double sin_ang = (old_3.y() - new_1->y()) / a;  // oppos / hypot
00210 
00211     const double a_sq = a*a;
00212     const double b_sq = b*b;
00213     const double c_sq = c*c;
00214 
00215     const double det =
00216       2*(a_sq*b_sq + b_sq*c_sq + a_sq*c_sq)
00217       - a_sq*a_sq - b_sq*b_sq - c_sq*c_sq;
00218 
00219     if (det < 0)
00220       return false;
00221 
00222     // OK, here's the unrotated coords of node-2:
00223 
00224     const double xp = (a*a + b*b - c*c) / (2.0*a);
00225     const double yp = sqrt(det) / (2.0*a);
00226 
00227     // Here's the two candidate re-rotated coords:
00228 
00229     const vec2d new_2a
00230       (new_1->x() + xp*cos_ang - yp*sin_ang,
00231        new_1->y() + xp*sin_ang + yp*cos_ang);
00232 
00233     const vec2d new_2b
00234       (new_1->x() + xp*cos_ang + yp*sin_ang,
00235        new_1->y() + xp*sin_ang - yp*cos_ang);
00236 
00237     const double d_2_2a = new_2a.distance_to(old_2);
00238     const double d_2_2b = new_2b.distance_to(old_2);
00239 
00240     *new_0 = old_0;
00241     *new_2 = (d_2_2a < d_2_2b) ? new_2a : new_2b;
00242     *new_3 = old_3;
00243 
00244     return true;
00245   }
00246 
00247   bool deltaTooBig(const Tuple4& delta)
00248   {
00249     return (fabs(delta[0]) > MAX_DELTA
00250             || fabs(delta[1]) > MAX_DELTA
00251             || fabs(delta[2]) > MAX_DELTA
00252             || fabs(delta[3]) > MAX_DELTA);
00253   }
00254 
00255   bool acceptNewDelta(const Tuple4& newdelta, const Tuple4& olddelta,
00256                       rutz::urand& urand)
00257   {
00258     const double energy_diff =
00259       newdelta[0]*newdelta[0] - olddelta[0]*olddelta[0]
00260       + newdelta[1]*newdelta[1] - olddelta[1]*olddelta[1]
00261       + newdelta[2]*newdelta[2] - olddelta[2]*olddelta[2]
00262       + newdelta[3]*newdelta[3] - olddelta[3]*olddelta[3];
00263 
00264     // Note, if energy_diff<0, then probability>1.
00265     const double probability = exp(-energy_diff/TEMPERATURE);
00266 
00267     return urand.fdraw() <= probability;
00268   }
00269 }
00270 
00271 GaborSnake::GaborSnake(int l, double spacing, int seed, int xcenter, int ycenter,
00272                        GaborPatchItemFactory& factory) :
00273   itsLength(l),
00274   itsElem(itsLength)
00275 {
00276   rutz::urand urand(seed);
00277 
00278   const double radius = (itsLength * spacing) / (2*M_PI);
00279 
00280   const double alpha_start = 2 * M_PI * urand.fdraw();
00281 
00282   for (int n = 0; n < itsLength; ++n)
00283     {
00284       const double alpha = alpha_start + 2 * M_PI * n / itsLength;
00285 
00286       itsElem[n].set_polar_rad(radius, -alpha);
00287     }
00288 
00289   const int ITERS = 400;
00290 
00291   for (int count = 0; count < ITERS; ++count)
00292     {
00293       const bool convergeOk = this->jiggle(urand);
00294       if ( !convergeOk )
00295         {
00296           LERROR("failed to converge");
00297           break;
00298         }
00299     }
00300 
00301   // Now reset so that the center of the ring is at the origin
00302   vec2d c;
00303 
00304   for (int n = 0; n < itsLength; ++n)
00305     c += itsElem[n];
00306 
00307   c /= itsLength;
00308 
00309   for (int n = 0; n < itsLength; ++n)
00310     itsElem[n] -= c;
00311 
00312   for (int n = 0; n < itsLength; ++n)
00313     {
00314       itsElem[n].x() += xcenter;
00315       itsElem[n].y() += ycenter;
00316     }
00317 
00318   for (int n = 0; n < itsLength; ++n)
00319     {
00320       const geom::vec2d pos(0.5 * (elem(n).x() + elem(n+1).x()),
00321                             0.5 * (elem(n).y() + elem(n+1).y()));
00322       const double theta = geom::rad_0_2pi(-elem(n).angle_to(elem(n+1)));
00323 
00324       itsPatches.push_back(factory.makeForeground(pos, theta));
00325     }
00326 }
00327 
00328 GaborSnake::~GaborSnake()
00329 {}
00330 
00331 rutz::shared_ptr<SearchItem> GaborSnake::getElement(int n) const
00332 {
00333   ASSERT(size_t(n) < itsPatches.size());
00334 
00335   return itsPatches[n];
00336 }
00337 
00338 /*        (pos.x[n],ypos[n])     position n                               */
00339 /*        (xpos[n+1],ypos[n+1]) position n+1                              */
00340 /*        (0.5*(xpos[n]+xpos[n+1]), 0.5*(ypos[n]+ypos[n+1]))              */
00341 /*                              location of patch n                       */
00342 /*        theta                 angle between pos n and pos n+1           */
00343 /*                              and orientation of patch n                */
00344 /*        delta                 angle difference between patch n and n+1  */
00345 /*                                                                        */
00346 /*                      theta[0]                                          */
00347 /*                             no[0]__________                            */
00348 /*                           ,-~ `-.                                      */
00349 /*                        ,-~       `-.                                   */
00350 /*                     ,-~   alpha[0]  `-.                                */
00351 /*             a[0] ,-~                   `-.  a[3]                       */
00352 /*               ,-~                         `-.                          */
00353 /*            ,-~                               `-.                       */
00354 /*         ,-~                                     `-.                    */
00355 /*      ,-~                                           `-.                 */
00356 /*no[1]~                                                 `-.   theta[3]   */
00357 /*    \   alpha[1]                                          `-.           */
00358 /*     \                                          alpha[3]    no[3]------ */
00359 /*      \                                                 _,-~            */
00360 /*       \                                            _,-~                */
00361 /*        \                                       _,-~                    */
00362 /*         \                                  _,-~                        */
00363 /*      a[1]\                             _,-~                            */
00364 /*           \                        _,-~                                */
00365 /*            \                   _,-~   a[2]                             */
00366 /*             \  alpha[2]    _,-~                                        */
00367 /*              \         _,-~                                            */
00368 /*               \    _,-~   theta[2]                                     */
00369 /*              no[2]~_________                                           */
00370 
00371 bool GaborSnake::jiggle(rutz::urand& urand)
00372 {
00373   int i[4];
00374   pickRandom4(itsLength, i, urand);
00375 
00376   const vec2d old_pos[4] =
00377     {
00378       itsElem[i[0]], itsElem[i[1]], itsElem[i[2]], itsElem[i[3]]
00379     };
00380 
00381   const Tuple4 old_theta = getThetas(old_pos);
00382   const Tuple4 old_alpha = getAlphas(old_theta);
00383 
00384   const Tuple4 old_delta
00385     (geom::rad_npi_pi(elem(i[0]).angle_to(elem(i[0]+1)) - elem(i[0]-1).angle_to(elem(i[0]))),
00386      geom::rad_npi_pi(elem(i[1]).angle_to(elem(i[1]+1)) - elem(i[1]-1).angle_to(elem(i[1]))),
00387      geom::rad_npi_pi(elem(i[2]).angle_to(elem(i[2]+1)) - elem(i[2]-1).angle_to(elem(i[2]))),
00388      geom::rad_npi_pi(elem(i[3]).angle_to(elem(i[3]+1)) - elem(i[3]-1).angle_to(elem(i[3]))));
00389 
00390   vec2d new_pos[4];
00391 
00392   double increment = INITIAL_INCREMENT;
00393 
00394   const int MAX_ITERS = 10000;
00395 
00396   int k = 0;
00397 
00398   for (; k < MAX_ITERS; ++k)
00399     {
00400       const double incr = urand.booldraw() ? -increment : increment;
00401 
00402       const int r = urand.idraw(4);
00403 
00404       const double incr_theta = old_theta[r] - incr;
00405 
00406       const bool ok =
00407         squashQuadrangle(old_pos[(r+0)%4], old_pos[(r+1)%4],
00408                          old_pos[(r+2)%4], old_pos[(r+3)%4],
00409                          &new_pos[(r+0)%4], &new_pos[(r+1)%4],
00410                          &new_pos[(r+2)%4], &new_pos[(r+3)%4],
00411                          incr_theta);
00412 
00413       if (!ok)
00414         {
00415           increment *= 0.5;
00416           continue;
00417         }
00418 
00419       const Tuple4 new_theta = getThetas(new_pos);
00420       const Tuple4 new_alpha = getAlphas(new_theta);
00421       const Tuple4 new_delta
00422         (old_delta[0] + (old_alpha[0]-new_alpha[0]),
00423          old_delta[1] + (old_alpha[1]-new_alpha[1]),
00424          old_delta[2] + (old_alpha[2]-new_alpha[2]),
00425          old_delta[3] + (old_alpha[3]-new_alpha[3]));
00426 
00427       if (deltaTooBig(new_delta))
00428         {
00429           increment *= 0.8;
00430           continue;
00431         }
00432 
00433       if (acceptNewDelta(new_delta, old_delta, urand))
00434         break;
00435     }
00436 
00437   bool didConverge = (k < MAX_ITERS);
00438 
00439   for (int n = 0; n < 4; ++n)
00440     this->transformPath(i[n], new_pos[n],
00441                         i[(n+1)%4], new_pos[(n+1)%4]);
00442 
00443   for (int n = 0; n < 4; ++n)
00444     {
00445       itsElem[i[n]] = new_pos[n];
00446     }
00447 
00448   return didConverge;
00449 }
00450 
00451 void GaborSnake::transformPath(int i1, const vec2d& new1,
00452                                int i2, const vec2d& new2)
00453 {
00454   const vec2d old1 = itsElem[i1];
00455   const vec2d old2 = itsElem[i2];
00456 
00457   // OK, our jiggling algorithm has determined new locations for node-1 and
00458   // node-2. Now, we want to adjust all the intermediate points
00459   // accordingly. This amounts to (1) a translation (move node-1 from its
00460   // old to new location), (2) a rotation (rotate node-2 around node-1 to
00461   // its new location), and (3) a scaling (contract or expand to put node-2
00462   // in the right spot). We can then apply these same transformations to
00463   // the intermediate points.
00464 
00465   /*                                                                   */
00466   /*               scale by      rotate by (a'-a)                      */
00467   /*               distance         degrees                            */
00468   /*                ratio              |                               */
00469   /*                  |                |                               */
00470   /*                  |                v                               */
00471   /*                  v                                                */
00472   /*    a11  a12              cos a'-a   -sin a'-a                     */
00473   /*  (          ) = d'/d * (                      )                   */
00474   /*    a21  a12              sin a'-a    cos a'-a                     */
00475   /*                                                                   */
00476 
00477   const double d = old1.distance_to(old2);
00478   const double a = old1.angle_to(old2);
00479 
00480   const double dp = new1.distance_to(new2);
00481   const double ap = new1.angle_to(new2);
00482 
00483   const double diff_a = ap - a;
00484 
00485   const double ratio_d = (dp/d);
00486 
00487   const double cos_diff_a = cos(diff_a);
00488   const double sin_diff_a = sin(diff_a);
00489 
00490   const double a11 =   ratio_d * cos_diff_a;
00491   const double a12 = - ratio_d * sin_diff_a;
00492   const double a21 =   ratio_d * sin_diff_a;
00493   const double a22 =   ratio_d * cos_diff_a;
00494 
00495   // Loop over all the nodes in between the nodes given by i1 and i2
00496   for (int n = (i1+1)%itsLength; n != i2; n = (n+1)%itsLength)
00497     {
00498       /*                                              */
00499       /*   x'      c1       a11   a12     x - b1      */
00500       /* (   ) = (    ) + (           ) (        )    */
00501       /*   y'      c2       a21   a12     y - b2      */
00502       /*                                              */
00503 
00504       const double diffx = itsElem[n].x() - old1.x();
00505       const double diffy = itsElem[n].y() - old1.y();
00506 
00507       itsElem[n].x() = new1.x() + a11*diffx + a12*diffy;
00508       itsElem[n].y() = new1.y() + a21*diffx + a22*diffy;
00509     }
00510 }
00511 
00512 // ######################################################################
00513 /* So things look consistent in everyone's emacs... */
00514 /* Local Variables: */
00515 /* mode: c++ */
00516 /* indent-tabs-mode: nil */
00517 /* End: */
00518 
00519 #endif // PSYCHO_GABORSNAKE_C_DEFINED
Generated on Sun May 8 08:41:13 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3