00001 /*!@file SceneUnderstanding/Contours.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/Contours.C $ 00035 // $Id: Contours.C 13906 2010-09-10 01:08:31Z lior $ 00036 // 00037 00038 #ifndef Contours_C_DEFINED 00039 #define Contours_C_DEFINED 00040 00041 00042 #include "Image/OpenCVUtil.H" 00043 #include "plugins/SceneUnderstanding/LFLineFitter/LFLineFitter.h" 00044 #include "plugins/SceneUnderstanding/Contours.H" 00045 #include "plugins/SceneUnderstanding/V1.H" 00046 00047 #include "Image/DrawOps.H" 00048 #include "Image/MathOps.H" 00049 #include "Image/Kernels.H" 00050 #include "Image/ColorMap.H" 00051 #include "Image/FilterOps.H" 00052 #include "Image/Transforms.H" 00053 #include "Image/fancynorm.H" 00054 #include "Image/Convolutions.H" 00055 #include "Simulation/SimEventQueue.H" 00056 #include "GUI/DebugWin.H" 00057 #include <math.h> 00058 #include <fcntl.h> 00059 #include <limits> 00060 #include <string> 00061 00062 const ModelOptionCateg MOC_Contours = { 00063 MOC_SORTPRI_3, "Contours-Related Options" }; 00064 00065 // Used by: SimulationViewerEyeMvt 00066 const ModelOptionDef OPT_ContoursShowDebug = 00067 { MODOPT_ARG(bool), "ContoursShowDebug", &MOC_Contours, OPTEXP_CORE, 00068 "Show debug img", 00069 "contours-debug", '\0', "<true|false>", "false" }; 00070 00071 00072 //Define the inst function name 00073 SIMMODULEINSTFUNC(Contours); 00074 00075 // ###################################################################### 00076 Contours::Contours(OptionManager& mgr, const std::string& descrName, 00077 const std::string& tagName) : 00078 SimModule(mgr, descrName, tagName), 00079 SIMCALLBACK_INIT(SimEventV2Output), 00080 SIMCALLBACK_INIT(SimEventContoursBias), 00081 SIMCALLBACK_INIT(SimEventSaveOutput), 00082 SIMCALLBACK_INIT(SimEventUserInput), 00083 itsShowDebug(&OPT_ContoursShowDebug, this) 00084 { 00085 00086 itsCurrentContour = -1; //for debug view 00087 } 00088 00089 // ###################################################################### 00090 Contours::~Contours() 00091 { 00092 } 00093 00094 // ###################################################################### 00095 void Contours::onSimEventV2Output(SimEventQueue& q, 00096 rutz::shared_ptr<SimEventV2Output>& e) 00097 { 00098 itsTensorFields = e->getTensorFields(); 00099 evolve(q); 00100 00101 00102 00103 //Layout<PixRGB<byte> > layout = getDebugImage(); 00104 //Image<PixRGB<byte> > tmp = layout.render(); 00105 //SHOWIMG(tmp); 00106 00107 } 00108 00109 // ###################################################################### 00110 void Contours::onSimEventContoursBias(SimEventQueue& q, 00111 rutz::shared_ptr<SimEventContoursBias>& e) 00112 { 00113 itsBiasContours.clear(); 00114 itsBiasContours = e->getContours(); 00115 00116 00117 LINFO("Size %i", (uint)itsBiasContours.size()); 00118 //Bias V1 00119 00120 if (itsBiasContours.size() > 0) 00121 { 00122 std::vector<V1::SpatialBias> v1Bias; 00123 for(uint i=0; i<itsBiasContours.size(); i++) 00124 { 00125 Point2D<int> center(0,0); 00126 for(uint j=0; j<itsBiasContours[i].points.size(); j++) 00127 { 00128 // tmp.setVal(itsBiasContours[i].points[j], 255.0F); 00129 00130 center += itsBiasContours[i].points[j]; 00131 } 00132 center /= itsBiasContours[i].size(); 00133 00134 //Image<float> tmp(320,240,ZEROS); 00135 //drawCircle(tmp, center, 11, 255.0F); 00136 //SHOWIMG(tmp); 00137 00138 //Bias V1 00139 V1::SpatialBias sb; 00140 sb.loc = center; 00141 sb.threshold = 0.01; 00142 sb.dims = Dims(50,50); 00143 v1Bias.push_back(sb); 00144 00145 } 00146 q.post(rutz::make_shared(new SimEventV1Bias(this, v1Bias))); 00147 00148 } 00149 00150 } 00151 00152 00153 // ###################################################################### 00154 void Contours::onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e) 00155 { 00156 if (itsShowDebug.getVal()) 00157 { 00158 // get the OFS to save to, assuming sinfo is of type 00159 // SimModuleSaveInfo (will throw a fatal exception otherwise): 00160 nub::ref<FrameOstream> ofs = 00161 dynamic_cast<const SimModuleSaveInfo&>(e->sinfo()).ofs; 00162 Layout<PixRGB<byte> > disp = getDebugImage(); 00163 if (disp.initialized()) 00164 ofs->writeRgbLayout(disp, "Contours", FrameInfo("Contours", SRC_POS)); 00165 } 00166 } 00167 00168 // ###################################################################### 00169 void Contours::onSimEventUserInput(SimEventQueue& q, rutz::shared_ptr<SimEventUserInput>& e) 00170 { 00171 00172 LINFO("Got event %s %ix%i key=%i", 00173 e->getWinName(), 00174 e->getMouseClick().i, 00175 e->getMouseClick().j, 00176 e->getKey()); 00177 00178 if (strcmp(e->getWinName(), "Contours")) 00179 return; 00180 00181 switch(e->getKey()) 00182 { 00183 case 10: //1 00184 itsCurrentContour++; 00185 if (itsCurrentContour > (int)itsContours.size()) itsCurrentContour = itsContours.size()-1; 00186 break; 00187 case 24: //q 00188 itsCurrentContour--; 00189 if (itsCurrentContour < 0) itsCurrentContour = 0; 00190 break; 00191 case 38: //a show all contours 00192 itsCurrentContour=-1; 00193 break; 00194 } 00195 00196 00197 evolve(q); 00198 00199 } 00200 00201 // ###################################################################### 00202 void Contours::evolve(SimEventQueue& q) 00203 { 00204 00205 00206 00207 //Process any biasing 00208 00209 int cid=0; 00210 itsContours.clear(); 00211 //if (itsBiasContours.size() > 0) 00212 //{ 00213 // for(uint i=0; i<itsBiasContours.size(); i++) 00214 // { 00215 // for(uint j=0; j<itsBiasContours[i].points.size(); j++) 00216 // { 00217 // contoursImg.setVal(itsBiasContours[i].points[j], cid); 00218 // itsBiasContours[i].ori.push_back(edgesOri.getVal(itsBiasContours[i].points[j])); 00219 // } 00220 // itsContours.push_back(itsBiasContours[i]); 00221 // cid++; 00222 // } 00223 //} 00224 00225 00226 00227 Image<float> edgesMag; //the edges mag map 00228 Image<float> edgesOri; //the edges ori map 00229 for(uint cidx=0; cidx<3; cidx++) 00230 { 00231 if (!edgesMag.initialized()) 00232 { 00233 edgesMag = Image<float>(itsTensorFields[cidx].getDims(), ZEROS); 00234 edgesOri = Image<float>(itsTensorFields[cidx].getDims(), ZEROS); 00235 } 00236 00237 EigenSpace eigen = getTensorEigen(itsTensorFields[cidx].getTensorField()); 00238 Image<float> features = eigen.l1-eigen.l2; 00239 for(uint i=0; i<features.size(); i++) 00240 if (features.getVal(i) > edgesMag.getVal(i)) 00241 { 00242 edgesMag.setVal(i, features.getVal(i)); 00243 float u = eigen.e1[1].getVal(i); 00244 float v = eigen.e1[0].getVal(i); 00245 edgesOri.setVal(i,atan(-u/v)); 00246 } 00247 } 00248 //SHOWIMG(edgesMag); 00249 00250 Image<float> contoursImg; //mask for which contours exsists 00251 std::vector<Point2D<int> > pointList = getPointList(edgesMag, edgesOri, contoursImg); 00252 00253 //Go through the points and follow the contour 00254 for(uint i=0; i<pointList.size(); i++) 00255 { 00256 if (contoursImg.getVal(pointList[i]) == UNKNOWN) //since we dont know were this pixel belongs too lets follow it 00257 { 00258 Contour contour = followContour(cid, pointList[i], contoursImg, edgesMag, edgesOri); 00259 if (contour.points.size() > 5) 00260 { 00261 itsContours.push_back(contour); 00262 cid++; 00263 } 00264 } 00265 } 00266 00267 00268 //sort the contours 00269 std::sort(itsContours.begin(), itsContours.end(), ContourCmp()); 00270 00271 //////**** Debug ****//// 00272 Image<PixRGB<byte> > byEdges = toRGB(itsTensorFields[2].getTokensMag(true)); 00273 Image<PixRGB<byte> > tmp(byEdges.getDims(), ZEROS); 00274 00275 for(uint i=0; i<itsContours.size(); i++) 00276 { 00277 Contour& contour = itsContours[i]; 00278 for(uint j=0; j<contour.points.size(); j++) 00279 tmp.setVal(contour.points[j], PixRGB<byte>(0,255,0)); 00280 } 00281 //SHOWIMG(tmp); 00282 00283 00284 00285 00286 q.post(rutz::make_shared(new SimEventContoursOutput(this, itsContours, edgesMag))); 00287 } 00288 00289 Contours::Contour Contours::followContour(int idx, Point2D<int> startLoc, Image<float>& contoursImg, 00290 const Image<float>& edgesMag, const Image<float>& edgesOri) 00291 { 00292 float origEdgeMag = edgesMag.getVal(startLoc); 00293 00294 00295 Image<float> t = edgesMag; 00296 inplaceNormalize(t, 0.0F, 255.0F); 00297 Image<PixRGB<byte> > tmp = t; 00298 00299 00300 Contour fcontour; 00301 fcontour.points.push_back(startLoc); 00302 fcontour.ori.push_back(edgesOri.getVal(startLoc)); 00303 contoursImg.setVal(startLoc, idx); 00304 00305 Contour contour; 00306 00307 //follow the contour one way 00308 Point2D<int> pEdge(-1,-1); 00309 for(uint i=0; i<fcontour.points.size(); i++) 00310 { 00311 Point2D<int> newEdge = findEdgeToFollow(fcontour.points[i], origEdgeMag, 00312 edgesMag, edgesOri, contoursImg, pEdge); 00313 00314 if (newEdge.isValid()) 00315 { 00316 pEdge = newEdge; 00317 tmp.setVal(newEdge, PixRGB<byte>(0,255,0)); 00318 contoursImg.setVal(newEdge,idx); 00319 fcontour.points.push_back(newEdge); 00320 fcontour.ori.push_back(edgesOri.getVal(newEdge)); 00321 } 00322 } 00323 00324 //Try to follow the other side of the contour 00325 Point2D<int> newEdge = findEdgeToFollow(startLoc, origEdgeMag, 00326 edgesMag, edgesOri, contoursImg, pEdge); 00327 00328 if (newEdge.isValid()) //If we found this, then follow 00329 { 00330 tmp.setVal(newEdge, PixRGB<byte>(0,255,0)); 00331 00332 Contour bcontour; 00333 bcontour.points.push_back(newEdge); 00334 bcontour.ori.push_back(edgesOri.getVal(newEdge)); 00335 contoursImg.setVal(newEdge,idx); 00336 00337 for(uint i=0; i<bcontour.points.size(); i++) 00338 { 00339 Point2D<int> newEdge = findEdgeToFollow(bcontour.points[i], origEdgeMag, 00340 edgesMag, edgesOri, contoursImg, pEdge); 00341 00342 if (newEdge.isValid()) 00343 { 00344 tmp.setVal(newEdge, PixRGB<byte>(0,255,0)); 00345 contoursImg.setVal(newEdge,idx); 00346 bcontour.points.push_back(newEdge); 00347 bcontour.ori.push_back(edgesOri.getVal(newEdge)); 00348 } 00349 //Image<PixRGB<byte> > tt = rescale(tmp, tmp.getDims()*3); 00350 //SHOWIMG(tt); 00351 } 00352 00353 //combine the contours in order so the contour will be continues 00354 for(int i=bcontour.size()-1; i>=0; i--) 00355 { 00356 contour.points.push_back(bcontour.points[i]); 00357 contour.ori.push_back(bcontour.ori[i]); 00358 } 00359 00360 } 00361 00362 //combine the contours in order so the contour will be continues 00363 for(uint i=0; i<fcontour.size(); i++) 00364 { 00365 contour.points.push_back(fcontour.points[i]); 00366 contour.ori.push_back(fcontour.ori[i]); 00367 } 00368 00369 return contour; 00370 } 00371 00372 Point2D<int> Contours::findEdgeToFollow(const Point2D<int>& edgeLoc, 00373 const float origEdgeMag, 00374 const Image<float>& edgesMag, const Image<float>& edgesOri, 00375 const Image<float>& contoursImg, 00376 const Point2D<int>& pEdge) 00377 { 00378 const int radius = 1; //search within this radius 00379 const float magDiffThreshold = 0.2; 00380 00381 //float edgeMag = edgesMag.getVal(edgeLoc); 00382 //LINFO("Find edge Loc %ix%i", edgeLoc.i, edgeLoc.j); 00383 00384 Point2D<int> newEdge(-1,-1); 00385 00386 //Weights for biasing 00387 float w1 = 1; 00388 float w2 = 1; 00389 float w3 = 1.5; 00390 00391 float maxDiff = 0; 00392 //Search for a new edge to add 00393 //LINFO("Getting edge to follow\n"); 00394 for(int x=edgeLoc.i-radius; x<=edgeLoc.i+radius; x++) 00395 for(int y=edgeLoc.j-radius; y<=edgeLoc.j+radius; y++) 00396 { 00397 Point2D<int> loc(x,y); 00398 if (edgeLoc != loc && 00399 contoursImg.coordsOk(loc) && 00400 contoursImg.getVal(loc) == UNKNOWN) 00401 { 00402 float mag = 1.0F - (fabs(origEdgeMag - edgesMag.getVal(loc))/origEdgeMag); 00403 00404 if (mag > magDiffThreshold) 00405 { 00406 float edgeOri = edgesOri.getVal(edgeLoc); 00407 00408 float dist = 1.0F - (edgeLoc.squdist(loc)/(2*radius*2)); 00409 00410 //Limit the search to only 90 degrees from us 00411 float oriDiff = 1.0F - (angDiff(edgeOri, edgesOri.getVal(loc))/(M_PI)); 00412 00413 //Point2D<int> t1 = pEdge - edgeLoc; 00414 //Point2D<int> t2 = loc - edgeLoc; 00415 //double edgeAng = angDiff(atan2(double(t1.j), double(t1.i)), atan2(double(t2.j), double(t2.i))); 00416 00417 float diff = w1*mag + w2*dist + w3*oriDiff; 00418 //LINFO("m %f d %f o(%f,%f) %f eo: %f total: %f", 00419 // mag, dist, edgeOri*180/M_PI, edgesOri.getVal(loc)*180/M_PI, oriDiff, edgeAng*180/M_PI, diff); 00420 if (diff > maxDiff) 00421 { 00422 newEdge = loc; 00423 maxDiff = diff; 00424 } 00425 } 00426 } 00427 } 00428 //LINFO("Choose %f", maxDiff); 00429 00430 return newEdge; 00431 } 00432 00433 00434 00435 std::vector<Point2D<int> > Contours::getPointList(TensorVoting& tv, Image<float>& contoursImg, 00436 Image<float>& edgesMag, Image<float>& edgesOri) 00437 { 00438 float maxMag = 1000; 00439 int nBins = 100; 00440 //float threshold = 0.1; 00441 00442 //initialize the contoursImg; 00443 contoursImg = Image<float>(tv.getDims(), NO_INIT); 00444 00445 EigenSpace eigen = getTensorEigen(tv.getTensorField()); 00446 Image<float> features = eigen.l1-eigen.l2; 00447 inplaceNormalize(features, 0.0F, maxMag); 00448 00449 edgesMag = features; 00450 //SHOWIMG(edgesMag); 00451 edgesOri = Image<float>(features.getDims(), ZEROS); 00452 00453 //Seperate the location above threshold into bins so we can sort them fast 00454 //Mark the locations that we will be used in the contours img 00455 // 00456 std::vector<std::vector<Point2D<int> > > pointBins(nBins); 00457 for(int j=0; j<features.getHeight(); j++) 00458 for(int i=0; i<features.getWidth(); i++) 00459 { 00460 float val = features.getVal(i,j); 00461 if (val > 0) //threshold*maxMag) 00462 { 00463 contoursImg.setVal(i,j, UNKNOWN); 00464 //Find the bin this val belongs to 00465 int idx = (int)(val * nBins / maxMag); 00466 if (idx >= nBins) idx = nBins - 1; //if val == maxMag; 00467 pointBins[idx].push_back(Point2D<int>(i,j)); 00468 00469 //Get the direction of the vote from e1, while the weight is l1-l2 00470 float u = eigen.e1[1].getVal(i,j); 00471 float v = eigen.e1[0].getVal(i,j); 00472 edgesOri.setVal(i,j, atan(-u/v)); 00473 } else { 00474 contoursImg.setVal(i,j, NOTDEFINED); 00475 } 00476 } 00477 00478 //return a sorted list; 00479 std::vector<Point2D<int> > points; 00480 for(int i=pointBins.size()-1; i > -1; i--) 00481 { 00482 for(uint j=0; j<pointBins[i].size(); j++) 00483 points.push_back(pointBins[i][j]); 00484 } 00485 00486 return points; 00487 00488 } 00489 00490 std::vector<Point2D<int> > Contours::getPointList(Image<float>& inMag, Image<float>& inOri, Image<float>& contoursImg) 00491 { 00492 float maxMag = 1000; 00493 int nBins = 100; 00494 float threshold = 0.1; 00495 00496 //initialize the contoursImg; 00497 contoursImg = Image<float>(inMag.getDims(), NO_INIT); 00498 inplaceNormalize(inMag, 0.0F, maxMag); 00499 00500 //Seperate the location above threshold into bins so we can sort them fast 00501 //Mark the locations that we will be used in the contours img 00502 // 00503 std::vector<std::vector<Point2D<int> > > pointBins(nBins); 00504 for(int j=0; j<inMag.getHeight(); j++) 00505 for(int i=0; i<inMag.getWidth(); i++) 00506 { 00507 float val = inMag.getVal(i,j); 00508 if (val > threshold*maxMag) 00509 { 00510 contoursImg.setVal(i,j, UNKNOWN); 00511 //Find the bin this val belongs to 00512 int idx = (int)(val * nBins / maxMag); 00513 if (idx >= nBins) idx = nBins - 1; //if val == maxMag; 00514 pointBins[idx].push_back(Point2D<int>(i,j)); 00515 } else { 00516 contoursImg.setVal(i,j, NOTDEFINED); 00517 } 00518 } 00519 00520 //return a sorted list; 00521 std::vector<Point2D<int> > points; 00522 for(int i=pointBins.size()-1; i > -1; i--) 00523 { 00524 for(uint j=0; j<pointBins[i].size(); j++) 00525 points.push_back(pointBins[i][j]); 00526 } 00527 00528 return points; 00529 00530 } 00531 00532 00533 Layout<PixRGB<byte> > Contours::getDebugImage() 00534 { 00535 Layout<PixRGB<byte> > outDisp; 00536 00537 00538 Layout<PixRGB<byte> > tensorDisp; 00539 Image<PixRGB<byte> > lumEdges = toRGB(itsTensorFields[0].getTokensMag(true)); 00540 Image<PixRGB<byte> > rgEdges = toRGB(itsTensorFields[1].getTokensMag(true)); 00541 Image<PixRGB<byte> > byEdges = toRGB(itsTensorFields[2].getTokensMag(true)); 00542 00543 tensorDisp = hcat(lumEdges, rgEdges); 00544 tensorDisp = hcat(tensorDisp, byEdges); 00545 00546 00547 //ShowContour; 00548 Image<PixRGB<byte> > contourImg(lumEdges.getDims(), ZEROS); 00549 ColorMap cm = ColorMap::LINES(100); 00550 if (itsCurrentContour != -1) 00551 { 00552 Contour& contour = itsContours[itsCurrentContour]; 00553 for(uint j=0; j<contour.points.size(); j++) 00554 contourImg.setVal(contour.points[j], cm[itsCurrentContour%cm.size()]); 00555 00556 char msg[255]; 00557 sprintf(msg, "C: %i", itsCurrentContour); 00558 writeText(contourImg, Point2D<int>(0,0), msg, 00559 PixRGB<byte>(255,255,255), 00560 PixRGB<byte>(0,0,0)); 00561 00562 } else { 00563 for(uint i=0; i<itsContours.size(); i++) 00564 { 00565 Contour& contour = itsContours[i]; 00566 for(uint j=0; j<contour.points.size(); j++) 00567 contourImg.setVal(contour.points[j], cm[i%cm.size()]); 00568 } 00569 } 00570 //for(uint i=0; i<itsBiasContours.size(); i++) 00571 //{ 00572 // for(uint j=0; j<itsBiasContours[i].points.size(); j++) 00573 // contourImg.setVal(itsBiasContours[i].points[j], PixRGB<byte>(0,255,0)); 00574 //} 00575 00576 outDisp = contourImg; //vcat(tensorDisp, contourImg); 00577 00578 return outDisp; 00579 00580 } 00581 00582 00583 // ###################################################################### 00584 /* So things look consistent in everyone's emacs... */ 00585 /* Local Variables: */ 00586 /* indent-tabs-mode: nil */ 00587 /* End: */ 00588 00589 #endif 00590