00001 /*!@file Neuro/EnvSegmenterCannyContour.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: Rob Peters <rjpeters at usc dot edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Neuro/EnvSegmenterCannyContour.C $ 00035 // $Id: EnvSegmenterCannyContour.C 12782 2010-02-05 22:14:30Z irock $ 00036 // 00037 00038 #include "Image/OpenCVUtil.H" // must be first to avoid conflicting defs of int64, uint64 00039 00040 #include "Neuro/EnvSegmenterCannyContour.H" 00041 00042 #include "Image/CutPaste.H" 00043 #include "Image/DrawOps.H" 00044 #include "Image/Image.H" 00045 #include "Image/Pixels.H" 00046 #include "GUI/DebugWin.H" 00047 00048 #ifdef HAVE_OPENCV 00049 00050 namespace 00051 { 00052 const int thresh = 50; 00053 00054 // helper function: 00055 // finds a cosine of angle between vectors 00056 // from pt0->pt1 and from pt0->pt2 00057 double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 ) 00058 { 00059 double dx1 = pt1->x - pt0->x; 00060 double dy1 = pt1->y - pt0->y; 00061 double dx2 = pt2->x - pt0->x; 00062 double dy2 = pt2->y - pt0->y; 00063 return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); 00064 } 00065 00066 CvSeq* findSquares(const Image<PixRGB<byte> >& in, CvMemStorage* storage, 00067 const int minarea, const int maxarea, const double mincos) 00068 { 00069 const int N = 11; 00070 00071 IplImage* img = img2ipl(in); 00072 00073 CvSize sz = cvSize( img->width & -2, img->height & -2 ); 00074 IplImage* timg = cvCloneImage( img ); // make a copy of input image 00075 IplImage* gray = cvCreateImage( sz, 8, 1 ); 00076 IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 ); 00077 // create empty sequence that will contain points - 00078 // 4 points per square (the square's vertices) 00079 CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage ); 00080 00081 // select the maximum ROI in the image 00082 // with the width and height divisible by 2 00083 cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height )); 00084 00085 // down-scale and upscale the image to filter out the noise 00086 cvPyrDown( timg, pyr, 7 ); 00087 cvPyrUp( pyr, timg, 7 ); 00088 IplImage* tgray = cvCreateImage( sz, 8, 1 ); 00089 00090 // find squares in every color plane of the image 00091 for (int c = 0; c < 3; ++c) 00092 { 00093 // extract the c-th color plane 00094 cvSetImageCOI( timg, c+1 ); 00095 cvCopy( timg, tgray, 0 ); 00096 00097 // try several threshold levels 00098 for (int l = 0; l < N; ++l) 00099 { 00100 // hack: use Canny instead of zero threshold level. 00101 // Canny helps to catch squares with gradient shading 00102 if( l == 0 ) 00103 { 00104 // apply Canny. Take the upper threshold from slider 00105 // and set the lower to 0 (which forces edges merging) 00106 cvCanny( tgray, gray, 0, thresh, 5 ); 00107 // dilate canny output to remove potential 00108 // holes between edge segments 00109 cvDilate( gray, gray, 0, 1 ); 00110 } 00111 else 00112 { 00113 // apply threshold if l!=0: 00114 // tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0 00115 cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY ); 00116 } 00117 00118 // find contours and store them all as a list 00119 CvSeq* contours = 0; 00120 cvFindContours( gray, storage, &contours, sizeof(CvContour), 00121 CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) ); 00122 00123 // test each contour 00124 while( contours ) 00125 { 00126 // approximate contour with accuracy proportional 00127 // to the contour perimeter 00128 CvSeq* result = 00129 cvApproxPoly( contours, sizeof(CvContour), storage, 00130 CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 ); 00131 // square contours should have 4 vertices after approximation 00132 // relatively large area (to filter out noisy contours) 00133 // and be convex. 00134 // Note: absolute value of an area is used because 00135 // area may be positive or negative - in accordance with the 00136 // contour orientation 00137 const double area = fabs(cvContourArea(result,CV_WHOLE_SEQ)); 00138 if (result->total == 4 && 00139 area >= minarea && area <= maxarea && 00140 cvCheckContourConvexity(result)) 00141 { 00142 double s = 0; 00143 00144 for (int i = 0; i < 4; ++i) 00145 { 00146 // find minimum angle between joint 00147 // edges (maximum of cosine) 00148 const double t = 00149 fabs(angle((CvPoint*)cvGetSeqElem( result, i % 4 ), 00150 (CvPoint*)cvGetSeqElem( result, (i-2) % 4 ), 00151 (CvPoint*)cvGetSeqElem( result, (i-1) % 4 ))); 00152 s = s > t ? s : t; 00153 } 00154 00155 00156 // if cosines of all angles are small 00157 // (all angles are ~90 degree) then write quandrangle 00158 // vertices to resultant sequence 00159 if (s < mincos) 00160 { 00161 for (int i = 0; i < 4; ++i) 00162 cvSeqPush(squares, 00163 (CvPoint*)cvGetSeqElem( result, i )); 00164 // LINFO("area=%f, mincos=%f", area, s); 00165 } 00166 } 00167 00168 // take the next contour 00169 contours = contours->h_next; 00170 } 00171 } 00172 } 00173 00174 // release all the temporary images 00175 cvReleaseImage( &gray ); 00176 cvReleaseImage( &pyr ); 00177 cvReleaseImage( &tgray ); 00178 cvReleaseImage( &timg ); 00179 cvReleaseImageHeader( &img ); 00180 00181 return squares; 00182 } 00183 00184 // the function draws all the squares in the image 00185 Image<PixRGB<byte> > drawSquares(const Image<PixRGB<byte> >&in, CvSeq* squares) 00186 { 00187 Image<PixRGB<byte> > out(in); 00188 00189 CvSeqReader reader; 00190 00191 // initialize reader of the sequence 00192 cvStartReadSeq(squares, &reader, 0); 00193 00194 // read 4 sequence elements at a time (all vertices of a square) 00195 for (int i = 0; i < squares->total; i += 4) 00196 { 00197 CvPoint pt[4]; 00198 00199 // read 4 vertices 00200 CV_READ_SEQ_ELEM( pt[0], reader ); 00201 CV_READ_SEQ_ELEM( pt[1], reader ); 00202 CV_READ_SEQ_ELEM( pt[2], reader ); 00203 CV_READ_SEQ_ELEM( pt[3], reader ); 00204 00205 for (int j = 0; j < 4; ++j) 00206 drawLine(out, 00207 Point2D<int>(pt[j].x, pt[j].y), 00208 Point2D<int>(pt[(j+1)%4].x, pt[(j+1)%4].y), 00209 PixRGB<byte>(0, 255, 0), 00210 2); 00211 } 00212 00213 return out; 00214 } 00215 00216 Rectangle getRectangle(CvSeq* cards) 00217 { 00218 CvSeqReader reader; 00219 // initialize reader of the sequence 00220 cvStartReadSeq( cards, &reader, 0 ); 00221 00222 Rectangle result; 00223 00224 if (cards->total > 0) 00225 { 00226 CvPoint pt[4]; 00227 // read 4 vertices 00228 CV_READ_SEQ_ELEM( pt[0], reader ); 00229 CV_READ_SEQ_ELEM( pt[1], reader ); 00230 CV_READ_SEQ_ELEM( pt[2], reader ); 00231 CV_READ_SEQ_ELEM( pt[3], reader ); 00232 00233 //Find the bounding box 00234 Point2D<int> tl(pt[0].x, pt[0].y), br(pt[0].x, pt[0].y); 00235 for(int i=1; i<4; i++) 00236 { 00237 if (pt[i].x < tl.i) tl.i = pt[i].x; 00238 else if (pt[i].x > br.i) br.i = pt[i].x; 00239 00240 if (pt[i].y < tl.j) tl.j = pt[i].y; 00241 else if (pt[i].y > br.j) br.j = pt[i].y; 00242 } 00243 tl.i -= 10; tl.j -= 10; 00244 br.i += 10; br.j += 10; 00245 00246 result = Rectangle::tlbrO(tl.j, tl.i, br.j, br.i); 00247 } 00248 00249 return result; 00250 } 00251 } 00252 00253 #endif //HAVE_OPENCV 00254 00255 // ###################################################################### 00256 EnvSegmenterCannyContour::EnvSegmenterCannyContour(OptionManager& mgr) 00257 : 00258 EnvSegmenter(mgr, "Embeddable Canny Contour FOA Segmenter", 00259 "EnvSegmenterCannyContour"), 00260 itsMinArea("CannyMinArea", this, 500, ALLOW_ONLINE_CHANGES), 00261 itsMaxArea("CannyMaxArea", this, 7000, ALLOW_ONLINE_CHANGES), 00262 itsMinCos("CannyMinCos", this, 0.1, ALLOW_ONLINE_CHANGES) 00263 #ifdef HAVE_OPENCV 00264 , 00265 itsStorage(cvCreateMemStorage(0)) 00266 #endif 00267 { 00268 #ifndef HAVE_OPENCV 00269 LFATAL("OpenCV must be installed in order to use this function"); 00270 #else 00271 ASSERT(itsStorage != 0); 00272 #endif 00273 } 00274 00275 // ###################################################################### 00276 EnvSegmenterCannyContour::~EnvSegmenterCannyContour() 00277 { 00278 #ifndef HAVE_OPENCV 00279 LERROR("OpenCV must be installed in order to use this function"); 00280 #else 00281 cvReleaseMemStorage(&itsStorage); 00282 #endif 00283 } 00284 00285 // ###################################################################### 00286 Rectangle EnvSegmenterCannyContour::getFoa(const Image<PixRGB<byte> >& rgbin, 00287 const Point2D<int>& center, 00288 Image<byte>* foamask, 00289 Image<PixRGB<byte> >* segmentdisp) const 00290 { 00291 #ifndef HAVE_OPENCV 00292 LFATAL("OpenCV must be installed in order to use this function"); 00293 return Rectangle(); 00294 #else 00295 00296 CvSeq* cards = findSquares(rgbin, itsStorage, itsMinArea.getVal(), itsMaxArea.getVal(), itsMinCos.getVal()); 00297 00298 const Rectangle result = getRectangle(cards); 00299 00300 const Image<PixRGB<byte> > out = drawSquares(rgbin, cards); 00301 00302 if (foamask) 00303 { 00304 *foamask = Image<byte>(rgbin.getDims(), ZEROS); 00305 00306 if (result.isValid()) 00307 inplaceClearRegion(*foamask, result, byte(255)); 00308 else 00309 foamask->setVal(center, 255); 00310 } 00311 00312 if (segmentdisp) 00313 *segmentdisp = out; 00314 00315 cvClearMemStorage(itsStorage); 00316 00317 return result.getOverlap(rgbin.getBounds()); 00318 #endif 00319 } 00320 00321 // ###################################################################### 00322 std::vector<Rectangle> EnvSegmenterCannyContour::getSquares(const Image<PixRGB<byte> >& rgbin, Image<PixRGB<byte> >* segmentDisp) 00323 { 00324 #ifndef HAVE_OPENCV 00325 LFATAL("OpenCV must be installed in order to use this function"); 00326 return std::vector<Rectangle>(); 00327 #else 00328 00329 CvSeq* squares = findSquares(rgbin, itsStorage, itsMinArea.getVal(), itsMaxArea.getVal(), itsMinCos.getVal()); 00330 00331 00332 if (segmentDisp) 00333 *segmentDisp = drawSquares(rgbin, squares); 00334 00335 //const Rectangle square = getRectangle(squares); 00336 std::vector<Rectangle> results; 00337 00338 00339 CvSeqReader reader; 00340 // initialize reader of the sequence 00341 cvStartReadSeq(squares, &reader, 0); 00342 00343 // read 4 sequence elements at a time (all vertices of a square) 00344 for (int i = 0; i < squares->total; i += 4) 00345 { 00346 CvPoint pt[4]; 00347 00348 // read 4 vertices 00349 CV_READ_SEQ_ELEM( pt[0], reader ); 00350 CV_READ_SEQ_ELEM( pt[1], reader ); 00351 CV_READ_SEQ_ELEM( pt[2], reader ); 00352 CV_READ_SEQ_ELEM( pt[3], reader ); 00353 00354 //Find the bounding box 00355 Point2D<int> tl(pt[0].x, pt[0].y), br(pt[0].x, pt[0].y); 00356 for(int i=1; i<4; i++) 00357 { 00358 if (pt[i].x < tl.i) tl.i = pt[i].x; 00359 else if (pt[i].x > br.i) br.i = pt[i].x; 00360 00361 if (pt[i].y < tl.j) tl.j = pt[i].y; 00362 else if (pt[i].y > br.j) br.j = pt[i].y; 00363 } 00364 tl.i -= 10; tl.j -= 10; 00365 br.i += 10; br.j += 10; 00366 Rectangle rect = Rectangle::tlbrO(tl.j, tl.i, br.j, br.i); 00367 results.push_back(rect.getOverlap(rgbin.getBounds())); 00368 } 00369 00370 cvClearMemStorage(itsStorage); 00371 00372 return results; 00373 #endif 00374 } 00375 00376 // ###################################################################### 00377 /* So things look consistent in everyone's emacs... */ 00378 /* Local Variables: */ 00379 /* mode: c++ */ 00380 /* indent-tabs-mode: nil */ 00381 /* End: */