contrib/oxl/mvl/FMPlanarNonLinFun.cxx
Go to the documentation of this file.
00001 // This is oxl/mvl/FMPlanarNonLinFun.cxx
00002 #ifdef VCL_NEEDS_PRAGMA_INTERFACE
00003 #pragma implementation
00004 #endif
00005 //:
00006 // \file
00007 
00008 #include "FMPlanarNonLinFun.h"
00009 
00010 #include <vcl_cstdlib.h> // for vcl_abort()
00011 #include <vcl_iostream.h>
00012 
00013 #include <vnl/vnl_math.h>
00014 #include <vnl/vnl_matrix.h>
00015 #include <vnl/vnl_transpose.h>
00016 #include <vnl/vnl_matops.h> // use vnl_matlab_print.h for pretty printing
00017 
00018 #include <vnl/vnl_cross_product_matrix.h>
00019 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00020 #include <vnl/algo/vnl_levenberg_marquardt.h>
00021 
00022 #include <vgl/vgl_homg_line_2d.h>
00023 #include <mvl/ImageMetric.h>
00024 #include <mvl/HomgOperator2D.h>
00025 #include <mvl/FMatrixPlanar.h>
00026 
00027 const int FMPlanarNonLinFun_nparams = 9;
00028 
00029 //-----------------------------------------------------------------------------
00030 //: Constructor
00031 //
00032 FMPlanarNonLinFun::FMPlanarNonLinFun(const ImageMetric* image_metric1,
00033                                      const ImageMetric* image_metric2,
00034                                      double /*outlier_distance_squared*/,
00035                                      vcl_vector<vgl_homg_point_2d<double> >& points1,
00036                                      vcl_vector<vgl_homg_point_2d<double> >& points2)
00037 : vnl_least_squares_function(FMPlanarNonLinFun_nparams, points1.size(), no_gradient)
00038 , data_size_(points1.size())
00039 , points1_(points1)
00040 , points2_(points2)
00041 , normalized_(2*data_size_)
00042 , image_metric1_(image_metric1)
00043 , image_metric2_(image_metric2)
00044 {
00045   // Form single array from both points1 and points2
00046   vcl_vector<vgl_homg_point_2d<double> > points(points1);
00047   for (unsigned i = 0; i < points2.size(); ++i)
00048     points.push_back(points2[i]);
00049 
00050   // Condition points
00051   normalized_.normalize(points);
00052 
00053   // Set up contitioning matrices
00054   denorm_matrix_     = normalized_.get_C();
00055   denorm_matrix_inv_ = normalized_.get_C_inverse();
00056 }
00057 
00058 FMPlanarNonLinFun::FMPlanarNonLinFun(const ImageMetric* image_metric1,
00059                                      const ImageMetric* image_metric2,
00060                                      double /*outlier_distance_squared*/,
00061                                      vcl_vector<HomgPoint2D>& points1,
00062                                      vcl_vector<HomgPoint2D>& points2):
00063    vnl_least_squares_function(FMPlanarNonLinFun_nparams, points1.size(), no_gradient),
00064   data_size_(points1.size()),
00065   normalized_(2*data_size_),
00066   image_metric1_(image_metric1),
00067   image_metric2_(image_metric2)
00068 {
00069   for (unsigned i = 0; i < points1.size(); ++i)
00070     points1_.push_back(vgl_homg_point_2d<double>(points1[i].x(),points1[i].y(),points1[i].w()));
00071   for (unsigned i = 0; i < points2.size(); ++i)
00072     points2_.push_back(vgl_homg_point_2d<double>(points2[i].x(),points2[i].y(),points2[i].w()));
00073   // Form single array
00074   vcl_vector<HomgPoint2D> points(points1);
00075   for (unsigned i = 0; i < points2.size(); ++i)
00076     points.push_back(points2[i]);
00077 
00078   // Condition points
00079   normalized_.normalize(points);
00080 
00081   // Set up contitioning matrices
00082   denorm_matrix_     = normalized_.get_C();
00083   denorm_matrix_inv_ = normalized_.get_C_inverse();
00084 }
00085 
00086 //-----------------------------------------------------------------------------
00087 //: Compute the planar F matrix and returns true if successful.
00088 //
00089 bool FMPlanarNonLinFun::compute(FMatrixPlanar* F)
00090 {
00091   // fm_fmatrix_nagmin
00092   vcl_cerr << "FMPlanarNonLinFun: matches = "<<data_size_<<", using "<<FMPlanarNonLinFun_nparams<<" parameters\n";
00093 
00094   /* transform F to well-conditioned frame. */
00095   const vnl_matrix<double>& post = denorm_matrix_inv_.as_ref();
00096   const vnl_matrix<double>& pre  = denorm_matrix_inv_.transpose().as_ref();
00097   FMatrixPlanar norm_F(pre * F->get_matrix() * post);
00098 
00099   /* parameterise it. */
00100   vnl_vector<double> f_params(FMPlanarNonLinFun_nparams);
00101   fmatrix_to_params (norm_F, f_params);
00102 
00103   vnl_levenberg_marquardt lm(*this);
00104   if (!lm.minimize(f_params))
00105        return false;
00106 
00107   vcl_cerr<<"FMPlanarNonLinFun: minimisation start error "
00108           << lm.get_start_error() / vcl_sqrt(double(data_size_))
00109           <<" end error "
00110           << lm.get_end_error() / vcl_sqrt(double(data_size_))
00111           <<vcl_endl;
00112 
00113   norm_F = params_to_fmatrix (f_params);
00114 
00115   F->set(vnl_transpose(denorm_matrix_) * norm_F.get_matrix() * denorm_matrix_);
00116 
00117   vcl_cerr << "fm_fmatrix_nagmin: accepted " << data_size_ << '/' << data_size_
00118            << " rms point-epipolar error " << lm.get_end_error() / vcl_sqrt(double(data_size_))
00119            << vcl_endl;
00120 
00121   return true;
00122 }
00123 
00124 //-----------------------------------------------------------------------------
00125 //: The virtual function from vnl_levenberg_marquardt which returns the RMS epipolar error and a vector of residuals.
00126 //
00127 void FMPlanarNonLinFun::f(vnl_vector<double> const& f_params, vnl_vector<double>& fx)
00128 {
00129      FMatrixPlanar norm_F = params_to_fmatrix(f_params);
00130 
00131      FMatrixPlanar F(vnl_transpose(denorm_matrix_) * norm_F.get_matrix() * denorm_matrix_);
00132 
00133      for (int i = 0; i < data_size_; ++i) {
00134           const vgl_homg_point_2d<double>& p1 = points1_[i];
00135           const vgl_homg_point_2d<double>& p2 = points2_[i];
00136 
00137           vgl_homg_line_2d<double> l12 = F.image2_epipolar_line(p1);
00138           double r1 = image_metric2_.perp_dist_squared(p2, l12);
00139 
00140           vgl_homg_line_2d<double> l21 = F.image1_epipolar_line(p2);
00141           double r2 = image_metric1_.perp_dist_squared(p1, l21);
00142 
00143           fx[i] = vcl_sqrt((r1 + r2) / 2.0);
00144      }
00145      // vcl_cerr << "Err = " << vcl_sqrt (distance_squared / (data_size_ * 2)) << vcl_endl;
00146 
00147 //   return vcl_sqrt (distance_squared / (data_size_ * 2)); // void function cannot return
00148 }
00149 
00150 void FMPlanarNonLinFun::fmatrix_to_params(const FMatrixPlanar& F,
00151                                           vnl_vector<double>& params)
00152 {
00153   if (FMPlanarNonLinFun_nparams == 9)
00154     fmatrix_to_params_awf(F, params);
00155   else
00156     fmatrix_to_params_mna(F, params);
00157 }
00158 
00159 FMatrixPlanar FMPlanarNonLinFun::params_to_fmatrix(const vnl_vector<double>& params)
00160 {
00161   if (FMPlanarNonLinFun_nparams == 9)
00162     return params_to_fmatrix_awf(params);
00163   else
00164     return params_to_fmatrix_mna(params);
00165 }
00166 
00167 //-----------------------------------------------------------------------------
00168 // Private Function: Converts from a 3x3 F matrix to the 6 parameters
00169 // for the planar form. See FMatrixPlanar.init(const vnl_matrix<double>&)
00170 // for algorithm details.
00171 //
00172 void FMPlanarNonLinFun::fmatrix_to_params_mna(const FMatrixPlanar& F,
00173                                               vnl_vector<double>& params)
00174 {
00175   // this converts to [e2]x[l]x[e1] form - see A Zisserman
00176   HomgPoint2D e1,e2;
00177   F.get_epipoles(&e1,&e2);
00178 
00179   vnl_symmetric_eigensystem<double>  symm_eig(F.get_matrix()+F.get_matrix().transpose());
00180 
00181   double eig0 = symm_eig.D(0,0);
00182   double eig1 = symm_eig.D(2,2);
00183 
00184   if (eig0 > 0 || eig1 < 0) {
00185     vcl_cerr << "ERROR in FMPlanarNonLinFun: vnl_symmetric_eigensystem<double>  is unsorted: " << symm_eig.D << vcl_endl;
00186     vcl_abort();
00187   }
00188 
00189   if (vcl_fabs(symm_eig.D(1,1)) > 1e-12)
00190     vcl_cerr << "FMPlanarNonLinFun: WARNING: middle eigenvalue not 0: " << symm_eig.D << vcl_endl;
00191 
00192   vnl_vector<double> v0(symm_eig.get_eigenvector(0));
00193   vnl_vector<double> v1(symm_eig.get_eigenvector(2));
00194 
00195   vnl_double_3 f1 = vcl_sqrt(eig1)*v1 + vcl_sqrt(-eig0)*v0;
00196   vnl_double_3 f2 = vcl_sqrt(eig1)*v1 - vcl_sqrt(-eig0)*v0;
00197 
00198   vnl_double_3 ls;
00199   if (vcl_fabs(HomgOperator2D::dot(e1,f1)/e1.w()) + vcl_fabs(HomgOperator2D::dot(e2,f1)/e2.w()) >
00200       vcl_fabs(HomgOperator2D::dot(e1,f2)/e1.w()) + vcl_fabs(HomgOperator2D::dot(e2,f2)/e2.w()) )
00201     ls = f1;
00202   else
00203     ls = f2;
00204 
00205   ls /= ls.magnitude();
00206 
00207   double ls_thi = vcl_acos(ls[2]);
00208   if (ls_thi < 0) ls_thi += vnl_math::pi;
00209 
00210   double ls_theta;
00211   if (ls[1] >= 0)
00212     ls_theta =  vcl_acos(ls[0]/vcl_sin(ls_thi));
00213   else
00214     ls_theta = -vcl_acos(ls[0]/vcl_sin(ls_thi));
00215 
00216   params[0] = ls_theta;
00217   params[1] = ls_thi;
00218   params[2] = e1.x()/e1.w();
00219   params[3] = e1.y()/e1.w();
00220   params[4] = e2.x()/e2.w();
00221   params[5] = e2.y()/e2.w();
00222 
00223 #ifdef PARANOID
00224   // Check parameterization
00225   {
00226     FMatrixPlanar back = params_to_fmatrix_mna(params);
00227     double norm = vnl_matops::homg_diff(back.get_matrix(), F.get_matrix());
00228     if (norm > 1e-12) {
00229       vcl_cerr << "FMPlanarNonLinFun: WARNING! deparameterization diff = " << norm << vcl_endl
00230                << "b = [" << back << "];\n"
00231                << "n = [" << F << "];\n";
00232     }
00233   }
00234 #endif
00235 }
00236 
00237 //-----------------------------------------------------------------------------
00238 // Private Function: Construct the fundamental matrix from the 6 parameters.
00239 // See FMatrixPlanar for more details.
00240 //
00241 FMatrixPlanar FMPlanarNonLinFun::params_to_fmatrix_mna(const vnl_vector<double>& params)
00242 {
00243      double ls1 = vcl_cos(params[0])*vcl_sin(params[1]);
00244      double ls2 = vcl_sin(params[0])*vcl_sin(params[1]);
00245      double ls3 = vcl_cos(params[1]);
00246 
00247      double list1[9] = {0,              -1.0,           params[5],
00248                         1,              0,              -params[4],
00249                         -params[5],     params[4],      0};
00250      double list2[9] = {0,-ls3,ls2,ls3,0,-ls1,-ls2,ls1,0};
00251      double list3[9] = {0,-1.0,params[3],1,0,-params[2],-params[3],params[2],0};
00252 
00253      vnl_matrix<double> mat1(3,3,9,list1),mat2(3,3,9,list2),mat3(3,3,9,list3);
00254 
00255      vnl_matrix<double> fmat = mat1*mat2*mat3;
00256 
00257      fmat /= fmat.fro_norm();
00258 
00259      return FMatrixPlanar(fmat);
00260 }
00261 
00262 void FMPlanarNonLinFun::fmatrix_to_params_awf(const FMatrixPlanar& F, vnl_vector<double>& params)
00263 {
00264   // this converts to [e2]x[l]x[e1] form - see A Zisserman
00265   HomgPoint2D e1,e2;
00266   F.get_epipoles(&e1,&e2);
00267 
00268   vnl_symmetric_eigensystem<double>  symm_eig(F.get_matrix()+F.get_matrix().transpose());
00269 
00270   double eig0 = symm_eig.D(0,0);
00271   double eig1 = symm_eig.D(2,2);
00272 
00273   if (eig0 > 0 || eig1 < 0) {
00274     vcl_cerr << "ERROR in FMPlanarNonLinFun: vnl_symmetric_eigensystem<double>  is unsorted: " << symm_eig.D << vcl_endl;
00275     vcl_abort();
00276   }
00277 
00278   if (vcl_fabs(symm_eig.D(1,1)) > 1e-12)
00279     vcl_cerr << "FMPlanarNonLinFun: WARNING: middle eigenvalue not 0: " << symm_eig.D << vcl_endl;
00280 
00281   vnl_vector<double> v0(symm_eig.get_eigenvector(0));
00282   vnl_vector<double> v1(symm_eig.get_eigenvector(2));
00283 
00284   vnl_double_3 f1 = vcl_sqrt(eig1)*v1 + vcl_sqrt(-eig0)*v0;
00285   vnl_double_3 f2 = vcl_sqrt(eig1)*v1 - vcl_sqrt(-eig0)*v0;
00286 
00287   vnl_double_3 ls;
00288   if (vcl_fabs(HomgOperator2D::dot(e1,f1)/e1.w()) + vcl_fabs(HomgOperator2D::dot(e2,f1)/e2.w()) >
00289       vcl_fabs(HomgOperator2D::dot(e1,f2)/e1.w()) + vcl_fabs(HomgOperator2D::dot(e2,f2)/e2.w()) )
00290     ls = f1;
00291   else
00292     ls = f2;
00293 
00294   ls /= ls.magnitude();
00295   double mag1 = e1.get_vector().magnitude();
00296   double mag2 = e2.get_vector().magnitude();
00297 
00298   params[0] = ls[0];
00299   params[1] = ls[1];
00300   params[2] = ls[2];
00301   params[3] = e1.x()/mag1;
00302   params[4] = e1.y()/mag1;
00303   params[5] = e1.w()/mag1;
00304   params[6] = e2.x()/mag2;
00305   params[7] = e2.y()/mag2;
00306   params[8] = e2.w()/mag2;
00307 
00308   // Check parameterization
00309   {
00310     FMatrixPlanar back = params_to_fmatrix_awf(params);
00311     double norm = vnl_matops::homg_diff(back.get_matrix().as_ref(), F.get_matrix().as_ref());
00312     if (norm > 1e-12) {
00313       vcl_cerr << "FMPlanarNonLinFun: WARNING! deparameterization diff = " << norm << vcl_endl
00314                << "b = [" << back << "];\n"
00315                << "n = [" << F << "];\n";
00316     }
00317   }
00318 }
00319 
00320 //-----------------------------------------------------------------------------
00321 // Private Function: Construct the fundamental matrix from the 6 parameters.
00322 // See FMatrixPlanar for more details.
00323 //
00324 FMatrixPlanar FMPlanarNonLinFun::params_to_fmatrix_awf(const vnl_vector<double>& params)
00325 {
00326   const double* v = params.data_block();
00327   vnl_cross_product_matrix L(v);
00328   vnl_cross_product_matrix E1(v+3);
00329   vnl_cross_product_matrix E2(v+6);
00330 
00331   vnl_matrix<double> fmat = E2.as_ref() * L * E1;
00332 
00333   fmat /= fmat.fro_norm();
00334 
00335   return FMatrixPlanar(fmat);
00336 }