00001 /*!@file RCBot/Motion/MotionEnergy.C detect motion in an image stream */ 00002 // //////////////////////////////////////////////////////////////////// // 00003 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00004 // University of Southern California (USC) and the iLab at USC. // 00005 // See http://iLab.usc.edu for information about this project. // 00006 // //////////////////////////////////////////////////////////////////// // 00007 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00008 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00009 // in Visual Environments, and Applications'' by Christof Koch and // 00010 // Laurent Itti, California Institute of Technology, 2001 (patent // 00011 // pending; application number 09/912,225 filed July 23, 2001; see // 00012 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00013 // //////////////////////////////////////////////////////////////////// // 00014 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00015 // // 00016 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00017 // redistribute it and/or modify it under the terms of the GNU General // 00018 // Public License as published by the Free Software Foundation; either // 00019 // version 2 of the License, or (at your option) any later version. // 00020 // // 00021 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00022 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00023 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00024 // PURPOSE. See the GNU General Public License for more details. // 00025 // // 00026 // You should have received a copy of the GNU General Public License // 00027 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00028 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00029 // Boston, MA 02111-1307 USA. // 00030 // //////////////////////////////////////////////////////////////////// // 00031 // 00032 // Primary maintainer for this file: Lior Elazary <lelazary@yahoo.com> 00033 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/RCBot/Motion/MotionEnergy.C $ 00034 // $Id: MotionEnergy.C 13756 2010-08-04 21:57:32Z siagian $ 00035 // 00036 00037 #include "MotionEnergy.H" 00038 00039 // ###################################################################### 00040 // ##### MotionEnergyPyrBuilder Functions: 00041 // ###################################################################### 00042 00043 //debuging 00044 //#define DEBUG_MotionEnergy 00045 00046 #ifdef DEBUG_MotionEnergy 00047 #include "GUI/DebugWin.H" 00048 #endif 00049 00050 // ###################################################################### 00051 template <class T> 00052 MotionEnergyPyrBuilder<T>::MotionEnergyPyrBuilder(const PyramidType typ, 00053 const float gabor_theta, 00054 const float intens, 00055 const int timeDomainSize, 00056 const float magThreshold ) : 00057 PyrBuilder<T>(), 00058 itsPtype(typ), 00059 itsGaborAngle(gabor_theta), 00060 itsGaborIntens(intens), 00061 itsTimeDomainSize(timeDomainSize), 00062 itsMagThreshold(magThreshold) 00063 { 00064 ASSERT(itsTimeDomainSize == 3 || itsTimeDomainSize == 5); 00065 } 00066 00067 // ###################################################################### 00068 template <class T> 00069 ImageSet<T> MotionEnergyPyrBuilder<T>::build(const Image<T>& image, 00070 const int firstlevel, 00071 const int depth, 00072 PyramidCache<T>* cache) 00073 { 00074 ImageSet<T> result(depth); 00075 return result; 00076 } 00077 00078 // ###################################################################### 00079 template <class T> 00080 void MotionEnergyPyrBuilder<T>::updateMotion(const Image<T>& image, 00081 const int depth) 00082 { 00083 00084 // create a pyramid with the input image 00085 ImageSet<T> pyr = buildPyrGeneric(image, 0, depth, itsPtype, 00086 itsGaborAngle, itsGaborIntens); 00087 00088 imgPyrQ.push_back(pyr); //build the time domain 00089 00090 //the first frame (push the same frame itsTimeDomainSize-1 times) 00091 if (imgPyrQ.size() == 1){ 00092 for(unsigned int i=0; i<itsTimeDomainSize-1; i++) 00093 imgPyrQ.push_back(pyr); //build the time domain 00094 } 00095 00096 if (imgPyrQ.size() > itsTimeDomainSize) //limit the image time queue 00097 imgPyrQ.pop_front(); 00098 } 00099 00100 // ###################################################################### 00101 // get the motion from the image by using sobel grad detector 00102 // to detect edge orientations in the space and time 00103 template <class T> 00104 Image<float> MotionEnergyPyrBuilder<T>::buildHorizontalMotionLevel(int scale) 00105 { 00106 // if we have too few imgs in the Q, just return an empty image: 00107 if (imgPyrQ.size() < itsTimeDomainSize) 00108 return Image<float>(); 00109 00110 int width = imgPyrQ[0][scale].getWidth(); 00111 int height = imgPyrQ[0][scale].getHeight(); 00112 00113 Image<float> motion(width, height, ZEROS); // motion place holder 00114 00115 // to make the operation look nice and readable 00116 // the parenthesis around the x are needed 00117 // otherwise a bug happens when using Pix(0,i-1) 00118 #define Pix(t,x) (*(imgT[t] + (y_pos*width)+(x))) 00119 00120 // build the time domain pointers 00121 typename Image<T>::iterator imgT[itsTimeDomainSize]; 00122 for (unsigned int i=0; i < itsTimeDomainSize; i++){ 00123 imgT[i] = imgPyrQ[i][scale].beginw(); 00124 } 00125 00126 for (int y_pos = 0; y_pos < height; y_pos++) 00127 { 00128 // start and end before the template runs out 00129 for (unsigned int i = (itsTimeDomainSize/2); i < width-(itsTimeDomainSize/2); i++){ 00130 // calculate the sobel Gx and Gy 00131 00132 float Gx = 0, Gy = 0; 00133 // use a sobel 3x3 or 5x5. based on timedomainsize 00134 switch(itsTimeDomainSize){ 00135 // 3x3 sobel operation 00136 case 3: 00137 Gx = -1*Pix(0,i-1) + 0 + 1*Pix(0,i+1) + 00138 -2*Pix(1,i-1) + 0 + 2*Pix(1,i+1) + 00139 -1*Pix(2,i-1) + 0 + 1*Pix(2,i+1); 00140 00141 Gy = 1*Pix(0,i-1) + 2*Pix(0,i) + 1*Pix(0,i+1) + 00142 0*Pix(1,i-1) + 0*Pix(1,i) + 0*Pix(1,i+1) + 00143 -1*Pix(2,i-1) + -2*Pix(2,i) + -1*Pix(2,i+1); 00144 break; 00145 00146 // 5x5 sobel operation 00147 case 5: 00148 Gx = -1*Pix(0,i-2) + -2*Pix(0,i-1) + 0*Pix(0,i) + 2*Pix(0,i+1) + 1*Pix(0,i+2) + 00149 -4*Pix(1,i-2) + -8*Pix(1,i-1) + 0*Pix(1,i) + 8*Pix(1,i+1) + 4*Pix(1,i+2) + 00150 -6*Pix(2,i-2) + -12*Pix(2,i-1) + 0*Pix(2,i) + 12*Pix(2,i+1) + 6*Pix(2,i+2) + 00151 -4*Pix(3,i-2) + -8*Pix(3,i-1) + 0*Pix(3,i) + 8*Pix(3,i+1) + 4*Pix(3,i+2) + 00152 -1*Pix(4,i-2) + -2*Pix(4,i-1) + 0*Pix(4,i) + 2*Pix(4,i+1) + 1*Pix(4,i+2); 00153 00154 Gy = 1*Pix(0,i-2) + 4*Pix(0,i-1) + 6*Pix(0,i) + 4*Pix(0,i+1) + 1*Pix(0,i+2) + 00155 2*Pix(1,i-2) + 8*Pix(1,i-1) + 12*Pix(1,i) + 8*Pix(1,i+1) + 2*Pix(1,i+2) + 00156 0*Pix(2,i-2) + 0*Pix(2,i-1) + 0*Pix(2,i) + 0*Pix(2,i+1) + 0*Pix(2,i+2) + 00157 -2*Pix(3,i-2) + -8*Pix(3,i-1) + -12*Pix(3,i) + -8*Pix(3,i+1) + -2*Pix(3,i+2) + 00158 -1*Pix(4,i-2) + -4*Pix(4,i-1) + -6*Pix(4,i) + -4*Pix(4,i+1) + -1*Pix(4,i+2); 00159 default: 00160 break; 00161 } 00162 00163 double mag = sqrt((Gx*Gx) + (Gy*Gy)); 00164 double angle = 0; // this represents the vel of the motion. 90 no motion. 00165 00166 if (mag > itsMagThreshold){ // try to not process motion as a result of noise 00167 // divide x by y and not y by x as in the orig sobel alg 00168 // this is to get the vector oriented perallel to the edge 00169 00170 if (Gy == 0){ // watch for divide by 0 00171 angle = atan(0); // + M_PI/2; 00172 } else { 00173 angle = atan((double)(Gx/Gy)); // + (M_PI/2); 00174 } 00175 00176 // flip angles to be at the top. 00177 // Could also be done by changing the convX and convY 00178 angle *= -1; 00179 00180 // Have all the angles pointing in the positive direction by 00181 // flipping the angle if its less then 0 00182 if (angle < 0) angle += M_PI; 00183 00184 // dismiss any angles that violates the motion 00185 if (angle > 0.15 && angle < M_PI-0.15) { 00186 motion.setVal(i, y_pos, (float)(angle) - (M_PI/2)); 00187 } else { 00188 motion.setVal(i, y_pos,0); 00189 } 00190 } else { 00191 motion.setVal(i, y_pos, 0); 00192 } 00193 } 00194 } 00195 00196 return motion; 00197 } 00198 00199 // ###################################################################### 00200 // get the motion from the image by using sobel grad detector 00201 // to detect edge orientations in the space and time 00202 template <class T> 00203 Image<float> MotionEnergyPyrBuilder<T>::buildVerticalMotionLevel(int scale) 00204 { 00205 // if we have too few imgs in the Q, just return an empty image: 00206 if (imgPyrQ.size() < itsTimeDomainSize) 00207 return Image<float>(); 00208 00209 int width = imgPyrQ[0][scale].getWidth(); 00210 int height = imgPyrQ[0][scale].getHeight(); 00211 00212 Image<float> motion(width, height, ZEROS); // motion place holder 00213 00214 // build the time domain pointers 00215 typename Image<T>::iterator imgT[itsTimeDomainSize]; 00216 for (unsigned int i=0; i<itsTimeDomainSize; i++) 00217 { 00218 imgT[i] = imgPyrQ[i][scale].beginw(); 00219 } 00220 00221 for (int x_pos = 0; x_pos<width; x_pos++){ 00222 00223 #ifdef DEBUG_MotionEnergy 00224 // build an image of one slice throught the time and y domain 00225 Image<byte> debugImg(itsTimeDomainSize, height, ZEROS); 00226 #endif 00227 00228 // start and end before the template runs out 00229 for (unsigned int i = (itsTimeDomainSize/2); i < height-(itsTimeDomainSize/2); i++) 00230 { 00231 // calculate the sobel Gx and Gy 00232 00233 float Gx=0, Gy=0; 00234 00235 // to make the operation look nice and readable 00236 // the parenthesis around the y are needed because when 00237 // otherwise a bug happends when using Pix2(0,i-1) 00238 #define Pix2(t,y) (*(imgT[t] + ((y)*width)+x_pos)) 00239 00240 #ifdef DEBUG_MotionEnergy 00241 // build the image 00242 for (uint j=0; j<itsTimeDomainSize; j++) 00243 { 00244 debugImg.setVal(j,i,Pix2(j,i)); 00245 } 00246 #endif 00247 // use a sobel 3x3 or 5x5. based on timedomainsize 00248 switch(itsTimeDomainSize){ 00249 // 3x3 sobel operation 00250 case 3: 00251 Gx = -1*Pix2(0,i+1) + 0 + 1*Pix2(2,i+1) + 00252 -2*Pix2(0,i) + 0 + 2*Pix2(2,i) + 00253 -1*Pix2(0,i-1) + 0 + 1*Pix2(2,i-1); 00254 00255 Gy = 1*Pix2(0,i+1) + 2*Pix2(1,i+1) + 1*Pix2(2,i+1) + 00256 0*Pix2(0,i) + 0*Pix2(1,i) + 0*Pix2(2,i) + 00257 -1*Pix2(0,i-1) + -2*Pix2(1,i-1) + -1*Pix2(2,i-1); 00258 00259 break; 00260 00261 // 5x5 sobel operation 00262 case 5: 00263 Gx = -1*Pix2(0,i+2) + -2*Pix2(1,i+2) + 0*Pix2(2,i+2) + 2*Pix2(3,i+2) + 1*Pix2(4,i+2) + 00264 -4*Pix2(0,i+1) + -8*Pix2(1,i+1) + 0*Pix2(2,i+1) + 8*Pix2(3,i+1) + 4*Pix2(4,i+1) + 00265 -6*Pix2(0,i) + -12*Pix2(1,i) + 0*Pix2(2,i) + 12*Pix2(3,i) + 6*Pix2(4,i) + 00266 -4*Pix2(0,i-1) + -8*Pix2(1,i-1) + 0*Pix2(2,i-1) + 8*Pix2(3,i-1) + 4*Pix2(4,i-1) + 00267 -1*Pix2(0,i-2) + -2*Pix2(1,i-2) + 0*Pix2(2,i-2) + 2*Pix2(3,i-2) + 1*Pix2(4,i-2); 00268 00269 Gy = 1*Pix2(0,i+2) + 4*Pix2(1,i+2) + 6*Pix2(2,i+2) + 4*Pix2(3,i+2) + 1*Pix2(4,i+2) + 00270 2*Pix2(0,i+1) + 8*Pix2(1,i+1) + 12*Pix2(2,i+1) + 8*Pix2(3,i+1) + 2*Pix2(4,i+1) + 00271 0*Pix2(0,i) + 0*Pix2(1,i) + 0*Pix2(2,i) + 0*Pix2(3,i) + 0*Pix2(4,i) + 00272 -2*Pix2(0,i-1) + -8*Pix2(1,i-1) + -12*Pix2(2,i-1) + -8*Pix2(3,i-1) + -2*Pix2(4,i-1) + 00273 -1*Pix2(0,i-2) + -4*Pix2(1,i-2) + -6*Pix2(2,i-2) + -4*Pix2(3,i-2) + -1*Pix2(4,i-2); 00274 default: 00275 break; 00276 } 00277 00278 double mag = sqrt((Gx*Gx) + (Gy*Gy)); 00279 double angle = 0; // this represents the vel of the motion. 90 no motion. 00280 00281 if (mag > itsMagThreshold){ // try to not process motion as a result of noise 00282 // divide x by y and not y by x as in the orig sobel alg 00283 // this is to get the vector oriented perallel to the edge 00284 00285 if (Gy == 0){ // watch for divide by 0 00286 angle = atan(0); // + M_PI/2; 00287 } else { 00288 angle = atan((double)(Gx/Gy)); // + (M_PI/2); 00289 } 00290 00291 angle *= -1; // flip angles to be at the top. Could also be done by changing the 00292 // convX and convY 00293 00294 // Have all the angles pointing in the positive direction by 00295 // fliping the angle if its less then 0 00296 if (angle < 0) angle += M_PI; 00297 00298 // dismiss any angles that violate the motion 00299 if (angle > 0.15 && angle < M_PI-0.15) { 00300 motion.setVal(x_pos, i, (float)(angle) - (M_PI/2)); 00301 } else { 00302 motion.setVal(x_pos, i, 0); 00303 } 00304 } else { 00305 motion.setVal(x_pos, i, 0); 00306 } 00307 00308 } 00309 #ifdef DEBUG_MotionEnergy 00310 Image<PixRGB<byte> > img = toRGB(rescale(debugImg, 256, 256)); 00311 SHOWIMG(img); 00312 //SHOWIMG(rescale(debugImg,Dims(256, 256))); 00313 #endif 00314 } 00315 00316 return motion; 00317 } 00318 00319 // ###################################################################### 00320 template <class T> 00321 ImageSet<float> MotionEnergyPyrBuilder<T>::buildHorizontalMotion() 00322 { 00323 const int depth = imgPyrQ[0].size(); 00324 00325 ImageSet<float> result(depth); 00326 00327 for (int scale = 0; scale < depth; ++scale) 00328 result[scale] = buildHorizontalMotionLevel(scale); 00329 00330 return result; 00331 } 00332 00333 // ###################################################################### 00334 // get the motion from the image by using sobel grad detector to detect edge orientations 00335 // in the space and time 00336 template <class T> 00337 ImageSet<float> MotionEnergyPyrBuilder<T>::buildVerticalMotion() 00338 { 00339 const int depth = imgPyrQ[0].size(); 00340 00341 ImageSet<float> result(depth); 00342 00343 for (int scale = 0; scale < depth; scale++) 00344 result[scale] = buildVerticalMotionLevel(scale); 00345 00346 return result; 00347 } 00348 00349 // ###################################################################### 00350 template <class T> 00351 float MotionEnergyPyrBuilder<T>::DrawVectors(Image<T> &img, Image<float> &motion) 00352 { 00353 //TODO: should check the mag is the same size as dir 00354 00355 Image<float>::const_iterator mag_ptr = motion.begin(); 00356 Image<float>::const_iterator mag_stop = motion.end(); 00357 00358 int inx=0; 00359 int avg_i = 0; 00360 double avg_angle = 0; 00361 00362 while (mag_ptr != mag_stop) 00363 { 00364 int y = inx/motion.getWidth(); 00365 int x = inx - (y*motion.getWidth()); 00366 00367 if (*mag_ptr != 0) { 00368 avg_i++; 00369 //avg_angle += (*mag_ptr+M_PI/2); 00370 avg_angle += (*mag_ptr); 00371 00372 int scale_x = x * (img.getWidth()/motion.getWidth()); 00373 int scale_y = y * (img.getHeight()/motion.getHeight()); 00374 drawLine(img, Point2D<int>(scale_x,scale_y), 00375 Point2D<int>((int)(scale_x+25*cos((*mag_ptr))), 00376 (int)(scale_y-25*sin((*mag_ptr)))), 00377 (byte)0); 00378 } 00379 mag_ptr++; 00380 inx++; 00381 } 00382 00383 if (avg_i > 0){ 00384 int xi = img.getWidth()/2; 00385 int yi = img.getHeight()/2; 00386 00387 drawLine(img,Point2D<int>(xi, yi), 00388 Point2D<int>((int)(xi+75*cos(avg_angle/avg_i)), 00389 (int)(yi-75*sin(avg_angle/avg_i))), 00390 (byte)0, 3); 00391 return avg_angle/avg_i; 00392 } 00393 00394 return -999; 00395 } 00396 00397 // ###################################################################### 00398 template <class T> 00399 MotionEnergyPyrBuilder<T>* MotionEnergyPyrBuilder<T>::clone() const 00400 { return new MotionEnergyPyrBuilder<T>(*this); } 00401 00402 // ###################################################################### 00403 template <class T> 00404 void MotionEnergyPyrBuilder<T>::reset() 00405 { 00406 // imgPyrQ.clear(); 00407 } 00408 00409 template class MotionEnergyPyrBuilder<byte>; 00410 00411 // ###################################################################### 00412 /* So things look consistent in everyone's emacs... */ 00413 /* Local Variables: */ 00414 /* indent-tabs-mode: nil */ 00415 /* End: */