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