00001 /*!@file BeoSub/BeoSubShapeDetector.C vision for the sub */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2003 // 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: Zack Gossman <gossman@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/BeoSub/BeoSubShapeDetector.C $ 00035 // $Id: BeoSubShapeDetector.C 9412 2008-03-10 23:10:15Z farhan $ 00036 // 00037 00038 #include "BeoSub/BeoSubShapeDetector.H" 00039 00040 #include "Image/MathOps.H" 00041 #include "Image/MorphOps.H" // for erodeImg(), closeImg() 00042 #include "Image/Transforms.H" 00043 #include "Util/MathFunctions.H" // for squareOf() inline function 00044 00045 #include <algorithm> // for std::max() 00046 00047 // ###################################################################### 00048 BeoSubCanny::BeoSubCanny(OptionManager& mgr, 00049 const std::string& descrName, 00050 const std::string& tagName): 00051 ModelComponent(mgr, descrName, tagName) 00052 { 00053 //set math details for blur and threshold and such. For now, harcoded 00054 sigma = .5; 00055 tlow = .5; 00056 thigh = .5; 00057 00058 color.resize(4, 0.0F); 00059 00060 hasSetup = false; 00061 debugmode = true; 00062 } 00063 00064 // ###################################################################### 00065 //FIX to accept color as an array rather than a char arg 00066 void BeoSubCanny::setupCanny(const char* colorArg, Image< PixRGB<byte> > image, bool debug) 00067 { 00068 00069 bool useColor = true; 00070 00071 debugmode = debug;//turns output on or off 00072 00073 if(!hasSetup){ 00074 segmenter = new segmentImageTrackMC<float,unsigned int,4> (image.getWidth()*image.getHeight()); 00075 int wif = image.getWidth()/4; 00076 int hif = image.getHeight()/4; 00077 segmenter->SITsetFrame(&wif,&hif); 00078 // Set display colors for output of tracking. Strictly aesthetic //TEST 00079 //And definitely not needed, although for some reason it's required --Z 00080 segmenter->SITsetCircleColor(0,255,0); 00081 segmenter->SITsetBoxColor(255,255,0,0,255,255); 00082 00083 segmenter->SITsetUseSmoothing(true,10); 00084 //method provided by Nathan to turn off adaptation (useful for dynamic environments) 00085 segmenter->SITtoggleColorAdaptation(false); 00086 //enable single-frame tracking 00087 segmenter->SITtoggleCandidateBandPass(false); 00088 } 00089 00090 00091 //COLORTRACKING DECS: note that much of these may not need to be in this file. Instead, the module could accept an array describing the color. 00092 00093 //Colors MUST use H2SV2 pixel values! use test-sampleH2SV2 to sample needed values! --Z 00094 00095 //NOTE: These color presets are extremely dependent on the camera hardware settings. It seems to be best to manually set the white blance and other settings. 00096 //0 = H1 (0-1), 1=H2 (0-1), 2=S (0-1), 3=V (0-1) 00097 if(!strcmp(colorArg, "Red")){ 00098 //RED 00099 color[0] = 0.76700F; color[1] = 0.85300F; color[2] = 0.80000F; color[3] = 0.97200F; 00100 } 00101 00102 else if(!strcmp(colorArg, "Green")){ 00103 //Green 00104 color[0] = 0.53000F; color[1] = 0.36000F; color[2] = 0.44600F; color[3] = 0.92000F; 00105 } 00106 00107 else if(!strcmp(colorArg, "Orange")){ 00108 //"ORANGE" 00109 color[0] = 0.80000F; color[1] = 0.80000F; color[2] = 0.55000F; color[3] = 1.00000F; 00110 } 00111 else if(!strcmp(colorArg, "Blue")){ 00112 //BLUE 00113 color[0] = 0.27500F; color[1] = 0.52500F; color[2] = 0.44500F; color[3] = 0.97200F; 00114 } 00115 else if(!strcmp(colorArg, "Yellow")){ 00116 //YELLOW 00117 color[0] = 0.89000F; color[1] = 0.65000F; color[2] = 0.90000F; color[3] = 0.97000F; 00118 } 00119 else if(!strcmp(colorArg, "White")){ 00120 color[0] = 0.70000F; color[1] = 0.70000F; color[2] = 0.04000F; color[3] = 1.00000F; 00121 } 00122 else if(!strcmp(colorArg, "Black")){ 00123 //color[0] = 0.60001F; color[1] = 0.66000F; color[2] = 0.24000F; color[3] = 0.70000F; 00124 color[0] = 0.79F; color[1] = 0.18F; color[2] = 0.22F; color[3] = 0.175F; 00125 } 00126 else{ 00127 useColor = false; 00128 } 00129 00130 //! +/- tollerance value on mean for track 00131 std::vector<float> std(4,0.0F); 00132 //NOTE that the saturation tolerance is important (if it goes any higher than this, it will nearly always recognize white!) 00133 std[0] = 0.30000; std[1] = 0.30000; std[2] = 0.34500; std[3] = 0.15000;//3 was .45, 2 was .445 00134 00135 if(!strcmp(colorArg, "White") || !strcmp(colorArg, "Black")){ 00136 std[0] = 1.0F; std[1] = 1.0F; std[2] = 0.1F; std[3] = 0.1F; 00137 } 00138 00139 //! normalizer over color values (highest value possible) 00140 std::vector<float> norm(4,0.0F); 00141 norm[0] = 1.0F; norm[1] = 1.0F; norm[2] = 1.0F; norm[3] = 1.0F; 00142 00143 //! how many standard deviations out to adapt, higher means less bias 00144 //The lower these are, the more strict recognition will be in subsequent frames 00145 //TESTED AND PROVED do NOT change unless you're SURE 00146 std::vector<float> adapt(4,0.0F); 00147 //adapt[0] = 3.5F; adapt[1] = 3.5F; adapt[2] = 3.5F; adapt[3] = 3.5F; 00148 adapt[0] = 5.0F; adapt[1] = 5.0F; adapt[2] = 5.0F; adapt[3] = 5.0F; 00149 00150 if(!strcmp(colorArg, "White") || !strcmp(colorArg, "Black")){ 00151 adapt[0] = 25.0F; adapt[1] = 25.0F; adapt[2] = 0.1F; adapt[3] = 0.1F; 00152 00153 } 00154 00155 //! highest value for color adaptation possible (hard boundry) 00156 std::vector<float> upperBound(4,0.0F); 00157 upperBound[0] = color[0] + 0.45F; upperBound[1] = color[1] + 0.45F; upperBound[2] = color[2] + 0.55F; upperBound[3] = color[3] + 0.55F; 00158 00159 //! lowest value for color adaptation possible (hard boundry) 00160 std::vector<float> lowerBound(4,0.0F); 00161 lowerBound[0] = color[0] - 0.45F; lowerBound[1] = color[1] - 0.45F; lowerBound[2] = color[2] - 0.55F; lowerBound[3] = color[3] - 0.55F; 00162 //END DECS 00163 00164 colorImg = image; 00165 imgW = colorImg.getWidth(); 00166 imgH = colorImg.getHeight(); 00167 00168 //color tracking stuff 00169 segmenter->SITsetTrackColor(&color,&std,&norm,&adapt,&upperBound,&lowerBound); 00170 00171 ww = imgW/4; 00172 hh = imgH/4; 00173 00174 Image<PixRGB<byte> > Aux; 00175 Aux.resize(100,450,true); 00176 Image<byte> temp; 00177 Image< PixRGB<byte> > display = colorImg; 00178 00179 if(debugmode){ 00180 if(!win.get()){ 00181 win.reset( new XWindow(colorImg.getDims(), -1, -1, "test-canny window") ); 00182 win->setPosition(0, 0); 00183 } 00184 win->drawImage(colorImg); 00185 } 00186 00187 //Perform color exclusion, if desired 00188 if(useColor){ 00189 00190 //Get candidate image representing blobs of desired color 00191 H2SVimage = colorImg;//changed since transfer to H2SV 00192 display = colorImg; 00193 segmenter->SITtrackImageAny(H2SVimage,&display,&Aux,true); 00194 temp = segmenter->SITreturnCandidateImage();//get image we'll be using for edge detection 00195 //NOTE: This line works off of a "weeded-out" version of the blob tracker. It may be best to switch to this once the colors are set VERY well. Unfortunately, it is picky enough about colors to make the color settings difficult to determine. FIX? 00196 //temp = (Image<byte>)(segmenter->SITreturnBlobMap()); 00197 00198 //set up structuring elements (disk) 00199 Image<byte> se1(5, 5, ZEROS); 00200 Point2D<int> t1(2,2); 00201 drawDisk(se1, t1, 2, static_cast<byte>(255)); 00202 00203 Image<byte> se2(9, 9, ZEROS); 00204 Point2D<int> t2(4,4); 00205 drawDisk(se2, t2, 3, static_cast<byte>(255)); 00206 00207 //Erode image to prevent noise NOTE: isn't working well, erodes too much, probably because temp is so lowrez 00208 //temp = erodeImg(temp, se1); 00209 00210 //Close candidate image to prevent holes 00211 //temp = closeImg(temp, se2); 00212 /*//the following is to exclude non-surrounded colors. was used for the bin 00213 // flood from corners: 00214 Image<byte> temp2 = binaryReverse(temp, byte(255)); 00215 floodClean(temp, temp2, Point2D<int>(0, 0), byte(255), byte(0)); 00216 00217 temp = temp2; 00218 floodClean(temp2, temp, Point2D<int>(temp.getWidth()-1, temp.getHeight()-1), byte(255), byte(0)); 00219 00220 temp = binaryReverse(temp, byte(255)); 00221 */ 00222 grayImg = (Image<byte>)(quickInterpolate(temp,4));//note that this is very low rez 00223 } 00224 else{ 00225 grayImg = luminance(colorImg); 00226 } 00227 00228 hasSetup = true; 00229 } 00230 00231 // ###################################################################### 00232 bool BeoSubCanny::runCanny(rutz::shared_ptr<ShapeModel>& shapeArg){ 00233 00234 00235 //Perform canny edge detection 00236 00237 canny(grayImg.getArrayPtr(), grayImg.getHeight(), grayImg.getWidth(), sigma, tlow, thigh, &edge, NULL); 00238 00239 Image<unsigned char> cannyCharImg(edge, grayImg.getWidth(), grayImg.getHeight()); 00240 00241 Image<float> cannyImg = cannyCharImg; 00242 00243 Point2D<int> cent = centroid(cannyCharImg); 00244 distMap = chamfer34(cannyImg, 5000.0f); 00245 00246 distMap = grayImg; 00247 00248 //Raster::WriteGray(cannyImg, outfilename, RASFMT_PNM);//edge image 00249 if(debugmode){ 00250 if(!xwin.get()){ 00251 xwin.reset(new XWindow(colorImg.getDims())); 00252 xwin->setPosition(colorImg.getWidth()+10, 0); 00253 } 00254 xwin->drawImage((Image< PixRGB<byte> >)grayImg); 00255 } 00256 00257 00258 00259 //perform shape detection 00260 //get initial dimensions for the shape 00261 00262 bool shapeFound = false; 00263 double** xi = (double**)calloc(6, sizeof(double)); 00264 00265 00266 //NOTE: HERE is where the initial direction matrix is built. Change these values to change axis assignment 00267 for(int i=0; i<6; i++) { 00268 xi[i] = (double*)calloc(6, sizeof(double)); 00269 xi[i][i] = 1.0; 00270 } 00271 //Reset initial axes 00272 xi[0][0] = 0.5; 00273 xi[0][1] = 0.5; 00274 00275 int iter = 0; 00276 double fret = 0.0; 00277 00278 //here, we will be using powell classes, and, instead of passing in calcDist, will be passing in a shape. 00279 shapeFound = powell(shapeArg->getDimensions(), xi, shapeArg->getNumDims(), 0.5, &iter, &fret, shapeArg); 00280 00281 if(debugmode){ 00282 double* p = shapeArg->getDimensions();//TEST 00283 if(shapeFound){ 00284 printf("\n\nShape found!\n"); 00285 printf("Final dimensions are -- "); 00286 for(int w = 1; w <=shapeArg->getNumDims(); w++){ 00287 printf("%d: %f ", w, p[w]);//outputs final values 00288 } 00289 printf("Certainty (lower is better): %f\n\n", fret); 00290 }else{ 00291 printf("\n\nShape not found. Certainty was %f\n\n", fret); 00292 } 00293 } 00294 00295 return(shapeFound); 00296 } 00297 00298 00299 00300 // ###################################################################### 00301 BeoSubCanny::~BeoSubCanny() 00302 { 00303 } 00304 00305 ///////////////////////FROM NRUTIL///////////////////////// 00306 // ###################################################################### 00307 double *BeoSubCanny::nrVector(long nl, long nh) 00308 /* allocate a double vector with subscript range v[nl..nh] */ 00309 { 00310 double *v; 00311 00312 v=(double *)malloc((size_t) ((nh-nl+2)*sizeof(double))); 00313 if (!v) printf("allocation failure in nrVector()"); 00314 return v-nl+1; 00315 } 00316 00317 // ###################################################################### 00318 void BeoSubCanny::free_nrVector(double *v, long nl, long nh) 00319 /* free a double vector allocated with nrVector() */ 00320 { 00321 free((FREE_ARG) (v+nl-1)); 00322 } 00323 00324 00325 // ###################################################################### 00326 void BeoSubCanny::mnbrak(double *ax, double *bx, double *cx, double *fa, 00327 double *fb, double *fc, 00328 rutz::shared_ptr<ShapeModel>& shape) 00329 { 00330 double ulim,u,r,q,fu,dum; 00331 00332 *fa=f1dim(*ax, shape); 00333 *fb=f1dim(*bx, shape); 00334 if (*fb > *fa) { 00335 SHFT(dum,*ax,*bx,dum) 00336 SHFT(dum,*fb,*fa,dum) 00337 } 00338 *cx=(*bx)+GOLD*(*bx-*ax); 00339 *fc=f1dim(*cx, shape); 00340 while (*fb > *fc) { 00341 r=(*bx-*ax)*(*fb-*fc); 00342 q=(*bx-*cx)*(*fb-*fa); 00343 u=(*bx)-((*bx-*cx)*q-(*bx-*ax)*r)/ 00344 (2.0*SIGN(std::max(fabs(q-r),TINY),q-r)); 00345 ulim=(*bx)+GLIMIT*(*cx-*bx); 00346 if ((*bx-u)*(u-*cx) > 0.0) { 00347 fu=f1dim(u, shape); 00348 if (fu < *fc) { 00349 *ax=(*bx); 00350 *bx=u; 00351 *fa=(*fb); 00352 *fb=fu; 00353 return; 00354 } else if (fu > *fb) { 00355 *cx=u; 00356 *fc=fu; 00357 return; 00358 } 00359 u=(*cx)+GOLD*(*cx-*bx); 00360 fu=f1dim(u, shape); 00361 } else if ((*cx-u)*(u-ulim) > 0.0) { 00362 fu=f1dim(u, shape); 00363 if (fu < *fc) { 00364 SHFT(*bx,*cx,u,*cx+GOLD*(*cx-*bx)) 00365 SHFT(*fb,*fc,fu,f1dim(u, shape)) 00366 } 00367 } else if ((u-ulim)*(ulim-*cx) >= 0.0) { 00368 u=ulim; 00369 fu=f1dim(u, shape); 00370 } else { 00371 u=(*cx)+GOLD*(*cx-*bx); 00372 fu=f1dim(u, shape); 00373 } 00374 SHFT(*ax,*bx,*cx,u) 00375 SHFT(*fa,*fb,*fc,fu) 00376 } 00377 } 00378 00379 double BeoSubCanny::brent(double ax, double bx, double cx, 00380 double tol, double *xmin, 00381 rutz::shared_ptr<ShapeModel>& shape) 00382 { 00383 int iter; 00384 double a,b,d=0.0,etemp,fu,fv,fw,fx,p,q,r,tol1,tol2,u,v,w,x,xm; 00385 double e=0.0; 00386 00387 a=(ax < cx ? ax : cx); 00388 b=(ax > cx ? ax : cx); 00389 x=w=v=bx; 00390 fw=fv=fx=f1dim(x, shape); 00391 for (iter=1;iter<=ITMAXB;iter++) { 00392 xm=0.5*(a+b); 00393 tol2=2.0*(tol1=tol*fabs(x)+ZEPS); 00394 if (fabs(x-xm) <= (tol2-0.5*(b-a))) { 00395 *xmin=x; 00396 return fx; 00397 } 00398 if (fabs(e) > tol1) { 00399 r=(x-w)*(fx-fv); 00400 q=(x-v)*(fx-fw); 00401 p=(x-v)*q-(x-w)*r; 00402 q=2.0*(q-r); 00403 if (q > 0.0) p = -p; 00404 q=fabs(q); 00405 etemp=e; 00406 e=d; 00407 if (fabs(p) >= fabs(0.5*q*etemp) || p <= q*(a-x) || p >= q*(b-x)) 00408 d=CGOLD*(e=(x >= xm ? a-x : b-x)); 00409 else { 00410 d=p/q; 00411 u=x+d; 00412 if (u-a < tol2 || b-u < tol2) 00413 d=SIGN(tol1,xm-x); 00414 } 00415 } else { 00416 d=CGOLD*(e=(x >= xm ? a-x : b-x)); 00417 } 00418 u=(fabs(d) >= tol1 ? x+d : x+SIGN(tol1,d)); 00419 fu=f1dim(u, shape); 00420 if (fu <= fx) { 00421 if (u >= x) a=x; else b=x; 00422 SHFT(v,w,x,u) 00423 SHFT(fv,fw,fx,fu) 00424 } else { 00425 if (u < x) a=u; else b=u; 00426 if (fu <= fw || w == x) { 00427 v=w; 00428 w=u; 00429 fv=fw; 00430 fw=fu; 00431 } else if (fu <= fv || v == x || v == w) { 00432 v=u; 00433 fv=fu; 00434 } 00435 } 00436 } 00437 printf("Too many iterations in brent"); 00438 *xmin=x; 00439 return fx; 00440 } 00441 00442 double BeoSubCanny::f1dim(double x, rutz::shared_ptr<ShapeModel>& shape) 00443 { 00444 int j; 00445 double f, *xt; 00446 xt=nrVector(1,ncom); 00447 00448 for (j=1;j<=ncom;j++) xt[j]=pcom[j]+x*xicom[j]; 00449 f=shape->calcDist(xt, distMap); 00450 free_nrVector(xt,1,ncom); 00451 return f; 00452 } 00453 00454 void BeoSubCanny::linmin(double p[], double xi[], int n, double *fret, 00455 rutz::shared_ptr<ShapeModel>& optimizee) 00456 { 00457 00458 int j; 00459 double xx,xmin,fx,fb,fa,bx,ax; 00460 00461 ncom=n; 00462 pcom=nrVector(1,n); 00463 xicom=nrVector(1,n); 00464 for (j=1;j<=n;j++) { 00465 pcom[j]=p[j]; 00466 xicom[j]=xi[j]; 00467 } 00468 00469 ax=0.0; 00470 xx=1.0; 00471 00472 mnbrak(&ax,&xx,&bx,&fa,&fx,&fb,optimizee); 00473 *fret=brent(ax,xx,bx,TOL,&xmin, optimizee); 00474 00475 for (j=1;j<=n;j++) { 00476 xi[j] *= xmin; 00477 p[j] += xi[j]; 00478 } 00479 free_nrVector(xicom,1,n); 00480 free_nrVector(pcom,1,n); 00481 } 00482 00483 bool BeoSubCanny::powell(double p[], double **xi, int n, double ftol, 00484 int *iter, double *fret, rutz::shared_ptr<ShapeModel>& optimizee) 00485 { 00486 int i,ibig,j; 00487 double del,fp,fptt,t, *pt, *ptt, *xit; 00488 00489 pt=nrVector(1,n); 00490 ptt=nrVector(1,n); 00491 xit=nrVector(1,n); 00492 00493 fptt = optimizee->getThreshold(); //hacky, to prevent warning 00494 00495 *fret=optimizee->calcDist(p, distMap); 00496 for (j=1;j<=n;j++) pt[j]=p[j]; 00497 for (*iter=1;;++(*iter)) { 00498 fp=(*fret); 00499 ibig=0; 00500 del=0.0; 00501 for (i=1;i<=n;i++) { 00502 for (j = 1; j <= n; j++){ 00503 //NOTE: This stupid change using -1 for the xi vector is necessary to handle weirdness of the nrVectors 00504 xit[j] = xi[j-1][i-1]; 00505 } 00506 fptt=(*fret); 00507 linmin(p,xit,n,fret,optimizee); 00508 if (fabs(fptt-(*fret)) > del) { 00509 del=fabs(fptt-(*fret)); 00510 ibig=i; 00511 } 00512 } 00513 if (2.0*fabs(fp-(*fret)) <= ftol*(fabs(fp)+fabs(*fret))) { 00514 00515 //optimizee->setDimensions(p);//TEST 00516 00517 //If cost is below threshold, then return that the shape was found 00518 //perhaps this is not in the most ideal spot. --Z 00519 if(fptt < optimizee->getThreshold()){//if cost estimate is below threshold, shape is present in image (very rudimentary, need to take dimensions into account) 00520 return(true);//test 00521 } 00522 //otherwise, return that it was not 00523 else{return(false);}//test 00524 } 00525 if (*iter == ITMAX){ 00526 //optimizee->setDimensions(p);//TEST 00527 printf("powell exceeding maximum iterations."); 00528 return(false);//test 00529 } 00530 for (j=1;j<=n;j++) { 00531 ptt[j]=2.0*p[j]-pt[j]; 00532 xit[j]=p[j]-pt[j]; 00533 pt[j]=p[j]; 00534 } 00535 //NOTE: in order to fix problem of things becoming too small or going outside of the image, either put a check right here or check inside calc dist. Then, if values cause trouble, REVERT to values of things in above for loop that were set BEFORE the previous change. This may not help, however, depending on how calcDist is called in its first instance in this function 00536 //NOTE: it may be better instead to have the calcDist function return values by reference. In that case, a shape could correct its own dimensions if they went out of whack. Unfortunately, this would not work if poweel would still go down the wrong path. FIX! 00537 fptt=optimizee->calcDist(ptt, distMap); 00538 if (fptt < fp) { 00539 t=2.0*(fp-2.0*(*fret)+fptt)*squareOf(fp-(*fret)-del)-del*squareOf(fp-fptt); 00540 if (t < 0.0) { 00541 linmin(p,xit,n,fret,optimizee); 00542 for (j=0;j<n;j++) { 00543 xi[j][ibig]=xi[j][n]; 00544 xi[j][n]=xit[j]; 00545 } 00546 } 00547 } 00548 00549 } 00550 } 00551 00552 /******************************************************************************* 00553 * PROCEDURE: canny 00554 * PURPOSE: To perform canny edge detection. 00555 * NAME: Mike Heath 00556 * DATE: 2/15/96 00557 //Pradeep: returns the centroid of the "white" pixels 00558 *******************************************************************************/ 00559 int BeoSubCanny::canny(unsigned char *image, int rows, int cols, float sigma, 00560 float tlow, float thigh, unsigned char **edge, char *fname) 00561 { 00562 FILE *fpdir=NULL; /* File to write the gradient image to. */ 00563 unsigned char *nms; /* Points that are local maximal magnitude. */ 00564 short int *smoothedim, /* The image after gaussian smoothing. */ 00565 *delta_x, /* The first devivative image, x-direction. */ 00566 *delta_y, /* The first derivative image, y-direction. */ 00567 *magnitude; /* The magnitude of the gadient image. */ 00568 float *dir_radians=NULL; /* Gradient direction image. */ 00569 00570 /**************************************************************************** 00571 * Perform gaussian smoothing on the image using the input standard 00572 * deviation. 00573 ****************************************************************************/ 00574 gaussian_smooth(image, rows, cols, sigma, &smoothedim); 00575 00576 /**************************************************************************** 00577 * Compute the first derivative in the x and y directions. 00578 ****************************************************************************/ 00579 derrivative_x_y(smoothedim, rows, cols, &delta_x, &delta_y); 00580 00581 /**************************************************************************** 00582 * This option to write out the direction of the edge gradient was added 00583 * to make the information available for computing an edge quality figure 00584 * of merit. 00585 ****************************************************************************/ 00586 if(fname != NULL){ 00587 /************************************************************************* 00588 * Compute the direction up the gradient, in radians that are 00589 * specified counteclockwise from the positive x-axis. 00590 *************************************************************************/ 00591 radian_direction(delta_x, delta_y, rows, cols, &dir_radians, -1, -1); 00592 00593 /************************************************************************* 00594 * Write the gradient direction image out to a file. 00595 *************************************************************************/ 00596 if((fpdir = fopen(fname, "wb")) == NULL){ 00597 fprintf(stderr, "Error opening the file %s for writing.\n", fname); 00598 exit(1); 00599 } 00600 fwrite(dir_radians, sizeof(float), rows*cols, fpdir); 00601 fclose(fpdir); 00602 free(dir_radians); 00603 } 00604 00605 /**************************************************************************** 00606 * Compute the magnitude of the gradient. 00607 ****************************************************************************/ 00608 magnitude_x_y(delta_x, delta_y, rows, cols, &magnitude); 00609 00610 /**************************************************************************** 00611 * Perform non-maximal suppression. 00612 ****************************************************************************/ 00613 if((nms = (unsigned char *) calloc(rows*cols,sizeof(unsigned char)))==NULL){ 00614 fprintf(stderr, "Error allocating the nms image.\n"); 00615 exit(1); 00616 } 00617 00618 non_max_supp(magnitude, delta_x, delta_y, rows, cols, nms); 00619 00620 /**************************************************************************** 00621 * Use hysteresis to mark the edge pixels. 00622 ****************************************************************************/ 00623 if((*edge=(unsigned char *)calloc(rows*cols,sizeof(unsigned char))) ==NULL){ 00624 fprintf(stderr, "Error allocating the edge image.\n"); 00625 exit(1); 00626 } 00627 //printf("\n%d %d %d %d %d %d\n", magnitude, nms, rows, cols, tlow, thigh); 00628 int centroid = apply_hysteresis(magnitude, nms, rows, cols, tlow, thigh, *edge); 00629 /**************************************************************************** 00630 * Free all of the memory that we allocated except for the edge image that 00631 * is still being used to store out result. 00632 ****************************************************************************/ 00633 free(smoothedim); 00634 free(delta_x); 00635 free(delta_y); 00636 free(magnitude); 00637 free(nms); 00638 return centroid; 00639 } 00640 00641 /******************************************************************************* 00642 * Procedure: radian_direction 00643 * Purpose: To compute a direction of the gradient image from component dx and 00644 * dy images. Because not all derriviatives are computed in the same way, this 00645 * code allows for dx or dy to have been calculated in different ways. 00646 * 00647 * FOR X: xdirtag = -1 for [-1 0 1] 00648 * xdirtag = 1 for [ 1 0 -1] 00649 * 00650 * FOR Y: ydirtag = -1 for [-1 0 1]' 00651 * ydirtag = 1 for [ 1 0 -1]' 00652 * 00653 * The resulting angle is in radians measured counterclockwise from the 00654 * xdirection. The angle points "up the gradient". 00655 *******************************************************************************/ 00656 void BeoSubCanny::radian_direction(short int *delta_x, short int *delta_y, int rows, 00657 int cols, float **dir_radians, int xdirtag, int ydirtag) 00658 { 00659 int r, c, pos; 00660 float *dirim=NULL; 00661 double dx, dy; 00662 00663 /**************************************************************************** 00664 * Allocate an image to store the direction of the gradient. 00665 ****************************************************************************/ 00666 if((dirim = (float *) calloc(rows*cols, sizeof(float))) == NULL){ 00667 fprintf(stderr, "Error allocating the gradient direction image.\n"); 00668 exit(1); 00669 } 00670 *dir_radians = dirim; 00671 00672 for(r=0,pos=0;r<rows;r++){ 00673 for(c=0;c<cols;c++,pos++){ 00674 dx = (double)delta_x[pos]; 00675 dy = (double)delta_y[pos]; 00676 00677 if(xdirtag == 1) dx = -dx; 00678 if(ydirtag == -1) dy = -dy; 00679 00680 dirim[pos] = (float)angle_radians(dx, dy); 00681 } 00682 } 00683 } 00684 00685 /******************************************************************************* 00686 * FUNCTION: angle_radians 00687 * PURPOSE: This procedure computes the angle of a vector with components x and 00688 * y. It returns this angle in radians with the answer being in the range 00689 * 0 <= angle <2*PI. 00690 *******************************************************************************/ 00691 double BeoSubCanny::angle_radians(double x, double y) 00692 { 00693 double xu, yu, ang; 00694 00695 xu = fabs(x); 00696 yu = fabs(y); 00697 00698 if((xu == 0) && (yu == 0)) return(0); 00699 00700 ang = atan(yu/xu); 00701 00702 if(x >= 0){ 00703 if(y >= 0) return(ang); 00704 else return(2*M_PI - ang); 00705 } 00706 else{ 00707 if(y >= 0) return(M_PI - ang); 00708 else return(M_PI + ang); 00709 } 00710 } 00711 00712 /******************************************************************************* 00713 * PROCEDURE: magnitude_x_y 00714 * PURPOSE: Compute the magnitude of the gradient. This is the square root of 00715 * the sum of the squared derivative values. 00716 * NAME: Mike Heath 00717 * DATE: 2/15/96 00718 *******************************************************************************/ 00719 void BeoSubCanny::magnitude_x_y(short int *delta_x, short int *delta_y, int rows, int cols, 00720 short int **magnitude) 00721 { 00722 int r, c, pos, sq1, sq2; 00723 00724 /**************************************************************************** 00725 * Allocate an image to store the magnitude of the gradient. 00726 ****************************************************************************/ 00727 if((*magnitude = (short *) calloc(rows*cols, sizeof(short))) == NULL){ 00728 fprintf(stderr, "Error allocating the magnitude image.\n"); 00729 exit(1); 00730 } 00731 00732 for(r=0,pos=0;r<rows;r++){ 00733 for(c=0;c<cols;c++,pos++){ 00734 sq1 = (int)delta_x[pos] * (int)delta_x[pos]; 00735 sq2 = (int)delta_y[pos] * (int)delta_y[pos]; 00736 (*magnitude)[pos] = (short)(0.5 + sqrt((float)sq1 + (float)sq2)); 00737 } 00738 } 00739 00740 } 00741 00742 /******************************************************************************* 00743 * PROCEDURE: derrivative_x_y 00744 * PURPOSE: Compute the first derivative of the image in both the x any y 00745 * directions. The differential filters that are used are: 00746 * 00747 * -1 00748 * dx = -1 0 +1 and dy = 0 00749 * +1 00750 * 00751 * NAME: Mike Heath 00752 * DATE: 2/15/96 00753 *******************************************************************************/ 00754 void BeoSubCanny::derrivative_x_y(short int *smoothedim, int rows, int cols, 00755 short int **delta_x, short int **delta_y) 00756 { 00757 int r, c, pos; 00758 00759 /**************************************************************************** 00760 * Allocate images to store the derivatives. 00761 ****************************************************************************/ 00762 if(((*delta_x) = (short *) calloc(rows*cols, sizeof(short))) == NULL){ 00763 fprintf(stderr, "Error allocating the delta_x image.\n"); 00764 exit(1); 00765 } 00766 if(((*delta_y) = (short *) calloc(rows*cols, sizeof(short))) == NULL){ 00767 fprintf(stderr, "Error allocating the delta_x image.\n"); 00768 exit(1); 00769 } 00770 00771 /**************************************************************************** 00772 * Compute the x-derivative. Adjust the derivative at the borders to avoid 00773 * losing pixels. 00774 ****************************************************************************/ 00775 for(r=0;r<rows;r++){ 00776 pos = r * cols; 00777 (*delta_x)[pos] = smoothedim[pos+1] - smoothedim[pos]; 00778 pos++; 00779 for(c=1;c<(cols-1);c++,pos++){ 00780 (*delta_x)[pos] = smoothedim[pos+1] - smoothedim[pos-1]; 00781 } 00782 (*delta_x)[pos] = smoothedim[pos] - smoothedim[pos-1]; 00783 } 00784 00785 /**************************************************************************** 00786 * Compute the y-derivative. Adjust the derivative at the borders to avoid 00787 * losing pixels. 00788 ****************************************************************************/ 00789 for(c=0;c<cols;c++){ 00790 pos = c; 00791 (*delta_y)[pos] = smoothedim[pos+cols] - smoothedim[pos]; 00792 pos += cols; 00793 for(r=1;r<(rows-1);r++,pos+=cols){ 00794 (*delta_y)[pos] = smoothedim[pos+cols] - smoothedim[pos-cols]; 00795 } 00796 (*delta_y)[pos] = smoothedim[pos] - smoothedim[pos-cols]; 00797 } 00798 } 00799 00800 /******************************************************************************* 00801 * PROCEDURE: gaussian_smooth 00802 * PURPOSE: Blur an image with a gaussian filter. 00803 * NAME: Mike Heath 00804 * DATE: 2/15/96 00805 *******************************************************************************/ 00806 void BeoSubCanny::gaussian_smooth(unsigned char *image, int rows, int cols, float sigma, 00807 short int **smoothedim) 00808 { 00809 int r, c, rr, cc, /* Counter variables. */ 00810 windowsize, /* Dimension of the gaussian kernel. */ 00811 center; /* Half of the windowsize. */ 00812 float *tempim, /* Buffer for separable filter gaussian smoothing. */ 00813 *kernel, /* A one dimensional gaussian kernel. */ 00814 dot, /* Dot product summing variable. */ 00815 sum; /* Sum of the kernel weights variable. */ 00816 00817 /**************************************************************************** 00818 * Create a 1-dimensional gaussian smoothing kernel. 00819 ****************************************************************************/ 00820 make_gaussian_kernel(sigma, &kernel, &windowsize); 00821 center = windowsize / 2; 00822 00823 /**************************************************************************** 00824 * Allocate a temporary buffer image and the smoothed image. 00825 ****************************************************************************/ 00826 if((tempim = (float *) calloc(rows*cols, sizeof(float))) == NULL){ 00827 fprintf(stderr, "Error allocating the buffer image.\n"); 00828 exit(1); 00829 } 00830 if(((*smoothedim) = (short int *) calloc(rows*cols, 00831 sizeof(short int))) == NULL){ 00832 fprintf(stderr, "Error allocating the smoothed image.\n"); 00833 exit(1); 00834 } 00835 00836 /**************************************************************************** 00837 * Blur in the x - direction. 00838 ****************************************************************************/ 00839 for(r=0;r<rows;r++){ 00840 for(c=0;c<cols;c++){ 00841 dot = 0.0; 00842 sum = 0.0; 00843 for(cc=(-center);cc<=center;cc++){ 00844 if(((c+cc) >= 0) && ((c+cc) < cols)){ 00845 dot += (float)image[r*cols+(c+cc)] * kernel[center+cc]; 00846 sum += kernel[center+cc]; 00847 } 00848 } 00849 tempim[r*cols+c] = dot/sum; 00850 } 00851 } 00852 00853 /**************************************************************************** 00854 * Blur in the y - direction. 00855 ****************************************************************************/ 00856 for(c=0;c<cols;c++){ 00857 for(r=0;r<rows;r++){ 00858 sum = 0.0; 00859 dot = 0.0; 00860 for(rr=(-center);rr<=center;rr++){ 00861 if(((r+rr) >= 0) && ((r+rr) < rows)){ 00862 dot += tempim[(r+rr)*cols+c] * kernel[center+rr]; 00863 sum += kernel[center+rr]; 00864 } 00865 } 00866 (*smoothedim)[r*cols+c] = (short int)(dot*BOOSTBLURFACTOR/sum + 0.5); 00867 } 00868 } 00869 00870 free(tempim); 00871 free(kernel); 00872 } 00873 00874 /******************************************************************************* 00875 * PROCEDURE: make_gaussian_kernel 00876 * PURPOSE: Create a one dimensional gaussian kernel. 00877 * NAME: Mike Heath 00878 * DATE: 2/15/96 00879 *******************************************************************************/ 00880 void BeoSubCanny::make_gaussian_kernel(float sigma, float **kernel, int *windowsize) 00881 { 00882 int i, center; 00883 float x, fx, sum=0.0; 00884 00885 *windowsize = int(1 + 2 * ceil(2.5 * sigma)); 00886 center = (*windowsize) / 2; 00887 00888 if((*kernel = (float *) calloc((*windowsize), sizeof(float))) == NULL){ 00889 fprintf(stderr, "Error callocing the gaussian kernel array.\n"); 00890 exit(1); 00891 } 00892 00893 for(i=0;i<(*windowsize);i++){ 00894 x = (float)(i - center); 00895 fx = pow(2.71828, -0.5*x*x/(sigma*sigma)) / (sigma * sqrt(6.2831853)); 00896 (*kernel)[i] = fx; 00897 sum += fx; 00898 } 00899 00900 for(i=0;i<(*windowsize);i++) (*kernel)[i] /= sum; 00901 00902 }