00001 /*!@file BeoSub/BeoSubCanny.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/BeoSubCanny.C $ 00035 // $Id: BeoSubCanny.C 14376 2011-01-11 02:44:34Z pez $ 00036 // 00037 00038 #include "BeoSub/BeoSubCanny.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 } 00125 else{ 00126 useColor = false; 00127 } 00128 00129 //! +/- tollerance value on mean for track 00130 std::vector<float> std(4,0.0F); 00131 //NOTE that the saturation tolerance is important (if it goes any higher than this, it will nearly always recognize white!) 00132 std[0] = 0.30000; std[1] = 0.30000; std[2] = 0.34500; std[3] = 0.15000;//3 was .45, 2 was .445 00133 00134 if(!strcmp(colorArg, "White") || !strcmp(colorArg, "Black")){ 00135 std[0] = 1.0F; std[1] = 1.0F; std[2] = 0.1F; std[3] = 0.1F; 00136 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 canny(grayImg.getArrayPtr(), grayImg.getHeight(), grayImg.getWidth(), sigma, tlow, thigh, &edge, NULL); 00237 00238 Image<unsigned char> cannyCharImg(edge, grayImg.getWidth(), grayImg.getHeight()); 00239 00240 Image<float> cannyImg = cannyCharImg; 00241 00242 00243 //Raster::WriteGray(cannyImg, outfilename, RASFMT_PNM);//edge image 00244 if(debugmode){ 00245 if(!xwin.get()){ 00246 xwin.reset(new XWindow(colorImg.getDims())); 00247 xwin->setPosition(colorImg.getWidth()+10, 0); 00248 } 00249 xwin->drawImage((Image< PixRGB<byte> >)grayImg); 00250 } 00251 00252 centroid(cannyCharImg); 00253 distMap = chamfer34(cannyImg, 5000.0f); 00254 00255 //perform shape detection 00256 //get initial dimensions for the shape 00257 00258 bool shapeFound = false; 00259 double** xi = (double**)calloc(6, sizeof(double)); 00260 00261 00262 //NOTE: HERE is where the initial direction matrix is built. Change these values to change axis assignment 00263 for(int i=0; i<6; i++) { 00264 xi[i] = (double*)calloc(6, sizeof(double)); 00265 xi[i][i] = 1.0; 00266 } 00267 //Reset initial axes 00268 xi[0][0] = 0.5; 00269 xi[0][1] = 0.5; 00270 00271 int iter = 0; 00272 double fret = 0.0; 00273 00274 //here, we will be using powell classes, and, instead of passing in calcDist, will be passing in a shape. 00275 shapeFound = powell(shapeArg->getDimensions(), xi, shapeArg->getNumDims(), 0.5, &iter, &fret, shapeArg); 00276 00277 if(debugmode){ 00278 double* p = shapeArg->getDimensions();//TEST 00279 if(shapeFound){ 00280 printf("\n\nShape found!\n"); 00281 printf("Final dimensions are -- "); 00282 for(int w = 1; w <=shapeArg->getNumDims(); w++){ 00283 printf("%d: %f ", w, p[w]);//outputs final values 00284 } 00285 printf("Certainty (lower is better): %f\n\n", fret); 00286 }else{ 00287 printf("\n\nShape not found. Certainty was %f\n\n", fret); 00288 } 00289 } 00290 00291 return(shapeFound); 00292 } 00293 00294 00295 00296 // ###################################################################### 00297 BeoSubCanny::~BeoSubCanny() 00298 { 00299 } 00300 00301 ///////////////////////FROM NRUTIL///////////////////////// 00302 // ###################################################################### 00303 double *BeoSubCanny::nrVector(long nl, long nh) 00304 /* allocate a double vector with subscript range v[nl..nh] */ 00305 { 00306 double *v; 00307 00308 v=(double *)malloc((size_t) ((nh-nl+2)*sizeof(double))); 00309 if (!v) printf("allocation failure in nrVector()"); 00310 return v-nl+1; 00311 } 00312 00313 // ###################################################################### 00314 void BeoSubCanny::free_nrVector(double *v, long nl, long nh) 00315 /* free a double vector allocated with nrVector() */ 00316 { 00317 free((FREE_ARG) (v+nl-1)); 00318 } 00319 00320 00321 // ###################################################################### 00322 void BeoSubCanny::mnbrak(double *ax, double *bx, double *cx, double *fa, 00323 double *fb, double *fc, 00324 rutz::shared_ptr<ShapeModel>& shape) 00325 { 00326 double ulim,u,r,q,fu,dum; 00327 00328 *fa=f1dim(*ax, shape); 00329 *fb=f1dim(*bx, shape); 00330 if (*fb > *fa) { 00331 SHFT(dum,*ax,*bx,dum) 00332 SHFT(dum,*fb,*fa,dum) 00333 } 00334 *cx=(*bx)+GOLD*(*bx-*ax); 00335 *fc=f1dim(*cx, shape); 00336 while (*fb > *fc) { 00337 r=(*bx-*ax)*(*fb-*fc); 00338 q=(*bx-*cx)*(*fb-*fa); 00339 u=(*bx)-((*bx-*cx)*q-(*bx-*ax)*r)/ 00340 (2.0*SIGN(std::max(fabs(q-r),TINY),q-r)); 00341 ulim=(*bx)+GLIMIT*(*cx-*bx); 00342 if ((*bx-u)*(u-*cx) > 0.0) { 00343 fu=f1dim(u, shape); 00344 if (fu < *fc) { 00345 *ax=(*bx); 00346 *bx=u; 00347 *fa=(*fb); 00348 *fb=fu; 00349 return; 00350 } else if (fu > *fb) { 00351 *cx=u; 00352 *fc=fu; 00353 return; 00354 } 00355 u=(*cx)+GOLD*(*cx-*bx); 00356 fu=f1dim(u, shape); 00357 } else if ((*cx-u)*(u-ulim) > 0.0) { 00358 fu=f1dim(u, shape); 00359 if (fu < *fc) { 00360 SHFT(*bx,*cx,u,*cx+GOLD*(*cx-*bx)) 00361 SHFT(*fb,*fc,fu,f1dim(u, shape)) 00362 } 00363 } else if ((u-ulim)*(ulim-*cx) >= 0.0) { 00364 u=ulim; 00365 fu=f1dim(u, shape); 00366 } else { 00367 u=(*cx)+GOLD*(*cx-*bx); 00368 fu=f1dim(u, shape); 00369 } 00370 SHFT(*ax,*bx,*cx,u) 00371 SHFT(*fa,*fb,*fc,fu) 00372 } 00373 } 00374 00375 double BeoSubCanny::brent(double ax, double bx, double cx, 00376 double tol, double *xmin, 00377 rutz::shared_ptr<ShapeModel>& shape) 00378 { 00379 int iter; 00380 double a,b,d=0.0,etemp,fu,fv,fw,fx,p,q,r,tol1,tol2,u,v,w,x,xm; 00381 double e=0.0; 00382 00383 a=(ax < cx ? ax : cx); 00384 b=(ax > cx ? ax : cx); 00385 x=w=v=bx; 00386 fw=fv=fx=f1dim(x, shape); 00387 for (iter=1;iter<=ITMAXB;iter++) { 00388 xm=0.5*(a+b); 00389 tol2=2.0*(tol1=tol*fabs(x)+ZEPS); 00390 if (fabs(x-xm) <= (tol2-0.5*(b-a))) { 00391 *xmin=x; 00392 return fx; 00393 } 00394 if (fabs(e) > tol1) { 00395 r=(x-w)*(fx-fv); 00396 q=(x-v)*(fx-fw); 00397 p=(x-v)*q-(x-w)*r; 00398 q=2.0*(q-r); 00399 if (q > 0.0) p = -p; 00400 q=fabs(q); 00401 etemp=e; 00402 e=d; 00403 if (fabs(p) >= fabs(0.5*q*etemp) || p <= q*(a-x) || p >= q*(b-x)) 00404 d=CGOLD*(e=(x >= xm ? a-x : b-x)); 00405 else { 00406 d=p/q; 00407 u=x+d; 00408 if (u-a < tol2 || b-u < tol2) 00409 d=SIGN(tol1,xm-x); 00410 } 00411 } else { 00412 d=CGOLD*(e=(x >= xm ? a-x : b-x)); 00413 } 00414 u=(fabs(d) >= tol1 ? x+d : x+SIGN(tol1,d)); 00415 fu=f1dim(u, shape); 00416 if (fu <= fx) { 00417 if (u >= x) a=x; else b=x; 00418 SHFT(v,w,x,u) 00419 SHFT(fv,fw,fx,fu) 00420 } else { 00421 if (u < x) a=u; else b=u; 00422 if (fu <= fw || w == x) { 00423 v=w; 00424 w=u; 00425 fv=fw; 00426 fw=fu; 00427 } else if (fu <= fv || v == x || v == w) { 00428 v=u; 00429 fv=fu; 00430 } 00431 } 00432 } 00433 printf("Too many iterations in brent"); 00434 *xmin=x; 00435 return fx; 00436 } 00437 00438 double BeoSubCanny::f1dim(double x, rutz::shared_ptr<ShapeModel>& shape) 00439 { 00440 int j; 00441 double f, *xt; 00442 xt=nrVector(1,ncom); 00443 00444 for (j=1;j<=ncom;j++) xt[j]=pcom[j]+x*xicom[j]; 00445 f=shape->calcDist(xt, distMap); 00446 free_nrVector(xt,1,ncom); 00447 return f; 00448 } 00449 00450 void BeoSubCanny::linmin(double p[], double xi[], int n, double *fret, 00451 rutz::shared_ptr<ShapeModel>& optimizee) 00452 { 00453 00454 int j; 00455 double xx,xmin,fx,fb,fa,bx,ax; 00456 00457 ncom=n; 00458 pcom=nrVector(1,n); 00459 xicom=nrVector(1,n); 00460 for (j=1;j<=n;j++) { 00461 pcom[j]=p[j]; 00462 xicom[j]=xi[j]; 00463 } 00464 00465 ax=0.0; 00466 xx=1.0; 00467 00468 mnbrak(&ax,&xx,&bx,&fa,&fx,&fb,optimizee); 00469 *fret=brent(ax,xx,bx,TOL,&xmin, optimizee); 00470 00471 for (j=1;j<=n;j++) { 00472 xi[j] *= xmin; 00473 p[j] += xi[j]; 00474 } 00475 free_nrVector(xicom,1,n); 00476 free_nrVector(pcom,1,n); 00477 } 00478 00479 bool BeoSubCanny::powell(double p[], double **xi, int n, double ftol, 00480 int *iter, double *fret, rutz::shared_ptr<ShapeModel>& optimizee) 00481 { 00482 int i,ibig,j; 00483 double del,fp,fptt,t, *pt, *ptt, *xit; 00484 00485 pt=nrVector(1,n); 00486 ptt=nrVector(1,n); 00487 xit=nrVector(1,n); 00488 00489 fptt = optimizee->getThreshold(); //hacky, to prevent warning 00490 00491 *fret=optimizee->calcDist(p, distMap); 00492 for (j=1;j<=n;j++) pt[j]=p[j]; 00493 for (*iter=1;;++(*iter)) { 00494 fp=(*fret); 00495 ibig=0; 00496 del=0.0; 00497 for (i=1;i<=n;i++) { 00498 for (j = 1; j <= n; j++){ 00499 //NOTE: This stupid change using -1 for the xi vector is necessary to handle weirdness of the nrVectors 00500 xit[j] = xi[j-1][i-1]; 00501 } 00502 fptt=(*fret); 00503 linmin(p,xit,n,fret,optimizee); 00504 if (fabs(fptt-(*fret)) > del) { 00505 del=fabs(fptt-(*fret)); 00506 ibig=i; 00507 } 00508 } 00509 if (2.0*fabs(fp-(*fret)) <= ftol*(fabs(fp)+fabs(*fret))) { 00510 00511 //optimizee->setDimensions(p);//TEST 00512 00513 //If cost is below threshold, then return that the shape was found 00514 //perhaps this is not in the most ideal spot. --Z 00515 if(fptt < optimizee->getThreshold()){//if cost estimate is below threshold, shape is present in image (very rudimentary, need to take dimensions into account) 00516 return(true);//test 00517 } 00518 //otherwise, return that it was not 00519 else{return(false);}//test 00520 } 00521 if (*iter == ITMAX){ 00522 //optimizee->setDimensions(p);//TEST 00523 printf("powell exceeding maximum iterations."); 00524 return(false);//test 00525 } 00526 for (j=1;j<=n;j++) { 00527 ptt[j]=2.0*p[j]-pt[j]; 00528 xit[j]=p[j]-pt[j]; 00529 pt[j]=p[j]; 00530 } 00531 //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 00532 //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! 00533 fptt=optimizee->calcDist(ptt, distMap); 00534 if (fptt < fp) { 00535 t=2.0*(fp-2.0*(*fret)+fptt)*squareOf(fp-(*fret)-del)-del*squareOf(fp-fptt); 00536 if (t < 0.0) { 00537 linmin(p,xit,n,fret,optimizee); 00538 for (j=0;j<n;j++) { 00539 xi[j][ibig]=xi[j][n]; 00540 xi[j][n]=xit[j]; 00541 } 00542 } 00543 } 00544 00545 } 00546 } 00547 00548 /******************************************************************************* 00549 * PROCEDURE: canny 00550 * PURPOSE: To perform canny edge detection. 00551 * NAME: Mike Heath 00552 * DATE: 2/15/96 00553 //Pradeep: returns the centroid of the "white" pixels 00554 *******************************************************************************/ 00555 int BeoSubCanny::canny(unsigned char *image, int rows, int cols, float sigma, 00556 float tlow, float thigh, unsigned char **edge, char *fname) 00557 { 00558 FILE *fpdir=NULL; /* File to write the gradient image to. */ 00559 unsigned char *nms; /* Points that are local maximal magnitude. */ 00560 short int *smoothedim, /* The image after gaussian smoothing. */ 00561 *delta_x, /* The first devivative image, x-direction. */ 00562 *delta_y, /* The first derivative image, y-direction. */ 00563 *magnitude; /* The magnitude of the gadient image. */ 00564 float *dir_radians=NULL; /* Gradient direction image. */ 00565 00566 /**************************************************************************** 00567 * Perform gaussian smoothing on the image using the input standard 00568 * deviation. 00569 ****************************************************************************/ 00570 gaussian_smooth(image, rows, cols, sigma, &smoothedim); 00571 00572 /**************************************************************************** 00573 * Compute the first derivative in the x and y directions. 00574 ****************************************************************************/ 00575 derrivative_x_y(smoothedim, rows, cols, &delta_x, &delta_y); 00576 00577 /**************************************************************************** 00578 * This option to write out the direction of the edge gradient was added 00579 * to make the information available for computing an edge quality figure 00580 * of merit. 00581 ****************************************************************************/ 00582 if(fname != NULL){ 00583 /************************************************************************* 00584 * Compute the direction up the gradient, in radians that are 00585 * specified counteclockwise from the positive x-axis. 00586 *************************************************************************/ 00587 radian_direction(delta_x, delta_y, rows, cols, &dir_radians, -1, -1); 00588 00589 /************************************************************************* 00590 * Write the gradient direction image out to a file. 00591 *************************************************************************/ 00592 if((fpdir = fopen(fname, "wb")) == NULL){ 00593 fprintf(stderr, "Error opening the file %s for writing.\n", fname); 00594 exit(1); 00595 } 00596 fwrite(dir_radians, sizeof(float), rows*cols, fpdir); 00597 fclose(fpdir); 00598 free(dir_radians); 00599 } 00600 00601 /**************************************************************************** 00602 * Compute the magnitude of the gradient. 00603 ****************************************************************************/ 00604 magnitude_x_y(delta_x, delta_y, rows, cols, &magnitude); 00605 00606 /**************************************************************************** 00607 * Perform non-maximal suppression. 00608 ****************************************************************************/ 00609 if((nms = (unsigned char *) calloc(rows*cols,sizeof(unsigned char)))==NULL){ 00610 fprintf(stderr, "Error allocating the nms image.\n"); 00611 exit(1); 00612 } 00613 00614 non_max_supp(magnitude, delta_x, delta_y, rows, cols, nms); 00615 00616 /**************************************************************************** 00617 * Use hysteresis to mark the edge pixels. 00618 ****************************************************************************/ 00619 if((*edge=(unsigned char *)calloc(rows*cols,sizeof(unsigned char))) ==NULL){ 00620 fprintf(stderr, "Error allocating the edge image.\n"); 00621 exit(1); 00622 } 00623 //printf("\n%d %d %d %d %d %d\n", magnitude, nms, rows, cols, tlow, thigh); 00624 int centroid = apply_hysteresis(magnitude, nms, rows, cols, tlow, thigh, *edge); 00625 /**************************************************************************** 00626 * Free all of the memory that we allocated except for the edge image that 00627 * is still being used to store out result. 00628 ****************************************************************************/ 00629 free(smoothedim); 00630 free(delta_x); 00631 free(delta_y); 00632 free(magnitude); 00633 free(nms); 00634 return centroid; 00635 } 00636 00637 /******************************************************************************* 00638 * Procedure: radian_direction 00639 * Purpose: To compute a direction of the gradient image from component dx and 00640 * dy images. Because not all derriviatives are computed in the same way, this 00641 * code allows for dx or dy to have been calculated in different ways. 00642 * 00643 * FOR X: xdirtag = -1 for [-1 0 1] 00644 * xdirtag = 1 for [ 1 0 -1] 00645 * 00646 * FOR Y: ydirtag = -1 for [-1 0 1]' 00647 * ydirtag = 1 for [ 1 0 -1]' 00648 * 00649 * The resulting angle is in radians measured counterclockwise from the 00650 * xdirection. The angle points "up the gradient". 00651 *******************************************************************************/ 00652 void BeoSubCanny::radian_direction(short int *delta_x, short int *delta_y, int rows, 00653 int cols, float **dir_radians, int xdirtag, int ydirtag) 00654 { 00655 int r, c, pos; 00656 float *dirim=NULL; 00657 double dx, dy; 00658 00659 /**************************************************************************** 00660 * Allocate an image to store the direction of the gradient. 00661 ****************************************************************************/ 00662 if((dirim = (float *) calloc(rows*cols, sizeof(float))) == NULL){ 00663 fprintf(stderr, "Error allocating the gradient direction image.\n"); 00664 exit(1); 00665 } 00666 *dir_radians = dirim; 00667 00668 for(r=0,pos=0;r<rows;r++){ 00669 for(c=0;c<cols;c++,pos++){ 00670 dx = (double)delta_x[pos]; 00671 dy = (double)delta_y[pos]; 00672 00673 if(xdirtag == 1) dx = -dx; 00674 if(ydirtag == -1) dy = -dy; 00675 00676 dirim[pos] = (float)angle_radians(dx, dy); 00677 } 00678 } 00679 } 00680 00681 /******************************************************************************* 00682 * FUNCTION: angle_radians 00683 * PURPOSE: This procedure computes the angle of a vector with components x and 00684 * y. It returns this angle in radians with the answer being in the range 00685 * 0 <= angle <2*PI. 00686 *******************************************************************************/ 00687 double BeoSubCanny::angle_radians(double x, double y) 00688 { 00689 double xu, yu, ang; 00690 00691 xu = fabs(x); 00692 yu = fabs(y); 00693 00694 if((xu == 0) && (yu == 0)) return(0); 00695 00696 ang = atan(yu/xu); 00697 00698 if(x >= 0){ 00699 if(y >= 0) return(ang); 00700 else return(2*M_PI - ang); 00701 } 00702 else{ 00703 if(y >= 0) return(M_PI - ang); 00704 else return(M_PI + ang); 00705 } 00706 } 00707 00708 /******************************************************************************* 00709 * PROCEDURE: magnitude_x_y 00710 * PURPOSE: Compute the magnitude of the gradient. This is the square root of 00711 * the sum of the squared derivative values. 00712 * NAME: Mike Heath 00713 * DATE: 2/15/96 00714 *******************************************************************************/ 00715 void BeoSubCanny::magnitude_x_y(short int *delta_x, short int *delta_y, int rows, int cols, 00716 short int **magnitude) 00717 { 00718 int r, c, pos, sq1, sq2; 00719 00720 /**************************************************************************** 00721 * Allocate an image to store the magnitude of the gradient. 00722 ****************************************************************************/ 00723 if((*magnitude = (short *) calloc(rows*cols, sizeof(short))) == NULL){ 00724 fprintf(stderr, "Error allocating the magnitude image.\n"); 00725 exit(1); 00726 } 00727 00728 for(r=0,pos=0;r<rows;r++){ 00729 for(c=0;c<cols;c++,pos++){ 00730 sq1 = (int)delta_x[pos] * (int)delta_x[pos]; 00731 sq2 = (int)delta_y[pos] * (int)delta_y[pos]; 00732 (*magnitude)[pos] = (short)(0.5 + sqrt((float)sq1 + (float)sq2)); 00733 } 00734 } 00735 00736 } 00737 00738 /******************************************************************************* 00739 * PROCEDURE: derrivative_x_y 00740 * PURPOSE: Compute the first derivative of the image in both the x any y 00741 * directions. The differential filters that are used are: 00742 * 00743 * -1 00744 * dx = -1 0 +1 and dy = 0 00745 * +1 00746 * 00747 * NAME: Mike Heath 00748 * DATE: 2/15/96 00749 *******************************************************************************/ 00750 void BeoSubCanny::derrivative_x_y(short int *smoothedim, int rows, int cols, 00751 short int **delta_x, short int **delta_y) 00752 { 00753 int r, c, pos; 00754 00755 /**************************************************************************** 00756 * Allocate images to store the derivatives. 00757 ****************************************************************************/ 00758 if(((*delta_x) = (short *) calloc(rows*cols, sizeof(short))) == NULL){ 00759 fprintf(stderr, "Error allocating the delta_x image.\n"); 00760 exit(1); 00761 } 00762 if(((*delta_y) = (short *) calloc(rows*cols, sizeof(short))) == NULL){ 00763 fprintf(stderr, "Error allocating the delta_x image.\n"); 00764 exit(1); 00765 } 00766 00767 /**************************************************************************** 00768 * Compute the x-derivative. Adjust the derivative at the borders to avoid 00769 * losing pixels. 00770 ****************************************************************************/ 00771 for(r=0;r<rows;r++){ 00772 pos = r * cols; 00773 (*delta_x)[pos] = smoothedim[pos+1] - smoothedim[pos]; 00774 pos++; 00775 for(c=1;c<(cols-1);c++,pos++){ 00776 (*delta_x)[pos] = smoothedim[pos+1] - smoothedim[pos-1]; 00777 } 00778 (*delta_x)[pos] = smoothedim[pos] - smoothedim[pos-1]; 00779 } 00780 00781 /**************************************************************************** 00782 * Compute the y-derivative. Adjust the derivative at the borders to avoid 00783 * losing pixels. 00784 ****************************************************************************/ 00785 for(c=0;c<cols;c++){ 00786 pos = c; 00787 (*delta_y)[pos] = smoothedim[pos+cols] - smoothedim[pos]; 00788 pos += cols; 00789 for(r=1;r<(rows-1);r++,pos+=cols){ 00790 (*delta_y)[pos] = smoothedim[pos+cols] - smoothedim[pos-cols]; 00791 } 00792 (*delta_y)[pos] = smoothedim[pos] - smoothedim[pos-cols]; 00793 } 00794 } 00795 00796 /******************************************************************************* 00797 * PROCEDURE: gaussian_smooth 00798 * PURPOSE: Blur an image with a gaussian filter. 00799 * NAME: Mike Heath 00800 * DATE: 2/15/96 00801 *******************************************************************************/ 00802 void BeoSubCanny::gaussian_smooth(unsigned char *image, int rows, int cols, float sigma, 00803 short int **smoothedim) 00804 { 00805 int r, c, rr, cc, /* Counter variables. */ 00806 windowsize, /* Dimension of the gaussian kernel. */ 00807 center; /* Half of the windowsize. */ 00808 float *tempim, /* Buffer for separable filter gaussian smoothing. */ 00809 *kernel, /* A one dimensional gaussian kernel. */ 00810 dot, /* Dot product summing variable. */ 00811 sum; /* Sum of the kernel weights variable. */ 00812 00813 /**************************************************************************** 00814 * Create a 1-dimensional gaussian smoothing kernel. 00815 ****************************************************************************/ 00816 make_gaussian_kernel(sigma, &kernel, &windowsize); 00817 center = windowsize / 2; 00818 00819 /**************************************************************************** 00820 * Allocate a temporary buffer image and the smoothed image. 00821 ****************************************************************************/ 00822 if((tempim = (float *) calloc(rows*cols, sizeof(float))) == NULL){ 00823 fprintf(stderr, "Error allocating the buffer image.\n"); 00824 exit(1); 00825 } 00826 if(((*smoothedim) = (short int *) calloc(rows*cols, 00827 sizeof(short int))) == NULL){ 00828 fprintf(stderr, "Error allocating the smoothed image.\n"); 00829 exit(1); 00830 } 00831 00832 /**************************************************************************** 00833 * Blur in the x - direction. 00834 ****************************************************************************/ 00835 for(r=0;r<rows;r++){ 00836 for(c=0;c<cols;c++){ 00837 dot = 0.0; 00838 sum = 0.0; 00839 for(cc=(-center);cc<=center;cc++){ 00840 if(((c+cc) >= 0) && ((c+cc) < cols)){ 00841 dot += (float)image[r*cols+(c+cc)] * kernel[center+cc]; 00842 sum += kernel[center+cc]; 00843 } 00844 } 00845 tempim[r*cols+c] = dot/sum; 00846 } 00847 } 00848 00849 /**************************************************************************** 00850 * Blur in the y - direction. 00851 ****************************************************************************/ 00852 for(c=0;c<cols;c++){ 00853 for(r=0;r<rows;r++){ 00854 sum = 0.0; 00855 dot = 0.0; 00856 for(rr=(-center);rr<=center;rr++){ 00857 if(((r+rr) >= 0) && ((r+rr) < rows)){ 00858 dot += tempim[(r+rr)*cols+c] * kernel[center+rr]; 00859 sum += kernel[center+rr]; 00860 } 00861 } 00862 (*smoothedim)[r*cols+c] = (short int)(dot*BOOSTBLURFACTOR/sum + 0.5); 00863 } 00864 } 00865 00866 free(tempim); 00867 free(kernel); 00868 } 00869 00870 /******************************************************************************* 00871 * PROCEDURE: make_gaussian_kernel 00872 * PURPOSE: Create a one dimensional gaussian kernel. 00873 * NAME: Mike Heath 00874 * DATE: 2/15/96 00875 *******************************************************************************/ 00876 void BeoSubCanny::make_gaussian_kernel(float sigma, float **kernel, int *windowsize) 00877 { 00878 int i, center; 00879 float x, fx, sum=0.0; 00880 00881 *windowsize = int(1 + 2 * ceil(2.5 * sigma)); 00882 center = (*windowsize) / 2; 00883 00884 if((*kernel = (float *) calloc((*windowsize), sizeof(float))) == NULL){ 00885 fprintf(stderr, "Error callocing the gaussian kernel array.\n"); 00886 exit(1); 00887 } 00888 00889 for(i=0;i<(*windowsize);i++){ 00890 x = (float)(i - center); 00891 fx = pow(2.71828, -0.5*x*x/(sigma*sigma)) / (sigma * sqrt(6.2831853)); 00892 (*kernel)[i] = fx; 00893 sum += fx; 00894 } 00895 00896 for(i=0;i<(*windowsize);i++) (*kernel)[i] /= sum; 00897 00898 }