contrib/mul/mfpf/mfpf_hog_box_finder.cxx
Go to the documentation of this file.
00001 #include "mfpf_hog_box_finder.h"
00002 //:
00003 // \file
00004 // \brief Searches for rectangular region using HOG features
00005 // \author Tim Cootes
00006 
00007 #include <vsl/vsl_binary_loader.h>
00008 #include <vcl_cmath.h>
00009 #include <vcl_cassert.h>
00010 
00011 #include <vil/vil_resample_bilin.h>
00012 #include <vil/io/vil_io_image_view.h>
00013 #include <vsl/vsl_vector_io.h>
00014 #include <vsl/vsl_indent.h>
00015 #include <vcl_algorithm.h>
00016 #include <vnl/vnl_math.h>
00017 
00018 #include <mipa/mipa_orientation_histogram.h>
00019 #include <mipa/mipa_sample_histo_boxes.h>
00020 #include <mipa/mipa_identity_normaliser.h>
00021 #include <mipa/mipa_block_normaliser.h>
00022 
00023 //: Divide elements of v by sum of last nA elements
00024 //  For histogram vectors these are the total sums
00025 inline void mfpf_norm_histo_vec(vnl_vector<double>& v, unsigned nA)
00026 {
00027   unsigned n=v.size();
00028   double sum = 0.0;
00029   for (unsigned i=1;i<=nA;++i) sum+=v[n-i];
00030   v/=sum;
00031 }
00032 
00033 //=======================================================================
00034 // Dflt ctor
00035 //=======================================================================
00036 
00037 mfpf_hog_box_finder::mfpf_hog_box_finder():normaliser_(mipa_identity_normaliser())
00038 {
00039   set_defaults();
00040 }
00041 
00042 //: Define default values
00043 void mfpf_hog_box_finder::set_defaults()
00044 {
00045   step_size_=1.0;
00046   search_ni_=5;
00047   search_nj_=5;
00048   nA_=0; dA_=0.0;
00049   ns_=0; ds_=1.0;
00050 
00051   nA_bins_=8;
00052   full360_=true;
00053   nc_=2;
00054   ni_=0;
00055   nj_=0;
00056   ref_x_=0;
00057   ref_y_=0;
00058 //  norm_method_=0;
00059   overlap_f_=1.0;
00060 }
00061 
00062 //=======================================================================
00063 // Destructor
00064 //=======================================================================
00065 
00066 mfpf_hog_box_finder::~mfpf_hog_box_finder()
00067 {
00068 }
00069 
00070 //: Define region and cost of region
00071 void mfpf_hog_box_finder::set(unsigned nA_bins, bool full360,
00072                               unsigned ni, unsigned nj, unsigned nc,
00073                               double ref_x, double ref_y,
00074                               const mfpf_vec_cost& cost,
00075                               const mbl_cloneable_nzptr<mipa_vector_normaliser>& normaliser)
00076 {
00077   cost_ = cost.clone();
00078   ref_x_ = ref_x;
00079   ref_y_ = ref_y;
00080 
00081   nA_bins_ = nA_bins;
00082   full360_ = full360;
00083   ni_      = ni;
00084   nj_      = nj;
00085   nc_      = nc;
00086 
00087   normaliser_ = normaliser;
00088 
00089   //: Block normalisers (and their derivatives) typically need their regions copying from this
00090   mipa_vector_normaliser* pNormaliser=normaliser_.ptr();
00091   mipa_block_normaliser* pBlockNormaliser= dynamic_cast<mipa_block_normaliser*>(pNormaliser);
00092   if (pBlockNormaliser)
00093   {
00094       pBlockNormaliser->set_region(2*ni_,2*nj_);
00095       pBlockNormaliser->set_nbins(nA_bins_);
00096   }
00097   //assert(norm_method>=0 && norm_method<=1);
00098   //norm_method_ = norm_method;
00099 }
00100 
00101 //: Relative size of region used for estimating overlap
00102 //  If 0.5, then overlap requires pt inside central 50% of region.
00103 void mfpf_hog_box_finder::set_overlap_f(double f)
00104 {
00105   overlap_f_=f;
00106 }
00107 
00108 
00109 //: Radius of circle containing modelled region
00110 double mfpf_hog_box_finder::radius() const
00111 {
00112   // Compute distance to each corner
00113   double wx = ni_-1;
00114   double x2 = vcl_max(ref_x_*ref_x_,(ref_x_-wx)*(ref_x_-wx));
00115   double wy = nj_-1;
00116   double y2 = vcl_max(ref_y_*ref_y_,(ref_y_-wy)*(ref_y_-wy));
00117   double r2 = x2+y2;
00118   if (r2<=1) return 1.0;
00119   return nc_*vcl_sqrt(r2);
00120 }
00121 
00122 
00123 //: Evaluate match at p, using u to define scale and orientation
00124 double mfpf_hog_box_finder::evaluate(const vimt_image_2d_of<float>& image,
00125                                      const vgl_point_2d<double>& p,
00126                                      const vgl_vector_2d<double>& u)
00127 {
00128   vgl_vector_2d<double> u1=step_size_*u;
00129   vgl_vector_2d<double> v1(-u1.y(),u1.x());
00130 
00131   assert(image.image().nplanes()==1);
00132 
00133   // Set up sample area with 1 unit border
00134   unsigned sni = 2+2*nc_*ni_;
00135   unsigned snj = 2+2*nc_*nj_;
00136   vil_image_view<float> sample(sni,snj);
00137 
00138   const vgl_point_2d<double> p0 = p-(1+nc_*ref_x_)*u1-(1+nc_*ref_y_)*v1;
00139 
00140   const vimt_transform_2d& s_w2i = image.world2im();
00141   vgl_point_2d<double> im_p0 = s_w2i(p0);
00142   vgl_vector_2d<double> im_u = s_w2i.delta(p0, u1);
00143   vgl_vector_2d<double> im_v = s_w2i.delta(p0, v1);
00144 
00145   vil_resample_bilin(image.image(),sample,
00146                      im_p0.x(),im_p0.y(),  im_u.x(),im_u.y(),
00147                      im_v.x(),im_v.y(),
00148                      sni,snj);
00149 
00150   vil_image_view<float> histo_im;
00151   mipa_orientation_histogram(sample,histo_im,nA_bins_,nc_,full360_);
00152 
00153   vnl_vector<double> v;
00154   mipa_sample_histo_boxes_3L(histo_im,0,0,v,ni_,nj_);
00155 
00156   normaliser_->normalise(v);
00157   //if (norm_method_==1) mfpf_norm_histo_vec(v,nA_bins_);
00158 
00159 
00160   return cost().evaluate(v);
00161 }
00162 
00163 //: Evaluate match at in a region around p
00164 // Returns a quality of fit at a set of positions.
00165 // response image (whose size and transform is set inside the
00166 // function), indicates the points at which the function was
00167 // evaluated.  response(i,j) is the fit at the point
00168 // response.world2im().inverse()(i,j).  The world2im() transformation
00169 // may be affine.
00170 void mfpf_hog_box_finder::evaluate_region(
00171                         const vimt_image_2d_of<float>& image,
00172                         const vgl_point_2d<double>& p,
00173                         const vgl_vector_2d<double>& u,
00174                         vimt_image_2d_of<double>& response)
00175 {
00176   // Note: search_ni/nj defined in units of u
00177   // However, search occurs in units of nc*u due to histogram pooling.
00178   // So work out how many steps that is, rounding up
00179   unsigned sni = (nc_+search_ni_-1)/nc_;
00180   unsigned snj = (nc_+search_nj_-1)/nc_;
00181 
00182   // Total size of region to sample
00183   int nsi = 2*nc_*(sni + ni_)+2;
00184   int nsj = 2*nc_*(snj + nj_)+2;
00185 
00186   assert(image.image().nplanes()==1);
00187   // Set up sample area with 1 unit border
00188   vil_image_view<float> sample(2+nsi,2+nsj);
00189   vgl_vector_2d<double> u1=step_size_*u;
00190   vgl_vector_2d<double> v1(-u1.y(),u1.x());
00191   const vgl_point_2d<double> p0 = p-(1+nc_*(sni+ref_x_))*u1
00192                                    -(1+nc_*(snj+ref_y_))*v1;
00193 
00194   const vimt_transform_2d& s_w2i = image.world2im();
00195   vgl_point_2d<double> im_p0 = s_w2i(p0);
00196   vgl_vector_2d<double> im_u = s_w2i.delta(p0, u1);
00197   vgl_vector_2d<double> im_v = s_w2i.delta(p0, v1);
00198 
00199   // Sample region of interest
00200   vil_resample_bilin(image.image(),sample,
00201                      im_p0.x(),im_p0.y(),  im_u.x(),im_u.y(),
00202                      im_v.x(),im_v.y(),
00203                      nsi,nsj);
00204 
00205   // Compute image of histograms (each cell is pool of nc x nc)
00206   vil_image_view<float> histo_im;
00207   mipa_orientation_histogram(sample,histo_im,nA_bins_,nc_,full360_);
00208 
00209   vnl_vector<double> v;
00210 
00211   int ni=1+2*sni;
00212   int nj=1+2*snj;
00213   response.image().set_size(ni,nj);
00214   double* r = response.image().top_left_ptr();
00215   vcl_ptrdiff_t r_jstep = response.image().jstep();
00216 
00217   for (unsigned j=0;j<(unsigned)nj;++j,r+=r_jstep)
00218   {
00219     for (int i=0;i<ni;++i)
00220     {
00221       mipa_sample_histo_boxes_3L(histo_im,i,j,v,ni_,nj_);
00222       //if (norm_method_==1) mfpf_norm_histo_vec(v,nA_bins_);
00223       normaliser_->normalise(v);
00224       r[i] = cost().evaluate(v);
00225       if (vnl_math_isnan(r[i]))
00226       {
00227         vcl_cerr<<is_a()<<"::evaluate_region: Response is NaN.\n"
00228                 <<*this<<'\n'
00229                 <<"i,j="<<i<<','<<j<<'\n'
00230                 <<"v.sum()="<<v.sum()<<'\n';
00231         vcl_abort();
00232       }
00233     }
00234   }
00235 
00236   // Set up transformation parameters
00237 
00238   // Point (i,j) in resp corresponds to p1+nc.i.u+nc.j.v,
00239   // an affine transformation for image to world
00240   const vgl_point_2d<double> p1 = p-nc_*sni*u1-nc_*snj*v1;
00241 
00242   vimt_transform_2d i2w;
00243   i2w.set_similarity(vgl_point_2d<double>(nc_*u1.x(),nc_*u1.y()),p1);
00244   response.set_world2im(i2w.inverse());
00245 }
00246 
00247 //: Search given image around p, using u to define scale and orientation
00248 //  On exit, new_p and new_u define position, scale and orientation of
00249 //  the best nearby match.  Returns a quality of fit measure at that
00250 //  point (the smaller the better).
00251 double mfpf_hog_box_finder::search_one_pose(const vimt_image_2d_of<float>& image,
00252                                             const vgl_point_2d<double>& p,
00253                                             const vgl_vector_2d<double>& u,
00254                                             vgl_point_2d<double>& new_p)
00255 {
00256   // Note: search_ni/nj defined in units of u
00257   // However, search occurs in units of nc*u due to histogram pooling.
00258   // So work out how many steps that is, rounding up
00259   unsigned sni = (nc_+search_ni_-1)/nc_;
00260   unsigned snj = (nc_+search_nj_-1)/nc_;
00261 
00262   // Total size of region to sample
00263   int nsi = 2*nc_*(sni + ni_)+2;
00264   int nsj = 2*nc_*(snj + nj_)+2;
00265 
00266   assert(image.image().nplanes()==1);
00267   // Set up sample area with 1 unit border
00268   vil_image_view<float> sample(2+nsi,2+nsj);
00269   vgl_vector_2d<double> u1=step_size_*u;
00270   vgl_vector_2d<double> v1(-u1.y(),u1.x());
00271   const vgl_point_2d<double> p0 = p-(1+nc_*(sni+ref_x_))*u1
00272                                    -(1+nc_*(snj+ref_y_))*v1;
00273 
00274   const vimt_transform_2d& s_w2i = image.world2im();
00275   vgl_point_2d<double> im_p0 = s_w2i(p0);
00276   vgl_vector_2d<double> im_u = s_w2i.delta(p0, u1);
00277   vgl_vector_2d<double> im_v = s_w2i.delta(p0, v1);
00278 
00279   // Sample region of interest
00280   vil_resample_bilin(image.image(), sample,
00281                      im_p0.x(),im_p0.y(), im_u.x(),im_u.y(), im_v.x(),im_v.y(),
00282                      nsi, nsj);
00283 
00284   // Compute image of histograms (each cell is pool of nc x nc)
00285   vil_image_view<float> histo_im;
00286   mipa_orientation_histogram(sample,histo_im,nA_bins_,nc_,full360_);
00287 
00288   vnl_vector<double> v;
00289 
00290   unsigned ni=1+2*sni;
00291   unsigned nj=1+2*snj;
00292 
00293   double best_r=9.99e9;
00294   int best_i=0,best_j=0;
00295   for (unsigned j=0;j<nj;++j)
00296   {
00297     for (unsigned int i=0;i<ni;++i)
00298     {
00299       mipa_sample_histo_boxes_3L(histo_im,i,j,v,ni_,nj_);
00300       //if (norm_method_==1) mfpf_norm_histo_vec(v,nA_bins_);
00301       normaliser_->normalise(v);
00302       double r = cost().evaluate(v);
00303       if (r<best_r) { best_r=r; best_i=i; best_j=j; }
00304     }
00305   }
00306 
00307   // Compute position of best point
00308   new_p = p+nc_*(best_i-sni)*u1+nc_*(best_j-snj)*v1;
00309   return best_r;
00310 }
00311 
00312 // Returns true if p is inside region at given pose
00313 bool mfpf_hog_box_finder::is_inside(const mfpf_pose& pose,
00314                                     const vgl_point_2d<double>& p,
00315                                     double f) const
00316 {
00317   // Set transform model frame -> World
00318   vimt_transform_2d t1;
00319   t1.set_similarity(2*nc_*step_size()*pose.u(),pose.p());
00320   // Compute position of p in model frame
00321   vgl_point_2d<double> q=t1.inverse()(p);
00322   q.x()/=f; q.y()/=f;  // To check that q in the central fraction f
00323   q.x()+=ref_x_;
00324   if (q.x()<0 || q.x()>(ni_-1)) return false;
00325   q.y()+=ref_y_;
00326   if (q.y()<0 || q.y()>(nj_-1)) return false;
00327   return true;
00328 }
00329 
00330 //: Return true if modelled regions at pose1 and pose2 overlap
00331 //  Checks if reference point of one is inside region of other
00332 bool mfpf_hog_box_finder::overlap(const mfpf_pose& pose1,
00333                                   const mfpf_pose& pose2) const
00334 {
00335   if (is_inside(pose1,pose2.p(),overlap_f_)) return true;
00336   if (is_inside(pose2,pose1.p(),overlap_f_)) return true;
00337   return false;
00338 }
00339 
00340 //: Generate points in ref frame that represent boundary
00341 //  Points of a contour around the shape.
00342 //  Used for display purposes.
00343 void mfpf_hog_box_finder::get_outline(vcl_vector<vgl_point_2d<double> >& pts) const
00344 {
00345   double s=2*nc_;
00346   pts.resize(7);
00347   vgl_vector_2d<double> r(s*ref_x_,s*ref_y_);
00348   pts[0]=vgl_point_2d<double>(0,s*nj_)-r;
00349   pts[1]=vgl_point_2d<double>(0,0);
00350   pts[2]=vgl_point_2d<double>(s*ni_,s*nj_)-r;
00351   pts[3]=vgl_point_2d<double>(0,s*nj_)-r;
00352   pts[4]=vgl_point_2d<double>(0,0)-r;
00353   pts[5]=vgl_point_2d<double>(s*ni_,0)-r;
00354   pts[6]=vgl_point_2d<double>(s*ni_,s*nj_)-r;
00355 }
00356 
00357 
00358 //=======================================================================
00359 // Method: is_a
00360 //=======================================================================
00361 
00362 vcl_string mfpf_hog_box_finder::is_a() const
00363 {
00364   return vcl_string("mfpf_hog_box_finder");
00365 }
00366 
00367 //: Create a copy on the heap and return base class pointer
00368 mfpf_point_finder* mfpf_hog_box_finder::clone() const
00369 {
00370   return new mfpf_hog_box_finder(*this);
00371 }
00372 
00373 //=======================================================================
00374 // Method: print
00375 //=======================================================================
00376 
00377 void mfpf_hog_box_finder::print_summary(vcl_ostream& os) const
00378 {
00379   os << "{ "<<'\n';
00380   vsl_indent_inc(os);
00381   os << vsl_indent()<<"size: " << ni_ << 'x' << nj_
00382      << " nc: " << nc_ <<" nA_bins: "<<nA_bins_
00383      << " ref_pt: (" << ref_x_ << ',' << ref_y_ << ')' <<'\n';
00384   if (full360_) os << vsl_indent()<<"Angle range: 0-360"<<'\n';
00385   else          os << vsl_indent()<<"Angle range: 0-180"<<'\n';
00386 #if 0
00387   if (norm_method_==0) os << vsl_indent()<<"norm: none"<<'\n';
00388   else                 os << vsl_indent()<<"norm: linear"<<'\n';
00389 #endif
00390 
00391   vcl_cout<<"The HOG's normaliser is:"<<vcl_endl;
00392   normaliser_->print_summary(os);
00393 
00394   os << vsl_indent()<< "cost: ";
00395   if (cost_.ptr()==0) os << "--"<<vcl_endl; else os << cost_<<'\n';
00396   os << vsl_indent();
00397   mfpf_point_finder::print_summary(os);
00398   os << '\n'
00399      << vsl_indent()<<"overlap_f: "<<overlap_f_<<'\n';
00400   vsl_indent_dec(os);
00401   os << vsl_indent()<<'}';
00402 }
00403 
00404 short mfpf_hog_box_finder::version_no() const
00405 {
00406   return 2;
00407 }
00408 
00409 
00410 void mfpf_hog_box_finder::b_write(vsl_b_ostream& bfs) const
00411 {
00412   vsl_b_write(bfs,version_no());
00413   mfpf_point_finder::b_write(bfs);  // Save base class
00414   vsl_b_write(bfs,nc_);
00415   vsl_b_write(bfs,ni_);
00416   vsl_b_write(bfs,nj_);
00417   vsl_b_write(bfs,nA_bins_);
00418   vsl_b_write(bfs,full360_);
00419   vsl_b_write(bfs,cost_);
00420   vsl_b_write(bfs,ref_x_);
00421   vsl_b_write(bfs,ref_y_);
00422   vsl_b_write(bfs,normaliser_);
00423   vsl_b_write(bfs,overlap_f_);
00424 }
00425 
00426 //=======================================================================
00427 // Method: load
00428 //=======================================================================
00429 
00430 void mfpf_hog_box_finder::b_read(vsl_b_istream& bfs)
00431 {
00432   if (!bfs) return;
00433   short version;
00434   vsl_b_read(bfs,version);
00435   switch (version)
00436   {
00437     case (1):
00438     case (2):
00439       mfpf_point_finder::b_read(bfs);  // Load in base class
00440       vsl_b_read(bfs,nc_);
00441       vsl_b_read(bfs,ni_);
00442       vsl_b_read(bfs,nj_);
00443       vsl_b_read(bfs,nA_bins_);
00444       vsl_b_read(bfs,full360_);
00445       vsl_b_read(bfs,cost_);
00446       vsl_b_read(bfs,ref_x_);
00447       vsl_b_read(bfs,ref_y_);
00448       vsl_b_read(bfs,normaliser_);
00449       if (version==1) overlap_f_=1.0;
00450       else            vsl_b_read(bfs,overlap_f_);
00451       break;
00452     default:
00453       vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&)\n"
00454                << "           Unknown version number "<< version << '\n';
00455       bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00456       return;
00457   }
00458 }
00459 
00460 //: Test equality
00461 bool mfpf_hog_box_finder::operator==(const mfpf_hog_box_finder& nc) const
00462 {
00463   if (!base_equality(nc)) return false;
00464   if (nc_!=nc.nc_) return false;
00465   if (ni_!=nc.ni_) return false;
00466   if (nj_!=nc.nj_) return false;
00467   if (normaliser_->is_a()!=nc.normaliser_->is_a()) return false; //bit looser than true equality
00468   if (nA_bins_!=nc.nA_bins_) return false;
00469   if (full360_!=nc.full360_) return false;
00470   if (vcl_fabs(ref_x_-nc.ref_x_)>1e-6) return false;
00471   if (vcl_fabs(ref_y_-nc.ref_y_)>1e-6) return false;
00472   if (vcl_fabs(overlap_f_-nc.overlap_f_)>1e-6) return false;
00473   // Strictly should compare costs
00474   return true;
00475 }
00476 
00477