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