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