14 namespace mrpt {
namespace srba {
20 #ifndef SRBA_DETAILED_TIME_PROFILING
21 # define SRBA_DETAILED_TIME_PROFILING 0 // Enabling this has a measurable impact in performance, so use only for debugging.
25 #if SRBA_DETAILED_TIME_PROFILING
26 # define DETAILED_PROFILING_ENTER(_STR) m_profiler.enter(_STR);
27 # define DETAILED_PROFILING_LEAVE(_STR) m_profiler.leave(_STR);
29 # define DETAILED_PROFILING_ENTER(_STR)
30 # define DETAILED_PROFILING_LEAVE(_STR)
36 template <
bool USE_SCHUR,
bool DENSE_CHOL,
class RBA_ENGINE>
47 template <
class KF2KF_POSE_TYPE,
class LM_TYPE,
class OBS_TYPE,
class RBA_OPTIONS>
49 const std::vector<size_t> & run_k2k_edges_in,
50 const std::vector<size_t> & run_feat_ids_in,
52 const std::vector<size_t> & in_observation_indices_to_optimize
59 m_profiler.enter(
"opt");
62 const size_t POSE_DIMS = KF2KF_POSE_TYPE::REL_POSE_DIMS;
63 const size_t LM_DIMS = LM_TYPE::LM_DIMS;
64 const size_t OBS_DIMS = OBS_TYPE::OBS_DIMS;
72 std::vector<size_t> run_k2k_edges; run_k2k_edges.reserve(run_k2k_edges_in.size());
73 std::vector<size_t> run_feat_ids; run_feat_ids.reserve(run_feat_ids_in.size());
75 for (
size_t i=0;i<run_k2k_edges_in.size();i++)
77 const typename TSparseBlocksJacobians_dh_dAp::col_t & col_i = rba_state.lin_system.dh_dAp.getCol( run_k2k_edges_in[i] );
78 if (!col_i.empty()) run_k2k_edges.push_back( run_k2k_edges_in[i] );
81 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
82 const TKeyFrameID from = rba_state.k2k_edges[run_k2k_edges_in[i]]->from;
83 const TKeyFrameID to = rba_state.k2k_edges[run_k2k_edges_in[i]]->to;
85 const TKeyFrameID from = rba_state.k2k_edges[run_k2k_edges_in[i]].from;
86 const TKeyFrameID to = rba_state.k2k_edges[run_k2k_edges_in[i]].to;
88 std::cerr <<
"[RbaEngine::optimize_edges] *Warning*: Skipping optimization of k2k edge #"<<run_k2k_edges_in[i] <<
" (" <<from <<
"->"<<to <<
") since no observation depends on it.\n";
93 for (
size_t i=0;i<run_feat_ids_in.size();i++)
96 ASSERT_(feat_id<rba_state.all_lms.size())
99 ASSERTMSG_(lm_e.rfp!=NULL,
"Trying to optimize an unknown feature ID")
100 ASSERTMSG_(!lm_e.has_known_pos,
"Trying to optimize a feature with fixed (known) value")
105 const typename TSparseBlocksJacobians_dh_df::col_t & col_i = rba_state.lin_system.dh_df.getCol( it_remap->second );
107 if (!col_i.empty()) run_feat_ids.push_back( run_feat_ids_in[i] );
108 else { std::cerr <<
"[RbaEngine::optimize_edges] *Warning*: Skipping optimization of k2f edge #"<<run_feat_ids_in[i] <<
" since no observation depends on it.\n"; }
115 const size_t nUnknowns_k2k = run_k2k_edges.size();
116 const size_t nUnknowns_k2f = run_feat_ids.size();
118 const size_t idx_start_f = POSE_DIMS*nUnknowns_k2k;
119 const size_t nUnknowns_scalars = POSE_DIMS*nUnknowns_k2k + LM_DIMS*nUnknowns_k2f;
123 vector<typename TSparseBlocksJacobians_dh_dAp::col_t*> dh_dAp(nUnknowns_k2k);
124 vector<k2k_edge_t *> k2k_edge_unknowns(nUnknowns_k2k);
125 for (
size_t i=0;i<nUnknowns_k2k;i++)
127 dh_dAp[i] = &rba_state.lin_system.dh_dAp.getCol( run_k2k_edges[i] );
128 k2k_edge_unknowns[i] =
129 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
130 rba_state.k2k_edges[run_k2k_edges[i]].pointer();
132 & rba_state.k2k_edges[run_k2k_edges[i]];
137 vector<typename TSparseBlocksJacobians_dh_df::col_t*> dh_df(nUnknowns_k2f);
138 vector<TRelativeLandmarkPos*> k2f_edge_unknowns(nUnknowns_k2f);
139 for (
size_t i=0;i<nUnknowns_k2f;i++)
147 dh_df[i] = &rba_state.lin_system.dh_df.getCol( it_remap->second );
148 k2f_edge_unknowns[i] = lm_e.rfp;
157 std::map<size_t,size_t> obs_global_idx2residual_idx;
159 std::vector<TObsUsed> involved_obs;
160 if (in_observation_indices_to_optimize.empty())
163 for (
size_t i=0;i<nUnknowns_k2k;i++)
166 typename TSparseBlocksJacobians_dh_dAp::col_t *col = dh_dAp[i];
170 const size_t global_obs_idx = it->first;
171 const size_t obs_idx = involved_obs.size();
173 obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
175 involved_obs.push_back(
TObsUsed (global_obs_idx,
176 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
177 &(*rba_state.all_observations[global_obs_idx])
179 &rba_state.all_observations[global_obs_idx]
185 for (
size_t i=0;i<nUnknowns_k2f;i++)
188 typename TSparseBlocksJacobians_dh_df::col_t *col = dh_df[i];
192 const size_t global_obs_idx = it->first;
196 if (it_o == obs_global_idx2residual_idx.end())
198 const size_t obs_idx = involved_obs.size();
200 obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
202 involved_obs.push_back(
TObsUsed( global_obs_idx,
203 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
204 &(*rba_state.all_observations[global_obs_idx])
206 &rba_state.all_observations[global_obs_idx]
216 const size_t nInObs = in_observation_indices_to_optimize.size();
217 for (
size_t i=0;i<nInObs;i++)
219 const size_t global_obs_idx = in_observation_indices_to_optimize[i];
220 const size_t obs_idx = involved_obs.size();
222 obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
224 involved_obs.push_back(
TObsUsed(global_obs_idx,
225 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
226 &(*rba_state.all_observations[global_obs_idx])
228 &rba_state.all_observations[global_obs_idx]
236 const size_t nObs = involved_obs.size();
243 std::set<TKeyFrameID> kfs_num_spantrees_to_update;
244 prepare_Jacobians_required_tree_roots(kfs_num_spantrees_to_update, dh_dAp, dh_df);
254 const size_t count_span_tree_num_update = rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update,
false );
260 std::vector<const pose_flag_t*> list_of_required_num_poses;
267 for (
size_t i=0;i<nObs;i++)
268 rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1;
274 const size_t count_jacobians = recompute_all_Jacobians(dh_dAp, dh_df, &list_of_required_num_poses );
280 for (
size_t i=0;i<list_of_required_num_poses.size();i++)
281 list_of_required_num_poses[i]->mark_outdated();
283 #if 0 // Save a sparse block representation of the Jacobian.
286 rba_state.lin_system.dh_dAp.getBinaryBlocksRepresentation(Jbin);
288 rba_state.lin_system.dh_dAp.saveToTextFileAsDense(
"sparse_jacobs.txt");
305 sparse_hessian_build_symbolic(
312 size_t nInvalidJacobs = 0;
314 nInvalidJacobs += sparse_hessian_update_numeric(HAp);
315 nInvalidJacobs += sparse_hessian_update_numeric(Hf);
316 nInvalidJacobs += sparse_hessian_update_numeric(HApf);
320 VERBOSE_LEVEL(1) <<
"[OPT] " << nInvalidJacobs <<
" Jacobian blocks ignored for 'invalid'.\n";
322 VERBOSE_LEVEL(2) <<
"[OPT] Individual Jacobs: " << count_jacobians <<
" #k2k_edges=" << nUnknowns_k2k <<
" #k2f_edges=" << nUnknowns_k2f <<
" #obs=" << nObs << endl;
326 if (m_verbose_level>=2 && !run_k2k_edges.empty())
328 std::cout <<
"[OPT] k2k_edges to optimize, initial value(s):\n";
329 ASSERT_(k2k_edge_unknowns.size()==run_k2k_edges.size())
330 for (
size_t i=0;i<run_k2k_edges.size();i++)
331 std::cout <<
" k2k_edge: " <<k2k_edge_unknowns[i]->from <<
"=>" << k2k_edge_unknowns[i]->to <<
",inv_pose=" << k2k_edge_unknowns[i]->inv_pose << std::endl;
337 ASSERT_ABOVEEQ_(OBS_DIMS*nObs,POSE_DIMS*nUnknowns_k2k+LM_DIMS*nUnknowns_k2f)
344 const double max_gradient_to_stop = 1e-15;
352 double Hess_diag_max = 0;
353 for (
size_t i=0;i<nUnknowns_k2k;i++)
357 const double Hii_max = HAp.
getCol(i)[i].num.diagonal().maxCoeff();
360 for (
size_t i=0;i<nUnknowns_k2f;i++)
364 const double Hii_max = Hf.
getCol(i)[i].num.diagonal().maxCoeff();
368 const double tau = 1e-3;
369 lambda = tau * Hess_diag_max;
380 double total_proj_error = reprojection_residuals(
386 double RMSE = std::sqrt(total_proj_error/nObs);
397 VERBOSE_LEVEL(1) <<
"[OPT] LM: Initial RMSE=" << RMSE <<
" #Jcbs=" << count_jacobians <<
" #k2k_edges=" << nUnknowns_k2k <<
" #k2f_edges=" << nUnknowns_k2f <<
" #obs=" << nObs << endl;
399 if (parameters.srba.feedback_user_iteration)
400 (*parameters.srba.feedback_user_iteration)(0,total_proj_error,RMSE);
404 Eigen::VectorXd minus_grad;
407 compute_minus_gradient( minus_grad, dh_dAp, dh_df, residuals, obs_global_idx2residual_idx);
413 my_solver_t my_solver(
414 m_verbose_level, m_profiler,
420 const double MAX_LAMBDA = this->parameters.srba.max_lambda;
423 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
424 vector<stlplus::smart_ptr<k2k_edge_t> > old_k2k_edge_unknowns;
425 vector<stlplus::smart_ptr<pose_flag_t> > old_span_tree;
426 vector<stlplus::smart_ptr<TRelativeLandmarkPos> > old_k2f_edge_unknowns;
428 vector<k2k_edge_t> old_k2k_edge_unknowns;
429 vector<pose_flag_t> old_span_tree;
430 vector<TRelativeLandmarkPos> old_k2f_edge_unknowns;
433 const std::string sLabelProfilerLM_iter =
mrpt::format(
"opt.lm_iteration_k2k=%03u_k2f=%03u", static_cast<unsigned int>(nUnknowns_k2k), static_cast<unsigned int>(nUnknowns_k2f) );
438 for (iter=0; iter<this->parameters.srba.max_iters && !stop; iter++)
444 if (lambda>=MAX_LAMBDA)
447 VERBOSE_LEVEL(2) <<
"[OPT] LM end criterion: lambda too large. " << lambda <<
">=" <<MAX_LAMBDA<<endl;
449 if (RMSE < this->parameters.srba.max_error_per_obs_to_stop)
452 VERBOSE_LEVEL(2) <<
"[OPT] LM end criterion: error too small. " << RMSE <<
"<" <<this->parameters.srba.max_error_per_obs_to_stop<<endl;
455 while(rho<=0 && !stop)
460 if ( !my_solver.solve(lambda) )
465 stop = (lambda>MAX_LAMBDA);
467 VERBOSE_LEVEL(2) <<
"[OPT] LM iter #"<< iter <<
" NotDefPos in Cholesky. Retrying with lambda=" << lambda << endl;
475 old_k2k_edge_unknowns.resize(nUnknowns_k2k);
476 for (
size_t i=0;i<nUnknowns_k2k;i++)
478 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
481 old_k2k_edge_unknowns[i] = *k2k_edge_unknowns[i];
485 old_k2f_edge_unknowns.resize(nUnknowns_k2f);
486 for (
size_t i=0;i<nUnknowns_k2f;i++)
488 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
491 old_k2f_edge_unknowns[i] = *k2f_edge_unknowns[i];
500 for (
size_t i=0;i<nUnknowns_k2k;i++)
503 const pose_t &old_pose = k2k_edge_unknowns[i]->inv_pose;
509 se_traits_t::pseudo_exp(incr,incrPose);
513 new_pose.composeFrom(incrPose, old_pose);
515 VERBOSE_LEVEL(3) <<
"[OPT] Update k2k_edge[" <<i<<
"]: eps=" << incr.transpose() <<
"\n" <<
" such that: " << old_pose <<
" => " << new_pose <<
"\n";
518 k2k_edge_unknowns[i]->inv_pose = new_pose;
526 for (
size_t i=0;i<nUnknowns_k2f;i++)
528 const double *delta_feat = &my_solver.delta_eps[idx_start_f+LM_DIMS*i];
529 for (
size_t k=0;k<LM_DIMS;k++)
530 k2f_edge_unknowns[i]->pos[k] += delta_feat[k];
543 const size_t nReqNumPoses = list_of_required_num_poses.size();
544 if (old_span_tree.size()!=nReqNumPoses) old_span_tree.resize(nReqNumPoses);
545 for (
size_t i=0;i<nReqNumPoses;i++)
547 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
549 old_span_tree[i]->pose = list_of_required_num_poses[i]->pose;
551 old_span_tree[i].pose = list_of_required_num_poses[i]->pose;
559 for (
size_t i=0;i<list_of_required_num_poses.size();i++)
560 list_of_required_num_poses[i]->mark_outdated();
562 rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update,
true );
570 double new_total_proj_error = reprojection_residuals(
576 const double new_RMSE = std::sqrt(new_total_proj_error/nObs);
578 const double error_reduction_ratio = total_proj_error>0 ? (total_proj_error - new_total_proj_error)/total_proj_error : 0;
582 rho = (total_proj_error - new_total_proj_error)/ (my_solver.delta_eps.array()*(lambda*my_solver.delta_eps + minus_grad).array() ).
sum();
589 bool do_relinearize = (error_reduction_ratio<0 || error_reduction_ratio> this->parameters.srba.min_error_reduction_ratio_to_relinearize );
591 VERBOSE_LEVEL(2) <<
"[OPT] LM iter #"<< iter <<
" RMSE: " << RMSE <<
" -> " << new_RMSE <<
", rho=" << rho <<
", Err.reduc.ratio="<< error_reduction_ratio <<
" => Relinearize?:" << (do_relinearize ?
"YES":
"NO") << endl;
592 if (parameters.srba.feedback_user_iteration)
593 (*parameters.srba.feedback_user_iteration)(iter,new_total_proj_error,new_RMSE);
598 residuals.swap( new_residuals );
600 total_proj_error = new_total_proj_error;
609 for (
size_t i=0;i<nObs;i++)
610 rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1;
615 recompute_all_Jacobians(dh_dAp, dh_df);
620 sparse_hessian_update_numeric(HAp);
621 sparse_hessian_update_numeric(Hf);
622 sparse_hessian_update_numeric(HApf);
625 my_solver.realize_relinearized();
630 compute_minus_gradient( minus_grad, dh_dAp, dh_df, residuals, obs_global_idx2residual_idx);
633 const double norm_inf_min_grad =
norm_inf(minus_grad);
634 if (norm_inf_min_grad<=max_gradient_to_stop)
636 VERBOSE_LEVEL(2) <<
"[OPT] LM end criterion: norm_inf(minus_grad) below threshold: " << norm_inf_min_grad <<
" <= " <<max_gradient_to_stop<<endl;
639 if (RMSE<this->parameters.srba.max_error_per_obs_to_stop)
641 VERBOSE_LEVEL(2) <<
"[OPT] LM end criterion: RMSE below threshold: " << RMSE <<
" < " <<this->parameters.srba.max_error_per_obs_to_stop<<endl;
644 if (rho>this->parameters.srba.max_rho)
646 VERBOSE_LEVEL(2) <<
"[OPT] LM end criterion: rho above threshold: " << rho <<
" > " <<this->parameters.srba.max_rho<<endl;
653 my_solver.realize_lambda_changed();
662 const size_t nReqNumPoses = list_of_required_num_poses.size();
663 for (
size_t i=0;i<nReqNumPoses;i++)
665 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
666 const_cast<pose_flag_t*
>(list_of_required_num_poses[i])->pose = old_span_tree[i]->pose;
668 const_cast<pose_flag_t*
>(list_of_required_num_poses[i])->pose = old_span_tree[i].pose;
674 for (
size_t i=0;i<nUnknowns_k2k;i++)
676 *k2k_edge_unknowns[i] =
677 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
678 *old_k2k_edge_unknowns[i];
680 old_k2k_edge_unknowns[i];
683 for (
size_t i=0;i<nUnknowns_k2f;i++)
685 *k2f_edge_unknowns[i] =
686 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
687 *old_k2f_edge_unknowns[i];
689 old_k2f_edge_unknowns[i];
695 VERBOSE_LEVEL(2) <<
"[OPT] LM iter #"<< iter <<
" no update,errs: " << sqrt(total_proj_error/nObs) <<
" < " << sqrt(new_total_proj_error/nObs) <<
" lambda=" << lambda <<endl;
698 stop = (lambda>MAX_LAMBDA);
700 my_solver.realize_lambda_changed();
710 cout <<
"residuals" << endl;
711 for(
size_t r = 0; r < residuals.size(); ++r )
713 cout << involved_obs[r].k2f->obs.obs.feat_id <<
","
714 << residuals[r][0] <<
","
715 << residuals[r][1] <<
","
716 << residuals[r][2] <<
","
719 const double totalres = residuals[r][0]*residuals[r][0]+
720 residuals[r][1]*residuals[r][1]+
721 residuals[r][2]*residuals[r][2]+
722 residuals[r][3]*residuals[r][3];
725 cout <<
" <-- spurious( " << totalres <<
")";
729 cout <<
"done" << endl;
738 rba_state.unknown_lms_inf_matrices.clear();
739 switch (parameters.srba.cov_recovery)
745 for (
size_t i=0;i<nUnknowns_k2f;i++)
747 if (!my_solver.was_ith_feature_invertible(i))
750 const typename hessian_traits_t::TSparseBlocksHessian_f::col_t & col_i = Hf.
getCol(i);
751 ASSERT_(col_i.rbegin()->first==i)
753 const typename hessian_traits_t::TSparseBlocksHessian_f::matrix_t & inf_mat_src = col_i.rbegin()->second.num;
754 typename hessian_traits_t::TSparseBlocksHessian_f::matrix_t & inf_mat_dst = rba_state.unknown_lms_inf_matrices[ run_feat_ids[i] ];
755 inf_mat_dst = inf_mat_src;
760 throw std::runtime_error(
"Unknown value found for 'parameters.srba.cov_recovery'");
764 if (parameters.srba.compute_condition_number)
772 const Eigen::JacobiSVD<mrpt::math::CMatrixDouble::Base> H_svd = dense_HAp.jacobiSvd();
773 out_info.
HAp_condition_number = H_svd.singularValues().maxCoeff()/H_svd.singularValues().minCoeff();
784 if (m_verbose_level>=2 && !run_k2k_edges.empty())
786 std::cout <<
"[OPT] k2k_edges to optimize, final value(s):\n";
787 ASSERT_(k2k_edge_unknowns.size()==run_k2k_edges.size())
788 for (
size_t i=0;i<run_k2k_edges.size();i++)
789 std::cout <<
" k2k_edge: " <<k2k_edge_unknowns[i]->from <<
"=>" << k2k_edge_unknowns[i]->to <<
",inv_pose=" << k2k_edge_unknowns[i]->inv_pose << std::endl;
797 m_profiler.leave(
"opt");
800 VERBOSE_LEVEL(1) <<
"[OPT] Final RMSE=" << RMSE <<
" #iters=" << iter <<
"\n";
EIGEN_STRONG_INLINE Scalar norm_inf() const
Compute the norm-infinite of a vector ($f[ ||{v}||_ $f]), ie the maximum absolute value of the elemen...
observation_traits_t::vector_residuals_t vector_residuals_t
#define DETAILED_PROFILING_ENTER(_STR)
vec_t::const_iterator const_iterator
uint64_t TLandmarkID
Numeric IDs for landmarks.
iterator find(const size_t i)
Constant-time find, returning an iterator to the pair or to end() if not found (that is...
This file implements several operations that operate element-wise on individual or pairs of container...
Don't recover any covariance information.
const Scalar * const_iterator
Each of the observations used during the optimization.
kf2kf_pose_traits_t::pose_flag_t pose_flag_t
KF2KF_POSE_TYPE::pose_t pose_t
The type of relative poses (e.g. mrpt::poses::CPose3D)
Keyframe-to-keyframe edge: an unknown of the problem.
landmark_traits< LM_TYPE >::TLandmarkEntry TLandmarkEntry
void BASE_IMPEXP pause(const std::string &msg=std::string("Press any key to continue...")) MRPT_NO_THROWS
Shows the message "Press any key to continue" (or other custom message) to the current standard outpu...
This base provides a set of functions for maths stuff.
CONTAINER::Scalar sum(const CONTAINER &v)
Computes the sum of all the elements.
std::string BASE_IMPEXP format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
landmark_traits_t::TRelativeLandmarkPos TRelativeLandmarkPos
One landmark position (relative to its base KF)
#define DETAILED_PROFILING_LEAVE(_STR)
A templated column-indexed efficient storage of block-sparse Jacobian or Hessian matrices, together with other arbitrary information.
col_t & getCol(const size_t idx)
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
#define ASSERTDEB_(f)
Defines an assertion mechanism - only when compiled in debug.
#define ASSERT_ABOVEEQ_(__A, __B)
Generic solver declaration.
#define VERBOSE_LEVEL(_LEVEL)
A partial specialization of CArrayNumeric for double numbers.
Approximate covariances of landmarks as the inverse of the hessian diagonal blocks.
#define ASSERTMSG_(f, __ERROR_MSG)
std::string sprintf_container(const char *fmt, const T &V)
Generates a string for a container in the format [A,B,C,...], and the fmt string for each vector elem...
void keep_max(T &var, const K test_val)
If the second argument is above the first one, set the first argument to this higher value...
void optimize_edges(const std::vector< size_t > &run_k2k_edges, const std::vector< size_t > &run_feat_ids_in, TOptimizeExtraOutputInfo &out_info, const std::vector< size_t > &observation_indices_to_optimize=std::vector< size_t >())
void getAsDense(mrpt::math::CMatrixDouble &D, const bool force_symmetry=false, const bool is_col_compressed=true) const
Builds a dense representation of the matrix and saves to a text file.
uint64_t TKeyFrameID
Numeric IDs for key-frames (KFs)