1 #ifndef rgrl_est_proj_rad_func_txx_
2 #define rgrl_est_proj_rad_func_txx_
3 //:
4 // \file
5 // \author Gehua Yang
6 // \date March 2007
7 // \brief a generic class to estimate a homogeneous projection matrix with radial distortion parameter(s) using L-M
8
9 #include <iostream>
10 #include <vector>
11 #include "rgrl_est_proj_rad_func.h"
12
13 #include <rgrl/rgrl_est_proj_func.hxx>
14 #include <rgrl/rgrl_estimator.h>
15 #include <rgrl/rgrl_match_set.h>
16 #include <rgrl/rgrl_set_of.h>
17
18 #include <vnl/vnl_matrix_fixed.h>
19 #include <vnl/vnl_vector_fixed.h>
20 #include <vnl/vnl_math.h>
21 #include <vnl/vnl_transpose.h>
22 #include <vnl/algo/vnl_levenberg_marquardt.h>
23 #include <vnl/algo/vnl_svd.h>
24
25 #ifdef _MSC_VER
26 # include <vcl_msvc_warnings.h>
27 #endif
28 #include <cassert>
29
30 template <unsigned int Tdim, unsigned int Fdim>
31 rgrl_est_proj_rad_func<Tdim, Fdim>::
rgrl_est_proj_rad_func(rgrl_set_of<rgrl_match_set_sptr> const & matches,unsigned int camera_dof,bool with_grad)32 rgrl_est_proj_rad_func( rgrl_set_of<rgrl_match_set_sptr> const& matches,
33 unsigned int camera_dof,
34 bool with_grad )
35 : rgrl_est_proj_func<Tdim, Fdim>( matches, with_grad ),
36 camera_dof_(camera_dof),
37 image_centre_(double(0)),
38 centre_mag_norm_const_(0)
39 {
40 //modify the dof in vnl_least_squares_function
41 vnl_least_squares_function::init(this->proj_size_-1+camera_dof_,
42 this->get_number_of_residuals() );
43
44 // temperary storage space
45 temp_rad_k_.resize( camera_dof_ );
46 }
47
48 template <unsigned int Tdim, unsigned int Fdim>
49 rgrl_est_proj_rad_func<Tdim, Fdim>::
rgrl_est_proj_rad_func(unsigned int camera_dof,bool with_grad)50 rgrl_est_proj_rad_func( unsigned int camera_dof,
51 bool with_grad )
52 : rgrl_est_proj_func<Tdim, Fdim>( with_grad ),
53 camera_dof_(camera_dof),
54 image_centre_(double(0)),
55 centre_mag_norm_const_(0)
56 {
57 //modify the dof in vnl_least_squares_function
58 vnl_least_squares_function::init(this->proj_size_-1+camera_dof_,
59 this->get_number_of_residuals() );
60
61 // temperary storage space
62 temp_rad_k_.resize( camera_dof_ );
63 }
64
65 //: convert parameters
66 template <unsigned int Tdim, unsigned int Fdim>
67 void
68 rgrl_est_proj_rad_func<Tdim, Fdim>::
convert_parameters(vnl_vector<double> & params,vnl_matrix_fixed<double,Tdim+1,Fdim+1> const & proj_matrix,std::vector<double> const & rad_dist,vnl_vector_fixed<double,Fdim> const & fc,vnl_vector_fixed<double,Tdim> const & tc,vnl_vector_fixed<double,Tdim> const & camera_centre)69 convert_parameters( vnl_vector<double>& params,
70 vnl_matrix_fixed<double, Tdim+1, Fdim+1>const& proj_matrix,
71 std::vector<double> const& rad_dist,
72 vnl_vector_fixed<double, Fdim> const& fc,
73 vnl_vector_fixed<double, Tdim> const& tc,
74 vnl_vector_fixed<double, Tdim> const& camera_centre )
75 {
76 // get params from the projection matrix
77 vnl_vector<double> proj_params;
78 rgrl_est_proj_func<Tdim, Fdim>::convert_parameters( proj_params, proj_matrix, fc, tc );
79
80 // check camera dof
81 assert( rad_dist.size() == camera_dof_ );
82
83 // copy to new params
84 params.set_size( proj_params.size() + rad_dist.size() );
85 for ( unsigned i=0; i<proj_params.size(); ++i )
86 params[i] = proj_params[i];
87 for ( unsigned i=0; i<rad_dist.size(); ++i )
88 params[i+proj_params.size()] = rad_dist[i];
89
90 // set camera centre
91 set_image_centre( camera_centre );
92 }
93
94 template <unsigned int Tdim, unsigned int Fdim>
95 void
96 rgrl_est_proj_rad_func<Tdim, Fdim>::
transfer_radial_params_into_temp_storage(vnl_vector<double> const & params) const97 transfer_radial_params_into_temp_storage( vnl_vector<double> const& params ) const
98 {
99 assert( params.size() + 1 == this->proj_size_ + camera_dof_ );
100 const unsigned int index_shift = this->proj_size_-1;
101 for ( unsigned i=0; i<camera_dof_; ++i )
102 temp_rad_k_[i] = params[index_shift+i];
103 }
104
105 //: convert parameters
106 template <unsigned int Tdim, unsigned int Fdim>
107 inline
108 void
109 rgrl_est_proj_rad_func<Tdim, Fdim>::
apply_radial_distortion(vnl_vector_fixed<double,Tdim> & mapped,vnl_vector_fixed<double,Tdim> const & p,std::vector<double> const & radk) const110 apply_radial_distortion( vnl_vector_fixed<double, Tdim> & mapped,
111 vnl_vector_fixed<double, Tdim> const& p,
112 std::vector<double> const& radk ) const
113 {
114 assert( radk.size() == camera_dof_ );
115
116 vnl_vector_fixed<double, Tdim> centred = p-image_centre_;
117 const double radial_dist = centred.squared_magnitude() / centre_mag_norm_const_;
118
119 double base = 1;
120 double coeff = 0;
121 for (double i : radk) {
122 base *= radial_dist;
123 coeff += i * base;
124 }
125 mapped = p + coeff*centred;
126 }
127
128 template <unsigned int Tdim, unsigned int Fdim>
129 void
130 rgrl_est_proj_rad_func<Tdim, Fdim>::
reduced_proj_rad_jacobian(vnl_matrix<double> & base_jac,vnl_matrix_fixed<double,Tdim+1,Fdim+1> const & proj,std::vector<double> const & rad_k,vnl_vector_fixed<double,Fdim> const & from) const131 reduced_proj_rad_jacobian( vnl_matrix<double> & base_jac,
132 vnl_matrix_fixed<double, Tdim+1, Fdim+1> const& proj,
133 std::vector<double> const& rad_k,
134 vnl_vector_fixed<double, Fdim> const& from ) const
135 {
136 assert( rad_k.size() == camera_dof_ );
137
138 const unsigned proj_dof = this->proj_size_ -1;
139 const unsigned param_size = proj_dof + camera_dof_;
140
141 // 0. set size
142 base_jac.set_size( Tdim, param_size );
143
144 // 1. get projection matrix jacobian
145 vnl_matrix_fixed<double, Tdim, (Tdim+1)*(Fdim+1)-1> dP_dp;
146 base_type::reduced_proj_jacobian( dP_dp, proj, from );
147
148 // 2. gradient w.r.t to mapped location
149 vnl_matrix_fixed<double, Tdim, Tdim > dD_dx;
150 vnl_vector_fixed<double, Tdim> mapped;
151 rgrl_est_proj_map_inhomo_point<Tdim, Fdim>( mapped, proj, from-this->from_centre_ );
152
153 const vnl_vector_fixed<double, Tdim> centred = mapped-image_centre_;
154 const double radial_dist = centred.squared_magnitude() / centre_mag_norm_const_;
155
156 // compute radial distortion coefficient
157 double base = 1;
158 double coeff = 0;
159 for (double i : rad_k) {
160 base *= radial_dist;
161 coeff += i * base;
162 }
163
164 // two part computation for dD_dx
165 // first part
166 dD_dx.set_identity();
167 dD_dx *= (1+coeff);
168
169 // second part, taking gradient on the squared radial distance
170 base = 2 / centre_mag_norm_const_;
171 for ( unsigned k=0; k<rad_k.size(); ++k )
172 {
173 const double base_coeff = double(k+1)*base*rad_k[k];
174
175 //upper triangular
176 for ( unsigned i=0; i<Tdim; ++i )
177 for ( unsigned j=i; j<Tdim; ++j ) {
178 dD_dx( i, j ) += base_coeff*centred[i]*centred[j];
179 }
180
181 // multiplication is at the end of loop,
182 // because in gradient it is to the power of (k-1), not k
183 base *= radial_dist;
184 }
185
186 // fill in lower triangular
187 for ( unsigned i=0; i<Tdim; ++i )
188 for ( unsigned j=i; j<Tdim; ++j )
189 dD_dx( j, i ) = dD_dx( i, j );
190
191
192 // 3. fill in base_jac
193 dP_dp = dD_dx * dP_dp;
194
195 for ( unsigned i=0; i<Tdim; ++i )
196 for ( unsigned j=0; j<dP_dp.cols(); ++j )
197 base_jac( i, j ) = dP_dp( i, j );
198
199 // 3. gradient w.r.t to k
200 base = 1;
201 for ( unsigned k=0; k<camera_dof_; ++k ) {
202 const unsigned index = k+proj_dof;
203 base *= radial_dist;
204
205 for ( unsigned i=0; i<Tdim; ++i )
206 base_jac( i, index ) = centred[i] * base;
207 }
208 }
209
210 template <unsigned int Tdim, unsigned int Fdim>
211 void
212 rgrl_est_proj_rad_func<Tdim, Fdim>::
full_proj_rad_jacobian(vnl_matrix<double> & base_jac,vnl_matrix_fixed<double,Tdim+1,Fdim+1> const & proj,std::vector<double> const & rad_k,vnl_vector_fixed<double,Fdim> const & from) const213 full_proj_rad_jacobian( vnl_matrix<double> & base_jac,
214 vnl_matrix_fixed<double, Tdim+1, Fdim+1> const& proj,
215 std::vector<double> const& rad_k,
216 vnl_vector_fixed<double, Fdim> const& from ) const
217 {
218 assert( rad_k.size() == camera_dof_ );
219
220 const unsigned param_size = this->proj_size_ + camera_dof_;
221
222 // 0. set size
223 base_jac.set_size( Tdim, param_size );
224
225 // 1. get projection matrix jacobian
226 vnl_matrix_fixed<double, Tdim, (Tdim+1)*(Fdim+1)> dP_dp;
227 base_type::full_proj_jacobian( dP_dp, proj, from );
228
229 // 2. gradient w.r.t to mapped location
230 vnl_matrix_fixed<double, Tdim, Tdim > dD_dx;
231 vnl_vector_fixed<double, Tdim> mapped;
232 rgrl_est_proj_map_inhomo_point<Tdim, Fdim>( mapped, proj, from-this->from_centre_ );
233
234 const vnl_vector_fixed<double, Tdim> centred = mapped-image_centre_;
235 const double radial_dist = centred.squared_magnitude() / centre_mag_norm_const_;
236
237 // compute radial distortion coefficient
238 double base = 1;
239 double coeff = 0;
240 for (double i : rad_k) {
241 base *= radial_dist;
242 coeff += i * base;
243 }
244
245 // two part computation for dD_dx
246 // first part
247 dD_dx.set_identity();
248 dD_dx *= (1+coeff);
249
250 // second part, taking gradient on the squared radial distance
251 base = 2 / centre_mag_norm_const_;
252 for ( unsigned k=0; k<rad_k.size(); ++k )
253 {
254 const double base_coeff = double(k+1)*base*rad_k[k];
255
256 //upper triangular
257 for ( unsigned i=0; i<Tdim; ++i )
258 for ( unsigned j=i; j<Tdim; ++j ) {
259 dD_dx( i, j ) += base_coeff*centred[i]*centred[j];
260 }
261
262 // multiplication is at the end of loop,
263 // because in gradient it is to the power of (k-1), not k
264 base *= radial_dist;
265 }
266
267 // fill in lower triangular
268 for ( unsigned i=0; i<Tdim; ++i )
269 for ( unsigned j=i; j<Tdim; ++j )
270 dD_dx( j, i ) = dD_dx( i, j );
271
272
273 // 3. fill in base_jac
274 dP_dp = dD_dx * dP_dp;
275
276 for ( unsigned i=0; i<Tdim; ++i )
277 for ( unsigned j=0; j<dP_dp.cols(); ++j )
278 base_jac( i, j ) = dP_dp( i, j );
279
280 // 3. gradient w.r.t to k
281 base = 1;
282 for ( unsigned k=0; k<camera_dof_; ++k ) {
283 const unsigned index = k+this->proj_size_;
284 base *= radial_dist;
285
286 for ( unsigned i=0; i<Tdim; ++i )
287 base_jac( i, index ) = centred[i] * base;
288 }
289 }
290
291 //: compute jacobian w.r.t. location
292 template <unsigned int Tdim, unsigned int Fdim>
293 void
294 rgrl_est_proj_rad_func<Tdim, Fdim>::
proj_jac_wrt_loc(vnl_matrix_fixed<double,Tdim,Fdim> & jac_loc,vnl_matrix_fixed<double,Tdim+1,Fdim+1> const & proj,std::vector<double> const & rad_k,vnl_vector_fixed<double,Fdim> const & from) const295 proj_jac_wrt_loc( vnl_matrix_fixed<double, Tdim, Fdim> & jac_loc,
296 vnl_matrix_fixed<double, Tdim+1, Fdim+1> const& proj,
297 std::vector<double> const& rad_k,
298 vnl_vector_fixed<double, Fdim> const& from ) const
299 {
300 // dP / dx
301 vnl_matrix_fixed<double, Tdim, Fdim> dP_dx;
302 rgrl_est_proj_func<Tdim, Fdim>::proj_jac_wrt_loc( dP_dx, proj, from );
303
304 // 2. gradient w.r.t to mapped location
305 vnl_matrix_fixed<double, Tdim, Tdim > dD_dx;
306 vnl_vector_fixed<double, Tdim> mapped;
307 rgrl_est_proj_map_inhomo_point<Tdim, Fdim>( mapped, proj, from-this->from_centre_ );
308
309 const vnl_vector_fixed<double, Tdim> centred = mapped-image_centre_;
310 const double radial_dist = centred.squared_magnitude() / centre_mag_norm_const_;
311
312 // compute radial distortion coefficient
313 double base = 1;
314 double coeff = 0;
315 for (double i : rad_k) {
316 base *= radial_dist;
317 coeff += i * base;
318 }
319
320 // two part computation for dD_dx
321 // first part
322 dD_dx.set_identity();
323 dD_dx *= (1+coeff);
324
325 // second part, taking gradient on the squared radial distance
326 base = 2 / centre_mag_norm_const_;
327 for ( unsigned k=0; k<rad_k.size(); ++k )
328 {
329 const double base_coeff = double(k+1)*base*rad_k[k];
330
331 //upper triangular
332 for ( unsigned i=0; i<Tdim; ++i )
333 for ( unsigned j=i; j<Tdim; ++j ) {
334 dD_dx( i, j ) += base_coeff*centred[i]*centred[j];
335 }
336
337 // multiplication is at the end of loop,
338 // because in gradient it is to the power of (k-1), not k
339 base *= radial_dist;
340 }
341
342 // fill in lower triangular
343 for ( unsigned i=0; i<Tdim; ++i )
344 for ( unsigned j=i; j<Tdim; ++j )
345 dD_dx( j, i ) = dD_dx( i, j );
346
347 // final jac
348 jac_loc = dD_dx * dP_dx;
349 }
350
351 template <unsigned int Tdim, unsigned int Fdim>
352 void
353 rgrl_est_proj_rad_func<Tdim, Fdim>::
f(vnl_vector<double> const & x,vnl_vector<double> & fx)354 f(vnl_vector<double> const& x, vnl_vector<double>& fx)
355 {
356 typedef rgrl_match_set::const_from_iterator FIter;
357 typedef FIter::to_iterator TIter;
358
359 vnl_vector_fixed<double, Tdim> distorted;
360 vnl_matrix_fixed<double, Tdim,Tdim> error_proj_sqrt;
361 unsigned int ind = 0;
362
363 // retrieve the projection matrix
364 vnl_matrix_fixed<double, Tdim+1, Fdim+1> proj;
365 base_type::restored_centered_proj( proj, x );
366
367 // retrieve the radial distortion parameters
368 transfer_radial_params_into_temp_storage( x );
369
370 for ( unsigned ms = 0; ms<this->matches_ptr_->size(); ++ms )
371 {
372 if ( (*this->matches_ptr_)[ms] != nullptr ) // if pointer is valid
373 {
374 rgrl_match_set const& one_set = *((*this->matches_ptr_)[ms]);
375 for ( FIter fi=one_set.from_begin(); fi!=one_set.from_end(); ++fi ) {
376 // map from point
377 vnl_vector_fixed<double, Fdim> from = fi.from_feature()->location();
378 map_loc( distorted, proj, temp_rad_k_, from );
379
380 for ( TIter ti=fi.begin(); ti!=fi.end(); ++ti ) {
381 vnl_vector_fixed<double, Tdim> to = ti.to_feature()->location();
382 error_proj_sqrt = ti.to_feature()->error_projector_sqrt();
383 double const wgt = std::sqrt(ti.cumulative_weight());
384 vnl_vector_fixed<double, Tdim> diff = error_proj_sqrt * (distorted - to);
385
386 // fill in
387 for ( unsigned i=0; i<Tdim; ++i )
388 fx(ind++) = wgt * diff[i];
389 }
390 }
391 }
392 }
393
394 // check
395 assert( ind == this->get_number_of_residuals() );
396 }
397
398 template <unsigned int Tdim, unsigned int Fdim>
399 void
400 rgrl_est_proj_rad_func<Tdim, Fdim>::
gradf(vnl_vector<double> const & x,vnl_matrix<double> & jacobian)401 gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian)
402 {
403 typedef rgrl_match_set::const_from_iterator FIter;
404 typedef FIter::to_iterator TIter;
405
406 assert( jacobian.rows() == this->get_number_of_residuals() );
407 assert( jacobian.cols()+1 == this->proj_size_+camera_dof_ );
408
409 vnl_matrix_fixed<double, Tdim, Tdim> error_proj_sqrt;
410 vnl_matrix<double> base_jac, jac;
411
412 // retrieve the projection matrix
413 vnl_matrix_fixed<double, Tdim+1, Fdim+1> proj;
414 base_type::restored_centered_proj( proj, x );
415
416 // retrieve the radial distortion parameters
417 transfer_radial_params_into_temp_storage( x );
418
419 unsigned int ind = 0;
420 for ( unsigned ms = 0; ms<this->matches_ptr_->size(); ++ms )
421 {
422 if ( (*this->matches_ptr_)[ms] ) // if pointer is valid
423 {
424 rgrl_match_set const& one_set = *((*this->matches_ptr_)[ms]);
425 for ( FIter fi=one_set.from_begin(); fi!=one_set.from_end(); ++fi )
426 {
427 // map from point
428 vnl_vector_fixed<double, Fdim> from = fi.from_feature()->location();
429
430 // jacobian computation
431 reduced_proj_rad_jacobian( base_jac, proj, temp_rad_k_, from );
432
433 for ( TIter ti=fi.begin(); ti!=fi.end(); ++ti ) {
434 //vnl_double_2 to = ti.to_feature()->location();
435 error_proj_sqrt = ti.to_feature()->error_projector_sqrt();
436 double const wgt = std::sqrt(ti.cumulative_weight());
437 jac = error_proj_sqrt * base_jac;
438 jac *= wgt;
439
440 // fill in
441 for ( unsigned i=0; i<Tdim; ++i,++ind ) {
442 for ( unsigned j=0; j<jac.cols(); ++j )
443 jacobian(ind,j) = jac(i, j);
444 }
445 }
446 }
447 }
448 }
449 }
450
451 template <unsigned int Tdim, unsigned int Fdim>
452 bool
453 rgrl_est_proj_rad_func<Tdim, Fdim>::
projective_estimate(vnl_matrix_fixed<double,Tdim+1,Fdim+1> & proj,std::vector<double> & rad_dist,vnl_matrix<double> & full_covar,vnl_vector_fixed<double,Fdim> & from_centre,vnl_vector_fixed<double,Tdim> & to_centre,vnl_vector_fixed<double,Tdim> const & camera_centre)454 projective_estimate( vnl_matrix_fixed<double, Tdim+1, Fdim+1>& proj,
455 std::vector<double>& rad_dist,
456 vnl_matrix<double>& full_covar,
457 vnl_vector_fixed<double, Fdim>& from_centre,
458 vnl_vector_fixed<double, Tdim>& to_centre,
459 vnl_vector_fixed<double, Tdim> const& camera_centre)
460 {
461 // compute weighted centres
462 // this function is going to resize the vnl_vector, use temporary ones instead
463 vnl_vector<double> fc, tc;
464 if ( !rgrl_est_compute_weighted_centres( *(this->matches_ptr_), fc, tc ) ) {
465 return false;
466 }
467 assert( fc.size() == from_centre.size() );
468 assert( tc.size() == to_centre.size() );
469 assert( rad_dist.size() == camera_dof_ );
470 from_centre = fc;
471 to_centre = tc;
472
473 // convert parameters
474 vnl_vector<double> p;
475 this->convert_parameters( p, proj, rad_dist, from_centre, to_centre, camera_centre );
476
477 vnl_levenberg_marquardt lm( *this );
478 //lm.set_trace( true );
479 //lm.set_check_derivatives( 1 );
480 // we don't need it to be super accurate
481 lm.set_f_tolerance( this->relative_threshold_ );
482 lm.set_max_function_evals( this->max_num_iterations_ );
483
484 bool ret;
485 if ( this->has_gradient() )
486 ret = lm.minimize_using_gradient(p);
487 else
488 ret = lm.minimize_without_gradient(p);
489 if ( !ret ) {
490 std::cerr << "Levenberg-Marquardt failed\n";
491 lm.diagnose_outcome(std::cerr);
492 return false;
493 }
494 // lm.diagnose_outcome(std::cout);
495
496 // convert parameters back into matrix form
497 this->restored_centered_proj( proj, p );
498 //std::cout << "Final params=" << proj << std::endl;
499
500 // copy distortion parameters
501 const unsigned int index_shift = this->proj_size_-1;
502 for ( unsigned i=0; i<camera_dof_; ++i )
503 rad_dist[i] = p[ i+index_shift ];
504
505 // compute covariance
506 // Jac^\top * Jac is INVERSE of the covariance
507 //
508 const unsigned int param_num = this->proj_size_+camera_dof_;
509 vnl_matrix<double> jac( this->get_number_of_residuals(), param_num-1 );
510 this->gradf( p, jac );
511 //
512
513 // SVD decomposition:
514 // Jac = U W V^\top
515 vnl_svd<double> svd( jac, this->zero_svd_thres_ );
516 if ( svd.rank()+1 < param_num ) {
517 std::cerr << "The covariance of projection matrix ranks less than "
518 << param_num-1 << "!\n"
519 << " The singular values are " << svd.W() << std::endl;
520 return false;
521 }
522
523 //invert the W matrix and square it
524 vnl_diag_matrix<double> invW( param_num-1 );
525 for ( unsigned i=0; i+1<param_num; ++i )
526 invW[i] = vnl_math::sqr( 1.0/svd.W(i) );
527
528 //compute inverse of Jac^\top * Jac
529 const vnl_matrix<double> covar( svd.V() * invW * vnl_transpose( svd.V() ) );
530
531 // convert the covar to full dof+1 matrix
532 // fill in the row and column of the fixed element
533 // with 0
534 full_covar.set_size( param_num, param_num );
535 full_covar.fill( 0.0 );
536
537 const unsigned int param_index
538 = this->index_row_*(Fdim+1)
539 + this->index_col_;
540 for ( unsigned i=0,ic=0; i<param_num; ++i,++ic ) {
541 if ( i==param_index ) { --ic; continue; }
542 for ( unsigned j=0,jc=0; j<param_num; ++j,++jc ) {
543 if ( j==param_index ) { --jc; continue; }
544 full_covar( i, j ) = covar( ic, jc );
545 }
546 }
547
548 return true;
549 }
550
551
552 #undef RGRL_EST_PROJ_RAD_FUNC_INSTANTIATE
553 #define RGRL_EST_PROJ_RAD_FUNC_INSTANTIATE( tdim, fdim ) \
554 template class rgrl_est_proj_rad_func< tdim, fdim >
555
556 #endif //rgrl_est_proj_rad_func_txx_
557