1 #include "rgrl_est_dis_homo2d_lm.h"
2 //:
3 // \file
4 #include <rgrl/rgrl_est_homography2d.h>
5 #include <rgrl/rgrl_trans_homography2d.h>
6 #include <rgrl/rgrl_trans_rad_dis_homo2d.h>
7 #include <rgrl/rgrl_trans_rigid.h>
8 #include <rgrl/rgrl_trans_translation.h>
9 #include <rgrl/rgrl_trans_similarity.h>
10 #include <rgrl/rgrl_trans_affine.h>
11 #include <rgrl/rgrl_match_set.h>
12 #include <rgrl/rgrl_internal_util.h>
13
14 #include "vnl/vnl_double_2.h"
15 #include "vnl/vnl_double_3.h"
16 #include "vnl/vnl_double_2x2.h"
17 #include "vnl/vnl_double_3x3.h"
18 #include "vnl/vnl_matrix_fixed.h"
19 #include "vnl/vnl_vector_fixed.h"
20 #include "vnl/vnl_math.h"
21 #include "vnl/vnl_fastops.h"
22 #include "vnl/vnl_transpose.h"
23 #include "vnl/vnl_least_squares_function.h"
24 #include <vnl/algo/vnl_orthogonal_complement.h>
25 #include <vnl/algo/vnl_levenberg_marquardt.h>
26 #include <vnl/algo/vnl_svd.h>
27
28 #include <cassert>
29 #ifdef _MSC_VER
30 # include "vcl_msvc_warnings.h"
31 #endif
32
33 static
34 inline
35 void
H2h(vnl_matrix_fixed<double,3,3> const & H,vnl_vector<double> & h)36 H2h( vnl_matrix_fixed<double,3,3> const& H, vnl_vector<double>& h )
37 {
38 // h is already size 11
39 // h.set_size( 9 );
40 for ( unsigned i=0; i<H.rows(); ++i )
41 for ( unsigned j=0; j<H.cols(); ++j )
42 h( i*3+j ) = H(i,j);
43 }
44
45 static
46 inline
47 void
h2H(vnl_vector<double> const & h,vnl_matrix_fixed<double,3,3> & H)48 h2H( vnl_vector<double> const& h, vnl_matrix_fixed<double,3,3>& H )
49 {
50 for ( unsigned i=0; i<3; ++i )
51 for ( unsigned j=0; j<3; ++j )
52 H(i,j) = h( i*3+j );
53 }
54
55 // map homography
56 static
57 inline
58 void
map_inhomo_point(vnl_double_2 & mapped,vnl_matrix_fixed<double,3,3> const & H,vnl_vector<double> const & loc)59 map_inhomo_point( vnl_double_2& mapped, vnl_matrix_fixed<double, 3, 3> const& H, vnl_vector<double> const& loc )
60 {
61 vnl_double_3 homo_from( loc[0], loc[1], 1 );
62 vnl_double_3 homo_to = H * homo_from;
63 mapped[0] = homo_to[0]/homo_to[2];
64 mapped[1] = homo_to[1]/homo_to[2];
65 }
66
67 //: Return the jacobian of the transform.
68 static
69 void
homo_wrt_loc(vnl_matrix_fixed<double,2,2> & jac_loc,vnl_matrix_fixed<double,3,3> const & H,vnl_vector_fixed<double,2> const & from_loc)70 homo_wrt_loc( vnl_matrix_fixed<double, 2, 2> & jac_loc,
71 vnl_matrix_fixed<double, 3, 3> const& H,
72 vnl_vector_fixed<double, 2> const& from_loc )
73 {
74 // The jacobian is a 2x2 matrix with entries
75 // [d(f_0)/dx d(f_0)/dy;
76 // d(f_1)/dx d(f_1)/dy]
77 //
78 const double mapped_w = H(2,0)*from_loc[0] + H(2,1)*from_loc[1] + H(2,2);
79
80 // w/ respect to x
81 jac_loc(0,0) = H(0,0)*( H(2,1)*from_loc[1]+H(2,2) ) - H(2,0)*( H(0,1)*from_loc[1] + H(0,2) );
82 jac_loc(1,0) = H(1,0)*( H(2,1)*from_loc[1]+H(2,2) ) - H(2,0)*( H(1,1)*from_loc[1] + H(1,2) );
83 // w/ respect to y
84 jac_loc(0,1) = H(0,1)*( H(2,0)*from_loc[0]+H(2,2) ) - H(2,1)*( H(0,0)*from_loc[0] + H(0,2) );
85 jac_loc(1,1) = H(1,1)*( H(2,0)*from_loc[0]+H(2,2) ) - H(2,1)*( H(1,0)*from_loc[0] + H(1,2) );
86
87 jac_loc *= (1/(mapped_w*mapped_w));
88 }
89
90
91 static
92 void
homo_wrt_h(vnl_matrix_fixed<double,2,9> & jac_h,vnl_matrix_fixed<double,3,3> const & H,vnl_vector_fixed<double,2> const & from_loc)93 homo_wrt_h( vnl_matrix_fixed<double, 2, 9> & jac_h,
94 vnl_matrix_fixed<double, 3, 3> const& H,
95 vnl_vector_fixed<double, 2> const& from_loc )
96 {
97 vnl_matrix_fixed<double, 3, 9 > jf(0.0); // homogeneous coordinate
98 vnl_matrix_fixed<double, 2, 3 > jg(0.0); // inhomo, [u/w, v/w]^T
99
100 // transform coordinate
101 vnl_double_3 from_homo( from_loc[0], from_loc[1], 1 );
102 vnl_double_3 mapped_homo = H * from_homo;
103
104 // homogeneous coordinate w.r.t homography parameters
105 jf(0,0) = jf(1,3) = jf(2,6) = from_homo[0]; // x
106 jf(0,1) = jf(1,4) = jf(2,7) = from_homo[1]; // y
107 jf(0,2) = jf(1,5) = jf(2,8) = 1.0;
108
109 // derivatives w.r.t division
110 jg(0,0) = 1.0/mapped_homo[2];
111 jg(0,2) = -mapped_homo[0]/vnl_math::sqr(mapped_homo[2]);
112 jg(1,1) = 1.0/mapped_homo[2];
113 jg(1,2) = -mapped_homo[1]/vnl_math::sqr(mapped_homo[2]);
114
115 // Apply chain rule: Jab_g(f(p)) = Jac_g * Jac_f
116 jac_h = jg * jf;
117 }
118
119 // distort image coordinate
120 static
121 inline
122 void
distort(vnl_double_2 & dis_loc,vnl_double_2 const & true_loc,double k1)123 distort( vnl_double_2& dis_loc, vnl_double_2 const& true_loc, double k1 )
124 {
125 const double c = 1 + k1 * true_loc.squared_magnitude();
126 dis_loc = c * true_loc;
127 }
128
129 // jacobian w.r.t k1 parameter
130 static
131 inline
132 void
distort_wrt_k1(vnl_double_2 & jac_k1,vnl_double_2 const & true_loc)133 distort_wrt_k1( vnl_double_2& jac_k1, vnl_double_2 const& true_loc )
134 {
135 const double c = true_loc.squared_magnitude();
136 jac_k1 = c * true_loc;
137 }
138
139
140 // jacobian w.r.t location
141 static
142 inline
143 void
distort_wrt_loc(vnl_double_2x2 & jac_loc,vnl_double_2 const & true_loc,double k1)144 distort_wrt_loc( vnl_double_2x2& jac_loc, vnl_double_2 const& true_loc, double k1 )
145 {
146 const double c = 1 + k1 * true_loc.squared_magnitude();
147
148 jac_loc(0,0) = c + 2*k1*vnl_math::sqr(true_loc[0]);
149 jac_loc(1,1) = c + 2*k1*vnl_math::sqr(true_loc[1]);
150 jac_loc(0,1) = jac_loc(1,0) = 2 * k1 * true_loc[0] * true_loc[1];
151 }
152
153
154 class rgrl_rad_dis_homo2d_func
155 : public vnl_least_squares_function
156 {
157 public:
158 //: ctor
rgrl_rad_dis_homo2d_func(rgrl_set_of<rgrl_match_set_sptr> const & matches,int num_res,bool with_grad=true)159 rgrl_rad_dis_homo2d_func( rgrl_set_of<rgrl_match_set_sptr> const& matches,
160 int num_res, bool with_grad = true )
161 : vnl_least_squares_function( 11, num_res, with_grad ? use_gradient : no_gradient ),
162 matches_ptr_( &matches ),
163 from_centre_(2, 0.0), to_centre_(2, 0.0)
164 { }
165
set_centres(vnl_vector<double> const & fc,vnl_vector<double> const & tc)166 void set_centres( vnl_vector<double> const& fc, vnl_vector<double> const& tc )
167 {
168 assert( fc.size() == 2 && tc.size() == 2 );
169 from_centre_ = fc;
170 to_centre_ = tc;
171 }
172
173 //: obj func value
174 void f(vnl_vector<double> const& x, vnl_vector<double>& fx) override;
175
176 //: Jacobian
177 void gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian) override;
178
179 protected:
180 typedef rgrl_match_set::const_from_iterator FIter;
181 typedef FIter::to_iterator TIter;
182
183 rgrl_set_of<rgrl_match_set_sptr> const* matches_ptr_;
184 vnl_double_2 from_centre_, to_centre_;
185 };
186
187 void
188 rgrl_rad_dis_homo2d_func::
f(vnl_vector<double> const & x,vnl_vector<double> & fx)189 f(vnl_vector<double> const& x, vnl_vector<double>& fx)
190 {
191 vnl_double_2 true_mapped, true_from, from, dis_mapped;
192 vnl_matrix_fixed<double,2,2> error_proj_sqrt;
193 double k1_from = x[9];
194 double k1_to = x[10];
195 vnl_double_3x3 H;
196 h2H( x, H );
197
198 unsigned int ind = 0;
199 for ( unsigned ms = 0; ms<matches_ptr_->size(); ++ms )
200 if ( (*matches_ptr_)[ms] != nullptr ) { // if pointer is valid
201
202 rgrl_match_set const& one_set = *((*matches_ptr_)[ms]);
203
204 for ( FIter fi=one_set.from_begin(); fi!=one_set.from_end(); ++fi )
205 {
206 // map from point
207 from = fi.from_feature()->location();
208 from -= from_centre_;
209
210 // Step 1.
211 distort( true_from, from, k1_from );
212 // Step 2.
213 map_inhomo_point( true_mapped, H, true_from.as_ref() );
214 // Step 3.
215 distort( dis_mapped, true_mapped, k1_to );
216
217 for ( TIter ti=fi.begin(); ti!=fi.end(); ++ti )
218 {
219 vnl_double_2 to = ti.to_feature()->location();
220 to -= to_centre_;
221 error_proj_sqrt = ti.to_feature()->error_projector_sqrt();
222 double const wgt = std::sqrt(ti.cumulative_weight());
223 vnl_double_2 diff = error_proj_sqrt * (dis_mapped - to);
224
225 // fill in
226 fx(ind) = wgt*diff[0];
227 fx(ind+1) = wgt*diff[1];
228 ind+=2;
229 }
230 }
231 }
232
233 // check
234 assert( ind == get_number_of_residuals() );
235 }
236
237 void
238 rgrl_rad_dis_homo2d_func::
gradf(vnl_vector<double> const & x,vnl_matrix<double> & jacobian)239 gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian)
240 {
241 assert( jacobian.rows() == get_number_of_residuals() && jacobian.cols() == 11 );
242
243 double k1_from = x[9];
244 double k1_to = x[10];
245 vnl_double_3x3 H;
246 h2H( x, H );
247
248 vnl_double_2x2 pu_pd;
249 vnl_double_2 pu_k1_from;
250 vnl_double_2x2 qu_pu;
251 vnl_matrix_fixed<double, 2, 9> qu_h;
252 vnl_double_2x2 qd_qu;
253 vnl_double_2 qd_k1_to;
254 vnl_matrix_fixed<double, 2, 9> qd_h;
255 vnl_double_2 qd_k1_from;
256
257 vnl_matrix_fixed<double,2,2> error_proj_sqrt;
258
259 unsigned int ind = 0;
260 for ( unsigned ms = 0; ms<matches_ptr_->size(); ++ms )
261
262 if ( (*matches_ptr_)[ms] ) { // if pointer is valid
263
264 rgrl_match_set const& one_set = *((*matches_ptr_)[ms]);
265
266 for ( FIter fi=one_set.from_begin(); fi!=one_set.from_end(); ++fi )
267 {
268 // Step 1. undistorted from coordinate and compute apu/apd
269 vnl_double_2 dis_from_loc = fi.from_feature()->location();
270 dis_from_loc -= from_centre_;
271
272 vnl_double_2 true_from_loc;
273 // make the trick: *distort*
274 distort( true_from_loc, dis_from_loc, k1_from );
275 distort_wrt_loc( pu_pd, dis_from_loc, k1_from );
276 distort_wrt_k1( pu_k1_from, dis_from_loc );
277
278 // Step 2. homography transformation
279 vnl_double_2 true_to_loc;
280 map_inhomo_point( true_to_loc, H, true_from_loc.as_ref() );
281 homo_wrt_loc( qu_pu, H, true_from_loc );
282 homo_wrt_h( qu_h, H, true_from_loc );
283
284 // Step 3. distorted To coodinates
285 distort_wrt_loc( qd_qu, true_to_loc, k1_to );
286 distort_wrt_k1( qd_k1_to, true_to_loc );
287
288 // Steop 4. apply chain rule
289 qd_h = qd_qu * qu_h;
290 qd_k1_from = qd_qu * qu_pu * pu_k1_from;
291
292 for ( TIter ti=fi.begin(); ti!=fi.end(); ++ti ) {
293 //vnl_double_2 to = ti.to_feature()->location();
294 error_proj_sqrt = ti.to_feature()->error_projector_sqrt();
295 double const wgt = std::sqrt(ti.cumulative_weight());
296
297 qd_k1_from = wgt * error_proj_sqrt * qd_k1_from;
298 qd_k1_to = wgt * error_proj_sqrt * qd_k1_to;
299 qd_h = wgt * error_proj_sqrt * qd_h;
300
301 // fill in
302 for ( unsigned i=0; i<9; i++ ) {
303 jacobian(ind, i) = qd_h(0, i);
304 jacobian(ind+1, i) = qd_h(1, i);
305 }
306 // k1_from
307 jacobian(ind, 9) = qd_k1_from[0];
308 jacobian(ind+1, 9) = qd_k1_from[1];
309 // k1_to
310 jacobian(ind, 10) = qd_k1_to[0];
311 jacobian(ind+1, 10) = qd_k1_to[1];
312
313 ind+=2;
314 }
315 }
316 }
317 }
318
319
320 // --------------------------------------------------------------------
321
322 rgrl_est_dis_homo2d_lm::
rgrl_est_dis_homo2d_lm(vnl_vector<double> const & from_centre,vnl_vector<double> const & to_centre,bool with_grad)323 rgrl_est_dis_homo2d_lm( vnl_vector<double> const& from_centre,
324 vnl_vector<double> const& to_centre,
325 bool with_grad )
326 : from_centre_( from_centre),
327 to_centre_( to_centre ),
328 with_grad_( with_grad )
329 {
330 assert( from_centre.size() == 2 && to_centre.size() == 2 );
331
332 rgrl_estimator::set_param_dof( 10 );
333
334 // default value
335 rgrl_nonlinear_estimator::set_max_num_iter( 50 );
336 rgrl_nonlinear_estimator::set_rel_thres( 1e-5 );
337 }
338
339 rgrl_transformation_sptr
340 rgrl_est_dis_homo2d_lm::
estimate(rgrl_set_of<rgrl_match_set_sptr> const & matches,rgrl_transformation const & cur_transform) const341 estimate( rgrl_set_of<rgrl_match_set_sptr> const& matches,
342 rgrl_transformation const& cur_transform ) const
343 {
344 // get initialization
345 vnl_matrix_fixed<double, 3, 3> init_H;
346 double k1_from = 0, k1_to = 0;
347
348 if ( cur_transform.is_type( rgrl_trans_rad_dis_homo2d::type_id() ) )
349 {
350 auto const& trans = static_cast<rgrl_trans_rad_dis_homo2d const&>( cur_transform );
351 init_H = trans.H();
352 k1_from = trans.k1_from();
353 k1_to = trans.k1_to();
354
355 // check centre
356 assert( from_centre_ == trans.from_centre() );
357 assert( to_centre_ == trans.to_centre() );
358 }
359 else {
360
361 if ( !rgrl_internal_util_upgrade_to_homography2D( init_H, cur_transform ) ) {
362
363 // use normalized DLT to initialize
364 DebugMacro( 0, "Use normalized DLT to initialize" );
365 rgrl_est_homography2d est_homo;
366 rgrl_transformation_sptr tmp_trans= est_homo.estimate( matches, cur_transform );
367 if ( !tmp_trans )
368 return nullptr;
369 auto const& trans = static_cast<rgrl_trans_homography2d const&>( *tmp_trans );
370 init_H = trans.H();
371 }
372
373 // make the init homography as a CENTERED one
374 // centered H_ = to_matrix * H * from_matrix^-1
375 //
376 vnl_matrix_fixed<double, 3, 3> to_trans;
377 to_trans.set_identity();
378 to_trans(0,2) = -to_centre_[0];
379 to_trans(1,2) = -to_centre_[1];
380
381 vnl_matrix_fixed<double, 3, 3> from_inv;
382 from_inv.set_identity();
383 from_inv(0,2) = from_centre_[0];
384 from_inv(1,2) = from_centre_[1];
385
386 init_H = to_trans * init_H * from_inv;
387 }
388
389
390 // count the number of constraints/residuals
391 typedef rgrl_match_set::const_from_iterator FIter;
392 unsigned int tot_num = 0;
393 for ( unsigned ms = 0; ms<matches.size(); ++ms )
394 if ( matches[ms] ) { // if pointer is valid
395 rgrl_match_set const& one_set = *(matches[ms]);
396 for ( FIter fi=one_set.from_begin(); fi!=one_set.from_end(); ++fi ) {
397 tot_num += fi.size() * 2; // each point provides two constraints
398 }
399 }
400
401 DebugMacro( 3, "From center: " << from_centre_
402 << " To center: " << to_centre_ << std::endl );
403
404 // normalize H
405 init_H /= init_H.array_two_norm();
406
407 // convert to vector form
408 vnl_vector<double> p( 11, 0.0 );
409 H2h( init_H, p );
410 p[9] = k1_from;
411 p[10] = k1_to;
412
413 // construct least square cost function
414 rgrl_rad_dis_homo2d_func dis_homo_func( matches, tot_num, with_grad_ );
415 dis_homo_func.set_centres( from_centre_.as_ref(), to_centre_.as_ref() );
416
417 vnl_levenberg_marquardt lm( dis_homo_func );
418 //lm.set_trace( true );
419 //lm.set_check_derivatives( 3 );
420 // we don't need it to be super accurate
421 lm.set_f_tolerance( relative_threshold_ );
422 lm.set_max_function_evals( max_num_iterations_ );
423
424 bool ret;
425 if ( with_grad_ )
426 ret = lm.minimize_using_gradient(p);
427 else
428 ret = lm.minimize_without_gradient(p);
429 if ( !ret ) {
430 WarningMacro( "Levenberg-Marquardt failed" );
431 return nullptr;
432 }
433 // lm.diagnose_outcome(std::cout);
434
435 // convert parameters back into matrix form
436 h2H( p, init_H );
437 k1_from = p[9];
438 k1_to = p[10];
439 // normalize H
440 init_H /= init_H.array_two_norm();
441
442 // compute covariance
443 // JtJ is INVERSE of jacobian
444 // vnl_svd<double> svd( lm.get_JtJ(), 1e-4 );
445 // Cannot use get_JtJ() because it is affected by the
446 // scale in homography parameter vector
447 // Thus, use the normalized p vector to compute Jacobian again
448 vnl_matrix<double> jac(tot_num, 11), jtj(11, 11);
449 dis_homo_func.gradf( p, jac );
450 //
451 vnl_fastops::AtA( jtj, jac );
452 // std::cout << "Jacobian:\n" << jac << "\n\nJtJ:\n" << jtj << "\n\nReal JtJ:\n" << jac.transpose() * jac << std::endl;
453
454
455 // compute the inverse of scatter matrix
456 #if 1
457 vnl_vector<double> homo_vector( 9 );
458 H2h( init_H, homo_vector );
459 vnl_matrix<double> block_compliment = vnl_orthogonal_complement( homo_vector );
460
461 vnl_matrix<double> compliment( 11, 10, 0.0 );
462
463 for ( unsigned i=0; i<9; ++i )
464 for ( unsigned j=0; j<8; ++j )
465 compliment(i,j) = block_compliment(i,j);
466
467 // distortion parameters
468 compliment(9,8) = 1.0;
469 compliment(10,9) = 1.0;
470
471 vnl_svd<double> svd( vnl_transpose(compliment) * jtj *compliment, 1e-6 );
472 if ( svd.rank() < 10 ) {
473 WarningMacro( "The covariance of homography ranks less than 8! ");
474 return nullptr;
475 }
476
477 vnl_matrix<double>covar = compliment * svd.inverse() * compliment.transpose();
478
479
480 #else
481 // compute inverse
482 //
483 vnl_svd<double> svd( jtj, 1e-6 );
484 DebugMacro(3, "SVD of JtJ: " << svd << std::endl);
485 // the second least singular value shall be greater than 0
486 // or Rank 11-1 = 10
487 if ( svd.rank() < 10 ) {
488 WarningMacro( "The covariance of homography ranks less than 8! ");
489 }
490 // pseudo inverse only use first 10 singular values
491 vnl_matrix<double> covar ( svd.pinverse(10) );
492 DebugMacro(3, "Covariance: " << covar << std::endl );
493
494 DebugMacro(2, "null vector: " << svd.nullvector() << " estimate: " << p << std::endl );
495 #endif
496
497 return new rgrl_trans_rad_dis_homo2d( init_H.as_ref(), k1_from, k1_to, covar, from_centre_.as_ref(), to_centre_.as_ref() );
498 }
499
500
501 rgrl_transformation_sptr
502 rgrl_est_dis_homo2d_lm::
estimate(rgrl_match_set_sptr matches,rgrl_transformation const & cur_transform) const503 estimate( rgrl_match_set_sptr matches,
504 rgrl_transformation const& cur_transform ) const
505 {
506 // use base class implementation
507 return rgrl_estimator::estimate( matches, cur_transform );
508 }
509
510 //: change frome_centre
511 void
512 rgrl_est_dis_homo2d_lm::
set_centres(vnl_vector<double> const & from_centre,vnl_vector<double> const & to_centre)513 set_centres( vnl_vector<double> const& from_centre,
514 vnl_vector<double> const& to_centre )
515 {
516 assert( from_centre.size() == 2 && to_centre.size() == 2 );
517
518 from_centre_ = from_centre;
519 to_centre_ = to_centre;
520 }
521
522 const std::type_info&
523 rgrl_est_dis_homo2d_lm::
transformation_type() const524 transformation_type() const
525 {
526 return rgrl_trans_rad_dis_homo2d::type_id();
527 }
528