00001 /*!@file Beobot/ImageSpring.C derived from the image template class; all the 00002 pixels are linked to theirs neighbors with springs */ 00003 00004 // //////////////////////////////////////////////////////////////////// // 00005 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00006 // University of Southern California (USC) and the iLab at USC. // 00007 // See http://iLab.usc.edu for information about this project. // 00008 // //////////////////////////////////////////////////////////////////// // 00009 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00010 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00011 // in Visual Environments, and Applications'' by Christof Koch and // 00012 // Laurent Itti, California Institute of Technology, 2001 (patent // 00013 // pending; application number 09/912,225 filed July 23, 2001; see // 00014 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00015 // //////////////////////////////////////////////////////////////////// // 00016 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00017 // // 00018 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00019 // redistribute it and/or modify it under the terms of the GNU General // 00020 // Public License as published by the Free Software Foundation; either // 00021 // version 2 of the License, or (at your option) any later version. // 00022 // // 00023 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00024 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00025 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00026 // PURPOSE. See the GNU General Public License for more details. // 00027 // // 00028 // You should have received a copy of the GNU General Public License // 00029 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00030 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00031 // Boston, MA 02111-1307 USA. // 00032 // //////////////////////////////////////////////////////////////////// // 00033 // 00034 // Primary maintainer for this file: Laurent Itti <itti@usc.edu> 00035 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Beobot/ImageSpring.C $ 00036 // $Id: ImageSpring.C 9412 2008-03-10 23:10:15Z farhan $ 00037 // 00038 00039 #include "Beobot/ImageSpring.H" 00040 00041 #include "Channels/Jet.H" 00042 #include "Image/ColorOps.H" 00043 #include "Image/DrawOps.H" 00044 #include "Image/FilterOps.H" 00045 #include "Image/IO.H" 00046 #include "Image/Image.H" 00047 #include "Image/MathOps.H" 00048 #include "Image/Pixels.H" 00049 #include "Image/ShapeOps.H" 00050 #include "Image/Transforms.H" 00051 #include "Util/Assert.H" 00052 #include "Util/MathFunctions.H" 00053 00054 00055 // ###################################################################### 00056 template<class T> 00057 void ImageSpring<T>::getStats() 00058 { 00059 ASSERT(this->initialized()); 00060 00061 // initialize mean as a zero Jet, same size as the image elements 00062 // and initialize the others on the same model 00063 mean = this->getVal(0); mean *= 0.0f; weight = mean; // initialize Jet for weight 00064 stdev = mean; T mom2(mean); 00065 00066 for (int n = 0; n < nbHeuristic; n++) 00067 { 00068 T randomPixel = this->getVal(randomUpToNotIncluding(this->getSize())); 00069 mean += randomPixel; 00070 inplaceSquare(randomPixel); 00071 mom2 += randomPixel; 00072 } 00073 mean /= (float)nbHeuristic; 00074 mom2 /= (float)nbHeuristic; 00075 T mean2(mean); inplaceSquare(mean2); 00076 00077 // E( ( X-E(X) )^2) = E(X^2)-E(X)^2 00078 // we might have negative values because of precision issues 00079 stdev = mom2; 00080 stdev -= mean2; 00081 inplaceRectify(stdev); 00082 stdev = sqrt(stdev); 00083 weight = inverse(stdev, 1.0f); // load weight values 00084 } 00085 00086 // ###################################################################### 00087 template<class T> 00088 void ImageSpring<T>::getStatsDist() 00089 { 00090 ASSERT(this->initialized()); 00091 meanDist = 0.0; double mom2 = 0.0; 00092 00093 for (int n = 0; n < nbHeuristic; n++) 00094 { 00095 int randomIndex = randomUpToNotIncluding(this->getSize()); 00096 T randomPixel = this->getVal(randomIndex); 00097 00098 bool neighborDefined = false; int neighborNumber, neighborIndex; 00099 int attempt = 0; 00100 while (! neighborDefined && attempt++ < 10) 00101 { 00102 neighborNumber = randomUpToNotIncluding(nbNeighbors) + 1; 00103 neighborDefined = 00104 getNeighbor(randomIndex, neighborNumber, neighborIndex); 00105 } 00106 if (neighborDefined) { 00107 T randomNeighbor = this->getVal(neighborIndex); 00108 double d = distance(randomPixel, randomNeighbor, weight); 00109 meanDist += d; mom2 += squareOf(d); 00110 } else n --; 00111 } 00112 meanDist /= (double)nbHeuristic; 00113 mom2 /= (double)nbHeuristic; 00114 00115 // E( ( X-E(X) )^2) = E(X^2)-E(X)^2 00116 stdevDist = sqrt(mom2 - squareOf(meanDist)); 00117 } 00118 00119 // ###################################################################### 00120 template<class T> 00121 void ImageSpring<T>::initClustering(bool initPosMasses) 00122 { 00123 // at the begining the masses and the pixel are at the same place 00124 if (initPosMasses) 00125 { 00126 for (int x = 0; x < this->getWidth(); x++) 00127 for (int y = 0; y < this->getHeight(); y++) 00128 setPos(Point2D<int>(x, y), float(x), float(y)); 00129 memcpy(oldPosX, posX, this->getSize() * sizeof(float)); 00130 memcpy(oldPosY, posY, this->getSize() * sizeof(float)); 00131 } 00132 00133 // initialize the image statistics & springs stiffnesses: 00134 getStats(); getStatsDist(); computeStiff(); 00135 } 00136 00137 // ###################################################################### 00138 template<class T> 00139 void ImageSpring<T>::computeStiff(void) 00140 { 00141 for (int currentIndex = 0; currentIndex < this->getSize(); currentIndex++) 00142 { 00143 Point2D<int> currentPoint; 00144 getXY(currentIndex, currentPoint); 00145 00146 // the masses should always try to come back to the pixels 00147 // they come from (to ensure spatial coherence): 00148 setStiff(currentPoint, 0, 0.5f); 00149 00150 T currval = this->getVal(currentIndex); 00151 00152 for (int n = 1; n <= nbNeighbors; n++) 00153 { 00154 int neighborIndex; 00155 if (getNeighbor(currentIndex, n, neighborIndex)) 00156 { 00157 float alikeness = distance(currval,this->getVal(neighborIndex),weight); 00158 00159 // we normalize that; 00160 // note the - sign (alikeness high if distance small) 00161 alikeness = -2.0f * (alikeness - meanDist) / stdevDist; 00162 00163 if (alikeness > 0.0f) setStiff(currentPoint, n, alikeness); 00164 else setStiff(currentPoint, n, 0.0); 00165 } 00166 } 00167 } 00168 } 00169 00170 // ###################################################################### 00171 template<class T> 00172 void ImageSpring<T>::computePos( const float dt ) 00173 { 00174 float *newPosX = new float[this->getSize()]; 00175 float *newPosY = new float[this->getSize()]; 00176 00177 for (int x = 0; x < this->getWidth(); x++) 00178 for (int y = 0; y < this->getHeight(); y++) 00179 { 00180 int ci; getIndex(Point2D<int>(x, y), ci); 00181 00182 // the position of the current mass 00183 float X = posX[ci], Y = posY[ci]; 00184 00185 // the force on the current mass; first from the grid: 00186 float fx = stiff[ci][0] * (float(x) - X); 00187 float fy = stiff[ci][0] * (float(y) - Y); 00188 00189 for (int n = 1; n <= nbNeighbors; n++) 00190 { 00191 int neighborIndex; 00192 if (getNeighbor(ci, n, neighborIndex)) 00193 { 00194 // the stiffness of the current spring 00195 float stif = stiff[ci][n]; 00196 00197 // update the force 00198 fx += stif * (posX[neighborIndex] - X); 00199 fy += stif * (posY[neighborIndex] - Y); 00200 } 00201 } 00202 00203 // friction 00204 const float gamma = 1.0f; 00205 00206 // update position according to equations 00207 newPosX[ci] = 2.0f * posX[ci] - oldPosX[ci] + dt * dt * fx 00208 - dt * gamma * (posX[ci] - oldPosX[ci]); 00209 newPosY[ci] = 2.0f * posY[ci] - oldPosY[ci] + dt * dt * fy 00210 - dt * gamma * (posY[ci] - oldPosY[ci]); 00211 } 00212 00213 // update the position arrays: 00214 delete [] oldPosX; delete [] oldPosY; oldPosX = posX; oldPosY = posY; 00215 posX = newPosX; posY = newPosY; 00216 } 00217 00218 // ###################################################################### 00219 template<class T> 00220 float ImageSpring<T>::getDistanceMasses(const int idx1, const int idx2) const 00221 { return sqrt(squareOf(posX[idx1]-posX[idx2]) + squareOf(posY[idx1]-posY[idx2])); } 00222 00223 // ###################################################################### 00224 template <class T> 00225 void ImageSpring<T>::getPositions(Image< PixRGB<byte> >& img, const int zoom) 00226 { 00227 img.resize(this->getWidth() * zoom, this->getHeight() * zoom, true); // clear 00228 float maxstiff = 0.0; 00229 00230 // first draw the springs: 00231 for (int i = 0; i < this->getSize(); i ++) 00232 { 00233 Point2D<int> pp(int(zoom * (posX[i] + 0.5)), int(zoom * (posY[i] + 0.5))); 00234 int ni; 00235 00236 for (int n = 1; n <= nbNeighbors; n ++) 00237 if (getNeighbor(i, n, ni)) 00238 { 00239 Point2D<int> np(int(zoom * (posX[ni]+0.5)), int(zoom * (posY[ni]+0.5))); 00240 drawLine(img, pp, np, 00241 PixRGB<byte>(150.0f + 50.0f * stiff[i][n], 00242 100.0f * stiff[i][n], 00243 0)); 00244 if (stiff[i][n] > maxstiff) maxstiff = stiff[i][n]; 00245 } 00246 } 00247 //LDEBUG("Max stiffness = %f", maxstiff); 00248 00249 // now draw the masses: 00250 PixRGB<byte> blue(0, 0, 255); 00251 for (int i = 0; i < this->getSize(); i ++) 00252 { 00253 Point2D<int> pp(int(zoom * (posX[i] + 0.5)), int(zoom * (posY[i] + 0.5))); 00254 drawDisk(img, pp, 2, blue); 00255 } 00256 } 00257 00258 //###################################################################### 00259 template <class T> 00260 void ImageSpring<T>::goGraph(Image<int32> &marked, const int currentIndex, 00261 const int32 color, const int begin) 00262 { 00263 // how many of my neighbors are already in the group 'color' ? 00264 int nbNeighborsSameColor = 0, neighborIndex; 00265 for (int n = 1; n <= nbNeighbors; n ++) 00266 if (getNeighbor(currentIndex, n, neighborIndex) && 00267 marked.getVal(neighborIndex) == color) 00268 nbNeighborsSameColor ++; 00269 00270 // if less than 5 and we are not just starting the algorithm 00271 // then forget it, I'm not really in this group 00272 if (begin <= 0 && nbNeighborsSameColor < 5) return; 00273 00274 // Yes, I have many friends in this group, mark me as belonging there 00275 marked.setVal(currentIndex, color); 00276 00277 // and check if my unmarked and spatially close neighbors are also 00278 // in the group or not. We decrement begin because the algo is at 00279 // least partly initialized 00280 for (int n = 1; n <= nbNeighbors; n ++) 00281 if (getNeighbor(currentIndex, n, neighborIndex) && 00282 marked.getVal(neighborIndex) == 0 && 00283 getDistanceMasses(currentIndex, neighborIndex) < 1.0) 00284 goGraph(marked, neighborIndex, color, begin - 1); 00285 } 00286 00287 //###################################################################### 00288 template <class T> 00289 void ImageSpring<T>::getClusteredImage(const Image< PixRGB<byte> > &scene, 00290 Image< PixRGB<byte> > &clusteredImage, 00291 Point2D<int> &supposedTrackCentroid, 00292 const Point2D<int>& previousTrackCentroid) 00293 { 00294 int preferedMeanX = 0, preferedMeanY = 0; 00295 00296 Image<int32> marked(this->getDims(), ZEROS); 00297 Image< PixRGB<byte> > clustered(scene.getDims(), NO_INIT); 00298 00299 int pX = scene.getWidth() / this->getWidth(); 00300 int pY = scene.getHeight() / this->getHeight(); 00301 00302 PixRGB<byte> whitePixel(255, 255, 255); 00303 00304 float highestScore = std::numeric_limits<float>::max(); 00305 // to make sure that it will be initialized by first cluster 00306 00307 // this color is not a color but merely a label 00308 // this is used in image 'marked', 0 being not marked 00309 int32 color = 0; 00310 00311 for (int x = 0; x < this->getWidth(); x++ ) 00312 for (int y = 0; y < this->getHeight(); y++ ) 00313 { 00314 color ++; 00315 00316 int meanX = 0, meanY = 0; 00317 00318 if (marked.getVal(x, y) == 0) // pixel is NOT marked yet 00319 { 00320 // marks all connex pixels to color 00321 goGraph(marked, x + this->getWidth() * y, color); 00322 00323 PixRGB<int32> meanPixel(0, 0, 0); 00324 PixRGB<byte> tmpPixel; 00325 int nbPixels = 0; 00326 00327 // get mean pixel value on newly marked zone: 00328 for (int xp = 0; xp < this->getWidth(); xp ++ ) 00329 for (int yp = 0; yp < this->getHeight(); yp ++) 00330 if (marked.getVal(xp, yp) == color) // this point just marked 00331 for (int dx = 0; dx < pX; dx ++) 00332 for (int dy = 0; dy < pY; dy ++) 00333 { 00334 scene.getVal(xp * pX + dx, yp * pY + dy, tmpPixel); 00335 meanPixel += tmpPixel; 00336 meanX += xp * pX + dx; 00337 meanY += yp * pY + dy; 00338 nbPixels ++; 00339 } 00340 meanPixel /= nbPixels; meanX /= nbPixels; meanY /= nbPixels; 00341 00342 // do not consider very small groups: 00343 bool ignoreGroup; 00344 if (nbPixels < scene.getWidth() * scene.getHeight() / 30) 00345 { meanPixel.set(0, 0, 0); ignoreGroup = true; } 00346 else 00347 ignoreGroup=false; 00348 00349 // set all pixels of that zone in 'clustered' 00350 // to that value 00351 for (int xp = 0; xp < this->getWidth(); xp ++) 00352 for (int yp = 0; yp < this->getHeight(); yp ++) 00353 if (marked.getVal(xp, yp) == color) // this point just marked 00354 for (int dx = 0; dx < pX; dx ++) 00355 for (int dy = 0; dy < pY; dy++) 00356 clustered.setVal(xp * pX + dx, yp * pY + dy, meanPixel); 00357 00358 // draw the cross at center of gravity 00359 if (!ignoreGroup) 00360 drawCross(clustered, Point2D<int>(meanX, meanY), whitePixel); 00361 00362 #define LIKE_CENTER 1.0 00363 #define LIKE_PREVIOUS 1.0 00364 #define LIKE_BIG 1.0 00365 00366 /* 00367 These defines define the behaviour of the algo which will 00368 determine which of the centroids is the track centroid. 00369 00370 We minimize a cost function : 00371 00372 LIKE_CENTER : importance of the centroid being close to 00373 the center bottom of the screen 00374 00375 LIKE_PREVIOUS : ... 00376 the previous centroid 00377 00378 LIKE_BIG : ... being the centroid of a bug cluster 00379 */ 00380 00381 float currentScore = 00382 LIKE_CENTER * 00383 sqrt( double( (float)squareOf(meanX - clustered.getWidth()/2) + 00384 (float)squareOf(meanY - clustered.getHeight()) ) ) 00385 / (float)clustered.getHeight() 00386 + LIKE_PREVIOUS * 00387 sqrt( double( squareOf(meanX - previousTrackCentroid.i) + 00388 squareOf(meanY - previousTrackCentroid.j) ) ) 00389 / (float)clustered.getHeight() 00390 - LIKE_BIG * ( (float)nbPixels / ((float)clustered.getHeight()* 00391 (float)clustered.getWidth())); 00392 /* note: it's kind of messy but the idea is that all the 00393 components be more or less between 0.0 and 1.0 */ 00394 00395 if ((currentScore < highestScore) && (!ignoreGroup)) 00396 // we have a new champion ! 00397 { 00398 preferedMeanX = meanX; preferedMeanY = meanY; 00399 highestScore = currentScore; 00400 } 00401 00402 } 00403 } 00404 00405 // dashed line in the center of the screen 00406 int xp = clustered.getWidth() / 2; 00407 for(int yp = 0; yp < clustered.getHeight(); yp ++) 00408 if (yp % 3 == 0) clustered.setVal(xp, yp, whitePixel); 00409 00410 // thicker cross at track center of gravity 00411 drawCross(clustered, Point2D<int>(preferedMeanX, preferedMeanY), whitePixel, 5, 2); 00412 00413 // output of the function 00414 clusteredImage = clustered; 00415 supposedTrackCentroid.i = preferedMeanX; 00416 supposedTrackCentroid.j = preferedMeanY; 00417 } 00418 00419 // ###################################################################### 00420 // Instantiate for float Jets: 00421 template class ImageSpring< Jet<float> >; 00422 00423 // ###################################################################### 00424 /* So things look consistent in everyone's emacs... */ 00425 /* Local Variables: */ 00426 /* indent-tabs-mode: nil */ 00427 /* End: */