00001 /*!@file SceneUnderstanding/TensorVoting.C */ 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: Lior Elazary <elazary@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/plugins/SceneUnderstanding/TensorVoting.C $ 00035 // $Id: TensorVoting.C 13878 2010-09-03 18:45:11Z lior $ 00036 // 00037 00038 #ifndef TensorVoting_C_DEFINED 00039 #define TensorVoting_C_DEFINED 00040 00041 #include "plugins/SceneUnderstanding/TensorVoting.H" 00042 00043 #include "Image/DrawOps.H" 00044 #include "Image/MathOps.H" 00045 //#include "Image/OpenCVUtil.H" 00046 #include "Image/Kernels.H" 00047 #include "Image/FilterOps.H" 00048 #include "Image/Transforms.H" 00049 #include "Image/fancynorm.H" 00050 #include "Image/Convolutions.H" 00051 #include "Simulation/SimEventQueue.H" 00052 #include "GUI/DebugWin.H" 00053 //#include "Image/OpenCVUtil.H" 00054 #include <math.h> 00055 #include <fcntl.h> 00056 #include <limits> 00057 #include <string> 00058 00059 // ###################################################################### 00060 TensorVoting::TensorVoting() : 00061 itsSigma(2) 00062 { 00063 createTensorFields(itsSigma); 00064 //generateImage(); 00065 } 00066 00067 void TensorVoting::createTensorFields(float sigma) 00068 { 00069 //Create the tensor fields first, so we can save time latter 00070 itsBallField = createBallTensorField(sigma); 00071 00072 itsStickFields.clear(); 00073 itsStickFields.resize(180); 00074 00075 for(int i=0; i<180; i++) 00076 { 00077 float u = cos(i*M_PI/180); 00078 float v = sin(i*M_PI/180); 00079 TensorField stick = createStickTensorField(u, v, sigma); 00080 itsStickFields[i] = stick; 00081 } 00082 00083 } 00084 00085 00086 void TensorVoting::generateImage() 00087 { 00088 //This is used for testing 00089 inputImg = Image<float>(241, 521, ZEROS); 00090 for(uint i=0; i<inputImg.size(); i++) 00091 inputImg[i] = -1; 00092 inputImg.setVal(21,266,0.000000); 00093 inputImg.setVal(23,303,6.709837); 00094 inputImg.setVal(23,306,0.000000); 00095 inputImg.setVal(27,334,14.036243); 00096 inputImg.setVal(32,356,14.036243); 00097 inputImg.setVal(37,372,15.945396); 00098 inputImg.setVal(48,399,24.443954); 00099 inputImg.setVal(57,418,24.443954); 00100 inputImg.setVal(64,427,32.005383); 00101 inputImg.setVal(76,443,39.805573); 00102 inputImg.setVal(89,456,53.130100); 00103 inputImg.setVal(109,470,49.398705); 00104 inputImg.setVal(131,478,73.300758); 00105 inputImg.setVal(140,478,90.000000); 00106 inputImg.setVal(173,485,60.945396); 00107 inputImg.setVal(185,493,57.994617); 00108 inputImg.setVal(199,503,50.194427); 00109 inputImg.setVal(218,516,75.963753); 00110 inputImg.setVal(231,520,63.434948); 00111 inputImg.setVal(236,517,233.130096); 00112 inputImg.setVal(240,496,206.565048); 00113 inputImg.setVal(223,481,251.565048); 00114 inputImg.setVal(213,475,233.130096); 00115 inputImg.setVal(186,450,219.805573); 00116 inputImg.setVal(182,439,200.556046); 00117 inputImg.setVal(174,418,203.962494); 00118 inputImg.setVal(167,405,206.565048); 00119 inputImg.setVal(152,373,203.962494); 00120 inputImg.setVal(143,348,197.525574); 00121 inputImg.setVal(136,328,197.525574); 00122 inputImg.setVal(133,309,187.594650); 00123 inputImg.setVal(131,303,180.000000); 00124 inputImg.setVal(129,279,180.000000); 00125 inputImg.setVal(130,258,180.000000); 00126 inputImg.setVal(131,244,180.000000); 00127 inputImg.setVal(134,216,171.869904); 00128 inputImg.setVal(136,203,165.963760); 00129 inputImg.setVal(144,173,163.300751); 00130 inputImg.setVal(152,151,159.443954); 00131 inputImg.setVal(161,131,156.037506); 00132 inputImg.setVal(172,109,151.699249); 00133 inputImg.setVal(175,101,158.198593); 00134 inputImg.setVal(185,72,164.744888); 00135 inputImg.setVal(187,59,168.690063); 00136 inputImg.setVal(189,46,180.000000); 00137 inputImg.setVal(177,18,243.434952); 00138 inputImg.setVal(168,15,270.000000); 00139 inputImg.setVal(140,19,291.801422); 00140 inputImg.setVal(122,27,300.963745); 00141 inputImg.setVal(102,44,320.194427); 00142 inputImg.setVal(89,61,323.130096); 00143 inputImg.setVal(80,74,333.434937); 00144 inputImg.setVal(65,103,335.224854); 00145 inputImg.setVal(60,114,338.198578); 00146 inputImg.setVal(50,138,338.198578); 00147 inputImg.setVal(42,160,340.016907); 00148 inputImg.setVal(38,173,345.963745); 00149 inputImg.setVal(33,196,345.963745); 00150 inputImg.setVal(28,216,348.690063); 00151 inputImg.setVal(23,248,353.290161); 00152 inputImg.setVal(23,250,0.000000); 00153 00154 00155 } 00156 00157 // ###################################################################### 00158 TensorVoting::~TensorVoting() 00159 { 00160 } 00161 00162 00163 TensorField TensorVoting::transImage(Image<float>& img) 00164 { 00165 00166 TensorField tensorField(img.getDims(), ZEROS); 00167 00168 for(int j=0; j<img.getHeight(); j++) 00169 for(int i=0; i<img.getWidth(); i++) 00170 { 00171 float val = img.getVal(i,j); 00172 if (val >= 0) 00173 { 00174 double x = cos((img.getVal(i)*M_PI/180) + (90*M_PI)/180); 00175 double y = sin((img.getVal(i)*M_PI/180) + (90*M_PI)/180); 00176 00177 tensorField.t1.setVal(img.getWidth()+1-i, j, x*x); 00178 tensorField.t2.setVal(img.getWidth()+1-i, j, x*y); 00179 tensorField.t3.setVal(img.getWidth()+1-i, j, x*y); 00180 tensorField.t4.setVal(img.getWidth()+1-i, j, y*y); 00181 } 00182 } 00183 00184 return tensorField; 00185 } 00186 00187 TensorField TensorVoting::createStickTensorField(float u, float v, float sigma) 00188 { 00189 // Calculate the window size from sigma using 00190 // equation 5.7 from Emerging Topics in Computer Vision 00191 // make the field odd, if it turns out to be even. 00192 double ws = floor( ceil(sqrt(-log(0.01)*sigma*sigma)*2) / 2 )*2 + 1; 00193 double wHalf = (ws-1)/2; 00194 00195 //// Turn the unit vector into a rotation matrix 00196 //double btheta = atan2(v,u); 00197 00198 00199 //Generate our theta's at each point in the 00200 //field, adjust by our base theta so we rotate 00201 //in funcion. Also generate the attenuation field at the same time 00202 //This is taken from Equation 00203 //5.2 in Emerging Topics in Computer Vision. Note our 00204 //thetas must be symmetric over the Y axis for the arc 00205 //length to be correct so there's a bit of a coordinate 00206 //translation. 00207 00208 Image<float> theta((int)ws, (int)ws, ZEROS); 00209 Image<float> DF((int)ws, (int)ws, ZEROS); 00210 double norm = sqrt(u*u + v*v); 00211 double uNorm = u/norm; 00212 double vNorm = v/norm; 00213 for(int j=0; j<ws; j++) 00214 for(int i=0; i<ws; i++) 00215 { 00216 double Zj = j-wHalf; 00217 double Zi = wHalf-i; 00218 double x = vNorm*Zi + uNorm*Zj; 00219 double y = uNorm*Zi - vNorm*Zj ; 00220 double th = atan2(y,x); 00221 theta.setVal(i,j, th); 00222 00223 //The attenuation field 00224 th = fabs(th); 00225 if (th > M_PI/2) 00226 th = M_PI - th; 00227 th = 4*th; //This was not in the original spec 00228 00229 double l = sqrt(x*x + y*y); 00230 00231 double s = 0; 00232 if (l != 0 && th != 0) 00233 s = (th*l)/sin(th); 00234 else if (l==0 || th == 0) 00235 s = l; 00236 00237 double k=0; 00238 if (l != 0) 00239 k = 2*sin(th)/l; 00240 00241 double c= (-16*log2(0.1)*(sigma-1))/(M_PI*M_PI); 00242 double df = exp(-((s*s+c*(k*k))/(sigma*sigma))); 00243 if (th <= M_PI/2) 00244 DF.setVal(i,j,df); 00245 } 00246 00247 //Generate the final tensor field attenuated 00248 TensorField stickField(theta.getDims(), NO_INIT); 00249 double bTheta = atan2(v,u); 00250 for(int j=0; j<theta.getHeight(); j++) 00251 for(int i=0; i<theta.getWidth(); i++) 00252 { 00253 double th = theta.getVal(i,j); 00254 double b1 = -sin(2*th + bTheta); 00255 double b2 = cos(2*th + bTheta); 00256 00257 double att = DF.getVal(i,j); 00258 stickField.t1.setVal(i, j, b1*b1*att); 00259 stickField.t2.setVal(i, j, b1*b2*att); 00260 stickField.t3.setVal(i, j, b1*b2*att); 00261 stickField.t4.setVal(i, j, b2*b2*att); 00262 } 00263 00264 return stickField; 00265 } 00266 00267 TensorField TensorVoting::createBallTensorField(float sigma) 00268 { 00269 00270 //Create a ball tensor field by spinning a stick tensor 00271 double wsize = ceil(sqrt(-log(0.01)*sigma*sigma)*2); 00272 wsize = floor(wsize/2)*2+1; 00273 00274 TensorField ballField(Dims((int)wsize, (int)wsize), ZEROS); 00275 for(float theta=0; theta < 2*M_PI; theta += (2*M_PI)/36) 00276 { 00277 float u = cos(theta); float v = sin(theta); 00278 00279 TensorField stickField = createStickTensorField(u,v,sigma); 00280 ballField += stickField; 00281 } 00282 00283 //Normalize the field 00284 ballField /= 36; 00285 00286 EigenSpace eigen = getTensorEigen(ballField); 00287 00288 return ballField; 00289 00290 } 00291 00292 00293 void TensorVoting::getBallVotes(Image<float>& img, 00294 TensorField& tensorField, float sigma) 00295 { 00296 00297 //Calculate Ball voting Field 00298 //TensorField ballField = createBallTensorField(sigma); 00299 TensorField ballField = itsBallField; 00300 00301 //Go through the image, and vote at given feature position 00302 for(int j=0; j<img.getHeight(); j++) 00303 for(int i=0; i<img.getWidth(); i++) 00304 { 00305 float val=img.getVal(i,j); 00306 00307 if (val > 0) 00308 { 00309 //Go through the vote template and vote 00310 for(int y=0; y<ballField.t1.getHeight(); y++) 00311 for(int x=0; x<ballField.t1.getWidth(); x++) 00312 { 00313 int ii = i+x - (ballField.t1.getWidth()/2); 00314 int jj = j+y - (ballField.t1.getHeight()/2); 00315 00316 if (tensorField.t1.coordsOk(ii,jj)) 00317 { 00318 tensorField.t1.setVal(ii, jj, 00319 tensorField.t1.getVal(ii,jj) + 00320 ballField.t1.getVal(x,y)*val); 00321 00322 tensorField.t2.setVal(ii, jj, 00323 tensorField.t2.getVal(ii,jj) + 00324 ballField.t2.getVal(x,y)*val); 00325 00326 tensorField.t3.setVal(ii, jj, 00327 tensorField.t3.getVal(ii,jj) + 00328 ballField.t3.getVal(x,y)*val); 00329 00330 tensorField.t4.setVal(ii, jj, 00331 tensorField.t4.getVal(ii,jj) + 00332 ballField.t4.getVal(x,y)*val); 00333 } 00334 00335 } 00336 } 00337 } 00338 } 00339 00340 TensorField TensorVoting::getStickVotes(const TensorField& tensorField, 00341 float sigma) 00342 { 00343 00344 //Calculate stick voting Field 00345 00346 EigenSpace eigen = getTensorEigen(tensorField); 00347 00348 TensorField voteField = tensorField; 00349 00350 //Go thought the tensor, and vote at given feature position 00351 for(int j=0; j<eigen.l1.getHeight(); j++) 00352 for(int i=0; i<eigen.l1.getWidth(); i++) 00353 { 00354 //if the difference between the eigenvectors is greater the 0 00355 //then we have a stick vote 00356 00357 float val=eigen.l1.getVal(i,j) - eigen.l2.getVal(i,j); 00358 if (val > 0) 00359 { 00360 //Get the direction of the vote from e1, while the weight is l1-l2 00361 float u = eigen.e1[1].getVal(i,j); 00362 float v = eigen.e1[0].getVal(i,j); 00363 00364 //int angIdx = round((atan(u/v)-(M_PI/2))*180/M_PI); 00365 int angIdx = (int)round(atan(-u/v)*180/M_PI); 00366 if (angIdx < 0) angIdx += 180; 00367 00368 //TensorField stickField = createStickTensorField(-u, v, sigma); 00369 TensorField stickField = itsStickFields.at(angIdx); 00370 00371 //Go through the vote template and vote 00372 for(int y=0; y<stickField.t1.getHeight(); y++) 00373 for(int x=0; x<stickField.t1.getWidth(); x++) 00374 { 00375 int ii = i+x - (stickField.t1.getWidth()/2); 00376 int jj = j+y - (stickField.t1.getHeight()/2); 00377 00378 if (voteField.t1.coordsOk(ii,jj)) 00379 { 00380 voteField.t1.setVal(ii, jj, 00381 voteField.t1.getVal(ii,jj) + 00382 stickField.t1.getVal(x,y)*val); 00383 voteField.t2.setVal(ii, jj, 00384 voteField.t2.getVal(ii,jj) + 00385 stickField.t2.getVal(x,y)*val); 00386 voteField.t3.setVal(ii, jj, 00387 voteField.t3.getVal(ii,jj) + 00388 stickField.t3.getVal(x,y)*val); 00389 voteField.t4.setVal(ii, jj, 00390 voteField.t4.getVal(ii,jj) + 00391 stickField.t4.getVal(x,y)*val); 00392 } 00393 } 00394 } 00395 } 00396 00397 return voteField; 00398 00399 } 00400 00401 TensorField TensorVoting::getStickVotes2(const TensorField& tensorField, 00402 float sigma) 00403 { 00404 00405 EigenSpace eigen = getTensorEigen(tensorField); 00406 00407 TensorField voteField(tensorField.t1.getDims(), ZEROS); 00408 00409 //Go thought the tensor, and vote at given feature position 00410 for(int j=0; j<eigen.l1.getHeight(); j++) 00411 for(int i=0; i<eigen.l1.getWidth(); i++) 00412 { 00413 //if the difference between the eigenvectors is greater the 0 00414 //then we have a stick vote 00415 00416 float val=eigen.l1.getVal(i,j) - eigen.l2.getVal(i,j); 00417 if (val > 0) 00418 { 00419 //Get the direction of the vote from e1, while the weight is l1-l2 00420 float u = eigen.e1[1].getVal(i,j); 00421 float v = eigen.e1[0].getVal(i,j); 00422 00423 //int angIdx = round((atan(u/v)-(M_PI/2))*180/M_PI); 00424 int angIdx = (int)round(atan(-u/v)*180/M_PI); 00425 if (angIdx < 0) angIdx += 180; 00426 00427 //TensorField stickField = createStickTensorField(-u, v, sigma); 00428 TensorField stickField = itsStickFields.at(angIdx); 00429 00430 //Go through the vote template and vote 00431 val=1; 00432 for(int y=0; y<stickField.t1.getHeight(); y++) 00433 for(int x=0; x<stickField.t1.getWidth(); x++) 00434 { 00435 int ii = i+x - (stickField.t1.getWidth()/2); 00436 int jj = j+y - (stickField.t1.getHeight()/2); 00437 00438 if (voteField.t1.coordsOk(ii,jj)) 00439 { 00440 voteField.t1.setVal(ii, jj, 00441 voteField.t1.getVal(ii,jj) + 00442 stickField.t1.getVal(x,y)*val); 00443 voteField.t2.setVal(ii, jj, 00444 voteField.t2.getVal(ii,jj) + 00445 stickField.t2.getVal(x,y)*val); 00446 voteField.t3.setVal(ii, jj, 00447 voteField.t3.getVal(ii,jj) + 00448 stickField.t3.getVal(x,y)*val); 00449 voteField.t4.setVal(ii, jj, 00450 voteField.t4.getVal(ii,jj) + 00451 stickField.t4.getVal(x,y)*val); 00452 } 00453 } 00454 } 00455 } 00456 00457 return voteField; 00458 00459 } 00460 00461 TensorField TensorVoting::calcSparseField(Image<float>& img) 00462 { 00463 00464 TensorField tensorField(img.getDims(), ZEROS); 00465 00466 for(uint i=0; i<img.size(); i++) 00467 { 00468 if (img[i] > 0) 00469 { 00470 tensorField.t1.setVal(i, 1); 00471 tensorField.t2.setVal(i, 0); 00472 tensorField.t3.setVal(i, 0); 00473 tensorField.t4.setVal(i, 1); 00474 } 00475 } 00476 00477 return tensorField; 00478 00479 } 00480 00481 TensorField TensorVoting::calcRefinedField(TensorField& tensorField, 00482 Image<float>& img, 00483 float sigma) 00484 { 00485 00486 TensorField ballVoteField = tensorField; 00487 getBallVotes(img, ballVoteField, sigma); 00488 00489 ////Erase anything that is not in the original image 00490 00491 for(uint i=0; i<img.size(); i++) 00492 if (img[i] == 0) 00493 { 00494 ballVoteField.t1.setVal(i, 0); 00495 ballVoteField.t2.setVal(i, 0); 00496 ballVoteField.t3.setVal(i, 0); 00497 ballVoteField.t4.setVal(i, 0); 00498 } 00499 00500 return (tensorField+ballVoteField); 00501 00502 } 00503 00504 TensorField TensorVoting::findFeatures(TensorField& tensorField, float sigma) 00505 { 00506 00507 EigenSpace eigen = getTensorEigen(tensorField); 00508 00509 // Image<float> im = img; 00510 // //Normalize the gray scale image from 0 to 1; 00511 // float minVal, maxVal; 00512 // getMinMax(im, minVal, maxVal); 00513 // for(uint i=0; i<im.size(); i++) 00514 // im[i] = im[i] / maxVal; 00515 00516 00517 //First step is to produce the initially encode the image 00518 //as sparse tensor tokens. 00519 TensorField sparseTf = calcSparseField(eigen.l1); 00520 00521 LINFO("Refined field"); 00522 TensorField refinedTf = calcRefinedField(sparseTf, eigen.l1, sigma); 00523 00524 00525 ////third run is to apply the stick tensor voting after 00526 ////zero'ing out the e2(l2) components so that everything 00527 ////is a stick vote. 00528 eigen = getTensorEigen(refinedTf); 00529 00530 eigen.l2.clear(); 00531 TensorField zeroTf = getTensor(eigen); 00532 00533 LINFO("Stick Votes"); 00534 tensorField = getStickVotes(zeroTf, sigma); 00535 LINFO("Done"); 00536 00537 return tensorField; 00538 00539 } 00540 00541 // ###################################################################### 00542 void TensorVoting::evolve() 00543 { 00544 SHOWIMG(inputImg); 00545 TensorField tensorField = transImage(inputImg); 00546 00547 00548 tensorField = findFeatures(tensorField, itsSigma); 00549 00550 //Show the features 00551 EigenSpace eigen = getTensorEigen(tensorField); 00552 Image<float> features = eigen.l1-eigen.l2; 00553 SHOWIMG(features); 00554 00555 00556 } 00557 00558 TensorField TensorVoting::evolve(const Image<PixRGB<byte> >& img) 00559 { 00560 00561 Image<float> lum = luminance(img); 00562 SHOWIMG(lum); 00563 TensorField tensorField = getTensor(lum); 00564 00565 //Extract tokens by keeping only the edges with values grater 00566 //then 10% of the max mag. 00567 Image<float> mag = getTensorMag(tensorField); 00568 float min, max; 00569 getMinMax(mag, min,max); 00570 00571 for(uint i=0; i<mag.size(); i++) 00572 if (mag[i] < max*0.10) 00573 tensorField.setVal(i,0); 00574 00575 tensorField = getStickVotes2(tensorField, itsSigma); 00576 00577 return tensorField; 00578 00579 } 00580 00581 TensorField TensorVoting::evolve(const TensorField& tf, bool performNonMaxSurp) 00582 { 00583 TensorField tensorField = tf; 00584 00585 ////Extract tokens by keeping only the tensors with values grater 00586 ////then 10% of the max mag. 00587 //Image<float> mag = getTensorMag(tensorField); 00588 00589 //float min, max; 00590 //getMinMax(mag, min,max); 00591 00592 //for(uint i=0; i<mag.size(); i++) 00593 // if (mag[i] < max*0.100) 00594 // tensorField.setVal(i,0); 00595 00596 itsTensorField = getStickVotes2(tensorField, itsSigma); 00597 00598 if (performNonMaxSurp) 00599 nonMaxSurp(itsTensorField); 00600 00601 00602 return itsTensorField; 00603 00604 } 00605 00606 Image<float> TensorVoting::getTokensMag(bool normalize) 00607 { 00608 EigenSpace eigen = getTensorEigen(itsTensorField); 00609 Image<float> tokens = eigen.l1-eigen.l2; 00610 00611 if (normalize) 00612 inplaceNormalize(tokens, 0.0F, 255.0F); 00613 00614 return tokens; 00615 00616 } 00617 00618 00619 // ###################################################################### 00620 /* So things look consistent in everyone's emacs... */ 00621 /* Local Variables: */ 00622 /* indent-tabs-mode: nil */ 00623 /* End: */ 00624 00625 #endif 00626