contrib/mul/fhs/tools/fhs_match_tree_model.cxx
Go to the documentation of this file.
00001 //:
00002 // \file
00003 // \author Tim Cootes
00004 // \brief Program using F&H method to locate matches on a pair of images given tree on one
00005 //
00006 // Example parameter file:
00007 // \verbatim
00008 //  image1_path: HandImages/Hand_1.jpg
00009 //  image2_path: HandImages/Hand_9.jpg
00010 //  points_path: hand_b.pts
00011 //  arcs_path: hand_b.arcs
00012 //  output_image1_path: output1.png
00013 //  output_image2_path: output_b9.png
00014 //  L_lo: 2
00015 //  L_hi: 2
00016 //  half_width: 7
00017 //  response_scale: 0.5
00018 // \endverbatim
00019 
00020 #include <vul/vul_arg.h>
00021 #include <vimt/vimt_image_2d_of.h>
00022 #include <vimt/vimt_gaussian_pyramid_builder_2d.h>
00023 #include <vimt/vimt_image_pyramid.h>
00024 #include <vimt/vimt_crop.h>
00025 #include <mbl/mbl_read_props.h>
00026 #include <vil/vil_load.h>
00027 #include <vul/vul_string.h>
00028 #include <vcl_algorithm.h>
00029 #include <vil/vil_save.h>
00030 #include <vil/vil_fill.h>
00031 #include <vil/vil_crop.h>
00032 #include <vil/vil_math.h>
00033 #include <vil/vil_convert.h>
00034 #include <vil/vil_resample_bilin.h>
00035 #include <vnl/vnl_math.h>
00036 #include <vgl/vgl_point_2d.h>
00037 #include <vgl/vgl_vector_2d.h>
00038 #include <fhs/fhs_searcher.h>
00039 #include <vimt/algo/vimt_normalised_correlation_2d.h>
00040 #include <mbl/mbl_draw_line.h>
00041 
00042 void print_usage()
00043 {
00044   vcl_cout<<"fhs_match_tree_model -p param_file\n"
00045           <<"Loads in parameter file, which defines images,\n"
00046           <<"points, the arcs defining a tree and related details.\n"
00047           <<"Constructs a tree model from the first image.\n"
00048           <<"Uses normalised correlation and this model to locate\n"
00049           <<"equivalent points on the second image."<<vcl_endl;
00050   vul_arg_display_usage_and_exit();
00051 }
00052 
00053 class fhs_model_params
00054 {
00055  public:
00056   vcl_string image1_path;
00057   vcl_string image2_path;
00058   vcl_string points_path;
00059   vcl_string arcs_path;
00060   vcl_string output_image1_path;
00061   vcl_string output_image2_path;
00062   int L_lo,L_hi;
00063   int half_width;
00064   double response_scale;
00065 };
00066 
00067 bool parse_param_file(const vcl_string& param_path,
00068                       fhs_model_params& params)
00069 {
00070   // ---------------------------------------------------------------
00071   // Load the parameters
00072   // ---------------------------------------------------------------
00073   vcl_ifstream ifs(param_path.c_str());
00074   if (!ifs)
00075   {
00076     vcl_cerr<<"Failed to open parameter file: "<<param_path<<'\n';
00077     return false;
00078   }
00079 
00080   mbl_read_props_type props = mbl_read_props_ws(ifs);
00081 
00082   if (props.find("image1_path")!=props.end())
00083     params.image1_path = props["image1_path"];
00084   else {vcl_cerr<<"No image1_path: specified.\n"; return false; }
00085 
00086   if (props.find("image2_path")!=props.end())
00087     params.image2_path = props["image2_path"];
00088   else {vcl_cerr<<"No image2_path: specified.\n"; return false; }
00089 
00090   if (props.find("output_image1_path")!=props.end())
00091     params.output_image1_path = props["output_image1_path"];
00092   else {vcl_cerr<<"No output_image1_path: specified.\n"; return false; }
00093 
00094   if (props.find("output_image2_path")!=props.end())
00095     params.output_image2_path = props["output_image2_path"];
00096   else {vcl_cerr<<"No output_image2_path: specified.\n"; return false; }
00097 
00098   if (props.find("points_path")!=props.end())
00099     params.points_path = props["points_path"];
00100   else {vcl_cerr<<"No points_path: specified.\n"; return false; }
00101 
00102   if (props.find("arcs_path")!=props.end())
00103     params.arcs_path = props["arcs_path"];
00104   else {vcl_cerr<<"No arcs2_path: specified.\n"; return false; }
00105 
00106   if (props.find("L_lo")!=props.end())
00107     params.L_lo = vul_string_atoi(props["L_lo"]);
00108   else {vcl_cerr<<"No L_lo: specified.\n"; return false; }
00109 
00110   if (props.find("half_width")!=props.end())
00111     params.half_width = vul_string_atoi(props["half_width"]);
00112   else {vcl_cerr<<"No half_width: specified.\n"; return false; }
00113 
00114   if (props.find("L_hi")!=props.end())
00115     params.L_hi = vul_string_atoi(props["L_hi"]);
00116   else {vcl_cerr<<"No L_hi: specified.\n"; return false; }
00117 
00118   params.response_scale=0.1;
00119   if (props.find("response_scale")!=props.end())
00120     params.response_scale = vul_string_atof(props["response_scale"]);
00121 
00122   return true;
00123 }
00124 
00125 //: Load in points in ISBE points format
00126 //  eg:
00127 //  n_points: 12
00128 //  {
00129 //    1 2
00130 //    3 4
00131 //    ....
00132 //  }
00133 bool fhs_load_points(const vcl_string& path,
00134                      vcl_vector<vgl_point_2d<double> >& pts)
00135 {
00136   vcl_ifstream ifs( path.c_str(), vcl_ios_in );
00137   if (!ifs)
00138     { vcl_cerr<<"Failed to load in points from "<<path<<'\n'; return false; }
00139   vcl_string label;
00140 
00141     // Possible extra data - ignore it
00142   int image_nx, image_ny, image_nz;
00143   int n = -1;
00144 
00145   ifs >> label;
00146   while (!ifs.eof()  && label != "}")
00147   {
00148     if (label.size()>=2 && label[0]=='/' && label[1]=='/')
00149     {
00150       // Comment: Read to the end of the line
00151       char buf[256];
00152       ifs.get(buf,256);
00153     }
00154     else
00155     if (label=="version:")
00156       ifs>>label;
00157     else if (label=="image_size_x:")
00158       ifs>>image_nx;
00159     else if (label=="image_size_y:")
00160       ifs>>image_ny;
00161     else if (label=="image_size_z:")
00162       ifs>>image_nz;
00163     else if (label=="n_points:")
00164       ifs>>n;
00165     else if (label=="{")
00166     {
00167       pts.resize(n);
00168       for (int i=0;i<n;++i)
00169       {
00170         double x,y;
00171         ifs>>x>>y;
00172         pts[i]=vgl_point_2d<double>(x,y);
00173       }
00174       ifs>>label;
00175       if (label!="}")
00176       {
00177         vcl_cerr<<"Expecting }, got "<<label<<'\n';
00178         return false;
00179       }
00180     }
00181     else
00182     {
00183       vcl_cerr<<"Unexpected label: <"<<label<<">\n";
00184       return false;
00185     }
00186     ifs>>label;
00187   }
00188   return true;
00189 }
00190 
00191 //: Load in pairs of indices indicating connections between points
00192 //  File format: Each line contains two integers
00193 bool fhs_load_arcs(const vcl_string& path,
00194                    vcl_vector<vcl_pair<int,int> >& pairs)
00195 {
00196   vcl_vector<int> v;
00197     vcl_ifstream ifs( path.c_str(), vcl_ios_in );
00198   if (!ifs)
00199     { vcl_cerr<<"Failed to load in arc links from "<<path<<'\n'; return false; }
00200 
00201   int x;
00202   ifs>>vcl_ws;
00203   while (!ifs.eof())
00204   {
00205     ifs>>x>>vcl_ws;
00206     v.push_back(x);
00207   }
00208 
00209   if (v.size()%2==1)
00210   {
00211     vcl_cerr<<"Odd number of indices supplied in "<<path<<'\n';
00212     return false;
00213   }
00214 
00215   pairs.resize(v.size()/2);
00216   for (unsigned i=0;i<pairs.size();++i)
00217     pairs[i] = vcl_pair<int,int>(v[2*i],v[2*i+1]);
00218   return true;
00219 }
00220 
00221 //: Sample normalised patches around each point
00222 //  Extract patches with given half-width around each pts[i]
00223 //  When near an image edge, patch may be truncated
00224 //  ref_pts defines position of original point relative to the patch origin
00225 void extract_normalised_patches(const vimt_image& image,
00226                                 const vcl_vector<vgl_point_2d<double> >& pts,
00227                                 int half_width,
00228                                 vcl_vector<vil_image_view<float> >& patch,
00229                                 vcl_vector<vgl_point_2d<double> >& ref_pts)
00230 {
00231   const vimt_image_2d_of<vxl_byte>& byte_im =
00232                static_cast<const vimt_image_2d_of<vxl_byte>&>(image);
00233   int ni = byte_im.image().ni();
00234   int nj = byte_im.image().nj();
00235   unsigned n=pts.size();
00236   for (unsigned i=0;i<n;++i)
00237   {
00238     vgl_point_2d<double> im_p = byte_im.world2im()(pts[i]);
00239     int px = vnl_math_rnd(im_p.x()+0.5);
00240     int py = vnl_math_rnd(im_p.y()+0.5);
00241 
00242     // Select region around point, allowing for image edges.
00243     int ilo = vcl_max(0,px-half_width);
00244     int ihi = vcl_min(ni-1,px+half_width);
00245     int jlo = vcl_max(0,py-half_width);
00246     int jhi = vcl_min(nj-1,py+half_width);
00247 
00248     // Compute position of reference point relative to corner
00249     int kx = px-ilo;
00250     int ky = py-jlo;
00251     ref_pts.push_back(vgl_point_2d<double>(kx,ky));
00252     vil_image_view<float> patch1;
00253     vil_convert_cast(vil_crop(byte_im.image(),ilo,1+ihi-ilo, jlo,1+jhi-jlo),
00254                      patch1);
00255     vil_math_normalise(patch1);
00256     patch.push_back(patch1);
00257   }
00258 }
00259 
00260 void draw_tree(vil_image_view<vxl_byte>& image,
00261                const vcl_vector<vgl_point_2d<double> >& pts,
00262                const vcl_vector<vcl_pair<int,int> >& pairs)
00263 {
00264   // Draw tree into image for display purposes
00265   for (unsigned i=0;i<pairs.size();++i)
00266     mbl_draw_line(image,
00267                   pts[pairs[i].first],
00268                   pts[pairs[i].second],vxl_byte(255));
00269 
00270   // Write position of selected points into the original image
00271   // for display purposes.
00272   for (unsigned i=0;i<pts.size();++i)
00273   {
00274     vil_fill_disk(image,pts[i].x(),pts[i].y(),4,vxl_byte(255));
00275   }
00276 }
00277 
00278 //: Generate a new image with resolution suitable for dest_L
00279 // Requires dest_L>src_L, else dest_im is a (shallow) copy of src_im
00280 void vimt_resample(const vimt_image_2d_of<float>& src_im, int src_L,
00281                    vimt_image_2d_of<float>& dest_im, int dest_L)
00282 {
00283   if (dest_L<=src_L)
00284   {
00285     dest_im=src_im;
00286     return;
00287   }
00288   unsigned ni = src_im.image().ni();
00289   unsigned nj = src_im.image().nj();
00290   unsigned s=1;
00291   for (int L=dest_L;L>=src_L;--L) s*=2;
00292 
00293   vil_resample_bilin(src_im.image(),dest_im.image(),1+s*(ni-1),1+s*(nj-1));
00294   vimt_transform_2d scale;
00295   scale.set_zoom_only(s,s,0.0,0.0);
00296   dest_im.set_world2im(scale*src_im.world2im());
00297 }
00298 
00299 
00300 int main( int argc, char* argv[] )
00301 {
00302   vul_arg<vcl_string> param_path("-p","Parameter file path");
00303 
00304   vul_arg_parse(argc, argv);
00305 
00306   if (param_path()=="")
00307   {
00308     print_usage();
00309     return 0;
00310   }
00311 
00312   fhs_model_params params;
00313   if (!parse_param_file(param_path(),params)) return 1;
00314 
00315   // ============================================
00316   // Attempt to load in images
00317   // ============================================
00318 
00319   vimt_image_2d_of<vxl_byte> image1,image2;
00320   image1.image() = vil_load(params.image1_path.c_str());
00321   if (image1.image().size()==0)
00322   {
00323     vcl_cerr<<"Unable to read in image from "<<params.image1_path<<'\n';
00324     return 1;
00325   }
00326   image2.image() = vil_load(params.image2_path.c_str());
00327   if (image2.image().size()==0)
00328   {
00329     vcl_cerr<<"Unable to read in image from "<<params.image2_path<<'\n';
00330     return 1;
00331   }
00332 
00333   // ============================================
00334   // Build image pyramids and select chosen level
00335   // ============================================
00336   vimt_gaussian_pyramid_builder_2d<vxl_byte> pyr_builder;
00337   vimt_image_pyramid image_pyr1,image_pyr2;
00338   pyr_builder.build(image_pyr1,image1);
00339   pyr_builder.build(image_pyr2,image2);
00340 
00341   int max_L = vcl_min(image_pyr1.hi(),image_pyr2.hi());
00342   if (params.L_lo<0  || params.L_lo>max_L  || params.L_hi<params.L_lo)
00343   {
00344     vcl_cerr<<"Levels must be in range [0,"<<max_L<<"], with lo<=hi\n";
00345     return 2;
00346   }
00347 
00348   // ============================================
00349   // Load in the points
00350   // ============================================
00351 
00352   vcl_vector<vgl_point_2d<double> > ref_pts;
00353   if (!fhs_load_points(params.points_path,ref_pts)) return 3;
00354 
00355   vcl_vector<unsigned> im_level(ref_pts.size());
00356   for (unsigned i=0;i<ref_pts.size();++i) im_level[i]=params.L_hi;
00357 
00358   // ============================================
00359   // Load in the arcs (links between pairs)
00360   // ============================================
00361   vcl_vector<vcl_pair<int,int> > pairs;
00362   if (!fhs_load_arcs(params.arcs_path,pairs)) return 4;
00363 
00364   // Check arc ends are all valid points
00365   for (unsigned i=0;i<pairs.size();++i)
00366   {
00367     if (pairs[i].first<0 || (unsigned int)(pairs[i].first)>=ref_pts.size())
00368     { vcl_cerr<<"Invalid point index "<<pairs[i].first<<'\n'; return 5; }
00369     if (pairs[i].second<0 || (unsigned int)(pairs[i].second)>=ref_pts.size())
00370     { vcl_cerr<<"Invalid point index "<<pairs[i].second<<'\n'; return 5; }
00371   }
00372 
00373   // ====================================================================
00374   // Create model, consisting of patches and a tree of relative positions
00375   // ====================================================================
00376 
00377   vcl_vector<vil_image_view<float> > patch;
00378   vcl_vector<vgl_point_2d<double> > patch_ref;  // Reference point
00379 
00380   // Extract patches for each point, pushing back onto patch,patch_ref
00381   extract_normalised_patches(image_pyr1(params.L_hi),ref_pts,
00382                              params.half_width,patch,patch_ref);
00383 
00384 
00385   // =================================================
00386   // Construct the arc model from the points and pairs
00387   // =================================================
00388 
00389   vcl_vector<fhs_arc> arcs(pairs.size());
00390   int root_node = pairs[0].first;
00391   for (unsigned i=0;i<pairs.size();++i)
00392   {
00393     int i1 = pairs[i].first;
00394     int i2 = pairs[i].second;
00395     vgl_vector_2d<double> dp = ref_pts[i2]-ref_pts[i1];
00396     double sd_x = vcl_max(double(1 << im_level[i]),0.2*dp.length());
00397     double sd_y = vcl_max(double(1 << im_level[i]),0.2*dp.length());
00398     arcs[i]=fhs_arc(i1,i2,dp.x(),dp.y(),sd_x*sd_x,sd_y*sd_y);
00399   }
00400 
00401 
00402   // =================================================
00403   // Apply filters to image2 (initially to whole image)
00404   // =================================================
00405   vcl_vector<vimt_image_2d_of<float> > feature_response(ref_pts.size());
00406   for (unsigned i=0;i<ref_pts.size();++i)
00407   {
00408     const vimt_image_2d_of<vxl_byte>& byte_im =
00409        static_cast<const vimt_image_2d_of<vxl_byte>&>(image_pyr2(im_level[i]));
00410 
00411     // Compute region over which to search (20% of image, centered on point)
00412     int ni=byte_im.image().ni();
00413     int xw = ni/8+1+params.half_width;
00414     int nj=byte_im.image().nj();
00415     int yw = nj/8+1+params.half_width;
00416     vgl_point_2d<double> im_p = byte_im.world2im()(ref_pts[i]);
00417     int xc = int(0.5+im_p.x());
00418     int yc = int(0.5+im_p.y());
00419     int ilo = vcl_max(0,xc-xw);
00420     int ihi = vcl_min(ni-1,xc+xw);
00421     int jlo = vcl_max(0,yc-yw);
00422     int jhi = vcl_min(nj-1,yc+yw);
00423     vimt_image_2d_of<vxl_byte> cropped_im = vimt_crop(byte_im,
00424                                                       ilo, 1+ihi-ilo,
00425                                                       jlo, 1+jhi-jlo);
00426 
00427     // Apply to whole image in first instance
00428     // Ideally would crop a region around expected position
00429     vimt_normalised_correlation_2d(cropped_im,feature_response[i],
00430                                    patch[i],patch_ref[i],float());
00431     if (int(im_level[i])!=params.L_lo)
00432     {
00433       // Resample the feature response at resolution of image params.L_lo
00434       vimt_image_2d_of<float> fr;
00435       fr.deep_copy(feature_response[i]);
00436       vimt_resample(fr,im_level[i], feature_response[i],params.L_lo);
00437     }
00438     // Need good values to be small, not large, so apply -ve factor
00439     vil_math_scale_values(feature_response[i].image(),-params.response_scale);
00440   }
00441 
00442   // ======================================================
00443   // Use fhs_searcher to locate equivalent points on image2
00444   // ======================================================
00445 
00446   fhs_searcher searcher;
00447   searcher.set_tree(arcs,root_node);
00448   searcher.search(feature_response);
00449   vcl_vector<vgl_point_2d<double> > pts2;
00450   searcher.best_points(pts2);
00451 
00452   // Draw tree into image for display purposes
00453   draw_tree(image1.image(),ref_pts,pairs);
00454 
00455   if (vil_save(image1.image(),params.output_image1_path.c_str()))
00456   {
00457     vcl_cout<<"Saved output image 1 to "<<params.output_image1_path<<vcl_endl;
00458   }
00459 
00460   // Draw tree into image for display purposes
00461   draw_tree(image2.image(),pts2,pairs);
00462 
00463   if (vil_save(image2.image(),params.output_image2_path.c_str()))
00464   {
00465     vcl_cout<<"Saved output image 2 to "<<params.output_image2_path<<vcl_endl;
00466   }
00467 
00468 
00469   return 0;
00470 }