contrib/oxl/osl/osl_canny_rothwell.cxx
Go to the documentation of this file.
00001 // This is oxl/osl/osl_canny_rothwell.cxx
00002 #include "osl_canny_rothwell.h"
00003 //:
00004 // \file
00005 
00006 #include <osl/osl_canny_rothwell_params.h>
00007 #include <osl/osl_kernel.h>
00008 #include <osl/osl_canny_smooth.h>
00009 #include <osl/osl_canny_gradient.h>
00010 #include <vnl/vnl_math.h>
00011 
00012 #include <vcl_list.h>
00013 #include <vcl_cmath.h>
00014 #include <vcl_cstdlib.h>
00015 #include <vcl_cstdio.h>
00016 #include <vcl_iostream.h>
00017 #include <vcl_cassert.h>
00018 
00019 const float DUMMYTHETA = 10000.0;
00020 
00021 //-----------------------------------------------------------------------------
00022 
00023 osl_canny_rothwell::osl_canny_rothwell(osl_canny_rothwell_params const &params)
00024   : osl_canny_base(params.sigma, params.low, params.high, params.verbose)
00025 {
00026   // Determine the size of the largest convolution kernel
00027   range_ = params.range;
00028   gauss_tail_ = 0.01f;   // Canny uses 0.001
00029   width_ = int(sigma_*vcl_sqrt(2*vcl_log(1/gauss_tail_))+1); // round up to int
00030   w0_ = width_;
00031   k_size_ = 2*width_+ 1;
00032   kernel_ = new float[k_size_];
00033 
00034   xdang_ = new vcl_list<int>;
00035   ydang_ = new vcl_list<int>;
00036   xjunc_ = new vcl_list<int>;
00037   yjunc_ = new vcl_list<int>;
00038   vlist_ = new vcl_list<osl_Vertex*>;
00039 
00040   dummy_ = 1000.0;
00041   jval_ = 2000.0;
00042 }
00043 
00044 //-----------------------------------------------------------------------------
00045 
00046 osl_canny_rothwell::~osl_canny_rothwell()
00047 {
00048   osl_canny_base_free_raw_image(smooth_);
00049   osl_canny_base_free_raw_image(dx_);
00050   osl_canny_base_free_raw_image(dy_);
00051   osl_canny_base_free_raw_image(grad_);
00052 
00053   osl_canny_base_free_raw_image(thick_);
00054   osl_canny_base_free_raw_image(thin_);
00055   osl_canny_base_free_raw_image(theta_);
00056 
00057   osl_canny_base_free_raw_image(dangling_);
00058   osl_canny_base_free_raw_image(junction_);
00059   osl_canny_base_free_raw_image(jx_);
00060   osl_canny_base_free_raw_image(jy_);
00061 
00062   //no point vlist_->clear();
00063   delete vlist_;
00064   delete [] kernel_;
00065   delete xdang_;
00066   delete ydang_;
00067   delete xjunc_;
00068   delete yjunc_;
00069 }
00070 
00071 
00072 //-----------------------------------------------------------------------------
00073 
00074 void osl_canny_rothwell::detect_edges(vil1_image const &image, vcl_list<osl_edge*> *edges, bool adaptive)
00075 {
00076   assert(edges!=0);
00077 
00078   xsize_  = image.height();
00079   ysize_  = image.width();
00080   xstart_ = 0;
00081   ystart_ = 0;
00082 
00083   if (verbose)
00084     vcl_cerr << "Doing Canny on image region "
00085              << xsize_ << " by " << ysize_ << vcl_endl
00086              << "Gaussian tail   = " << gauss_tail_ << vcl_endl
00087              << "Sigma           = " << sigma_ << vcl_endl
00088              << "Kernel size     = " << k_size_ << vcl_endl
00089              << "Upper threshold = " << high_ << vcl_endl
00090              << "Lower threshold = " << low_ << vcl_endl
00091              << "Smoothing range = " << range_ << vcl_endl << vcl_endl;
00092 
00093   smooth_   = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00094   dx_       = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00095   dy_       = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00096   grad_     = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00097   thick_    = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00098   thin_     = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00099   theta_    = osl_canny_base_make_raw_image(xsize_, ysize_, (float*)0);
00100   dangling_ = osl_canny_base_make_raw_image(xsize_, ysize_, (int*)0);
00101   junction_ = osl_canny_base_make_raw_image(xsize_, ysize_, (int*)0);
00102   jx_       = osl_canny_base_make_raw_image(xsize_, ysize_, (int*)0);
00103   jy_       = osl_canny_base_make_raw_image(xsize_, ysize_, (int*)0);
00104 
00105   osl_canny_base_fill_raw_image(theta_ ,xsize_, ysize_, DUMMYTHETA);
00106   osl_canny_base_fill_raw_image(smooth_,xsize_, ysize_, 0.0f);
00107   osl_canny_base_fill_raw_image(dx_,    xsize_, ysize_, 0.0f);
00108   osl_canny_base_fill_raw_image(dy_,    xsize_, ysize_, 0.0f);
00109   osl_canny_base_fill_raw_image(grad_,  xsize_, ysize_, 0.0f);
00110   osl_canny_base_fill_raw_image(thick_, xsize_, ysize_, 0.0f);
00111   osl_canny_base_fill_raw_image(thin_,  xsize_, ysize_, 0.0f);
00112 
00113   // Do the traditional Canny parts
00114   if (verbose) vcl_cerr << "setting convolution kernel and zeroing images\n";
00115   osl_kernel_DOG(sigma_, kernel_, k_size_, width_);
00116 
00117   if (verbose) vcl_cerr << "smoothing the image\n";
00118   osl_canny_smooth_rothwell(image, kernel_, width_, k_size_, smooth_);
00119 
00120   if (verbose) vcl_cerr << "computing derivatives\n";
00121   osl_canny_gradient_central(xsize_, ysize_, smooth_, dx_, dy_, grad_);
00122 
00123   if (verbose) vcl_cerr << "doing non-maximal suppression\n";
00124   Non_maximal_suppression();
00125 
00126   // Thin the edge image, though keep the original thick one
00127   if (verbose) vcl_cerr << "thinning edges\n";
00128   osl_canny_base_copy_raw_image(VCL_OVERLOAD_CAST(float const*const*, thick_),
00129                                 VCL_OVERLOAD_CAST(float *const*, thin_), xsize_, ysize_);
00130   Thin_edges();
00131 
00132   if (verbose) vcl_cerr << "doing hysteresis\n";
00133   Initial_hysteresis();
00134 
00135   if ( adaptive ) {
00136     // Do Canny around the remaining ends at smaller scales to improve
00137     // topology. We wish to do the adaptive Canny until the region of
00138     // influence is less than `range' pixels
00139     double min_sigma = range_ / vcl_sqrt(-2.0*vcl_log(gauss_tail_));
00140     if (verbose) vcl_cerr << "\nadaptive Canny with smoothing sigma bound = " << min_sigma << vcl_endl;
00141 
00142     // Try to fix single pixel breaks in the edgel chains
00143     if (verbose) vcl_cerr << "searching for dangling ends\n";
00144     Find_dangling_ends();
00145     if (verbose) vcl_cerr << xdang_->size() << " dangling edges found initially\n"
00146                           << "looking for single pixel breaks - ";
00147     Jump_single_breaks();
00148     Thin_edges();   // Must thin after jumping
00149     Find_dangling_ends();
00150     if (verbose) vcl_cerr << xdang_->size() << " dangling edges found after joining\n";
00151 
00152     while ( sigma_ > min_sigma ) {
00153       // Locate junctions in the edge image
00154       if (verbose) vcl_cerr << "computing current junction set";
00155       Find_junctions();
00156 
00157       if (verbose) vcl_cerr << "\nrunning adaptive Canny\n";
00158       Adaptive_Canny(image);
00159 
00160       // Repeat the thinning and pixel-jumping process
00161       if (verbose) vcl_cerr << "thinning edges - reprise\n";
00162       Thin_edges();
00163 
00164       Find_dangling_ends();
00165       if (verbose) vcl_cerr << xdang_->size() << " dangling edges found after scale reduction\n"
00166                             << "looking for single pixel breaks - ";
00167       Jump_single_breaks();
00168       Thin_edges();
00169       Find_dangling_ends();
00170       if (verbose) vcl_cerr << xdang_->size() << " dangling edges found after re-joining\n";
00171     }
00172   }
00173 
00174   // Locate junctions in the edge image
00175   if (verbose) vcl_cerr << "locating junctions in the edge image - ";
00176   Find_junctions();
00177   if (verbose) vcl_cerr << xjunc_->size() << " junctions found\n";
00178   Find_junction_clusters();
00179   if (verbose)  vcl_cerr << vlist_->size() << " junction clusters found\n";
00180 
00181   // Finally do edge following to extract the edge data from the thin_ image
00182   if (verbose) vcl_cerr << "doing final edge following\n";
00183   Final_hysteresis(edges);
00184   if (verbose) vcl_cerr << "finished Canny\n";
00185 }
00186 
00187 
00188 //-----------------------------------------------------------------------------
00189 //
00190 //:
00191 // Non-maximally suppresses the output image by searching along the edge
00192 // normal and checking that the test edge has a greater that the interpolated
00193 // neighbours in the direction. We have also included sub-pixel interpolation
00194 // of the peak position by parabolic fitting.  Writes edges into the thick_
00195 // image.
00196 //
00197 void osl_canny_rothwell::Non_maximal_suppression()
00198 {
00199   float h1=0,h2=0;
00200   float k = float(vnl_math::deg_per_rad);
00201 
00202   // Add 1 to get rid of border effects
00203   for (unsigned int x=w0_; x+2+w0_<xsize_; ++x)  {
00204     float *g0 = grad_[x];
00205     float *g1 = grad_[x+1];
00206     float *g2 = grad_[x+2];
00207     float *dx = dx_[x+1];
00208     float *dy = dy_[x+1];
00209 
00210     for (unsigned int y=w0_; y+2+w0_<ysize_; ++y)  {
00211       // First check that we have an edge
00212       if ( g1[y+1] > low_ ) {
00213         double theta = k*vcl_atan2(dy[y+1],dx[y+1]);
00214 
00215         // Now work out which direction wrt the eight-way
00216         // neighbours the edge normal points
00217         int orient = int(theta/45.0+8) % 4;
00218 
00219         // And now compute the interpolated heights
00220         float grad;
00221         switch ( orient ) {
00222          case 0:
00223           grad = dy[y+1]/dx[y+1];
00224           h1 = grad*g0[y] + (1 - grad)*g0[y+1];
00225           h2 = grad*g2[y] + (1 - grad)*g2[y+1];
00226           break;
00227          case 1:
00228           grad = dx[y+1]/dy[y+1];
00229           h1 = grad*g0[y] + (1 - grad)*g1[y];
00230           h2 = grad*g2[y] + (1 - grad)*g1[y];
00231           break;
00232          case 2:
00233           grad = -dx[y+1]/dy[y+1];
00234           h1 = grad*g2[y] + (1 - grad)*g1[y];
00235           h2 = grad*g0[y] + (1 - grad)*g1[y];
00236           break;
00237          case 3:
00238           grad = -dy[y+1]/dx[y+1];
00239           h1 = grad*g2[y] + (1 - grad)*g2[y+1];
00240           h2 = grad*g0[y] + (1 - grad)*g0[y+1];
00241           break;
00242          default:
00243           vcl_cerr << "*** ERROR ON SWITCH IN NMS ***: orient="<< orient<< '\n';
00244           vcl_abort();
00245         }
00246 
00247         // If the edge is greater than h1 and h2 we are at a peak,
00248         // therefore do subpixel interpolation by fitting a parabola
00249         // along the NMS line and finding its peak
00250         if ( (g1[y+1]>h1) && (g1[y+1]>h2) )
00251         {
00252           float fraction = (h1-h2)/(2*(h1-2*g1[y+1]+h2));
00253           float newx=0.f, newy=0.f;
00254           switch ( orient ) {
00255            case 0:
00256             newx = x + fraction;
00257             newy = y + dy[y+1]/dx[y+1]*fraction;
00258             break;
00259            case 1:
00260             newx = x + dx[y+1]/dy[y+1]*fraction;
00261             newy = y + fraction;
00262             break;
00263            case 2:
00264             newx = x + dx[y+1]/dy[y+1]*fraction;
00265             newy = y + fraction;
00266             break;
00267            case 3:
00268             newx = x - fraction;
00269             newy = y - dy[y+1]/dx[y+1]*fraction;
00270             break;
00271            default:
00272             vcl_cerr<<"*** ERROR ON SWITCH IN NMS ***: orient="<< orient<< '\n';
00273             vcl_abort();
00274           }
00275 
00276           // Now store the edge data, re-use dx_[][] and dy_[][]
00277           // for sub-pixel locations (don't worry about the junk
00278           // that is already in them).
00279           // + 0.5 is to account for targetjr display offset
00280           thick_[x+1][y+1] = g1[y+1]; // Should this be interpolated height --
00281           dx[y+1] = newx + 1.5f;   // = g1[y+1] + frac*(h2-h1)/4 ?
00282           dy[y+1] = newy + 1.5f;
00283           theta_[x+1][y+1] = float(theta);
00284         }
00285       }
00286     }
00287   }
00288 }
00289 
00290 
00291 //-----------------------------------------------------------------------------
00292 //
00293 //:
00294 // Hysteresis follows edgel chains that lie above the low_ threshold and
00295 // have at least one edgel above the high_ threshold. Once we have followed,
00296 // the good edgelchains are re-written to the thin_ image for further
00297 // processing.
00298 //
00299 void osl_canny_rothwell::Initial_hysteresis()
00300 {
00301   vcl_list<int> xcoords,ycoords;
00302   vcl_list<float> grad;
00303   vcl_list<osl_edgel_chain*> edges;
00304   float *thin,*px,*py,*pg;
00305   osl_edgel_chain *edgels;
00306 
00307   // Find a point above high_ and start to follow it.
00308   // First time round we are just trying to get rid of the weak dangling chains
00309   // and so we will record the good edges and then re-insert them in the thin_
00310   // image and follow a second time.
00311   edges.clear();
00312   for (unsigned int x=w0_; x+w0_<xsize_; ++x)  {
00313     thin = thin_[x];
00314     for (unsigned int y=w0_; y+w0_<ysize_; ++y)
00315       if ( thin[y]>high_ ) {
00316         Initial_follow(thin_, xsize_, ysize_, low_,
00317                        x,y,&xcoords,&ycoords,&grad);
00318 
00319         // Create an edge chain and add to the list
00320         edgels = new osl_edgel_chain(xcoords.size());
00321         px = edgels->GetX();
00322         py = edgels->GetY();
00323         pg = edgels->GetGrad();
00324         while ( xcoords.size() ) {
00325           *(px++) = float(xcoords.front()); xcoords.pop_front();
00326           *(py++) = float(ycoords.front()); ycoords.pop_front();
00327           *(pg++) = grad.front(); grad.pop_front();
00328         }
00329         edges.push_front(edgels);
00330       }
00331   }
00332 
00333   // Now re-create the thin_ image
00334   osl_canny_base_fill_raw_image(thin_, xsize_, ysize_, 0.0f);
00335   while (edges.size()) {
00336 
00337     edgels = edges.front(); edges.pop_front();
00338     px = edgels->GetX();
00339     py = edgels->GetY();
00340     pg = edgels->GetGrad();
00341     for (unsigned int i=0; i<edgels->size(); ++i)
00342       thin_[int(px[i])][int(py[i])] = pg[i];
00343     delete edgels;
00344   }
00345 }
00346 
00347 
00348 //-----------------------------------------------------------------------------
00349 
00350 // see osl_canny_ox.cxx
00351 extern osl_Vertex *osl_find(vcl_list<osl_Vertex*> const *l, osl_Vertex const &v);
00352 extern osl_Vertex *osl_find(vcl_list<osl_Vertex*> const *l, float x, float y);
00353 
00354 //:
00355 // Hysteresis follows edgel chains that lie above the low_ threshold and
00356 // have at least one edgel above the high_ threshold. Due to the Initial_hysteresis
00357 // phase, all edges greater than low_ will be by default good and so have a member
00358 // greater than high_.
00359 //
00360 void osl_canny_rothwell::Final_hysteresis(vcl_list<osl_edge*> *edges)
00361 {
00362   vcl_list<int> xcoords,ycoords;
00363   vcl_list<float> grad;
00364   float *thin,*px,*py,*pg,*pt,val;
00365 
00366   chain_no_ = 10;  // Must be set to a number >= 1
00367 
00368   // Find a point above high_ and start to follow it (but not a dummy point).
00369   edges->clear();
00370   for (unsigned int x=w0_; x+w0_<xsize_; ++x)  {
00371     thin = thin_[x];
00372     for (unsigned int y=w0_; y+w0_<ysize_; ++y) {
00373       // Due to Initial_hysteresis we can follow everything > low_
00374       if ( thin[y]<=low_ || junction_[x][y] )
00375         continue;
00376 
00377       // Set up the following variable [what a pointless comment]
00378       chain_no_++;
00379 
00380       // clear the lists
00381       xcoords.clear();
00382       ycoords.clear();
00383       grad.clear();
00384 
00385       // follow in one direction [? fsm]
00386       Final_follow(x,y, &xcoords,&ycoords,&grad, 0);
00387 
00388       // We may have picked up the edgel chain somewhere
00389       // away from its ends. Therefore, reverse the list
00390       // and try to follow again.
00391       xcoords.reverse();
00392       ycoords.reverse();
00393       grad.reverse();
00394       Final_follow(x,y,&xcoords,&ycoords,&grad,1);
00395 
00396       // Check that we have at least two endpoints to
00397       // the list, otherwise go to next loop
00398       if ( xcoords.size() < 2 )
00399         // vcl_cerr << "short list found in Final_follow\n";
00400         continue;
00401 
00402       // count the number of non-dummy edgels
00403       int count=0;
00404       for (vcl_list<float>::iterator i=grad.begin(); i!=grad.end(); ++i)
00405         if ( (*i) != dummy_ )
00406           count++;
00407 
00408       // If the count is less than two we cannot accept
00409       // the edgelchain. Smallest chain must either be `ee',
00410       // `je', `ej' or `jj' (for `e'dge and `j'unction)
00411       if ( count < 2 )
00412         continue;
00413 
00414       // Create an osl_edgel_chain
00415       osl_edgel_chain *dc = new osl_edgel_chain(count);
00416       px = dc->GetX();     py = dc->GetY();
00417       pg = dc->GetGrad();  pt = dc->GetTheta();
00418 
00419       // Write the edgels and end points to the osl_edgel_chain
00420       //dc->SetStart(xcoords.front()+xstart_, ycoords.front()+ystart_);
00421       while (count) {
00422         int tmpx = xcoords.front(); xcoords.pop_front();
00423         int tmpy = ycoords.front(); ycoords.pop_front();
00424         val = grad.front(); grad.pop_front();
00425         if ( val != dummy_ ) {
00426           --count;
00427           // If we are not at a junction use sub-pixel value
00428           if ( val != jval_ ) {
00429             *(px++) = dx_[tmpx][tmpy] + xstart_;
00430             *(py++) = dy_[tmpx][tmpy] + ystart_;
00431             *(pg++) = val;
00432           }
00433           else {
00434             *(px++) = float(tmpx + xstart_);
00435             *(py++) = float(tmpy + ystart_);
00436             *(pg++) = 0.0f;   // Mark the gradient as zero at a junction
00437           }
00438           if (theta_[tmpx][tmpy] == DUMMYTHETA) {
00439             const float k = float(vnl_math::deg_per_rad);
00440             float *dx = dx_[tmpx];
00441             float *dy = dy_[tmpx];
00442 
00443             // *** Bug fix, Samer Abdallah May 10, 1995:  next line was
00444             // theta_[tmpx][tmpy]  = k*vcl_atan2(dy[y],dx[y]);
00445             theta_[tmpx][tmpy]  = k*(float)vcl_atan2(dy[tmpy],dx[tmpy]);
00446           }
00447 
00448           *(pt++) = theta_[tmpx][tmpy];
00449         }
00450 //      dc->SetEnd(tmpx+xstart_, tmpy+ystart_);
00451       }
00452 
00453       // Just check whether we have created a trivial edgechain
00454       // (can happen due to the presence of dummy points)
00455       if ( (dc->size()==2) &&
00456            (dc->GetX(0)==dc->GetX(1)) &&
00457            (dc->GetY(0)==dc->GetY(1)) ) {
00458         delete dc; // osl_IUDelete(dc);
00459         continue;
00460       }
00461 
00462       else if ( dc->size() > 1 ) {
00463         // Create an edge for the image topology
00464 
00465         osl_Vertex *v1 = new osl_Vertex(dc->GetX(0), dc->GetY(0));
00466         osl_Vertex *v2 = new osl_Vertex(dc->GetX(dc->size()-1), dc->GetY(dc->size()-1));
00467 
00468         // Check whether each vertex is a junction
00469         osl_Vertex *V1=osl_find(vlist_, *v1);
00470         osl_Vertex *V2=osl_find(vlist_, *v2);
00471 
00472         // If neither are junctions we may have formed a single isolated
00473         // chain that should have common vertex endpoints.
00474         bool single_chain = false;
00475         if ( !V1 && !V2 ) {
00476           // compute difference (dx, dy) between endpoints.
00477           float dx = dc->GetX(0) - dc->GetX(dc->size()-1);
00478           float dy = dc->GetY(0) - dc->GetY(dc->size()-1);
00479           if ( dx*dx+dy*dy < 4 ) { //  if dist < 2 pixels, it is closed
00480             V1 = v1;
00481             V2 = v1;
00482             osl_IUDelete(v2);
00483             single_chain = true;
00484           }
00485         }
00486         if ( !single_chain ) {
00487           if ( !V1 )
00488             V1 = v1;
00489           else
00490           { osl_IUDelete (v1); }
00491           if ( !V2 )
00492             V2 = v2;
00493           else
00494           { osl_IUDelete(v2); }
00495         }
00496 
00497         // Note that the edge can start and end in the same place.
00498         // However, if this is so the DigitalCurve has positive length
00499         //dc->SetStart(dc->GetX(0), dc->GetY(0));
00500         //dc->SetEnd(dc->GetX(dc->size()-1), dc->GetY(dc->size()-1));
00501 
00502         edges->push_front(new osl_edge(*dc, V1, V2));
00503         delete dc;
00504       }
00505 
00506     } // end of y-loop
00507   } // end of x-loop
00508 }
00509 
00510 
00511 //-----------------------------------------------------------------------------
00512 //
00513 //:
00514 // Method to thin the image using the variation of Tsai-Fu thinning used
00515 // by Van-Duc Nguyen in Geo-Calc. This relies on computing the genus of
00516 // an edge location, and removing it if it is not a dangling chain as has
00517 // genus zero.
00518 //
00519 void osl_canny_rothwell::Thin_edges()
00520 {
00521   // Now do the thinning. Do it twice: the first time to try to remove
00522   // dummy_ edges, and then other edges -- 0.001 turns <= to <
00523   float threshold=dummy_-0.001f;
00524 
00525   for (int i=0; i<2; threshold=low_,++i)
00526   {
00527     int count = 1;     // count set to nonzero value
00528     while (count) //  Thin until no Pixels are removed
00529     {
00530       count = 0;
00531       for (unsigned int x=w0_; x+w0_<xsize_; ++x)
00532         for (unsigned int y=w0_; y+w0_<ysize_; ++y)
00533         {
00534           if ( thin_[x][y] <= threshold )
00535             continue;
00536 
00537           int a = thin_[x-1][y-1] > low_ ? 1 : 0;
00538           int b = thin_[x  ][y-1] > low_ ? 1 : 0;
00539           int c = thin_[x+1][y-1] > low_ ? 1 : 0;
00540           int d = thin_[x+1][y  ] > low_ ? 1 : 0;
00541           int e = thin_[x+1][y+1] > low_ ? 1 : 0;
00542           int f = thin_[x  ][y+1] > low_ ? 1 : 0;
00543           int g = thin_[x-1][y+1] > low_ ? 1 : 0;
00544           int h = thin_[x-1][y  ] > low_ ? 1 : 0;
00545 
00546           int genus = a+b+c+d+e+f+g+h;
00547 
00548           // If the pixel is dangling, record, and go to next loop
00549           if ( genus == 1 )
00550             continue;
00551 
00552           genus += h*a*b+b*c*d+d*e*f+f*g*h-a*b-b*c-c*d-d*e-e*f-f*g
00553                  - g*h-h*a-h*b-b*d-d*f-f*h-1;
00554 
00555           // If the genus is zero delete the edge
00556           if ( genus == 0 ) {
00557             ++count;
00558             thin_[x][y] = 0.0;
00559           }
00560         }
00561     }
00562   }
00563 }
00564 
00565 
00566 //-----------------------------------------------------------------------------
00567 //
00568 //: Searches for single pixel breaks near dangling ends and patches them up.
00569 // This is destructive on the dangling lists.
00570 //
00571 void osl_canny_rothwell::Jump_single_breaks()
00572 {
00573   // Take each dangling end in turn and determine whether there is
00574   // another edgel within the sixteen one-pixel-distant-neighbours.
00575 
00576   int x,y,i,j;
00577   float **t = thin_;
00578   // xdang_->reset();  ydang_->reset();
00579 
00580   while (xdang_->size()) {
00581 
00582     x = xdang_->front(); xdang_->pop_front();
00583     y = ydang_->front(); ydang_->pop_front();
00584 
00585     // This is messy for efficiency
00586     if      (thin_[x-1][y-1]) {i=-1; j=-1;}
00587     else if (thin_[x  ][y-1]) {i= 0; j=-1;}
00588     else if (thin_[x+1][y-1]) {i= 1; j=-1;}
00589     else if (thin_[x+1][y  ]) {i= 1; j= 0;}
00590     else if (thin_[x+1][y+1]) {i= 1; j= 1;}
00591     else if (thin_[x  ][y+1]) {i= 0; j= 1;}
00592     else if (thin_[x-1][y+1]) {i=-1; j= 1;}
00593     else if (thin_[x-1][y  ]) {i=-1; j= 0;}
00594     else { i = j = 0; } // Dummy declaration
00595 
00596     // If the entrant edgel is diagonal
00597     if ( i && j ) {
00598 
00599       if      (t[x-2*i][y-2*j]>low_) {
00600         t[x-i][y-j]=dummy_;thick_[x-i][y-j]=dummy_;}
00601       else if ((t[x+i][y-2*j]>low_)||(t[x  ][y-2*j]>low_)||(t[x-i][y-2*j]>low_)) {
00602         t[x  ][y-j]=dummy_;thick_[x  ][y-j]=dummy_;}
00603       else if ((t[x-2*i][y+j]>low_)||(t[x-2*i][y  ]>low_)||(t[x-2*i][y-j]>low_)) {
00604         t[x-i][y]=dummy_;thick_[x-i][y  ]=dummy_;}
00605       else if ((!(t[x+2*i][y  ]>low_))&&((t[x+2*i][y-j]>low_)||(t[x+2*i][y-2*j]>low_))) {
00606         t[x+i][y-j]=dummy_;thick_[x+i][y-j]=dummy_;}
00607       else if ((!(t[x  ][y+2*j]>low_))&&((t[x-i][y+2*j]>low_)||(t[x-2*i][y+2*j]>low_))) {
00608         t[x-i][y+j]=dummy_;thick_[x-i][y+j]=dummy_;}
00609     }
00610 
00611     // For entrant with i=0
00612     else if ( i == 0 ) {
00613 
00614       if ((t[x-1][y-2*j]>low_)||(t[x  ][y-2*j]>low_)||(t[x+1][y-2*j]>low_)) {
00615         t[x  ][y-j]=dummy_;thick_[x  ][y-j]=dummy_;}
00616       else if ((t[x+2][y-j]>low_)||(t[x+2][y-2*j]>low_)) {
00617         t[x+1][y-j]=dummy_;thick_[x+1][y-j]=dummy_;}
00618       else if ((t[x-2][y-j]>low_)||(t[x-2][y-2*j]>low_)) {
00619         t[x-1][y-j]=dummy_;thick_[x-1][y-j]=dummy_;}
00620       else if ((!(t[x-2][y+j]>low_))&&(t[x-2][y]>low_)) {
00621         t[x-1][y  ]=dummy_;thick_[x-1][y  ]=dummy_;}
00622       else if ((!(t[x+2][y+j]>low_))&&(t[x+2][y]>low_)) {
00623         t[x+1][y  ]=dummy_;thick_[x+1][y  ]=dummy_;}
00624     }
00625     // or finally for j=0
00626 
00627     // *** Bug fix, Samer Abdallah May 10, 1995:  next line was
00628     // else if ( i == 0 )
00629     else if ( j == 0 ) {
00630 
00631       if      ((t[x-2*i][y-1]>low_)||(t[x-2*i][y]>low_)||(t[x-2*i][y]>low_)) {
00632         t[x-i][y]=dummy_;thick_[x-i][y]=dummy_;}
00633       else if ((t[x-i][y+2]>low_)||(t[x-2*i][y+2]>low_)) {
00634         t[x-1][y+j]=dummy_;thick_[x-1][y+j]=dummy_;}
00635       else if ((t[x-i][y-2]>low_)||(t[x-2*i][y-2]>low_)) {
00636         t[x-1][y-j]=dummy_;thick_[x-1][y-j]=dummy_;}
00637       else if ((!(t[x+i][y-2]>low_))&&(t[x][y-2]>low_)) {
00638         t[x][y-1]=dummy_;thick_[x][y-1]=dummy_;}
00639       else if ((!(t[x+i][y+2]>low_))&&(t[x][y+2]>low_)) {
00640         t[x][y+1]=dummy_;thick_[x][y+1]=dummy_;}
00641     }
00642   }
00643 }
00644 
00645 
00646 //-----------------------------------------------------------------------------
00647 //
00648 //: Searches for high contrast changes in the vicinity of dangling ends.
00649 // This is done by halving the previously used Canny sigma to reduce the effects of
00650 // smoothing near corners. Ultimately the kernel size will be scaled down until
00651 // its radius of influence is only two pixels; at that stage pixel-jumping should
00652 // fix any problems.
00653 //
00654 void osl_canny_rothwell::Adaptive_Canny(vil1_image const &image)
00655 {
00656   // Reset the smoothing kernel parameters by
00657   // halving the size of the smoothing sigma
00658   old_sigma_ = sigma_;  sigma_ /= 2.0f;
00659   old_width_ = width_;
00660   width_ = int(sigma_*vcl_sqrt(2*vcl_log(1.0/gauss_tail_))+1);
00661   old_k_size_ = k_size_;  k_size_ = 2*width_+ 1;
00662   delete kernel_; kernel_ = new float [k_size_];
00663   osl_kernel_DOG(sigma_, kernel_, k_size_, width_);
00664 
00665   // Define the new ROI to account for the domain of the old smoothing
00666   // kernel and for that of the new one
00667   int image_size = old_k_size_ + k_size_ - 1;
00668   int half_size = (image_size - 1)/2;
00669 
00670   if (verbose) vcl_cerr << "new image region "
00671                         << image_size << " by " << image_size << vcl_endl
00672                         << "Sigma           = " << sigma_ << vcl_endl
00673                         << "Kernel size     = " << k_size_ << vcl_endl;
00674 
00675   // Set up the new images
00676   float **dx   = osl_canny_base_make_raw_image(image_size,image_size, (float*)0);
00677   float **dy   = osl_canny_base_make_raw_image(image_size,image_size, (float*)0);
00678   float **grad = osl_canny_base_make_raw_image(image_size,image_size, (float*)0);
00679 
00680   // For each dangling-end (X,Y), search for more edges at the reduced scale
00681   int count=0;
00682   if (verbose) vcl_cerr << "percentage of endings examined =   0";
00683   typedef vcl_list<int>::iterator it;
00684   for (it i=xdang_->begin(), j=ydang_->begin(); i!=xdang_->end() && j!=ydang_->end(); ++i, ++j)
00685   {
00686     //xdang_->reset(),ydang_->reset(); xdang_->next(),ydang_->next(); )  {
00687 
00688     int X = (*i)/*xdang_->value()*/, Y = (*j)/*ydang_->value()*/;
00689     int x0 = X-half_size,   y0 = Y - half_size;  // Region origin in image coords
00690 
00691     // Make sure that the region around the end is within the
00692     // bounds of the original image, and that the dangling end
00693     // is not a neighbour to a junction (if it is we recover the
00694     // topology more robustly by growing something else towards
00695     // it, rather than the other way round)
00696     if ( !Junction_neighbour(junction_, X, Y) &&
00697          (x0>=0) && ((unsigned int)x0+image_size<=xsize_) &&
00698          (y0>=0) && ((unsigned int)y0+image_size<=ysize_) )
00699     {
00700       // Compute the new image intensity gradients
00701       osl_canny_smooth_rothwell_adaptive(image, x0,y0,image_size, kernel_, width_, k_size_, dx,dy,grad);
00702 
00703       // Delete the effects of the thick_ image - we don't want to
00704       // locate edges in the same places as before
00705       Subtract_thick(x0,y0,image_size,grad);
00706 
00707       // Now, if possible, grow the edgelchain out from (X,Y) in a
00708       // one dimensional direction up to the boundary of the old kernel
00709       int newx,newy;
00710       X -= x0;  Y -= y0;   // Do a coordinate shift; centre in local coords
00711       for (int ii=1; ii<=old_k_size_ && Dangling_end(X+x0,Y+y0)==1; ++ii)
00712       {
00713         // Find the eight-way neighbour with the strongest edge
00714         newx = X;  newy = Y;
00715         Best_eight_way(X,Y,grad,&newx,&newy);
00716         // If no new edge has been found we should break
00717         if ( (newx==X) && (newy==Y) )
00718           break;
00719         // Else record a dummy edgel
00720         else {
00721           thin_[newx+x0][newy+y0] = dummy_;  thick_[newx+x0][newy+y0] = dummy_;
00722           X = newx;  Y = newy;
00723         }
00724       }
00725     }
00726     if (verbose) vcl_fprintf(stderr,"\b\b\b%3d", int(10*((++count)*10/xdang_->size())));
00727   }
00728   if (verbose)   vcl_cerr << vcl_endl;
00729 
00730   // Remove the image arrays
00731   osl_canny_base_free_raw_image(dx);
00732   osl_canny_base_free_raw_image(dy);
00733   osl_canny_base_free_raw_image(grad);
00734 }
00735 
00736 
00737 //-----------------------------------------------------------------------------
00738 //
00739 //: Wherever the thick_ image has an edge marked set the new gradient value to zero.
00740 void osl_canny_rothwell::Subtract_thick(int x0, int y0, int image_size, float **grad)
00741 {
00742   for (int x=width_; x<image_size-width_; ++x)
00743     for (int y=width_; y<image_size-width_; ++y)
00744       if ( thick_[x+x0][y+y0] > low_ )
00745         grad[x][y] = 0.0f;
00746 }
00747 
00748 
00749 //-----------------------------------------------------------------------------
00750 //
00751 //: Returns the eight-way neighbour with the strongest contrast change.
00752 //
00753 //fsm: No, this routine returns the last of the eight neighbours with contrast
00754 //change greater than 'low_'. Probably a bug. FIXME
00755 void osl_canny_rothwell::Best_eight_way(int x, int y, float **grad, int *xnew, int *ynew)
00756 {
00757   float max = low_;
00758   if ( grad[x-1][y-1] > max ) { *xnew = x-1;  *ynew = y-1; }
00759   if ( grad[x  ][y-1] > max ) { *xnew = x;    *ynew = y-1; }
00760   if ( grad[x+1][y-1] > max ) { *xnew = x+1;  *ynew = y-1; }
00761   if ( grad[x+1][y  ] > max ) { *xnew = x+1;  *ynew = y;   }
00762   if ( grad[x+1][y+1] > max ) { *xnew = x+1;  *ynew = y+1; }
00763   if ( grad[x  ][y+1] > max ) { *xnew = x;    *ynew = y+1; }
00764   if ( grad[x-1][y+1] > max ) { *xnew = x-1;  *ynew = y+1; }
00765   if ( grad[x-1][y  ] > max ) { *xnew = x-1;  *ynew = y;   }
00766 
00767   // Zero all of those places tested
00768   for (int j=y-1; j<=y+1; ++j)
00769     for (int i=x-1; i<=x+1; ++i)
00770       grad[i][j] = 0.0;
00771 }
00772 
00773 
00774 //-----------------------------------------------------------------------------
00775 //
00776 //: Searches for the junctions in the image.
00777 //
00778 void osl_canny_rothwell::Find_dangling_ends()
00779 {
00780   // Reset the dangling ends
00781   xdang_->clear();
00782   ydang_->clear();
00783   osl_canny_base_fill_raw_image(dangling_, xsize_, ysize_, 0);
00784 
00785   for (unsigned int x=w0_; x+w0_<xsize_; ++x)
00786     for (unsigned int y=w0_; y+w0_<ysize_; ++y)
00787     {
00788       if (Dangling_end(x,y) == 1) {
00789         xdang_->push_front(x);
00790         ydang_->push_front(y);
00791         dangling_[x][y] = 1;
00792       }
00793     }
00794 }
00795 
00796 
00797 //-----------------------------------------------------------------------------
00798 //
00799 //: Tests whether a point is a dangling end and return 1 in that case.
00800 //  Otherwise the return value could be 0 or between 2 and 8.
00801 //
00802 int osl_canny_rothwell::Dangling_end(int x, int y)
00803 {
00804   if ( thin_[x][y] <= low_ )
00805     return 0;
00806 
00807   int a = thin_[x-1][y-1] > low_ ? 1 : 0;
00808   int b = thin_[x  ][y-1] > low_ ? 1 : 0;
00809   int c = thin_[x+1][y-1] > low_ ? 1 : 0;
00810   int d = thin_[x+1][y  ] > low_ ? 1 : 0;
00811   int e = thin_[x+1][y+1] > low_ ? 1 : 0;
00812   int f = thin_[x  ][y+1] > low_ ? 1 : 0;
00813   int g = thin_[x-1][y+1] > low_ ? 1 : 0;
00814   int h = thin_[x-1][y  ] > low_ ? 1 : 0;
00815 
00816   return a+b+c+d+e+f+g+h;
00817 }
00818 
00819 
00820 //-----------------------------------------------------------------------------
00821 //
00822 //: Searches for the junctions in the image.
00823 //
00824 void osl_canny_rothwell::Find_junctions()
00825 {
00826   // Reset the junction variables
00827   xjunc_->clear();
00828   yjunc_->clear();
00829   osl_canny_base_fill_raw_image(junction_, xsize_, ysize_, 0);
00830 
00831   for (unsigned int x=w0_; x+w0_<xsize_; ++x)
00832     for (unsigned int y=w0_; y+w0_<ysize_; ++y)
00833     {
00834       if (Dangling_end(x,y) > 2) {
00835         xjunc_->push_front(x);
00836         yjunc_->push_front(y);
00837         junction_[x][y] = 1;
00838       }
00839     }
00840 }
00841 
00842 
00843 //-----------------------------------------------------------------------------
00844 //
00845 //: Locate junction clusters using the following method of hysteresis.
00846 //
00847 void osl_canny_rothwell::Find_junction_clusters()
00848 {
00849   vcl_list<int> xcoords,ycoords,xvertices,yvertices,xjunc,yjunc;
00850   // Find a junction and follow
00851   xvertices.clear();  yvertices.clear();
00852   xjunc.clear();      yjunc.clear();
00853   for (unsigned int x=w0_; x+w0_<xsize_; ++x)
00854     for (unsigned int y=w0_; y+w0_<ysize_; ++y)
00855       if ( junction_[x][y] ) {
00856 
00857         // Each cluster is written to (xcoords,ycooords)
00858         xcoords.clear();  ycoords.clear();
00859         Follow_junctions(junction_, x,y,&xcoords,&ycoords);
00860 
00861         // Find the `centre' of the cluster. This is defined as the
00862         // junction closest to the centre of gravity of the cluster
00863         int x0,y0;
00864         Cluster_centre_of_gravity(jx_, jy_, xcoords,ycoords,x0,y0);
00865 
00866         // Add both the junctions and the new cluster centre to
00867         // the main lists
00868         xvertices.push_front(x0);
00869         yvertices.push_front(y0);
00870         xjunc.insert(xjunc.begin(), xcoords.begin(), xcoords.end()); //xjunc.prepend(xcoords);
00871         yjunc.insert(yjunc.begin(), ycoords.begin(), ycoords.end()); //yjunc.prepend(ycoords);
00872       }
00873 
00874   // Reset the junction image - this is order dependent because
00875   // the cluster centres appear in both lists
00876   // xjunc.reset();  yjunc.reset();
00877   while ( xjunc.size() ) {
00878     junction_[xjunc.front()][yjunc.front()] = 1;
00879     xjunc.pop_front();
00880     yjunc.pop_front();
00881   }
00882 
00883   // Construct the list of junction cluster centres
00884   vlist_->clear();
00885   for (vcl_list<int>::iterator i=xvertices.begin(), j=yvertices.begin();
00886        i!=xvertices.end() && j!=yvertices.end();
00887        ++i, ++j) {
00888     //for (xvertices.reset(),yvertices.reset(); xvertices.next(),yvertices.next(); )  {
00889 
00890     osl_Vertex *v = new osl_Vertex( float((*i)/*xvertices.value()*/+xstart_),
00891                                     float((*j)/*yvertices.value()*/+ystart_));
00892     vlist_->push_front(v);
00893     junction_[(*i)/*xvertices.value()*/][(*j)/*yvertices.value()*/] = 2;
00894   }
00895 
00896   xvertices.clear();
00897   yvertices.clear();
00898 }