contrib/mul/fhs/tools/mr_find_matches.cxx
Go to the documentation of this file.
00001 //:
00002 // \file
00003 // \author Tim Cootes
00004 // \brief Example program using F&H method to locate matches on a pair of images
00005 
00006 #include <vul/vul_arg.h>
00007 #include <vimt/vimt_image_2d_of.h>
00008 #include <vimt/vimt_gaussian_pyramid_builder_2d.h>
00009 #include <vimt/vimt_image_pyramid.h>
00010 #include <vimt/vimt_crop.h>
00011 #include <vil/algo/vil_corners.h>
00012 #include <vil/algo/vil_find_peaks.h>
00013 #include <vil/vil_load.h>
00014 #include <vil/vil_save.h>
00015 #include <vil/vil_fill.h>
00016 #include <vil/vil_crop.h>
00017 #include <vil/vil_math.h>
00018 #include <vil/vil_convert.h>
00019 #include <vil/vil_resample_bilin.h>
00020 #include <mbl/mbl_index_sort.h>
00021 #include <vnl/vnl_math.h>
00022 #include <vgl/vgl_point_2d.h>
00023 #include <vgl/vgl_vector_2d.h>
00024 #include <fhs/fhs_searcher.h>
00025 #include <vimt/algo/vimt_normalised_correlation_2d.h>
00026 #include <mbl/mbl_minimum_spanning_tree.h>
00027 #include <mbl/mbl_draw_line.h>
00028 
00029 void print_usage()
00030 {
00031   vcl_cout<<"find_matches -i1 image1.jpg -i2 image2.jpg -Llo 0 Lhi 2\n"
00032           <<"Loads in image1 and image2.\n"
00033           <<"Locates a set of interesting features (corners) on levels of image1.\n"
00034           <<"Constructs a tree model of their relative positions.\n"
00035           <<"Points at level L are linked to the nearest points at level L+1\n"
00036           <<"Uses normalised correlation and this model to locate\n"
00037           <<"equivalent points on the second image."<<vcl_endl;
00038   vul_arg_display_usage_and_exit();
00039 }
00040 
00041 //: Write tree into the image
00042 // Draw disks at each point, and lines between linked points
00043 void draw_tree(vil_image_view<vxl_byte>& image,
00044                const vcl_vector<vgl_point_2d<double> >& pts,
00045                const vcl_vector<vcl_pair<int,int> >& pairs)
00046 {
00047   // Draw tree into image for display purposes
00048   for (unsigned i=0;i<pairs.size();++i)
00049     mbl_draw_line(image,
00050                   pts[pairs[i].first],
00051                   pts[pairs[i].second],vxl_byte(255));
00052 
00053   // Write position of selected points into the original image
00054   // for display purposes.
00055   for (unsigned i=0;i<pts.size();++i)
00056   {
00057     vil_fill_disk(image,pts[i].x(),pts[i].y(),4,vxl_byte(255));
00058   }
00059 }
00060 
00061 //: Apply corner operator to image and select strongest corners
00062 void get_strongest_corners(const vimt_image& image,
00063                            vcl_vector<vgl_point_2d<double> >& pts,
00064                            unsigned max_n)
00065 {
00066   const vimt_image_2d_of<vxl_byte>& byte_im =
00067                static_cast<const vimt_image_2d_of<vxl_byte>&>(image);
00068   vimt_image_2d_of<float> corner_im;
00069   corner_im.set_world2im(byte_im.world2im());
00070   vil_corners(byte_im.image(),corner_im.image());
00071 
00072   vcl_vector<unsigned> pi,pj;
00073   float threshold = 4.0f;
00074   vil_find_peaks_3x3(pi,pj,corner_im.image(),threshold);
00075 
00076   // Evaluate corner strength at each point (pi[i],pj[i])
00077   unsigned n = pi.size();
00078   vcl_vector<float> corner_str(n);
00079   for (unsigned i=0;i<n;++i)
00080     corner_str[i] = corner_im.image()(pi[i],pj[i]);
00081 
00082   // Sort and generate a list of image points and equivalent world points
00083   vcl_vector<unsigned> index;
00084   mbl_index_sort(corner_str,index);
00085 
00086   unsigned n_c = vcl_min(max_n,n);
00087   vcl_vector<vgl_point_2d<int> > im_pts(n_c);
00088   pts.resize(n_c);
00089   vimt_transform_2d im2w = byte_im.world2im().inverse();
00090   for (unsigned i=0;i<n_c;++i)
00091   {
00092     im_pts[i] = vgl_point_2d<int>(pi[index[n-1-i]],pj[index[n-1-i]]);
00093     pts[i]  = im2w(pi[index[n-1-i]],pj[index[n-1-i]]);
00094   }
00095 }
00096 
00097 //: Sample normalised patches around each point
00098 //  Extract patches with given half-width around each pts[i]
00099 //  When near an image edge, patch may be truncated
00100 //  ref_pts defines position of original point relative to the patch origin
00101 void extract_normalised_patches(const vimt_image& image,
00102                                 const vcl_vector<vgl_point_2d<double> >& pts,
00103                                 int half_width,
00104                                 vcl_vector<vil_image_view<float> >& patch,
00105                                 vcl_vector<vgl_point_2d<double> >& ref_pts)
00106 {
00107   const vimt_image_2d_of<vxl_byte>& byte_im =
00108                static_cast<const vimt_image_2d_of<vxl_byte>&>(image);
00109   int ni = byte_im.image().ni();
00110   int nj = byte_im.image().nj();
00111   unsigned n=pts.size();
00112   for (unsigned i=0;i<n;++i)
00113   {
00114     vgl_point_2d<double> im_p = byte_im.world2im()(pts[i]);
00115     int px = vnl_math_rnd(im_p.x()+0.5);
00116     int py = vnl_math_rnd(im_p.y()+0.5);
00117 
00118     // Select region around point, allowing for image edges.
00119     int ilo = vcl_max(0,px-half_width);
00120     int ihi = vcl_min(ni-1,px+half_width);
00121     int jlo = vcl_max(0,py-half_width);
00122     int jhi = vcl_min(nj-1,py+half_width);
00123 
00124     // Compute position of reference point relative to corner
00125     int kx = px-ilo;
00126     int ky = py-jlo;
00127     ref_pts.push_back(vgl_point_2d<double>(kx,ky));
00128     vil_image_view<float> patch1;
00129     vil_convert_cast(vil_crop(byte_im.image(),ilo,1+ihi-ilo, jlo,1+jhi-jlo),
00130                      patch1);
00131     vil_math_normalise(patch1);
00132     patch.push_back(patch1);
00133   }
00134 }
00135 
00136 //: Return index of closest point in pts[lo,hi]
00137 unsigned closest_pt_index(const vcl_vector<vgl_point_2d<double> >& pts,
00138                           unsigned lo, unsigned hi,
00139                           vgl_point_2d<double> p)
00140 {
00141   unsigned c=lo;
00142   double min_d = (pts[lo]-p).sqr_length();
00143   for (unsigned i=lo+1;i<=hi;++i)
00144   {
00145     double d = (pts[i]-p).sqr_length();
00146     if (d<min_d) {min_d=d; c=i;}
00147   }
00148   return c;
00149 }
00150 
00151 //: Generate a new image with resolution suitable for dest_L
00152 // Requires dest_L>src_L, else dest_im is a (shallow) copy of src_im
00153 void vimt_resample(const vimt_image_2d_of<float>& src_im, int src_L,
00154                    vimt_image_2d_of<float>& dest_im, int dest_L)
00155 {
00156   if (dest_L<=src_L)
00157   {
00158     dest_im=src_im;
00159     return;
00160   }
00161   unsigned ni = src_im.image().ni();
00162   unsigned nj = src_im.image().nj();
00163   unsigned s=1;
00164   for (int L=dest_L;L>=src_L;--L) s*=2;
00165 
00166   vil_resample_bilin(src_im.image(),dest_im.image(),1+s*(ni-1),1+s*(nj-1));
00167   vimt_transform_2d scale;
00168   scale.set_zoom_only(s,s,0.0,0.0);
00169   dest_im.set_world2im(scale*src_im.world2im());
00170 }
00171 
00172 int main( int argc, char* argv[] )
00173 {
00174   vul_arg<vcl_string> image1_path("-i1","Input image 1");
00175   vul_arg<vcl_string> image2_path("-i2","Input image 2");
00176   vul_arg<vcl_string> output_image1_path("-o1","Output image 1","output1.png");
00177   vul_arg<vcl_string> output_image2_path("-o2","Output image 1","output2.png");
00178   vul_arg<int> level_lo("-Llo","Image pyramid level to work on",0);
00179   vul_arg<int> level_hi("-Lhi","Image pyramid level to work on",2);
00180   vul_arg<unsigned> nc("-n","Number of points to select at coarse level",5);
00181   vul_arg<unsigned> w("-w","Half width of filters",7);
00182   vul_arg<double> f("-f","Relative strength of intensity vs shape",0.1);
00183 
00184   vul_arg_parse(argc, argv);
00185 
00186   if (image1_path()=="" || image2_path()=="")
00187   {
00188     print_usage();
00189     return 0;
00190   }
00191 
00192   // ============================================
00193   // Attempt to load in images
00194   // ============================================
00195 
00196   vimt_image_2d_of<vxl_byte> image1,image2;
00197   image1.image() = vil_load(image1_path().c_str());
00198   if (image1.image().size()==0)
00199   {
00200     vcl_cerr<<"Unable to read in image from "<<image1_path()<<'\n';
00201     return 1;
00202   }
00203   image2.image() = vil_load(image2_path().c_str());
00204   if (image2.image().size()==0)
00205   {
00206     vcl_cerr<<"Unable to read in image from "<<image2_path()<<'\n';
00207     return 1;
00208   }
00209 
00210   // ============================================
00211   // Build image pyramids and select chosen level
00212   // ============================================
00213   vimt_gaussian_pyramid_builder_2d<vxl_byte> pyr_builder;
00214   vimt_image_pyramid image_pyr1,image_pyr2;
00215   pyr_builder.build(image_pyr1,image1);
00216   pyr_builder.build(image_pyr2,image2);
00217 
00218   int max_L = vcl_min(image_pyr1.hi(),image_pyr2.hi());
00219   if (level_lo()<0  || level_hi()>max_L  || level_hi()<level_lo())
00220   {
00221     vcl_cerr<<"Levels must be in range [0,"<<max_L<<"], with lo<=hi\n";
00222     return 1;
00223   }
00224 
00225   // ====================================================================
00226   // Create model, consisting of patches and a tree of relative positions
00227   // ====================================================================
00228 
00229   vcl_vector<vgl_point_2d<double> > pts;
00230   vcl_vector<vil_image_view<float> > patch;
00231   vcl_vector<vgl_point_2d<double> > patch_ref;  // Reference point
00232   vcl_vector<vcl_pair<int,int> > pairs;
00233   vcl_vector<unsigned> im_level;  // Image level for each point
00234 
00235   // Search the coarsest image for corners
00236   get_strongest_corners(image_pyr1(level_hi()),pts,nc());
00237 
00238   // Construct tree structure for points
00239   mbl_minimum_spanning_tree(pts,pairs);
00240 
00241   // Extract patches for each point, pushing back onto patch,patch_ref
00242   extract_normalised_patches(image_pyr1(level_hi()),pts,w(),patch,patch_ref);
00243 
00244   for (unsigned i=0;i<pts.size();++i) im_level.push_back(level_hi());
00245 
00246   vcl_cout<<"Found "<<pts.size()<<" points at level "<<level_hi()<<vcl_endl;
00247 
00248   // Generate more points at each level
00249   int max_pts = nc();
00250   unsigned i0 = 0;          // Record index of first point at level above
00251   unsigned i1 = pts.size()-1; // Record index of last point at level above
00252   for (int L=level_hi()-1;L>=level_lo();--L)
00253   {
00254     max_pts*=3;
00255     vcl_vector<vgl_point_2d<double> > L_pts;
00256 
00257     // Search the coarsest image for corners
00258     get_strongest_corners(image_pyr1(L),L_pts,max_pts);
00259 
00260     // Extract patches for each point, pushing back onto patch,patch_ref
00261     extract_normalised_patches(image_pyr1(L),L_pts,w(),patch,patch_ref);
00262 
00263     // For each new point, find the closest point in the levels above
00264     // Change 0,i1 to i0,i1 to consider only level above
00265     for (unsigned i=0;i<L_pts.size();++i)
00266     {
00267       pts.push_back(L_pts[i]);
00268       vcl_pair<int,int> pair_i(pts.size()-1,
00269                                closest_pt_index(pts,0,i1,L_pts[i]));
00270       pairs.push_back(pair_i);
00271       im_level.push_back(L);
00272     }
00273 
00274     vcl_cout<<"Found "<<L_pts.size()<<" points at level "<<L<<vcl_endl;
00275 
00276     i0=i1+1;
00277     i1=pts.size()-1;
00278   }
00279 
00280 
00281   // Draw tree into image for display purposes
00282   draw_tree(image1.image(),pts,pairs);
00283 
00284   if (vil_save(image1.image(),output_image1_path().c_str()))
00285   {
00286     vcl_cout<<"Saved output image 1 to "<<output_image1_path()<<vcl_endl;
00287   }
00288 
00289   // =================================================
00290   // Construct the arc model from the points and pairs
00291   // =================================================
00292 
00293   vcl_vector<fhs_arc> arcs(pairs.size());
00294   int root_node = pairs[0].first;
00295   for (unsigned i=0;i<pairs.size();++i)
00296   {
00297     int i1 = pairs[i].first;
00298     int i2 = pairs[i].second;
00299     vgl_vector_2d<double> dp = pts[i2]-pts[i1];
00300     double sd_x = vcl_max(double(1<<im_level[i]),0.2*dp.length());
00301     double sd_y = vcl_max(double(1<<im_level[i]),0.2*dp.length());
00302     arcs[i]=fhs_arc(i1,i2,dp.x(),dp.y(),sd_x*sd_x,sd_y*sd_y);
00303   }
00304 
00305   // =================================================
00306   // Apply filters to image2 (initially to whole image)
00307   // =================================================
00308   vcl_vector<vimt_image_2d_of<float> > feature_response(pts.size());
00309   for (unsigned i=0;i<pts.size();++i)
00310   {
00311     const vimt_image_2d_of<vxl_byte>& byte_im =
00312        static_cast<const vimt_image_2d_of<vxl_byte>&>(image_pyr2(im_level[i]));
00313 
00314     // Compute region over which to search (20% of image, centered on point)
00315     int ni=byte_im.image().ni();
00316     int xw = ni/10+1+w();
00317     int nj=byte_im.image().nj();
00318     int yw = nj/10+1+w();
00319     vgl_point_2d<double> im_p = byte_im.world2im()(pts[i]);
00320     int xc = int(0.5+im_p.x());
00321     int yc = int(0.5+im_p.y());
00322     int ilo = vcl_max(0,xc-xw);
00323     int ihi = vcl_min(ni-1,xc+xw);
00324     int jlo = vcl_max(0,yc-yw);
00325     int jhi = vcl_min(nj-1,yc+yw);
00326     vimt_image_2d_of<vxl_byte> cropped_im = vimt_crop(byte_im,
00327                                                       ilo, 1+ihi-ilo,
00328                                                       jlo, 1+jhi-jlo);
00329 
00330     // Apply to whole image in first instance
00331     // Ideally would crop a region around expected position
00332     vimt_normalised_correlation_2d(cropped_im,feature_response[i],
00333                                    patch[i],patch_ref[i],float());
00334     if (im_level[i]!=(unsigned int)level_lo())
00335     {
00336       // Resample the feature response at resolution of image level_lo()
00337       vimt_image_2d_of<float> fr;
00338       fr.deep_copy(feature_response[i]);
00339       vimt_resample(fr,im_level[i], feature_response[i],level_lo());
00340     }
00341     // Need good values to be small, not large, so apply -ve factor
00342     vil_math_scale_values(feature_response[i].image(),-f());
00343   }
00344 
00345   // ======================================================
00346   // Use fhs_searcher to locate equivalent points on image2
00347   // ======================================================
00348 
00349   fhs_searcher searcher;
00350   searcher.set_tree(arcs,root_node);
00351   searcher.search(feature_response);
00352   vcl_vector<vgl_point_2d<double> > pts2;
00353   searcher.best_points(pts2);
00354 
00355   // Draw tree into image for display purposes
00356   draw_tree(image2.image(),pts2,pairs);
00357 
00358   if (vil_save(image2.image(),output_image2_path().c_str()))
00359   {
00360     vcl_cout<<"Saved output image 2 to "<<output_image2_path()<<vcl_endl;
00361   }
00362 
00363 
00364   return 0;
00365 }