core/vpgl/algo/vpgl_rational_adjust_multipt.cxx
Go to the documentation of this file.
00001 #include "vpgl_rational_adjust_multipt.h"
00002 //:
00003 // \file
00004 #include <vcl_cmath.h>
00005 #include <vcl_cassert.h>
00006 #include <vgl/vgl_point_3d.h>
00007 #include <vnl/vnl_numeric_traits.h>
00008 #include <vnl/algo/vnl_levenberg_marquardt.h>
00009 #include <vpgl/algo/vpgl_backproject.h>
00010 #include <vpgl/algo/vpgl_ray_intersect.h>
00011 #include <vpgl/algo/vpgl_rational_adjust_onept.h>
00012 
00013 #include <vcl_limits.h>
00014 
00015 double compute_projection_error(vcl_vector<vpgl_rational_camera<double> > const& cams,
00016                                 vcl_vector<vgl_point_2d<double> > const& corrs, vgl_point_3d<double>& intersection)
00017 {
00018   vcl_vector<vpgl_rational_camera<double> >::const_iterator cit = cams.begin();
00019   vcl_vector<vgl_point_2d<double> >::const_iterator rit = corrs.begin();
00020   double error = 0.0;
00021   for (; cit!=cams.end() && rit!=corrs.end(); ++cit, ++rit)
00022   {
00023     vgl_point_2d<double> uvp = (*cit).project(intersection);
00024     vgl_point_2d<double> uv = *rit;
00025     double err = vcl_sqrt(vcl_pow(uv.x()-uvp.x(), 2.0) + vcl_pow(uv.y()-uvp.y(), 2));
00026     error += err;
00027   }
00028   return error;
00029 }
00030 
00031 double error_corr(vpgl_rational_camera<double> const& cam, vgl_point_2d<double> const& corr, vgl_point_3d<double> const& intersection)
00032 {
00033   vgl_point_2d<double> uvp = cam.project(intersection);
00034   return vcl_sqrt(vcl_pow(corr.x()-uvp.x(), 2.0) + vcl_pow(corr.y()-uvp.y(), 2));
00035 }
00036 
00037 //: assumes an initial estimate for intersection values, only refines the intersection and computes a re-projection error
00038 double re_projection_error(vcl_vector<vpgl_rational_camera<double> > const& cams,
00039                            vcl_vector<vcl_vector<vgl_point_2d<double> > > const& corrs, // for each 3d corr (outer vector), 2d locations for each cam (inner vector)
00040                            vcl_vector<vgl_point_3d<double> > const& intersections,
00041                            vcl_vector<vgl_point_3d<double> >& finals)
00042 {
00043   double error = 100000.0;
00044 
00045   finals.clear();
00046   for (unsigned int i = 0; i < corrs.size(); ++i) {
00047     vgl_point_3d<double> final;
00048     if (!vpgl_rational_adjust_onept::refine_intersection_pt(cams, corrs[i],intersections[i], final))
00049       return error;
00050     finals.push_back(final);
00051   }
00052 
00053   error = 0;
00054   for (unsigned int i = 0; i < corrs.size(); ++i) {
00055     error += compute_projection_error(cams, corrs[i], finals[i]);
00056   }
00057   return error;
00058 }
00059 
00060 //: assumes an initial estimate for intersection values, only refines the intersection and computes a re-projection error for each corr separately
00061 void re_projection_error(vcl_vector<vpgl_rational_camera<double> > const& cams,
00062                          vcl_vector<vcl_vector<vgl_point_2d<double> > > const& corrs, // for each 3d corr (outer vector), 2d locations for each cam (inner vector)
00063                          vcl_vector<vgl_point_3d<double> > const& intersections,
00064                          vcl_vector<vgl_point_3d<double> >& finals,
00065                          vnl_vector<double>& errors)
00066 {
00067   double error = 100000.0;
00068   errors.fill(error);
00069 
00070   finals.clear();
00071   for (unsigned int i = 0; i < corrs.size(); ++i) {
00072     vgl_point_3d<double> final;
00073     if (!vpgl_rational_adjust_onept::refine_intersection_pt(cams, corrs[i],intersections[i], final))
00074       return;
00075     finals.push_back(final);
00076   }
00077 
00078   unsigned k = 0;
00079   // return an error value for each cam for each corr
00080   for (unsigned int i = 0; i < corrs.size(); ++i) {
00081     for (unsigned int j = 0; j < cams.size(); ++j) {
00082       errors[k] = error_corr(cams[j], corrs[i][j], intersections[i]);
00083       k++;
00084     }
00085   }
00086 }
00087 
00088 
00089 void print_perm(vcl_vector<unsigned>& params_indices)
00090 {
00091   for (unsigned int i = 0; i < params_indices.size(); ++i)
00092     vcl_cout << params_indices[i] << ' ';
00093   vcl_cout << vcl_endl;
00094 }
00095 
00096 //: to generate the permutations, always increment the one at the very end by one; if it exceeds max, then increment the one before as well, etc.
00097 bool increment_perm(vcl_vector<unsigned>& params_indices, unsigned size)
00098 {
00099   if (!params_indices.size())  // no need to permute!!
00100     return true;
00101 
00102   params_indices[params_indices.size()-1] += 1;
00103   if (params_indices[params_indices.size()-1] == size) {  // carry on
00104     params_indices[params_indices.size()-1] = 0;
00105 
00106     if (params_indices.size() < 2)
00107       return true;  // we're done
00108 
00109     int current_i = (int)params_indices.size()-2;
00110     while (current_i >= 0) {
00111       params_indices[current_i] += 1;
00112       if (params_indices[current_i] < size)
00113         break;
00114       if (current_i == 0)
00115         return true;
00116       params_indices[current_i] = 0;
00117       current_i -= 1;
00118     }
00119   }
00120   return false;
00121 }
00122 
00123 //: performs an exhaustive search in the parameter space of the cameras.
00124 bool vpgl_rational_adjust_multiple_pts::
00125 adjust(vcl_vector<vpgl_rational_camera<double> > const& cams,
00126        vcl_vector<vcl_vector< vgl_point_2d<double> > > const& corrs,  // a vector of correspondences for each cam
00127        double radius, int n,       // divide radius into n intervals to generate camera translation space
00128        vcl_vector<vgl_vector_2d<double> >& cam_translations,          // output translations for each cam
00129        vcl_vector<vgl_point_3d<double> >& intersections)
00130 {
00131   cam_translations.clear();
00132   intersections.clear();
00133   intersections.resize(corrs.size());
00134   if (!cams.size() || !corrs.size() || cams.size() != corrs.size())
00135     return false;
00136   if (!corrs[0].size())
00137     return false;
00138   unsigned int cnt_corrs_for_each_cam = (unsigned)corrs[0].size();
00139   for (unsigned int i = 1; i < corrs.size(); ++i)
00140     if (corrs[i].size() != cnt_corrs_for_each_cam)  // there needs to be same number of corrs for each cam
00141       return false;
00142 
00143   // turn the correspondences into the format that we'll need
00144   vcl_vector<vgl_point_2d<double> > temp(cams.size());
00145   vcl_vector<vcl_vector<vgl_point_2d<double> > > corrs_reformatted(cnt_corrs_for_each_cam, temp);
00146 
00147   for (unsigned int i = 0; i < cnt_corrs_for_each_cam; ++i) { // for each corr
00148     for (unsigned int j = 0; j < corrs.size(); ++j) // for each cam (corr size and cams size are equal)
00149       corrs_reformatted[i][j] = corrs[j][i];
00150   }
00151   // find the best intersections for all the correspondences using the given cameras to compute good initial estimates for z of each correspondence
00152   vcl_vector<vgl_point_3d<double> > intersections_initial;
00153   for (unsigned int i = 0; i < corrs_reformatted.size(); ++i) {
00154     vgl_point_3d<double> pt;
00155     if (!vpgl_rational_adjust_onept::find_intersection_point(cams, corrs_reformatted[i], pt))
00156       return false;
00157     intersections_initial.push_back(pt);
00158   }
00159 
00160   // search the camera translation space
00161   int param_cnt = 2*(int)cams.size();
00162   double increment = radius/n;
00163   vcl_vector<double> param_values;
00164   param_values.push_back(0.0);
00165   for (int i = 1; i <= n; ++i) {
00166     param_values.push_back(i*increment);
00167     param_values.push_back(-i*increment);
00168   }
00169   for (unsigned int i = 0; i < param_values.size(); ++i)
00170     vcl_cout << param_values[i] << ' ';
00171   vcl_cout << '\n';
00172 
00173   // now for each param go through all possible param values
00174   vcl_vector<unsigned> params_indices(param_cnt, 0);
00175   int cnt = (int)vcl_pow((float)param_cnt, (float)param_values.size());
00176   vcl_cout << "will try: " << cnt << " param combinations: ";
00177   vcl_cout.flush();
00178   bool done = false;
00179   double big_value = 10000000.0;
00180   double min_error = big_value;
00181   vcl_vector<unsigned> params_indices_best(params_indices);
00182   while (!done) {
00183     vcl_cout << '.';
00184     vcl_cout.flush();
00185     vcl_vector<vpgl_rational_camera<double> > current_cams(cams);
00186     // translate current cams
00187     for (unsigned int i = 0; i < current_cams.size(); ++i) {
00188       double u_off,v_off;
00189       current_cams[i].image_offset(u_off, v_off);
00190       current_cams[i].set_image_offset(u_off + param_values[params_indices[i*2]], v_off + param_values[params_indices[i*2+1]]);
00191     }
00192 
00193     // use the initial estimates to compute re-projection errors
00194     vcl_vector<vgl_point_3d<double> > finals;
00195     double err = re_projection_error(current_cams, corrs_reformatted, intersections_initial, finals);
00196 
00197     if (err < min_error) {
00198       min_error = err;
00199       params_indices_best = params_indices;
00200       intersections = finals;
00201     }
00202     done = increment_perm(params_indices, (unsigned)param_values.size());
00203   }
00204   if (min_error < big_value) {
00205     vcl_cout << " done! found global min! min error: " << min_error << '\n';
00206     vcl_vector<vpgl_rational_camera<double> > current_cams(cams);
00207     // return translations
00208     vcl_cout << "translations for each camera:" << vcl_endl;
00209     for (unsigned int i = 0; i < current_cams.size(); ++i) {
00210       vgl_vector_2d<double> tr(param_values[params_indices_best[i*2]], param_values[params_indices_best[i*2+1]]);
00211       vcl_cout << tr << vcl_endl;
00212       cam_translations.push_back(tr);
00213       double u_off,v_off;
00214       current_cams[i].image_offset(u_off,v_off);
00215       current_cams[i].set_image_offset(u_off + param_values[params_indices_best[i*2]], v_off + param_values[params_indices_best[i*2+1]]);
00216     }
00217   }
00218   else {
00219     vcl_cout << " done! no global min!\n";
00220     return false;
00221   }
00222   return true;
00223 }
00224 
00225 vpgl_cam_trans_search_lsqr::
00226 vpgl_cam_trans_search_lsqr(vcl_vector<vpgl_rational_camera<double> > const& cams,
00227                            vcl_vector< vcl_vector<vgl_point_2d<double> > > const& image_pts,  // for each 3D corr, an array of 2D corrs for each camera
00228                            vcl_vector< vgl_point_3d<double> > const& initial_pts)
00229   :  vnl_least_squares_function(2*(unsigned)cams.size(), (unsigned)(cams.size()*image_pts.size()), vnl_least_squares_function::no_gradient),
00230      initial_pts_(initial_pts),
00231      cameras_(cams),
00232      corrs_(image_pts)
00233 {}
00234 
00235 void vpgl_cam_trans_search_lsqr::f(vnl_vector<double> const& translation,   // size is 2*cams.size()
00236                                    vnl_vector<double>& projection_errors)  // size is cams.size()*image_pts.size() --> compute a residual for each 3D corr point
00237 {
00238   // compute the new set of cameras with the current cam parameters
00239   vcl_vector<vpgl_rational_camera<double> > current_cams(cameras_);
00240   // translate current cams
00241   for (unsigned int i = 0; i < current_cams.size(); ++i) {
00242     double u_off,v_off;
00243     current_cams[i].image_offset(u_off, v_off);
00244     current_cams[i].set_image_offset(u_off + translation[i*2], v_off + translation[i*2+1]);
00245   }
00246   // compute the projection error for each cam for each corr
00247   // use the initial estimates to compute re-projection errors
00248   re_projection_error(current_cams, corrs_, initial_pts_, finals_, projection_errors);
00249 }
00250 
00251 void vpgl_cam_trans_search_lsqr::get_finals(vcl_vector<vgl_point_3d<double> >& finals)
00252 {
00253   finals = finals_;
00254 }
00255 
00256 //: run Lev-Marq optimization to search the param space to find the best parameter setting
00257 bool vpgl_rational_adjust_multiple_pts::
00258   adjust_lev_marq(vcl_vector<vpgl_rational_camera<double> > const& cams,
00259                   vcl_vector<vcl_vector< vgl_point_2d<double> > > const& corrs, // a vector of correspondences for each cam
00260                   vcl_vector<vgl_vector_2d<double> >& cam_translations,         // output translations for each cam
00261                   vcl_vector<vgl_point_3d<double> >& intersections)             // output 3d locations for each correspondence
00262 {
00263   cam_translations.clear();
00264   intersections.clear();
00265   intersections.resize(corrs.size());
00266   if (!cams.size() || !corrs.size() || cams.size() != corrs.size())
00267     return false;
00268   if (!corrs[0].size())
00269     return false;
00270   unsigned int cnt_corrs_for_each_cam = (unsigned)corrs[0].size();
00271   for (unsigned int i = 1; i < corrs.size(); ++i)
00272     if (corrs[i].size() != cnt_corrs_for_each_cam)  // there needs to be same number of corrs for each cam
00273       return false;
00274 
00275   // turn the correspondences into the format that we'll need
00276   vcl_vector<vgl_point_2d<double> > temp(cams.size());
00277   vcl_vector<vcl_vector<vgl_point_2d<double> > > corrs_reformatted(cnt_corrs_for_each_cam, temp);
00278 
00279   for (unsigned int i = 0; i < cnt_corrs_for_each_cam; ++i) { // for each corr
00280     for (unsigned int j = 0; j < corrs.size(); ++j) // for each cam (corr size and cams size are equal)
00281       corrs_reformatted[i][j] = corrs[j][i];
00282   }
00283   // find the best intersections for all the correspondences using the given cameras to compute good initial estimates for z of each correspondence
00284   vcl_vector<vgl_point_3d<double> > intersections_initial;
00285   for (unsigned int i = 0; i < corrs_reformatted.size(); ++i) {
00286     vgl_point_3d<double> pt;
00287     if (!vpgl_rational_adjust_onept::find_intersection_point(cams, corrs_reformatted[i], pt))
00288       return false;
00289     intersections_initial.push_back(pt);
00290   }
00291   // now refine those using Lev_Marq
00292   for (unsigned int i = 0; i < corrs_reformatted.size(); ++i) {
00293     vgl_point_3d<double> final;
00294     if (!vpgl_rational_adjust_onept::refine_intersection_pt(cams, corrs_reformatted[i],intersections_initial[i], final))
00295       return false;
00296     intersections_initial[i] = final;
00297   }
00298   for (unsigned int i = 0; i < intersections_initial.size(); ++i)
00299     vcl_cout << "before adjustment initial 3D intersection point: " << intersections_initial[i] << vcl_endl;
00300 
00301   // search the camera translation space using Lev-Marq
00302   vpgl_cam_trans_search_lsqr transsf(cams, corrs_reformatted, intersections_initial);
00303   vnl_levenberg_marquardt levmarq(transsf);
00304   levmarq.set_verbose(true);
00305   // Set the x-tolerance.  When the length of the steps taken in X (variables)
00306   // are no longer than this, the minimization terminates.
00307   levmarq.set_x_tolerance(1e-10);
00308   // Set the epsilon-function.  This is the step length for FD Jacobian.
00309   levmarq.set_epsilon_function(0.01);
00310   // Set the f-tolerance.  When the successive RMS errors are less than this,
00311   // minimization terminates.
00312   levmarq.set_f_tolerance(1e-15);
00313   // Set the maximum number of iterations
00314   levmarq.set_max_function_evals(10000);
00315   vnl_vector<double> translations(2*(unsigned)cams.size(), 0.0);
00316   vcl_cout << "Minimization x epsilon: " << levmarq.get_f_tolerance() << vcl_endl;
00317 
00318   // Minimize the error and get the best intersection point
00319   levmarq.minimize(translations);
00320   levmarq.diagnose_outcome();
00321   transsf.get_finals(intersections);
00322   vcl_cout << "final translations:" << vcl_endl;
00323   for (unsigned int i = 0; i < cams.size(); ++i) {
00324     vgl_vector_2d<double> trans(translations[2*i], translations[2*i+1]);
00325     cam_translations.push_back(trans);
00326     vcl_cout << trans << '\n';
00327   }
00328   return true;
00329 }