contrib/mul/msm/msm_zoom_aligner.cxx
Go to the documentation of this file.
00001 //:
00002 // \file
00003 // \author Tim Cootes
00004 // \brief Calculate and apply translation + scale transform
00005 
00006 #include "msm_zoom_aligner.h"
00007 #include <vnl/vnl_vector.h>
00008 #include <vsl/vsl_binary_loader.h>
00009 #include <vcl_cstddef.h> // for std::size_t
00010 #include <vcl_cassert.h>
00011 
00012 //=======================================================================
00013 
00014   //: Compute parameters for inverse transformation
00015 vnl_vector<double> msm_zoom_aligner::inverse(const vnl_vector<double>& t) const
00016 {
00017   vnl_vector<double> q(3);
00018   double s=vcl_exp(t[0]);
00019   q[0]=-t[0];
00020   q[1]=-t[1]/s;
00021   q[2]=-t[2]/s;
00022   return q;
00023 }
00024 
00025   //: Apply the transformation to the given points
00026 void msm_zoom_aligner::apply_transform(const msm_points& points,
00027                                        const vnl_vector<double>& trans,
00028                                        msm_points& new_points) const
00029 {
00030   new_points=points;
00031   new_points.transform_by(vcl_exp(trans[0]),0.0,trans[1],trans[2]);
00032 }
00033 
00034 //: Return scaling applied by the transform with given parameters.
00035 double msm_zoom_aligner::scale(const vnl_vector<double>& trans) const
00036 {
00037   assert(trans.size()==3);
00038   return vcl_exp(trans[0]);
00039 }
00040 
00041 
00042   //: Estimate parameter which best map ref_points to points2
00043   //  Minimises ||points2-T(ref_points)||^2.
00044   //  Takes advantage of assumed properties of ref_points (eg CoG=origin,
00045   //  unit size etc) to perform efficiently.
00046   //
00047   //  When used with a shape model of form ref_points+Pb, where the modes P
00048   //  have certain orthogonality properties with respect to the ref shape,
00049   //  this can give the optimal transformation into a tangent plane, independent
00050   //  of the current parameters.  In this case a one-shot method can be used
00051   //  to compute the optimal shape and pose parameters, rather than an iterative
00052   //  method which is required where the orthogonality properties do not hold,
00053   //  or where weights are considered.
00054 void msm_zoom_aligner::calc_transform_from_ref(const msm_points& ref_pts,
00055                                                const msm_points& pts2,
00056                                                vnl_vector<double>& trans) const
00057 {
00058   vgl_point_2d<double> cog2 = pts2.cog();
00059   const double* p1 = ref_pts.vector().begin();
00060   const double* p2 = pts2.vector().begin();
00061   const double* p1_end = ref_pts.vector().end();
00062 
00063   double dot_sum=0.0;
00064 
00065   for (;p1!=p1_end;p1+=2,p2+=2)
00066   {
00067     double dpx = p1[0];
00068     double dpy = p1[1];
00069     double dtx = p2[0]-cog2.x();
00070     double dty = p2[1]-cog2.y();
00071 
00072     dot_sum += dpx*dtx + dpy*dty;
00073   }
00074 
00075   trans.set_size(3);
00076   trans[0] = vcl_log(dot_sum);
00077   trans[1] = cog2.x();
00078   trans[2] = cog2.y();
00079 }
00080 
00081   //: Estimate parameter which best map points1 to points2
00082   //  Minimises ||points2-T(points1)||^2
00083 void msm_zoom_aligner::calc_transform(const msm_points& pts1,
00084                                       const msm_points& pts2,
00085                                       vnl_vector<double>& trans) const
00086 {  vgl_point_2d<double> cog1 = pts1.cog();
00087   vgl_point_2d<double> cog2 = pts2.cog();
00088   const double* p1 = pts1.vector().begin();
00089   const double* p2 = pts2.vector().begin();
00090   const double* p1_end = pts1.vector().end();
00091 
00092   double x2_sum=0.0;
00093   double dot_sum=0.0;
00094 
00095   for (;p1!=p1_end;p1+=2,p2+=2)
00096   {
00097     double dpx = p1[0]-cog1.x();
00098     double dpy = p1[1]-cog1.y();
00099     double dtx = p2[0]-cog2.x();
00100     double dty = p2[1]-cog2.y();
00101 
00102     x2_sum += dpx*dpx + dpy*dpy;
00103     dot_sum += dpx*dtx + dpy*dty;
00104   }
00105 
00106   trans.set_size(3);
00107   double s = dot_sum/x2_sum;
00108   trans[0] = vcl_log(s);
00109   trans[1] = cog2.x() - s*cog1.x();
00110   trans[2] = cog2.y() - s*cog1.y();
00111 }
00112 
00113 // Compute weighted CoG
00114 inline vgl_point_2d<double> msm_wtd_cog(const msm_points& pts,
00115                                         const vnl_vector<double>& wts)
00116 {
00117   const double* v=pts.vector().data_block();
00118   const double* w=wts.data_block();
00119   unsigned int n=pts.size();
00120   const double* end_v=v+2*n;
00121   double cx=0.0,cy=0.0,w_sum=0.0;
00122   for (;v!=end_v;v+=2,++w)
00123   { cx+=w[0]*v[0]; cy+=w[0]*v[1]; w_sum+=w[0]; }
00124 
00125   if (w_sum>0) { cx/=w_sum; cy/=w_sum; }
00126   return vgl_point_2d<double>(cx,cy);
00127 }
00128 
00129   //: Estimate parameters which map points1 to points2 allowing for weights
00130   //  Minimises sum of weighted squares error in frame of pts2,
00131   //  ie sum w_i * ||p2_i - T(p1_i)||
00132 void msm_zoom_aligner::calc_transform_wt(const msm_points& pts1,
00133                                          const msm_points& pts2,
00134                                          const vnl_vector<double>& wts,
00135                                          vnl_vector<double>& trans) const
00136 {
00137   vgl_point_2d<double> cog1 = msm_wtd_cog(pts1,wts);
00138   vgl_point_2d<double> cog2 = msm_wtd_cog(pts2,wts);
00139   const double* p1 = pts1.vector().begin();
00140   const double* p2 = pts2.vector().begin();
00141   const double* w=wts.data_block();
00142   assert(wts.size()==pts1.size());
00143   const double* p1_end = pts1.vector().end();
00144 
00145   double x2_sum=0.0;
00146   double dot_sum=0.0;
00147 
00148   for (;p1!=p1_end;p1+=2,p2+=2,++w)
00149   {
00150     double dpx = p1[0]-cog1.x();
00151     double dpy = p1[1]-cog1.y();
00152     double dtx = p2[0]-cog2.x();
00153     double dty = p2[1]-cog2.y();
00154 
00155     x2_sum  += w[0]*(dpx*dpx + dpy*dpy);
00156     dot_sum += w[0]*(dpx*dtx + dpy*dty);
00157   }
00158 
00159   trans.set_size(3);
00160   double s = dot_sum/x2_sum;
00161   trans[0] = vcl_log(s);
00162   trans[1] = cog2.x() - s*cog1.x();
00163   trans[2] = cog2.y() - s*cog1.y();
00164 }
00165 
00166   //: Estimate parameters which map points allowing for anisotropic wts
00167 //  Errors on point i are weighted by wt_mat[i] in pts2 frame.
00168 //  ie error is sum (p2_i-T(p1_i)'*wt_mat[i]*(p2_i-T(p1_i)
00169 void msm_zoom_aligner::calc_transform_wt_mat(const msm_points& pts1,
00170                                              const msm_points& pts2,
00171                                              const vcl_vector<msm_wt_mat_2d>& wt_mat,
00172                                              vnl_vector<double>& trans) const
00173 {
00174   assert(pts2.size()==pts1.size());
00175   assert(wt_mat.size()==pts1.size());
00176   // Compute weighted CoGs
00177   msm_wt_mat_2d w_sum(0,0,0);
00178   double x1=0.0,y1=0.0;
00179   double x2=0.0,y2=0.0;
00180   const double* p1 = pts1.vector().begin();
00181   const double* p2 = pts2.vector().begin();
00182   const double* p1_end = pts1.vector().end();
00183   vcl_vector<msm_wt_mat_2d>::const_iterator w=wt_mat.begin();
00184   for (;p1!=p1_end;p1+=2,p2+=2,++w)
00185   {
00186     double wa=w->m11(), wb=w->m12(), wc=w->m22();
00187     w_sum += (*w);
00188     x1 += wa*p1[0]+wb*p1[1];
00189     y1 += wb*p1[0]+wc*p1[1];
00190     x2 += wa*p2[0]+wb*p2[1];
00191     y2 += wb*p2[0]+wc*p2[1];
00192   }
00193   msm_wt_mat_2d w_inv = w_sum.inverse();
00194   double v11=w_inv.m11(), v12=w_inv.m12(), v22=w_inv.m22();
00195   vgl_point_2d<double> cog1(v11*x1+v12*y1,v12*x1+v22*y1);
00196   vgl_point_2d<double> cog2(v11*x2+v12*y2,v12*x2+v22*y2);
00197 
00198   // Need to compute the a,b terms relative to these centres.
00199 
00200   double sum1=0,txx_sum = 0;
00201 
00202   p1 = pts1.vector().begin();
00203   p2 = pts2.vector().begin();
00204   w=wt_mat.begin();
00205   for (;p1!=p1_end;p1+=2,p2+=2,++w)
00206   {
00207     double dpx = p1[0]-cog1.x();
00208     double dpy = p1[1]-cog1.y();
00209     double dtx = p2[0]-cog2.x();
00210     double dty = p2[1]-cog2.y();
00211 
00212     double wa=w->m11(), wb=w->m12(), wc=w->m22();
00213 
00214     double wx = wa*dpx + wb*dpy;
00215     double wy = wb*dpx + wc*dpy;
00216     sum1 += dpx*wx + dpy*wy;
00217     txx_sum += dtx*wx + dty*wy;
00218   }
00219 
00220   trans.set_size(3);
00221   double s = txx_sum/sum1;
00222   trans[0] = vcl_log(s);
00223   trans[1] = cog2.x() - s*cog1.x();
00224   trans[2] = cog2.y() - s*cog1.y();
00225 }
00226 
00227   //: Apply transform to weight matrices (ie ignore translation component)
00228 void msm_zoom_aligner::transform_wt_mat(const vcl_vector<msm_wt_mat_2d>& wt_mat,
00229                                         const vnl_vector<double>& trans,
00230                                         vcl_vector<msm_wt_mat_2d>& new_wt_mat) const
00231 {
00232   double s=vcl_exp(trans[0]);
00233   new_wt_mat.resize(wt_mat.size());
00234   for (unsigned i=0;i<wt_mat.size();++i)
00235     new_wt_mat[i]=wt_mat[i].transform_by(s,0);
00236 }
00237 
00238 //: Returns params of pose such that pose(x) = pose1(pose2(x))
00239 vnl_vector<double> msm_zoom_aligner::compose(
00240                          const vnl_vector<double>& pose1,
00241                          const vnl_vector<double>& pose2) const
00242 {
00243   double s1=vcl_exp(pose1[0]);
00244 
00245   vnl_vector<double> p(3);
00246   p[0]= pose1[0]+pose2[0];
00247   p[1]= s1*pose2[1]+pose1[1];
00248   p[2]= s1*pose2[2]+pose1[2];
00249   return p;
00250 }
00251 
00252 //: Apply transform to generate points in some reference frame
00253 //  For instance, depending on transform, may translate so the
00254 //  centre of gravity is at the origin and scale to a unit size.
00255 void msm_zoom_aligner::normalise_shape(msm_points& points) const
00256 {
00257   vgl_point_2d<double> cog = points.cog();
00258   points.translate_by(-cog.x(),-cog.y());
00259   double L = points.vector().magnitude();
00260   if (L>1e-6) points.scale_by(1.0/L);
00261 }
00262 
00263 
00264 //: Find poses which align a set of points
00265 //  On exit ref_mean_shape is the mean shape in the reference
00266 //  frame, pose_to_ref[i] maps points[i] into the reference
00267 //  frame (ie pose is the mapping from the reference frame to
00268 //  the target frames).
00269 // \param average_pose Average mapping from ref to target frame
00270 void msm_zoom_aligner::align_set(const vcl_vector<msm_points>& points,
00271                                  msm_points& ref_mean_shape,
00272                                  vcl_vector<vnl_vector<double> >& pose_to_ref,
00273                                  vnl_vector<double>& average_pose) const
00274 {
00275   vcl_size_t n_shapes = points.size();
00276   assert(n_shapes>0);
00277   pose_to_ref.resize(n_shapes);
00278 
00279   // Use first shape as initial reference
00280   ref_mean_shape = points[0];
00281   normalise_shape(ref_mean_shape);
00282 
00283   // Record normalised first shape to define initial orientation
00284   msm_points base_shape = ref_mean_shape;
00285 
00286   vnl_vector<double> pose_from_ref;
00287   msm_points new_mean=ref_mean_shape;
00288   average_pose.set_size(3);
00289 
00290   double change=1.0;
00291 
00292   unsigned n_its=0;
00293 
00294   while (change>1e-6 && n_its<10)
00295   {
00296     ref_mean_shape = new_mean;
00297     normalise_shape(ref_mean_shape);
00298 
00299     average_pose.fill(0);
00300 
00301     for (unsigned i=0;i<n_shapes;++i)
00302     {
00303       calc_transform_from_ref(ref_mean_shape,points[i],pose_from_ref);
00304       pose_to_ref[i]=inverse(pose_from_ref);
00305       average_pose+=pose_from_ref;
00306     }
00307 
00308     mean_of_transformed(points,pose_to_ref,new_mean);
00309 
00310     // new_mean should be in the subspace orthogonal to the mean
00311 
00312     change = (new_mean.vector()-ref_mean_shape.vector()).rms();
00313     n_its++;
00314   }
00315 
00316   average_pose/=n_shapes;
00317 }
00318 
00319 //=======================================================================
00320 
00321 vcl_string msm_zoom_aligner::is_a() const
00322 {
00323   return vcl_string("msm_zoom_aligner");
00324 }
00325 
00326 //: Create a copy on the heap and return base class pointer
00327 msm_aligner* msm_zoom_aligner::clone() const
00328 {
00329   return new msm_zoom_aligner(*this);
00330 }
00331 
00332