1 #include <cmath>
2 #include <limits>
3 #include <utility>
4 #include "vpgl_rational_adjust_multipt.h"
5 //:
6 // \file
7 #include <cassert>
8 #include "vgl/vgl_point_3d.h"
9 #include "vnl/vnl_numeric_traits.h"
10 #include <vnl/algo/vnl_levenberg_marquardt.h>
11 #include <vpgl/algo/vpgl_backproject.h>
12 #include <vpgl/algo/vpgl_ray_intersect.h>
13 #include <vpgl/algo/vpgl_rational_adjust_onept.h>
14 
15 #ifdef _MSC_VER
16 #  include "vcl_msvc_warnings.h"
17 #endif
18 
19 double
compute_projection_error(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<vgl_point_2d<double>> const & corrs,vgl_point_3d<double> & intersection)20 compute_projection_error(std::vector<vpgl_rational_camera<double>> const & cams,
21                          std::vector<vgl_point_2d<double>> const & corrs,
22                          vgl_point_3d<double> & intersection)
23 {
24   auto cit = cams.begin();
25   auto rit = corrs.begin();
26   double error = 0.0;
27   for (; cit != cams.end() && rit != corrs.end(); ++cit, ++rit)
28   {
29     vgl_point_2d<double> uvp = (*cit).project(intersection);
30     vgl_point_2d<double> uv = *rit;
31     double err = std::sqrt(std::pow(uv.x() - uvp.x(), 2.0) + std::pow(uv.y() - uvp.y(), 2));
32     error += err;
33   }
34   return error;
35 }
36 
37 double
error_corr(vpgl_rational_camera<double> const & cam,vgl_point_2d<double> const & corr,vgl_point_3d<double> const & intersection)38 error_corr(vpgl_rational_camera<double> const & cam,
39            vgl_point_2d<double> const & corr,
40            vgl_point_3d<double> const & intersection)
41 {
42   vgl_point_2d<double> uvp = cam.project(intersection);
43   return std::sqrt(std::pow(corr.x() - uvp.x(), 2.0) + std::pow(corr.y() - uvp.y(), 2));
44 }
45 
46 //: assumes an initial estimate for intersection values, only refines the intersection and computes a re-projection
47 // error
48 double
re_projection_error(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<float> const & cam_weights,std::vector<std::vector<vgl_point_2d<double>>> const & corrs,std::vector<vgl_point_3d<double>> const & intersections,std::vector<vgl_point_3d<double>> & finals)49 re_projection_error(std::vector<vpgl_rational_camera<double>> const & cams,
50                     std::vector<float> const & cam_weights,
51                     std::vector<std::vector<vgl_point_2d<double>>> const &
52                       corrs, // for each 3d corr (outer vector), 2d locations for each cam (inner vector)
53                     std::vector<vgl_point_3d<double>> const & intersections,
54                     std::vector<vgl_point_3d<double>> & finals)
55 {
56   double error = 100000.0;
57 
58   finals.clear();
59   for (unsigned int i = 0; i < corrs.size(); ++i)
60   {
61     vgl_point_3d<double> final;
62     if (!vpgl_rational_adjust_onept::refine_intersection_pt(cams, cam_weights, corrs[i], intersections[i], final))
63       return error;
64     finals.push_back(final);
65   }
66 
67   error = 0;
68   for (unsigned int i = 0; i < corrs.size(); ++i)
69   {
70     error += compute_projection_error(cams, corrs[i], finals[i]);
71   }
72   return error;
73 }
74 
75 //: assumes an initial estimate for intersection values, only refines the intersection and computes a re-projection
76 // error for each corr separately
77 void
re_projection_error(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<float> const & cam_weights,std::vector<std::vector<vgl_point_2d<double>>> const & corrs,std::vector<vgl_point_3d<double>> const & intersections,std::vector<vgl_point_3d<double>> & finals,vnl_vector<double> & errors)78 re_projection_error(std::vector<vpgl_rational_camera<double>> const & cams,
79                     std::vector<float> const & cam_weights,
80                     std::vector<std::vector<vgl_point_2d<double>>> const &
81                       corrs, // for each 3d corr (outer vector), 2d locations for each cam (inner vector)
82                     std::vector<vgl_point_3d<double>> const & intersections,
83                     std::vector<vgl_point_3d<double>> & finals,
84                     vnl_vector<double> & errors)
85 {
86   double error = 100000.0;
87   errors.fill(error);
88 
89   finals.clear();
90   for (unsigned int i = 0; i < corrs.size(); ++i)
91   {
92     vgl_point_3d<double> final;
93     if (!vpgl_rational_adjust_onept::refine_intersection_pt(cams, cam_weights, corrs[i], intersections[i], final))
94       return;
95     finals.push_back(final);
96   }
97 
98   unsigned k = 0;
99   // return an error value for each cam for each corr
100   for (unsigned int i = 0; i < corrs.size(); ++i)
101   {
102     for (unsigned int j = 0; j < cams.size(); ++j)
103     {
104       errors[k] = error_corr(cams[j], corrs[i][j], intersections[i]);
105       k++;
106     }
107   }
108 }
109 
110 
111 void
print_perm(std::vector<unsigned> & params_indices)112 print_perm(std::vector<unsigned> & params_indices)
113 {
114   for (unsigned int params_indice : params_indices)
115     std::cout << params_indice << ' ';
116   std::cout << std::endl;
117 }
118 
119 //: to generate the permutations, always increment the one at the very end by one; if it exceeds max, then increment the
120 // one before as well, etc.
121 bool
increment_perm(std::vector<unsigned> & params_indices,unsigned size)122 increment_perm(std::vector<unsigned> & params_indices, unsigned size)
123 {
124   if (params_indices.empty()) // no need to permute!!
125     return true;
126 
127   params_indices[params_indices.size() - 1] += 1;
128   if (params_indices[params_indices.size() - 1] == size)
129   { // carry on
130     params_indices[params_indices.size() - 1] = 0;
131 
132     if (params_indices.size() < 2)
133       return true; // we're done
134 
135     int current_i = (int)params_indices.size() - 2;
136     while (current_i >= 0)
137     {
138       params_indices[current_i] += 1;
139       if (params_indices[current_i] < size)
140         break;
141       if (current_i == 0)
142         return true;
143       params_indices[current_i] = 0;
144       current_i -= 1;
145     }
146   }
147   return false;
148 }
149 
150 //: performs an exhaustive search in the parameter space of the cameras.
151 bool
adjust(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<float> const & cam_weights,std::vector<std::vector<vgl_point_2d<double>>> const & corrs,double radius,int n,std::vector<vgl_vector_2d<double>> & cam_translations,std::vector<vgl_point_3d<double>> & intersections)152 vpgl_rational_adjust_multiple_pts::adjust(
153   std::vector<vpgl_rational_camera<double>> const & cams,
154   std::vector<float> const & cam_weights,
155   std::vector<std::vector<vgl_point_2d<double>>> const & corrs, // a vector of correspondences for each cam
156   double radius,
157   int n, // divide radius into n intervals to generate camera translation space
158   std::vector<vgl_vector_2d<double>> & cam_translations, // output translations for each cam
159   std::vector<vgl_point_3d<double>> & intersections)
160 {
161   cam_translations.clear();
162   intersections.clear();
163   intersections.resize(corrs.size());
164   if (cams.empty() || corrs.empty() || cams.size() != corrs.size())
165     return false;
166   if (corrs[0].empty())
167     return false;
168   auto cnt_corrs_for_each_cam = (unsigned)corrs[0].size();
169   for (unsigned int i = 1; i < corrs.size(); ++i)
170     if (corrs[i].size() != cnt_corrs_for_each_cam) // there needs to be same number of corrs for each cam
171       return false;
172 
173   // turn the correspondences into the format that we'll need
174   std::vector<vgl_point_2d<double>> temp(cams.size());
175   std::vector<std::vector<vgl_point_2d<double>>> corrs_reformatted(cnt_corrs_for_each_cam, temp);
176 
177   for (unsigned int i = 0; i < cnt_corrs_for_each_cam; ++i)
178   {                                                 // for each corr
179     for (unsigned int j = 0; j < corrs.size(); ++j) // for each cam (corr size and cams size are equal)
180       corrs_reformatted[i][j] = corrs[j][i];
181   }
182   // find the best intersections for all the correspondences using the given cameras to compute good initial estimates
183   // for z of each correspondence
184   std::vector<vgl_point_3d<double>> intersections_initial;
185   for (const auto & i : corrs_reformatted)
186   {
187     vgl_point_3d<double> pt;
188     if (!vpgl_rational_adjust_onept::find_intersection_point(cams, cam_weights, i, pt))
189       return false;
190     intersections_initial.push_back(pt);
191   }
192 
193   // search the camera translation space
194   int param_cnt = 2 * (int)cams.size();
195   double increment = radius / n;
196   std::vector<double> param_values;
197   param_values.push_back(0.0);
198   for (int i = 1; i <= n; ++i)
199   {
200     param_values.push_back(i * increment);
201     param_values.push_back(-i * increment);
202   }
203   for (double param_value : param_values)
204     std::cout << param_value << ' ';
205   std::cout << '\n';
206 
207   // now for each param go through all possible param values
208   std::vector<unsigned> params_indices(param_cnt, 0);
209   int cnt = (int)std::pow((float)param_cnt, (float)param_values.size());
210   std::cout << "will try: " << cnt << " param combinations: ";
211   std::cout.flush();
212   bool done = false;
213   double big_value = 10000000.0;
214   double min_error = big_value;
215   std::vector<unsigned> params_indices_best(params_indices);
216   while (!done)
217   {
218     std::cout << '.';
219     std::cout.flush();
220     std::vector<vpgl_rational_camera<double>> current_cams(cams);
221     // translate current cams
222     for (unsigned int i = 0; i < current_cams.size(); ++i)
223     {
224       double u_off, v_off;
225       current_cams[i].image_offset(u_off, v_off);
226       current_cams[i].set_image_offset(u_off + param_values[params_indices[i * 2]],
227                                        v_off + param_values[params_indices[i * 2 + 1]]);
228     }
229 
230     // use the initial estimates to compute re-projection errors
231     std::vector<vgl_point_3d<double>> finals;
232     double err = re_projection_error(current_cams, cam_weights, corrs_reformatted, intersections_initial, finals);
233 
234     if (err < min_error)
235     {
236       min_error = err;
237       params_indices_best = params_indices;
238       intersections = finals;
239     }
240     done = increment_perm(params_indices, (unsigned)param_values.size());
241   }
242   if (min_error < big_value)
243   {
244     std::cout << " done! found global min! min error: " << min_error << '\n';
245     std::vector<vpgl_rational_camera<double>> current_cams(cams);
246     // return translations
247     std::cout << "translations for each camera:" << std::endl;
248     for (unsigned int i = 0; i < current_cams.size(); ++i)
249     {
250       vgl_vector_2d<double> tr(param_values[params_indices_best[i * 2]], param_values[params_indices_best[i * 2 + 1]]);
251       std::cout << tr << std::endl;
252       cam_translations.push_back(tr);
253       double u_off, v_off;
254       current_cams[i].image_offset(u_off, v_off);
255       current_cams[i].set_image_offset(u_off + param_values[params_indices_best[i * 2]],
256                                        v_off + param_values[params_indices_best[i * 2 + 1]]);
257     }
258   }
259   else
260   {
261     std::cout << " done! no global min!\n";
262     return false;
263   }
264   return true;
265 }
266 
vpgl_cam_trans_search_lsqr(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<float> cam_weights,std::vector<std::vector<vgl_point_2d<double>>> const & image_pts,std::vector<vgl_point_3d<double>> initial_pts)267 vpgl_cam_trans_search_lsqr::vpgl_cam_trans_search_lsqr(
268   std::vector<vpgl_rational_camera<double>> const & cams,
269   std::vector<float> cam_weights,
270   std::vector<std::vector<vgl_point_2d<double>>> const &
271     image_pts, // for each 3D corr, an array of 2D corrs for each camera
272   std::vector<vgl_point_3d<double>> initial_pts)
273   : vnl_least_squares_function(2 * (unsigned)cams.size(),
274                                (unsigned)(cams.size() * image_pts.size()),
275                                vnl_least_squares_function::no_gradient)
276   , initial_pts_(std::move(initial_pts))
277   , cameras_(cams)
278   , cam_weights_(std::move(cam_weights))
279   , corrs_(image_pts)
280 {}
281 
282 void
f(vnl_vector<double> const & translation,vnl_vector<double> & projection_errors)283 vpgl_cam_trans_search_lsqr::f(vnl_vector<double> const & translation, // size is 2*cams.size()
284                               vnl_vector<double> & projection_errors) // size is cams.size()*image_pts.size() -->
285                                                                       // compute a residual for each 3D corr point
286 {
287   // compute the new set of cameras with the current cam parameters
288   std::vector<vpgl_rational_camera<double>> current_cams(cameras_);
289   // translate current cams
290   for (unsigned int i = 0; i < current_cams.size(); ++i)
291   {
292     double u_off, v_off;
293     current_cams[i].image_offset(u_off, v_off);
294     current_cams[i].set_image_offset(u_off + translation[i * 2], v_off + translation[i * 2 + 1]);
295   }
296   // compute the projection error for each cam for each corr
297   // use the initial estimates to compute re-projection errors
298   re_projection_error(current_cams, cam_weights_, corrs_, initial_pts_, finals_, projection_errors);
299 }
300 
301 void
get_finals(std::vector<vgl_point_3d<double>> & finals)302 vpgl_cam_trans_search_lsqr::get_finals(std::vector<vgl_point_3d<double>> & finals)
303 {
304   finals = finals_;
305 }
306 
307 //: run Lev-Marq optimization to search the param space to find the best parameter setting
308 bool
adjust_lev_marq(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<float> const & cam_weights,std::vector<std::vector<vgl_point_2d<double>>> const & corrs,std::vector<vgl_vector_2d<double>> & cam_translations,std::vector<vgl_point_3d<double>> & intersections)309 vpgl_rational_adjust_multiple_pts::adjust_lev_marq(
310   std::vector<vpgl_rational_camera<double>> const & cams,
311   std::vector<float> const & cam_weights,
312   std::vector<std::vector<vgl_point_2d<double>>> const & corrs, // a vector of correspondences for each cam
313   std::vector<vgl_vector_2d<double>> & cam_translations,        // output translations for each cam
314   std::vector<vgl_point_3d<double>> & intersections)            // output 3d locations for each correspondence
315 {
316   cam_translations.clear();
317   intersections.clear();
318   intersections.resize(corrs.size());
319   if (cams.empty() || corrs.empty() || cams.size() != corrs.size())
320     return false;
321   if (corrs[0].empty())
322     return false;
323   auto cnt_corrs_for_each_cam = (unsigned)corrs[0].size();
324   for (unsigned int i = 1; i < corrs.size(); ++i)
325     if (corrs[i].size() != cnt_corrs_for_each_cam) // there needs to be same number of corrs for each cam
326       return false;
327 
328   // turn the correspondences into the format that we'll need
329   std::vector<vgl_point_2d<double>> temp(cams.size());
330   std::vector<std::vector<vgl_point_2d<double>>> corrs_reformatted(cnt_corrs_for_each_cam, temp);
331 
332   for (unsigned int i = 0; i < cnt_corrs_for_each_cam; ++i)
333   {                                                 // for each corr
334     for (unsigned int j = 0; j < corrs.size(); ++j) // for each cam (corr size and cams size are equal)
335       corrs_reformatted[i][j] = corrs[j][i];
336   }
337   // find the best intersections for all the correspondences using the given cameras to compute good initial estimates
338   // for z of each correspondence
339   std::vector<vgl_point_3d<double>> intersections_initial;
340   for (const auto & i : corrs_reformatted)
341   {
342     vgl_point_3d<double> pt;
343     if (!vpgl_rational_adjust_onept::find_intersection_point(cams, cam_weights, i, pt))
344       return false;
345     intersections_initial.push_back(pt);
346   }
347   // now refine those using Lev_Marqs
348   for (unsigned int i = 0; i < corrs_reformatted.size(); ++i)
349   {
350     vgl_point_3d<double> final;
351     if (!vpgl_rational_adjust_onept::refine_intersection_pt(
352           cams, cam_weights, corrs_reformatted[i], intersections_initial[i], final))
353       return false;
354     intersections_initial[i] = final;
355   }
356   for (const auto & i : intersections_initial)
357     std::cout << "before adjustment initial 3D intersection point: " << i << std::endl;
358 
359   // search the camera translation space using Lev-Marq
360   vpgl_cam_trans_search_lsqr transsf(cams, cam_weights, corrs_reformatted, intersections_initial);
361   vnl_levenberg_marquardt levmarq(transsf);
362   levmarq.set_verbose(true);
363   // Set the x-tolerance.  When the length of the steps taken in X (variables)
364   // are no longer than this, the minimization terminates.
365   levmarq.set_x_tolerance(1e-10);
366   // Set the epsilon-function.  This is the step length for FD Jacobian.
367   levmarq.set_epsilon_function(0.01);
368   // Set the f-tolerance.  When the successive RMS errors are less than this,
369   // minimization terminates.
370   levmarq.set_f_tolerance(1e-15);
371   // Set the maximum number of iterations
372   levmarq.set_max_function_evals(10000);
373   vnl_vector<double> translations(2 * (unsigned)cams.size(), 0.0);
374   std::cout << "Minimization x epsilon: " << levmarq.get_f_tolerance() << std::endl;
375 
376   // Minimize the error and get the best intersection point
377   levmarq.minimize(translations);
378   levmarq.diagnose_outcome();
379   transsf.get_finals(intersections);
380   std::cout << "final translations:" << std::endl;
381   for (unsigned int i = 0; i < cams.size(); ++i)
382   {
383     // if cam weight is 1 for one of them, it is a special case, then pass 0 as translation for that one, cause
384     // projections still cause a tiny translation, ignore that
385     vgl_vector_2d<double> trans(0.0, 0.0);
386     if (cam_weights[i] == 1.0f)
387     {
388       cam_translations.push_back(trans);
389     }
390     else
391     {
392       trans.set(translations[2 * i], translations[2 * i + 1]);
393       cam_translations.push_back(trans);
394     }
395     // sanity check
396     if (std::abs(trans.x()) > 200 || std::abs(trans.y()) > 200)
397     {
398       std::cerr << " trans: " << trans << " failed sanity check! returning false!\n";
399       return false;
400     }
401     std::cout << trans << '\n';
402   }
403   return true;
404 }
405 
406 // run Lev-Marq optimization to search the param spae to find the best parameter setting
407 bool
adjust_lev_marq(std::vector<vpgl_rational_camera<double>> const & cams,std::vector<float> const & cam_weights,std::vector<std::vector<vgl_point_2d<double>>> const & corrs,vgl_point_3d<double> const & initial_pt,double const & zmin,double const & zmax,std::vector<vgl_vector_2d<double>> & cam_translations,std::vector<vgl_point_3d<double>> & intersections,double const relative_diameter)408 vpgl_rational_adjust_multiple_pts::adjust_lev_marq(
409   std::vector<vpgl_rational_camera<double>> const & cams,       // cameras that will be corrected
410   std::vector<float> const & cam_weights,                       // camera weight parameters
411   std::vector<std::vector<vgl_point_2d<double>>> const & corrs, // a vector of correspondences for each cam
412   vgl_point_3d<double> const & initial_pt,                      // initial 3-d point for back-projection
413   double const & zmin,                                          // minimum allowed height of the 3-d intersection point
414   double const & zmax,                                          // maximum allowed height of the 3-d intersection point
415   std::vector<vgl_vector_2d<double>> & cam_translations,        // output translations for each camera
416   std::vector<vgl_point_3d<double>> & intersections,            // output 3-d locations for each correspondence
417   double const relative_diameter)
418 {
419   cam_translations.clear();
420   intersections.clear();
421   intersections.clear();
422   intersections.resize(corrs.size());
423   if (cams.empty() || corrs.empty() || cams.size() != corrs.size())
424     return false;
425   if (corrs[0].empty())
426     return false;
427   auto cnt_corrs_for_each_cam = (unsigned)corrs[0].size();
428   for (unsigned i = 1; i < corrs.size(); ++i)
429     if (corrs[i].size() != cnt_corrs_for_each_cam)
430       return false;
431 
432   // reformat the correspondences array
433   std::vector<vgl_point_2d<double>> temp(cams.size());
434   std::vector<std::vector<vgl_point_2d<double>>> corrs_reformatted(cnt_corrs_for_each_cam, temp);
435 
436   for (unsigned int i = 0; i < cnt_corrs_for_each_cam; ++i)
437   {                                                 // for each corr
438     for (unsigned int j = 0; j < corrs.size(); ++j) // for each cam (corr size and cams size are equal)
439       corrs_reformatted[i][j] = corrs[j][i];
440   }
441   // find the best 3-d intersections for all the correspondences using the given cameras to compute good initial
442   // estimates for z of each correspondence
443   std::vector<vgl_point_3d<double>> intersections_initial;
444   for (const auto & i : corrs_reformatted)
445   {
446     vgl_point_3d<double> pt;
447     if (!vpgl_rational_adjust_onept::find_intersection_point(
448           cams, cam_weights, i, initial_pt, zmin, zmax, pt, relative_diameter))
449       return false;
450     intersections_initial.push_back(pt);
451   }
452 
453   // now refine the intersections using Lev_Marqs
454   for (unsigned i = 0; i < corrs_reformatted.size(); i++)
455   {
456     vgl_point_3d<double> final;
457     if (!vpgl_rational_adjust_onept::refine_intersection_pt(
458           cams, cam_weights, corrs_reformatted[i], intersections_initial[i], final, relative_diameter))
459       return false;
460     intersections_initial[i] = final;
461   }
462   for (const auto & i : intersections_initial)
463     std::cout << "before adjustment initial 3D intersection point: " << i << std::endl;
464 
465   // search the camera translation space using Lev-Marq
466   vpgl_cam_trans_search_lsqr transsf(cams, cam_weights, corrs_reformatted, intersections_initial);
467   vnl_levenberg_marquardt levmarq(transsf);
468   levmarq.set_verbose(true);
469   // Set the x-tolerance.  Minimization terminates when the length of the steps taken in X (variables) are less than
470   // input x-tolerance
471   levmarq.set_x_tolerance(1e-10);
472   // Set the epsilon-function.  This is the step length for FD Jacobian
473   levmarq.set_epsilon_function(0.01);
474   // Set the f-tolerance.  Minimization terminates when the successive RSM errors are less then this
475   levmarq.set_f_tolerance(1e-15);
476   // Set the maximum number of iterations
477   levmarq.set_max_function_evals(10000);
478   vnl_vector<double> translations(2 * (unsigned)cams.size(), 0.0);
479   std::cout << "Minimization x epsilon: " << levmarq.get_f_tolerance() << std::endl;
480 
481   // Minimize the error and get the best intersection point
482   levmarq.minimize(translations);
483   levmarq.diagnose_outcome();
484   transsf.get_finals(intersections);
485   std::cout << "final translations:" << std::endl;
486   for (unsigned i = 0; i < cams.size(); i++)
487   {
488     // if cam weight is 1 for one of them, it is a special case, then pass 0 as translation for that one, cause
489     // projections still cause a tiny translation, ignore that
490     vgl_vector_2d<double> trans(0.0, 0.0);
491     if (cam_weights[i] == 1.0f)
492     {
493       cam_translations.push_back(trans);
494     }
495     else
496     {
497       trans.set(translations[2 * i], translations[2 * i + 1]);
498       cam_translations.push_back(trans);
499     }
500     if (std::abs(trans.x()) > 200 || std::abs(trans.y()) > 200)
501     {
502       std::cerr << " trans: " << trans << " failed sanity check! returning false!\n";
503       return false;
504     }
505     std::cout << trans << '\n';
506   }
507   return true;
508 }
509