00001 /*!@file Beobot/BeobotVisualCortex.C Implementation of navigation algorithm */ 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: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Beobot/BeobotVisualCortex.C $ 00035 // $Id: BeobotVisualCortex.C 9412 2008-03-10 23:10:15Z farhan $ 00036 // 00037 00038 #include "Beobot/BeobotVisualCortex.H" 00039 00040 #include "Beobot/beobot-defs.H" 00041 #include "Channels/Jet.H" 00042 #include "Image/ColorOps.H" // for luminance(), getRGBY(), etc. 00043 #include "Image/MathOps.H" 00044 #include "Image/Pixels.H" 00045 #include "Image/PyramidOps.H" 00046 #include "Image/ShapeOps.H" // for downSize() 00047 #include "Image/Transforms.H" 00048 #include "Image/fancynorm.H" // for maxNormalize() 00049 #include "Util/Assert.H" 00050 #include "Util/Timer.H" 00051 00052 #include <cmath> // for sqrt() 00053 #include <limits> // for numeric_limits<float>::max() instead of FLOATMAX 00054 00055 00056 //###################################################################### 00057 BeobotVisualCortex::BeobotVisualCortex() 00058 { initialized = false; } 00059 00060 // ###################################################################### 00061 void BeobotVisualCortex::init(const int imgw, const int imgh, 00062 const int lev_min, const int lev_max, 00063 const int delta_min, const int delta_max, 00064 const int smlev, const int nborient, 00065 const MaxNormType normtype, const int jlev, 00066 const int jdepth, const int nbneig, 00067 nub::soft_ref<Beowulf> beow) 00068 { 00069 iw = imgw; ih = imgh; 00070 lmin = lev_min; lmax = lev_max; dmin = delta_min; dmax = delta_max; 00071 sml = smlev; nori = nborient; nortyp = normtype; beo = beow; 00072 jetlevel = jlev; jetdepth = jdepth; nbneigh = nbneig; 00073 00074 scene.resize(iw, ih, true); 00075 00076 JetSpec *js = new JetSpec; 00077 js->addIndexRange(RG, RAW, jlev, jlev + jdepth - 1); 00078 js->addIndexRange(BY, RAW, jlev, jlev + jdepth - 1); 00079 js->addIndexRange(INTENS, RAW, jlev, jlev + jdepth - 1); 00080 js->addIndexRange(ORI, RAW, 0, nborient - 1); // orientations 00081 js->addIndexRange(ORI, RAW, jlev, jlev + jdepth - 1); 00082 js->print(); 00083 jetSpec = rutz::make_shared(js); 00084 00085 jets.init(iw >> jetlevel, ih >> jetlevel, nbneigh); 00086 ImageSpring<Jet <float> >::iterator itr = jets.beginw(), stop = jets.endw(); 00087 while (itr != stop) { itr->init(jetSpec); itr ++; } 00088 00089 sminput.resize(iw >> sml, ih >> sml, true); 00090 initialized = true; 00091 } 00092 00093 //####################################################################### 00094 void BeobotVisualCortex::newVisualInput(Image< PixRGB<byte> >& newscene) 00095 { scene = newscene; iw = scene.getWidth(); ih = scene.getHeight(); } 00096 00097 // ###################################################################### 00098 Image< PixRGB<byte> >* BeobotVisualCortex::getScenePtr() 00099 { return &scene; } 00100 00101 // ###################################################################### 00102 void BeobotVisualCortex::process(const int frame) 00103 { 00104 currframe = frame; 00105 sminput.clear(); nbcmap = 0; nbjmap = 0; 00106 00107 if (beo.get()) // use parallel version (we are master) 00108 { 00109 masterProcess(frame); 00110 00111 while (nbcmap != NBCMAP || nbjmap != NBJMAP) masterCollect(); 00112 } 00113 else // process everything on current CPU 00114 singleCPUprocess(frame); 00115 00116 // finalize computation of saliency map input: 00117 sminput = maxNormalize(sminput, 0.0f, 9.0f, nortyp); 00118 00119 // find most salient location: 00120 float maxval; 00121 findMax(sminput, winner, maxval); 00122 00123 // rescale those coordinates to scale of original image: 00124 winner.i <<= sml; winner.i += 1 << (sml - 1); 00125 winner.j <<= sml; winner.j += 1 << (sml - 1); 00126 } 00127 00128 // ###################################################################### 00129 void BeobotVisualCortex::processStart(const int frame) 00130 { 00131 if (beo.isInvalid()) LFATAL("This is for parallel processing only!"); 00132 currframe = frame; 00133 masterProcess(frame); 00134 } 00135 00136 // ###################################################################### 00137 void BeobotVisualCortex::processEnd(const int frame) 00138 { 00139 if (beo.isInvalid()) LFATAL("This is for parallel processing only!"); 00140 sminput.clear(); nbcmap = 0; nbjmap = 0; currframe = frame; Timer tim; 00141 while((nbcmap != NBCMAP || nbjmap != NBJMAP) && tim.get() < 80) 00142 { 00143 masterCollect(); 00144 //LINFO("frame %d: nc=%d nj=%d",frame,nbcmap,nbjmap); 00145 } 00146 00147 // finalize computation of saliency map input: 00148 sminput = maxNormalize(sminput, 0.0f, 9.0f, nortyp); 00149 00150 // find most salient location: 00151 float maxval; 00152 findMax(sminput, winner, maxval); 00153 00154 // rescale those coordinates to scale of original image: 00155 winner.i <<= sml; winner.i += 1 << (sml - 1); 00156 winner.j <<= sml; winner.j += 1 << (sml - 1); 00157 } 00158 00159 // ###################################################################### 00160 void BeobotVisualCortex::singleCPUprocess(const int frame) 00161 { 00162 currframe = frame; 00163 00164 Image<float> lumf = luminance(scene); // from Image_ColorOps.H 00165 00166 Image<float> rgf, byf; 00167 getRGBY(scene, rgf, byf, byte(25)); // from Image_ColorOps.H 00168 00169 // compute intensity: 00170 ImageSet<float> intensPyr = buildPyrGaussian(lumf, 0, sml + dmax + 1, 5); 00171 00172 // compute colors: 00173 ImageSet<float> rgPyr = buildPyrGaussian(rgf, 0, sml + dmax + 1, 5); 00174 ImageSet<float> byPyr = buildPyrGaussian(byf, 0, sml + dmax + 1, 5); 00175 00176 // compute orientations: 00177 ImageSet<float> oriPyr[nori]; 00178 for (int i = 0; i < nori; i ++) 00179 { 00180 oriPyr[i] = buildPyrOriented(lumf, 0, sml + dmax + 1, 5, 00181 float(i) * 180.0f / float(nori)); 00182 } 00183 00184 // compute flicker: 00185 if (prevlum.initialized() == false) prevlum = lumf; 00186 prevlum -= lumf; 00187 ImageSet<float> flickPyr = buildPyrGaussian(prevlum, 0, sml + dmax + 1, 5); 00188 prevlum = lumf; 00189 00190 // compute comspicuity maps and accumulate into sminput: 00191 Image<float> cmap; 00192 sminput.resize(intensPyr[sml].getDims(), true); 00193 computeCmap(intensPyr, cmap); sminput += cmap; 00194 computeCmap(rgPyr, cmap); sminput += cmap; 00195 computeCmap(byPyr, cmap); sminput += cmap; 00196 computeCmap(flickPyr, cmap); sminput += cmap; 00197 for (int i = 0; i < nori; i++) 00198 { computeCmap(oriPyr[i], cmap); sminput += cmap; } 00199 00200 // fill-in the jets -- IGNORE FLICKER CHANNEL: 00201 ImageSpring< Jet<float> >::iterator jt = jets.beginw(); 00202 Point2D<int> p; int step = 1 << jetlevel; 00203 int jmax = jets.getHeight() * step, imax = jets.getWidth() * step; 00204 for (p.j = 0; p.j < jmax; p.j += step) 00205 for (p.i = 0; p.i < imax; p.i += step) 00206 { 00207 for (int k = jetlevel; k < jetlevel + jetdepth; k ++) 00208 { 00209 float rgval = getPyrPixel(rgPyr, p, k); jt->setVal(rgval, RG, RAW, k); 00210 float byval = getPyrPixel(byPyr, p, k); jt->setVal(byval, BY, RAW, k); 00211 float ival = getPyrPixel(intensPyr, p, k); jt->setVal(ival, INTENS, RAW, k); 00212 for (int o = 0; o < nori; o ++) 00213 { 00214 float oval = getPyrPixel(oriPyr[o], p, k); 00215 jt->setVal(oval, ORI, RAW, o, k); 00216 } 00217 } 00218 jt ++; 00219 } 00220 00221 nbcmap = NBCMAP; nbjmap = NBJMAP; 00222 } 00223 00224 //###################################################################### 00225 void BeobotVisualCortex::masterProcess(const int frame) 00226 { 00227 ASSERT(initialized); 00228 00229 TCPmessage smsg; 00230 currframe = frame; nbcmap = 0; 00231 00232 // compute luminance and send it off: 00233 Image<byte> lum = luminance(scene); 00234 smsg.reset(frame, BEO_LUMFLICK); 00235 smsg.addImage(lum); 00236 beo->send(0, smsg); // send off to intensity/RG/BY/flick slave 00237 smsg.setAction(BEO_ORI0_45); 00238 beo->send(1, smsg); // send off to ori0/ori45 slave 00239 smsg.setAction(BEO_ORI90_135); 00240 beo->send(2, smsg); // send off to ori90/ori135 slave 00241 00242 // compute RG and BY and send them off: 00243 Image<byte> r, g, b, y; getRGBY(scene, r, g, b, y, (byte)25); 00244 smsg.reset(frame, BEO_REDGREEN); 00245 smsg.addImage(r); smsg.addImage(g); 00246 beo->send(0, smsg); // send off to intensity/RG/BY/flick slave 00247 smsg.reset(frame, BEO_BLUEYELLOW); 00248 smsg.addImage(b); smsg.addImage(y); 00249 beo->send(0, smsg); // send off to intensity/RG/BY/flick slave 00250 } 00251 00252 // ###################################################################### 00253 void BeobotVisualCortex::slaveProcess() 00254 { 00255 TCPmessage rmsg; int rframe, raction, rnode = -1; // receive from any node 00256 int nbrec = 0; 00257 while (beo->receive(rnode, rmsg, rframe, raction, 5)) // wait up to 5ms 00258 { 00259 //LINFO("GOT frame %d, action %d from node %d", rframe, raction, rnode); 00260 //Timer tim; 00261 switch(raction) 00262 { 00263 case BEO_INIT: // ############################## 00264 { 00265 // ooops, someone wants to re-initialize us! 00266 // reinitialization of beowulf is handled automatically. 00267 } 00268 break; 00269 case BEO_LUMFLICK: // ############################## 00270 { 00271 // get the luminance image out of the message: 00272 Image<byte> ima = rmsg.getElementByteIma(); 00273 Image<float> fima = ima; // convert to float 00274 00275 // compute intensity maps and send to collector: 00276 computeFeature(fima, Gaussian5, 0.0, rframe, BEO_FMAP_I); 00277 00278 // compute flicker maps and send to collector: 00279 if (prevlum.initialized() == false) prevlum = fima; 00280 prevlum -= fima; 00281 computeFeature(prevlum, Gaussian5, 0.0, rframe, BEO_FMAP_F); 00282 prevlum = fima; 00283 } 00284 break; 00285 case BEO_REDGREEN: // ############################## 00286 computeFeature2(rmsg, Gaussian5, 0.0, BEO_FMAP_RG); 00287 break; 00288 case BEO_BLUEYELLOW: // ############################## 00289 computeFeature2(rmsg, Gaussian5, 0.0, BEO_FMAP_BY); 00290 break; 00291 case BEO_ORI0_45: // ############################## 00292 { 00293 // get the luminance image out of the message: 00294 Image<byte> ima = rmsg.getElementByteIma(); 00295 Image<float> fima = ima; // convert to float 00296 00297 // compute 0deg orientation maps and send to collector: 00298 computeFeature(fima, Oriented5, 0.0, rframe, BEO_FMAP_O0); 00299 00300 // compute 45deg orientation maps and send to collector: 00301 computeFeature(fima, Oriented5, 45.0, rframe, BEO_FMAP_O45); 00302 } 00303 break; 00304 case BEO_ORI90_135: // ############################## 00305 { 00306 // get the luminance image out of the message: 00307 Image<byte> ima = rmsg.getElementByteIma(); 00308 Image<float> fima = ima; // convert to float 00309 00310 // compute 90deg orientation maps and send to collector: 00311 computeFeature(fima, Oriented5, 90.0, rframe, BEO_FMAP_O90); 00312 00313 // compute 135deg orientation maps and send to collector: 00314 computeFeature(fima, Oriented5, 135.0, rframe, BEO_FMAP_O135); 00315 } 00316 break; 00317 default: // ############################## 00318 LERROR("Bogus action %d -- IGNORING.", raction); 00319 break; 00320 } 00321 //LINFO("Job %d/%d from %d completed in %dms", 00322 // rframe, raction, rnode, tim.get()); 00323 00324 // limit number of receives, so we don't hold CPU too long: 00325 nbrec ++; if (nbrec > 3) break; 00326 } 00327 } 00328 00329 // ###################################################################### 00330 void BeobotVisualCortex::masterCollect() 00331 { 00332 // receive various conspicuity maps 00333 int32 rframe, raction, rnode = -1, recnb = 0; // receive from any node 00334 TCPmessage rmsg; 00335 while(beo->receive(rnode, rmsg, rframe, raction, 5)) // wait up to 5ms 00336 { 00337 //LINFO("received %d/%d from %d while at %d", 00338 // rframe, raction, rnode, currframe); 00339 if (rframe != currframe) 00340 { 00341 LERROR("Dropping old map, type %d for frame %d while at frame %d", 00342 raction, rframe, currframe); 00343 continue; 00344 } 00345 00346 // collect conspicuity maps: 00347 if (raction == BEO_CMAP) 00348 { 00349 nbcmap ++; 00350 00351 // get the map: 00352 Image<float> ima = rmsg.getElementFloatIma(); 00353 00354 // add received cmap to saliency map input: 00355 sminput += ima; 00356 } 00357 00358 // collect feature maps: 00359 if (raction >= BEO_FMAP_RG && raction <= BEO_FMAP_O135) 00360 { 00361 nbjmap ++; 00362 00363 VisualFeature jf = COLOR; 00364 std::vector<int> v; 00365 switch(raction) 00366 { 00367 case BEO_FMAP_RG: jf = RG; break; 00368 case BEO_FMAP_BY: jf = BY; break; 00369 case BEO_FMAP_I: jf = INTENS; break; 00370 case BEO_FMAP_F: jf = FLICKER; break; 00371 case BEO_FMAP_O0: jf = ORI; v.push_back(0); break; 00372 case BEO_FMAP_O45: jf = ORI; v.push_back(1); break; 00373 case BEO_FMAP_O90: jf = ORI; v.push_back(2); break; 00374 case BEO_FMAP_O135: jf = ORI; v.push_back(3); break; 00375 default: LFATAL("Bogus feature map type %d", raction); 00376 } 00377 v.push_back(0); // add an index for the scale 00378 00379 // get the maps: 00380 Image<float> ima[jetdepth]; 00381 for (int i = 0; i < jetdepth; i ++) 00382 { 00383 ima[i] = rmsg.getElementFloatIma(); 00384 } 00385 00386 // fill in the jets -- IGNORE FLICKER CHANNEL: 00387 if (jf != FLICKER) { 00388 ImageSpring< Jet<float> >::iterator jet_itr = jets.beginw(); 00389 00390 for (int j = 0; j < jets.getHeight(); j ++) 00391 for (int i = 0; i < jets.getWidth(); i ++) 00392 { 00393 float ii = float(i), jj = float(j); 00394 for (int k = 0; k < jetdepth; k ++) 00395 { 00396 float ii2 = ii, jj2 = jj; 00397 if (ii2 > ima[k].getWidth()-1) 00398 ii2 = ima[k].getWidth()-1; 00399 if (jj2 > ima[k].getHeight()-1) 00400 jj2 = ima[k].getHeight()-1; 00401 00402 v[v.size() - 1] = k + jetlevel; 00403 00404 // set the jet, using bilinear interpolation: 00405 jet_itr->setValV(ima[k].getValInterp(ii2, jj2), 00406 jf, RAW, v); 00407 00408 // ready for next scale: 00409 ii *= 0.5f; jj *= 0.5f; 00410 } 00411 } 00412 } 00413 } 00414 //LINFO("frame: %d nbcmap=%d nbjmap=%d", currframe, nbcmap, nbjmap); 00415 00416 // limit number of receives, so we don't hold CPU for too long: 00417 recnb ++; if (recnb > 20) break; 00418 } 00419 } 00420 00421 // ###################################################################### 00422 void BeobotVisualCortex::getWinner(Point2D<int>& win) const 00423 { win.i = winner.i; win.j = winner.j; } 00424 00425 //###################################################################### 00426 void BeobotVisualCortex::initSprings(bool initPosMasses) 00427 { jets.initClustering(initPosMasses); } 00428 00429 00430 // ###################################################################### 00431 void BeobotVisualCortex::iterateSprings(const float dt) 00432 { jets.computePos(dt); } 00433 00434 //###################################################################### 00435 void BeobotVisualCortex::getClusteredImage(Image< PixRGB<byte> > 00436 &clusteredImage, 00437 Point2D<int> &supposedTrackCentroid, 00438 const Point2D<int>& 00439 previousTrackCentroid) 00440 { 00441 jets.getClusteredImage(scene, clusteredImage, supposedTrackCentroid, 00442 previousTrackCentroid); 00443 } 00444 00445 // ###################################################################### 00446 void BeobotVisualCortex::getPositions(Image< PixRGB<byte> > &img, 00447 const int zoom) 00448 { jets.getPositions(img, zoom); } 00449 00450 // ###################################################################### 00451 void BeobotVisualCortex::computeFeature(TCPmessage &rmsg, 00452 const PyramidType ptyp, 00453 const float ori, 00454 const int maptype) 00455 { 00456 // get the image out of the message: 00457 Image<byte> ima = rmsg.getElementByteIma(); 00458 Image<float> fima = ima; // convert to float 00459 00460 // compute maps and send to collector: 00461 computeFeature(fima, ptyp, ori, rmsg.getID(), maptype); 00462 } 00463 00464 // ###################################################################### 00465 void BeobotVisualCortex::computeFeature2(TCPmessage &rmsg, 00466 const PyramidType ptyp, 00467 const float ori, 00468 const int maptype) 00469 { 00470 // get the two images out of the message: 00471 Image<byte> ima1 = rmsg.getElementByteIma(); 00472 Image<byte> ima2 = rmsg.getElementByteIma(); 00473 Image<float> fima = ima1 - ima2; 00474 00475 // compute maps and send to collector: 00476 computeFeature(fima, ptyp, ori, rmsg.getID(), maptype); 00477 } 00478 00479 // ###################################################################### 00480 void BeobotVisualCortex::computeFeature(const Image<float>& fima, 00481 const PyramidType ptyp, 00482 const float ori, 00483 const int32 id, const int32 maptype) 00484 { 00485 // compute pyramid: 00486 ImageSet<float> pyr = buildPyrGeneric(fima, 0, lmax + dmax + 1, 00487 ptyp, ori); 00488 00489 // now send off a message with the raw features, for the jets: 00490 TCPmessage smsg(id, maptype); 00491 for (int i = 0; i < jetdepth; i ++) 00492 smsg.addImage(pyr.getImage(i + jetlevel)); 00493 beo->send(-1, smsg); 00494 00495 // compute conspicuity map: 00496 Image<float> cmap; 00497 computeCmap(pyr, cmap); 00498 00499 // send cmap off to master: 00500 smsg.reset(id, BEO_CMAP); 00501 smsg.addImage(cmap); 00502 beo->send(-1, smsg); 00503 } 00504 00505 // ###################################################################### 00506 void BeobotVisualCortex::computeCmap(const ImageSet<float>& pyr, 00507 Image<float>& cmap) 00508 { 00509 // clear conspicuity map: 00510 cmap.resize(pyr[sml].getDims(), true); 00511 00512 // compute conspicuity map from feature maps: 00513 for (int delta = dmin; delta <= dmax; delta ++) 00514 for (int lev = lmin; lev <= lmax; lev ++) 00515 { 00516 Image<float> tmp = centerSurround(pyr, lev, lev + delta, true); 00517 tmp = downSize(tmp, cmap.getWidth(), cmap.getHeight()); 00518 inplaceAddBGnoise(tmp, 255.0); 00519 tmp = maxNormalize(tmp, MAXNORMMIN, MAXNORMMAX, nortyp); 00520 cmap += tmp; 00521 } 00522 if (nortyp == VCXNORM_MAXNORM) 00523 cmap = maxNormalize(cmap, MAXNORMMIN, MAXNORMMAX, nortyp); 00524 else 00525 cmap = maxNormalize(cmap, 0.0f, 0.0f, nortyp); 00526 } 00527 00528 // ###################################################################### 00529 /* So things look consistent in everyone's emacs... */ 00530 /* Local Variables: */ 00531 /* indent-tabs-mode: nil */ 00532 /* End: */