contrib/brl/bseg/sdet/sdet_nms.cxx
Go to the documentation of this file.
00001 // This is brl/bseg/sdet/sdet_nms.cxx
00002 #include "sdet_nms.h"
00003 //:
00004 // \file
00005 
00006 #include <vcl_cstdio.h>
00007 #include <vcl_cstdlib.h>   // for vcl_abs(int) and vcl_sqrt()
00008 #include <vnl/vnl_matrix.h>
00009 #include <vnl/algo/vnl_svd.h>
00010 #include <vgl/vgl_homg_point_2d.h>
00011 #include <vgl/vgl_homg_line_2d.h>
00012 #include <vgl/algo/vgl_homg_operators_2d.h>
00013 
00014 //---------------------------------------------------------------
00015 // Constructors
00016 //----------------------------------------------------------------
00017 
00018 //: default constructor
00019 sdet_nms::sdet_nms():
00020   thresh_(0.0),
00021   parabola_fit_type_(sdet_nms_params::PFIT_3_POINTS),
00022   margin_(1),
00023   rel_thresh_(2.5),
00024   use_adaptive_thresh_(true),
00025   dir_x_(vil_image_view<double>(0,0,1)), // FIXME: this does not make sense: reference member is initialized to a temporary that doesn't persist after the constructor exits
00026   dir_y_(vil_image_view<double>(0,0,1)), // idem
00027   grad_mag_(vil_image_view<double>(0,0,1)), // idem
00028   x_(0,0, 0.0),
00029   y_(0,0, 0.0),
00030   dir_(0,0, 0.0),
00031   mag_(0,0, 0.0),
00032   deriv_(0,0, 0.0)
00033 {
00034 }
00035 
00036 //: Constructor from a parameter block, gradient magnitudes given as an image and directions given as component image
00037 sdet_nms::sdet_nms(const sdet_nms_params& nsp, const vil_image_view<double>& dir_x,
00038                    const vil_image_view<double>& dir_y, const vil_image_view<double>& grad_mag) :
00039   thresh_(nsp.thresh_),
00040   parabola_fit_type_(nsp.pfit_type_),
00041   margin_(nsp.margin_),
00042   rel_thresh_(nsp.rel_thresh_),
00043   use_adaptive_thresh_(nsp.use_adaptive_thresh_),
00044   dir_x_(dir_x),
00045   dir_y_(dir_y),
00046   grad_mag_(grad_mag),
00047   x_(grad_mag.nj(), grad_mag.ni(), 0.0),
00048   y_(grad_mag.nj(), grad_mag.ni(), 0.0),
00049   dir_(grad_mag.nj(), grad_mag.ni(), 0.0),
00050   mag_(grad_mag.nj(), grad_mag.ni(), 0.0),
00051   deriv_(grad_mag.nj(), grad_mag.ni(), 0.0)
00052 {
00053 }
00054 
00055 //-------------------------------------------------------------------------
00056 
00057 // backward compatible method
00058 void sdet_nms::apply(bool collect_tokens,
00059                      vcl_vector<vgl_point_2d<double> >& loc,
00060                      vcl_vector<double>& orientation,
00061                      vcl_vector<double>& mag)
00062 {
00063   vcl_vector<double> d2f;
00064   vcl_vector<vgl_point_2d<int> > pix_loc;
00065 
00066   //call the main method
00067   apply(collect_tokens, loc, orientation, mag, d2f, pix_loc);
00068 }
00069 
00070 void sdet_nms::apply(bool collect_tokens,
00071                      vcl_vector<vgl_point_2d<double> >& loc,
00072                      vcl_vector<double>& orientation,
00073                      vcl_vector<double>& mag,
00074                      vcl_vector<double>& d2f)
00075 {
00076   vcl_vector<vgl_point_2d<int> > pix_loc;
00077 
00078   //call the main method
00079   apply(collect_tokens, loc, orientation, mag, d2f, pix_loc);
00080 }
00081 
00082 //: Apply the algorithm
00083 void sdet_nms::apply(bool collect_tokens,
00084                      vcl_vector<vgl_point_2d<double> >& loc,
00085                      vcl_vector<double>& orientation,
00086                      vcl_vector<double>& mag,
00087                      vcl_vector<double>& d2f,
00088                      vcl_vector<vgl_point_2d<int> >& pix_loc)
00089 {
00090   double f[3], s_list[3];
00091 
00092   // run non-maximum suppression at every point inside the margins
00093   for (unsigned x = margin_; x < grad_mag_.ni()-margin_; x++) {
00094     for (unsigned y = margin_; y < grad_mag_.nj()-margin_; y++)
00095     {
00096       if (grad_mag_(x,y) < thresh_) //threshold by gradient magnitude
00097         continue;
00098 
00099       double gx = dir_x_(x,y);
00100       double gy = dir_y_(x,y);
00101       vgl_vector_2d<double> direction(gx,gy);
00102       normalize(direction);
00103 
00104       //The gradient has to be non-degenerate
00105       if (vcl_abs(direction.x()) < 10e-6 && vcl_abs(direction.y()) < 10e-6)
00106         continue;
00107 
00108       //now compute the values orthogonal to the edge and fit a parabola
00109       int face_num = intersected_face_number(direction); assert(face_num != -1);
00110       double s = intersection_parameter(direction, face_num); assert(s != -1000);
00111       f_values(x, y, direction, s, face_num, f);
00112       s_list[0] = -s; s_list[1] = 0.0; s_list[2] = s;
00113 
00114       //Amir: removed this maximum check because, there can be a maxima between f- and f+ even when f<f- or f<f+
00115 
00116       ////Maximum check
00117       //if ( (f[1]-f[0])>1e-2 && (f[1]-f[2])>1e-2) //epsilon checks instead of absolute
00118       //{
00119         //fit a parabola to the data
00120         double max_val = f[1]; //default (should be updated by parabola fit)
00121         double grad_val = 0.0; //should be updated by parabola fit
00122 
00123         //compute location of extrema
00124         double s_star = (parabola_fit_type_ == sdet_nms_params::PFIT_3_POINTS) ?
00125                             subpixel_s(s_list, f, max_val, grad_val) : subpixel_s(x, y, direction, max_val);
00126 
00127         if (vcl_fabs(s_star)< 0.7)
00128         {
00129           //record this edgel
00130           x_(y,x) = x + s_star * direction.x();
00131           y_(y,x) = y + s_star * direction.y();
00132           dir_(y,x) = vcl_atan2(direction.x(), -direction.y());
00133           mag_(y,x) = max_val; //the mag at the max of the parabola
00134           deriv_(y,x) = grad_val;
00135         }
00136       //}
00137     }
00138   }
00139 
00140   //post-process the NMS edgel map to reduce the occurrence of duplicate edgels
00141   for (unsigned x = margin_; x < grad_mag_.ni()-margin_; x++) {
00142     for (unsigned y = margin_; y < grad_mag_.nj()-margin_; y++)
00143     {
00144       if (mag_(y,x)==0.0)
00145         continue;
00146 
00147       //use the orientation of the edgel to determine the closest neighbors that could produce a duplicate edgel
00148 
00149       //Hack: for now just look over all the 8-neighbors
00150       for (int ii=-1; ii<2; ii++) {
00151         for (int jj=-1; jj<2; jj++) {
00152 
00153           if (ii==0 && jj==0) continue;
00154 
00155           //if there is an edgel at this location, compute the distance to the current edgel
00156           if (mag_(y+jj,x+ii)>0.0) {
00157             double dx = x_(y+jj,x+ii) - x_(y,x);
00158             double dy = y_(y+jj,x+ii) - y_(y,x);
00159 
00160             if ((dx*dx+dy*dy)<0.1) { //closeness threshold, may be made into a parameter if anyone cares
00161               mag_(y,x)=0.0; //kill the current edgel
00162               continue;
00163             }
00164           }
00165         }
00166       }
00167     }
00168   }
00169 
00170   //if seeking tokens, form tokens from the remaining edgels
00171   if (collect_tokens) {
00172     for (unsigned x = margin_; x < grad_mag_.ni()-margin_; x++) {
00173       for (unsigned y = margin_; y < grad_mag_.nj()-margin_; y++)
00174       {
00175         if (mag_(y,x)==0.0)
00176           continue;
00177 
00178         loc.push_back(vgl_point_2d<double>(x_(y,x), y_(y,x)));
00179         orientation.push_back(dir_(y,x));
00180         mag.push_back(mag_(y,x));
00181         d2f.push_back(deriv_(y,x));
00182 
00183         //also return the pixel location so that they can be used for tracing algorithms
00184         pix_loc.push_back(vgl_point_2d<int>(x, y));
00185       }
00186     }
00187   }
00188 }
00189 
00190 int sdet_nms::intersected_face_number(const vgl_vector_2d<double>& direction)
00191 {
00192   if (direction.x() >= 0 && direction.y() >= 0)
00193   {
00194     if (direction.x() >= direction.y())
00195       return 1;
00196     else
00197       return 2;
00198   }
00199   else if (direction.x() < 0 && direction.y() >= 0)
00200   {
00201     if (vcl_abs(direction.x()) < direction.y())
00202       return 3;
00203     else
00204       return 4;
00205   }
00206   else if (direction.x() < 0 && direction.y() < 0)
00207   {
00208     if (vcl_abs(direction.x()) >= vcl_abs(direction.y()))
00209       return 5;
00210     else
00211       return 6;
00212   }
00213   else if (direction.x() >= 0 && direction.y() < 0)
00214   {
00215     if (direction.x() < vcl_abs(direction.y()))
00216       return 7;
00217     else
00218       return 8;
00219   }
00220   return -1;
00221 }
00222 
00223 double sdet_nms::intersection_parameter(const vgl_vector_2d<double>& direction, int face_num)
00224 {
00225   if (face_num == 1 || face_num == 8)
00226     return 1.0/direction.x();
00227   else if (face_num == 2 || face_num == 3)
00228     return 1.0/direction.y();
00229   else if (face_num == 4 || face_num == 5)
00230     return -1.0/direction.x();
00231   else if (face_num == 6 || face_num == 7)
00232     return -1.0/direction.y();
00233   return -1000.0;
00234 }
00235 
00236 void sdet_nms::f_values(int x, int y, const vgl_vector_2d<double>& direction, double s, int face_num, double *f)
00237 {
00238   int corners[4];
00239   get_relative_corner_coordinates(face_num, corners);
00240 
00241   vgl_vector_2d<double> intersection_point = s * direction;
00242   vgl_vector_2d<double> corner1(corners[0], corners[1]); //have to convert to double for subtraction
00243   vgl_vector_2d<double> corner2(corners[2], corners[3]); //have to convert to double for subtraction
00244   double distance1 = length(intersection_point - corner1);
00245   double distance2 = length(intersection_point - corner2);
00246   double value1 = grad_mag_(x+corners[0], y+corners[1]);
00247   double value2 = grad_mag_(x+corners[2], y+corners[3]);
00248   double f_plus = value1 * distance2 + value2 * distance1;
00249 
00250   intersection_point = -s * direction;
00251   corner1.set(-corners[0], -corners[1]); //have to convert to double for subtraction
00252   corner2.set(-corners[2], -corners[3]); //have to convert to double for subtraction
00253   distance1 = length(intersection_point - corner1);
00254   distance2 = length(intersection_point - corner2);
00255   value1 = grad_mag_(x-corners[0], y-corners[1]);
00256   value2 = grad_mag_(x-corners[2], y-corners[3]);
00257   double f_minus = value1 * distance2 + value2 * distance1;
00258 
00259   f[0] = f_minus;
00260   f[1] = grad_mag_(x,y);
00261   f[2] = f_plus;
00262 }
00263 
00264 void sdet_nms::get_relative_corner_coordinates(int face_num, int *corners)
00265 {
00266   switch (face_num)
00267   {
00268    case 1:
00269      corners[0] = 1;
00270      corners[1] = 0;
00271      corners[2] = 1;
00272      corners[3] = 1;
00273      break;
00274    case 2:
00275      corners[0] = 1;
00276      corners[1] = 1;
00277      corners[2] = 0;
00278      corners[3] = 1;
00279      break;
00280    case 3:
00281      corners[0] = 0;
00282      corners[1] = 1;
00283      corners[2] = -1;
00284      corners[3] = 1;
00285     break;
00286    case 4:
00287      corners[0] = -1;
00288      corners[1] = 1;
00289      corners[2] = -1;
00290      corners[3] = 0;
00291     break;
00292    case 5:
00293      corners[0] = -1;
00294      corners[1] = 0;
00295      corners[2] = -1;
00296      corners[3] = -1;
00297     break;
00298    case 6:
00299      corners[0] = -1;
00300      corners[1] = -1;
00301      corners[2] = 0;
00302      corners[3] = -1;
00303     break;
00304    case 7:
00305      corners[0] = 0;
00306      corners[1] = -1;
00307      corners[2] = 1;
00308      corners[3] = -1;
00309     break;
00310    case 8:
00311      corners[0] = 1;
00312      corners[1] = -1;
00313      corners[2] = 1;
00314      corners[3] = 0;
00315     break;
00316    default:
00317      corners[0] = 0;
00318      corners[1] = 0;
00319      corners[2] = 0;
00320      corners[3] = 0;
00321   }
00322 }
00323 
00324 double sdet_nms::subpixel_s(double *s, double *f, double &max_f, double &max_d)
00325 {
00326   //double A = f[2] / ((s[2]-s[0])*(s[2]-s[1]));
00327   //double B = f[1] / ((s[1]-s[0])*(s[1]-s[2]));
00328   //double C = f[0] / ((s[0]-s[1])*(s[0]-s[2]));
00329   //double s_star = ((A+B)*s[0] + (A+C)*s[1] + (B+C)*s[2]) / (2*(A+B+C));
00330 
00331 
00332   //new version: assumes s[1]=0 and s[2]=-s[0]
00333   double A = -(f[1] - (f[0]+f[2])/2.0)/(s[2]*s[2]);
00334   double B = -(f[0]-f[2])/(2*s[2]);
00335   double C = f[1];
00336 
00337   double s_star = -B/(2*A);
00338 
00339   max_f = A*s_star*s_star + B*s_star + C;
00340 
00341   //second derivative
00342   double d2f = 2*A;
00343   max_d = d2f;
00344 
00345   if (A<0) { //make sure this is a maximum
00346     if (use_adaptive_thresh_) {
00347 #if 0 // no longer used...
00348       //derivatives at f+ and f-
00349       double d2fp = 2*A*s[2] + B;
00350       double d2fm = 2*A*s[0] + B;
00351       if (vcl_fabs(d2fp)>rel_thresh_ || vcl_fabs(d2fm)>rel_thresh_)
00352 #endif // 0
00353       if (d2f<-rel_thresh_)//d2f is always negative at a maxima
00354         return s_star;
00355       else
00356         return 5.0; //not reliable
00357     }
00358     else
00359       return s_star;
00360   }
00361   else
00362     return 5.0; //not a maximum
00363 
00364 //From gevd NMS
00365 #if 0
00366   // Fit a parabola through 3 points with strict local max/min.
00367   // Return the offset location, and value of the maximum/minimum.
00368   float
00369   gevd_float_operators::InterpolateParabola(float y_1, float y_0, float y_2,
00370                                             float&y)
00371   {
00372     float diff1 = y_2 - y_1;      // first derivative
00373     float diff2 = 2 * y_0 - y_1 - y_2; // second derivative
00374     y = y_0 + diff1 * diff1 / (8 * diff2);        // interpolate y as max/min
00375     return diff1 / (2 * diff2);   // interpolate x offset
00376 }
00377 #endif // 0
00378 }
00379 
00380 double sdet_nms::subpixel_s(int x, int y, const vgl_vector_2d<double>& direction, double &max_f)
00381 {
00382   double d;
00383   double s;
00384   double f;
00385   vgl_homg_point_2d<double> p1(0.0, 0.0);
00386   vgl_homg_point_2d<double> p2(direction.x(), direction.y());
00387   vgl_homg_line_2d<double> line1(p1,p2);
00388   //construct the matrices
00389   vnl_matrix<double> A(9, 3);
00390   vnl_matrix<double> B(9, 1);
00391   vnl_matrix<double> P(3, 1);
00392   int index = 0;
00393   for (int j = -1; j <= 1; j++)
00394   {
00395     for (int i = -1; i <= 1; i++)
00396     {
00397       find_distance_s_and_f_for_point(i, j, line1, d, s, direction);
00398       f = grad_mag_(x+i,y+j);
00399       A(index, 0) = vcl_pow(s,2.0);
00400       A(index, 1) = s;
00401       A(index, 2) = 1.0;
00402       B(index, 0) = f;
00403       index++;
00404     }
00405   }
00406 //  vnl_matrix<double> A_trans = A.transpose();
00407 //  vnl_matrix<double> temp = vnl_matrix_inverse<double> (A_trans*A);
00408 //  vnl_matrix<double> temp2 = temp * A_trans;
00409 //  P = temp2 * B;
00410   vnl_svd<double> svd(A);
00411   P = svd.solve(B);
00412   double s_star = -P(1,0)/(2*P(0,0));
00413 
00414 
00415   max_f = P(0,0)*s_star*s_star + P(1,0)*s_star + P(2,0);
00416 
00417   if (P(0,0)<0)
00418     return s_star;
00419   else
00420     return 5.0; //not a maxima
00421 }
00422 
00423 void sdet_nms::find_distance_s_and_f_for_point(int x, int y, vgl_homg_line_2d<double> line,
00424                                                double &d, double &s, const vgl_vector_2d<double>& direction)
00425 {
00426   vgl_homg_point_2d<double> point(x,y);
00427   vgl_homg_line_2d<double> perp_line = vgl_homg_operators_2d<double>::perp_line_through_point(line, point);
00428   vgl_homg_point_2d<double> intersection_point_homg = vgl_homg_operators_2d<double>::intersection(line, perp_line);
00429   vgl_point_2d<double> intersection_point(intersection_point_homg);
00430   vgl_vector_2d<double> d_helper(x-intersection_point.x(), y-intersection_point.y());
00431   d = length(d_helper);
00432   s = intersection_point.x() / direction.x();
00433 }
00434 
00435 //----------------------------------------------------------
00436 //: Clear internal storage
00437 //
00438 void sdet_nms::clear()
00439 {
00440   mag_.clear();
00441 }