contrib/mul/fhs/fhs_searcher.cxx
Go to the documentation of this file.
00001 #include "fhs_searcher.h"
00002 //:
00003 // \file
00004 // \author Tim Cootes
00005 // \brief Use F&H's DP style algorithm to search for global solutions
00006 
00007 #include <vcl_cassert.h>
00008 #include <vcl_cmath.h>
00009 #include <vcl_cstdlib.h> // for std::abort()
00010 #include <vnl/vnl_math.h>
00011 #include <vgl/vgl_point_2d.h>
00012 #include <vgl/vgl_vector_2d.h>
00013 #include <vil/vil_bilin_interp.h>
00014 #include <vil/algo/vil_quad_distance_function.h>
00015 #include <vimt/vimt_bilin_interp.h>
00016 
00017 //: Default constructor
00018 fhs_searcher::fhs_searcher()
00019   : geom_wt_(1.0)
00020 {
00021 }
00022 
00023 //: Set tree defining relationships between features
00024 //  Input arcs define neighbour relationships in any order.
00025 //  root_node defines which feature to be used as the root
00026 void fhs_searcher::set_tree(const vcl_vector<fhs_arc>& arcs,
00027                             unsigned root_node)
00028 {
00029   if (!fhs_order_tree_from_root(arcs,arc_,children_,root_node))
00030   {
00031     vcl_cerr<<"fhs_searcher::set_tree() Failed to set up the tree.\n";
00032     return;
00033   }
00034 
00035   // Fill in elements of arc_to_j_
00036   arc_to_j_.resize(arc_.size()+1);
00037   for (unsigned i=0;i<arc_.size();++i)
00038     arc_to_j_[arc_[i].j()]=i;
00039 }
00040 
00041 //: Set scaling applied to shape cost (default 1.0)
00042 void fhs_searcher::set_geom_wt(double geom_wt)
00043 {
00044   geom_wt_=geom_wt;
00045 }
00046   
00047 
00048 //: Index of root node (set by last call to set_tree()
00049 unsigned fhs_searcher::root_node() const
00050 {
00051   assert(arc_.size()>0);
00052   return arc_[0].i();
00053 }
00054 
00055 //: Combine responses for image im_index
00056 void fhs_searcher::combine_responses(unsigned im_index,
00057                                      const vimt_image_2d_of<float>& feature_response)
00058 {
00059   sum_im_[im_index].deep_copy(feature_response);
00060 
00061   for (unsigned ci = 0; ci<children_[im_index].size();++ci)
00062   {
00063     unsigned child_node = children_[im_index][ci];
00064     const fhs_arc& arc_to_c = arc_[arc_to_j_[child_node]];
00065 
00066     // Add offset version of child_node information to sum_im_
00067 
00068     // An inefficient initial implementation, assuming a
00069     // large cost for points sampled outside any response image
00070     vimt_transform_2d i2w = sum_im_[im_index].world2im().inverse();
00071     vimt_transform_2d c_w2i =  sum_im_[child_node].world2im();
00072 
00073     vgl_vector_2d<double> dp(arc_to_c.dx(),arc_to_c.dy());
00074 
00075     // Check if transformation between images is an integer multiple
00076     vgl_point_2d<double> p0 = c_w2i(i2w(0,0)+dp);
00077     vgl_point_2d<double> p1 = c_w2i(i2w(1,0)+dp);
00078     vgl_point_2d<double> p2 = c_w2i(i2w(0,1)+dp);
00079     vgl_vector_2d<double> dx = p1-p0;
00080     vgl_vector_2d<double> dy = p2-p0;
00081     // Check that dx=(0,1) and dy=(1,0)
00082     bool is_int_transform = (vcl_fabs(dx.x()-1)<1e-4) &&
00083                             (vcl_fabs(dx.y()  )<1e-4) &&
00084                             (vcl_fabs(dy.x()  )<1e-4) &&
00085                             (vcl_fabs(dy.y()-1)<1e-4);
00086 
00087     vil_image_view<float>& sum_image = sum_im_[im_index].image();
00088     unsigned ni = sum_image.ni();
00089     unsigned nj = sum_image.nj();
00090     float* s_data = sum_image.top_left_ptr();
00091     vcl_ptrdiff_t s_istep = sum_image.istep();
00092     vcl_ptrdiff_t s_jstep = sum_image.jstep();
00093     unsigned cni = dist_im_[child_node].image().ni();
00094     unsigned cnj = dist_im_[child_node].image().nj();
00095     const float* c_data = dist_im_[child_node].image().top_left_ptr();
00096     vcl_ptrdiff_t c_istep = dist_im_[child_node].image().istep();
00097     vcl_ptrdiff_t c_jstep = dist_im_[child_node].image().jstep();
00098 
00099     if (is_int_transform)
00100     {
00101       // Use fast pointer based approach to performing the addition
00102       int ci0 = vnl_math_rnd(p0.x());
00103       int cj  = vnl_math_rnd(p0.y());
00104       const float * c_row = c_data + cj*c_jstep + ci0*c_istep;
00105       float *s_row = s_data;
00106       for (unsigned j=0;j<nj;++j,++cj,c_row+=c_jstep,s_row+=s_jstep)
00107       {
00108         int ci = ci0;
00109         const float *c_pix = c_row;
00110         float * s_pix = s_row;
00111 
00112         if (cj<0 || (unsigned int)cj+1>=cnj)  // Whole row is illegal
00113         {
00114           for (unsigned i=0;i<ni;++i, s_pix+=s_istep)
00115             *s_pix += 9999;  // Illegal if off the image
00116         }
00117         else
00118         {
00119           for (unsigned i=0;i<ni;++i,++ci,c_pix+=c_istep, s_pix+=s_istep)
00120           {
00121             if (ci<0 || (unsigned int)ci+1>=cni)
00122               *s_pix += 9999;  // Illegal if off the image
00123             else
00124               *s_pix += *c_pix;
00125           }
00126         }
00127       }
00128     }
00129     else
00130     {
00131       for (unsigned j=0;j<nj;++j)
00132         for (unsigned i=0;i<ni;++i)
00133         {
00134           // Compute point in target image (in image co-ords)
00135           vgl_point_2d<double> p = c_w2i(i2w(i,j)+dp);
00136 
00137           if (p.x()<0 || p.y()<0 || p.x()+1>=cni || p.y()+1>=cnj)
00138             sum_image(i,j) += 9999.0f;  // Illegal if off the image
00139           else
00140             sum_image(i,j) += (float)vil_bilin_interp_unsafe(p.x(),p.y(),
00141                                                              c_data,c_istep,c_jstep);
00142         }
00143     }
00144   }
00145 }
00146 
00147 //: Perform global search
00148 //  Images of feature response supplied.  The transformation
00149 //  (world2im()) for each image can be used to indicate regions
00150 //  which don't necessarily overlap.  However, effective displacements
00151 //  are assumed to be in pixel sized steps.
00152 //
00153 //  After calling search(), results can be obtained using
00154 //  points() and best_points() etc
00155 void fhs_searcher::search(const vcl_vector<vimt_image_2d_of<float> >& feature_response)
00156 {
00157   assert(feature_response.size()==n_points());
00158 
00159   sum_im_.resize(n_points());
00160   dist_im_.resize(n_points());
00161   pos_im_.resize(n_points());
00162 
00163   // Work from leaves back to root
00164   for (int a=int(arc_.size())-1; a>=0; --a)
00165   {
00166     // arc_[a].j() is unique, and is only visited after its children
00167     unsigned node_a = arc_[a].j();
00168 
00169     // Set sum_im_[node_a] to be the feature response image
00170     // combined with the suitably offset summed responses from
00171     // the children of node_a
00172     combine_responses(node_a,feature_response[node_a]);
00173 
00174     // Compute scaling applied to x and y axes
00175     const vimt_transform_2d& w2im = feature_response[node_a].world2im();
00176     double sx = w2im.delta(vgl_point_2d<double>(0,0),
00177                            vgl_vector_2d<double>(1,0)).length();
00178     double sy = w2im.delta(vgl_point_2d<double>(0,0),
00179                            vgl_vector_2d<double>(0,1)).length();
00180 
00181 
00182     vil_quad_distance_function(sum_im_[node_a].image(),
00183                                geom_wt_/(sx*sx*arc_[a].var_x()),
00184                                geom_wt_/(sy*sy*arc_[a].var_y()),
00185                                dist_im_[node_a].image(),
00186                                pos_im_[node_a].image());
00187     dist_im_[node_a].set_world2im(sum_im_[node_a].world2im());
00188     pos_im_[node_a].set_world2im(sum_im_[node_a].world2im());
00189   }
00190 
00191   // Now combine children of root node
00192   unsigned root_node = arc_[0].i();
00193   combine_responses(root_node,feature_response[root_node]);
00194 
00195   // sum_im_[root_node] now contains quality of fit result for solution
00196 }
00197 
00198 //: Compute optimal position of all points given position of root
00199 //  Assumes search() has been called first
00200 void fhs_searcher::points_from_root(const vgl_point_2d<double>& root_pt,
00201                                     vcl_vector<vgl_point_2d<double> >& pts) const
00202 {
00203   if (n_points()<2)
00204   {
00205     vcl_cerr<<"fhs_searcher::points_from_root() Not initialised.\n";
00206     vcl_abort();
00207   }
00208 
00209   pts.resize(n_points());
00210   pts[root_node()]=root_pt;
00211 
00212   // Propagate solution through the tree
00213   // arc_ ordered so that parents always precede leaves
00214   for (vcl_vector<fhs_arc>::const_iterator a=arc_.begin();a!=arc_.end();++a)
00215   {
00216     // Compute mean predicted position for leaf point
00217     vgl_point_2d<double> p_j0 = pts[a->i()]
00218                                  +vgl_vector_2d<double>(a->dx(),a->dy());
00219 
00220     // Look up position allowing for offset (in image coords)
00221     double px = vimt_bilin_interp_safe(pos_im_[a->j()],p_j0,0);
00222     double py = vimt_bilin_interp_safe(pos_im_[a->j()],p_j0,1);
00223     // Project back to world co-ords
00224     pts[a->j()] = pos_im_[a->j()].world2im().inverse()(px,py);
00225   }
00226 }
00227 
00228 static vgl_point_2d<int> min_image_point(const vil_image_view<float>& image)
00229 {
00230   const unsigned ni=image.ni(),nj=image.nj();
00231   const vcl_ptrdiff_t istep = image.istep(),jstep=image.jstep();
00232   const float* row = image.top_left_ptr();
00233   unsigned best_i=0,best_j=0;
00234   float min_val = *row;
00235   for (unsigned j=0;j<nj;++j,row+=jstep)
00236   {
00237     const float* pixel = row;
00238     for (unsigned i=0;i<ni;++i,pixel+=istep)
00239       if (*pixel<min_val) {min_val=*pixel, best_i=i; best_j=j; }
00240   }
00241 
00242   return vgl_point_2d<int>(best_i,best_j);
00243 }
00244 
00245 //: Compute optimal position of all points
00246 //  Assumes search() has been called first
00247 double fhs_searcher::best_points(vcl_vector<vgl_point_2d<double> >& pts) const
00248 {
00249   // Find position of global minima in root quality image
00250   vgl_point_2d<int> p_im = min_image_point(sum_im_[root_node()].image());
00251   vgl_point_2d<double> root_pt = sum_im_[root_node()].world2im().inverse()(p_im.x(),p_im.y());
00252   points_from_root(root_pt,pts);
00253 
00254   return sum_im_[root_node()].image()(p_im.x(),p_im.y());
00255 }
00256 
00257 //: Return final total cost image for root
00258 const vimt_image_2d_of<float>& fhs_searcher::root_cost_image() const
00259 {
00260   return sum_im_[root_node()];
00261 }