Main Page | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Class Members | File Members

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

Generated on Thu Jan 10 14:44:46 2008 for contrib/mul/fhs by  doxygen 1.4.4