SRBA: Sparser Relative Bundle Adjustment
|
00001 /* +---------------------------------------------------------------------------+ 00002 | Mobile Robot Programming Toolkit (MRPT) | 00003 | http://www.mrpt.org/ | 00004 | | 00005 | Copyright (c) 2005-2015, Individual contributors, see AUTHORS file | 00006 | See: http://www.mrpt.org/Authors - All rights reserved. | 00007 | Released under BSD License. See details in http://www.mrpt.org/License | 00008 +---------------------------------------------------------------------------+ */ 00009 00010 #pragma once 00011 00012 #include <mrpt/math/ops_containers.h> // norm_inf() 00013 00014 namespace srba { 00015 00016 #ifndef SRBA_DETAILED_TIME_PROFILING 00017 # define SRBA_DETAILED_TIME_PROFILING 0 // Enabling this has a measurable impact in performance, so use only for debugging. 00018 #endif 00019 00020 // Macros: 00021 #if SRBA_DETAILED_TIME_PROFILING 00022 # define DETAILED_PROFILING_ENTER(_STR) m_profiler.enter(_STR); 00023 # define DETAILED_PROFILING_LEAVE(_STR) m_profiler.leave(_STR); 00024 #else 00025 # define DETAILED_PROFILING_ENTER(_STR) 00026 # define DETAILED_PROFILING_LEAVE(_STR) 00027 #endif 00028 00029 namespace internal 00030 { 00032 template <bool USE_SCHUR, bool DENSE_CHOL,class RBA_ENGINE> 00033 struct solver_engine; 00034 00035 // Implemented in lev-marq_solvers.h 00036 } 00037 00038 00039 // ------------------------------------------ 00040 // optimize_edges 00041 // (See header for docs) 00042 // ------------------------------------------ 00043 template <class KF2KF_POSE_TYPE,class LM_TYPE,class OBS_TYPE,class RBA_OPTIONS> 00044 void RbaEngine<KF2KF_POSE_TYPE,LM_TYPE,OBS_TYPE,RBA_OPTIONS>::optimize_edges( 00045 const std::vector<size_t> & run_k2k_edges_in, 00046 const std::vector<size_t> & run_feat_ids_in, 00047 TOptimizeExtraOutputInfo & out_info, 00048 const std::vector<size_t> & in_observation_indices_to_optimize 00049 ) 00050 { 00051 using namespace std; 00052 // This method deals with many common tasks to any optimizer: update Jacobians, prepare Hessians, etc. 00053 // The specific solver method details are implemented in "my_solver_t": 00054 typedef internal::solver_engine<RBA_OPTIONS::solver_t::USE_SCHUR,RBA_OPTIONS::solver_t::DENSE_CHOLESKY,rba_engine_t> my_solver_t; 00055 00056 m_profiler.enter("opt"); 00057 00058 out_info.clear(); 00059 00060 // Problem dimensions: 00061 const size_t POSE_DIMS = kf2kf_pose_t::REL_POSE_DIMS; 00062 const size_t LM_DIMS = landmark_t::LM_DIMS; 00063 const size_t OBS_DIMS = obs_t::OBS_DIMS; 00064 00065 00066 // Filter out those unknowns which for sure do not have any observation, 00067 // which can be checked by looking for empty columns in the sparse Jacobian. 00068 // ------------------------------------------------------------------------------- 00069 DETAILED_PROFILING_ENTER("opt.filter_unknowns") 00070 00071 std::vector<size_t> run_k2k_edges; run_k2k_edges.reserve(run_k2k_edges_in.size()); 00072 std::vector<size_t> run_feat_ids; run_feat_ids.reserve(run_feat_ids_in.size()); 00073 00074 std::set<TKeyFrameID> touched_KFs; // Only for stats 00075 std::set<TLandmarkID> touched_LMs; // Only for stats 00076 00077 for (size_t i=0;i<run_k2k_edges_in.size();i++) 00078 { 00079 const typename TSparseBlocksJacobians_dh_dAp::col_t & col_i = rba_state.lin_system.dh_dAp.getCol( run_k2k_edges_in[i] ); 00080 if (!col_i.empty()) { 00081 run_k2k_edges.push_back( run_k2k_edges_in[i] ); 00082 touched_KFs.insert( rba_state.k2k_edges[run_k2k_edges_in[i]].from ); 00083 touched_KFs.insert( rba_state.k2k_edges[run_k2k_edges_in[i]].to ); 00084 } 00085 else 00086 { 00087 const TKeyFrameID from = rba_state.k2k_edges[run_k2k_edges_in[i]].from; 00088 const TKeyFrameID to = rba_state.k2k_edges[run_k2k_edges_in[i]].to; 00089 mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED, true /*cerr*/); 00090 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"; 00091 mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL, true /*cerr*/); 00092 } 00093 } 00094 00095 const mrpt::utils::map_as_vector<size_t,size_t> & dh_df_remap = rba_state.lin_system.dh_df.getColInverseRemappedIndices(); 00096 for (size_t i=0;i<run_feat_ids_in.size();i++) 00097 { 00098 const TLandmarkID feat_id = run_feat_ids_in[i]; 00099 ASSERT_(feat_id<rba_state.all_lms.size()) 00100 00101 const typename rba_problem_state_t::TLandmarkEntry &lm_e = rba_state.all_lms[feat_id]; 00102 ASSERTMSG_(lm_e.rfp!=NULL, "Trying to optimize an unknown feature ID") 00103 ASSERTMSG_(!lm_e.has_known_pos,"Trying to optimize a feature with fixed (known) value") 00104 00105 mrpt::utils::map_as_vector<size_t,size_t>::const_iterator it_remap = dh_df_remap.find(feat_id); // O(1) with map_as_vector 00106 ASSERT_(it_remap != dh_df_remap.end()) 00107 00108 const typename TSparseBlocksJacobians_dh_df::col_t & col_i = rba_state.lin_system.dh_df.getCol( it_remap->second ); 00109 00110 if (!col_i.empty()) { 00111 run_feat_ids.push_back( feat_id ); 00112 touched_LMs.insert( feat_id ); 00113 } 00114 else { 00115 mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED, true /*cerr*/); 00116 std::cerr << "[RbaEngine::optimize_edges] *Warning*: Skipping optimization of k2f edge #"<< feat_id << " since no observation depends on it.\n"; 00117 mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL, true /*cerr*/); 00118 } 00119 } 00120 00121 DETAILED_PROFILING_LEAVE("opt.filter_unknowns") 00122 00123 // Build list of unknowns, and their corresponding columns in the Sparse Jacobian: 00124 // ------------------------------------------------------------------------------- 00125 const size_t nUnknowns_k2k = run_k2k_edges.size(); 00126 const size_t nUnknowns_k2f = run_feat_ids.size(); 00127 out_info.num_kf_optimized = touched_KFs.size(); 00128 out_info.num_lm_optimized = touched_LMs.size(); 00129 00130 const size_t idx_start_f = POSE_DIMS*nUnknowns_k2k; // In the vector of unknowns, the 0-based first index of the first feature variable (before that, all are SE(3) edges) 00131 const size_t nUnknowns_scalars = POSE_DIMS*nUnknowns_k2k + LM_DIMS*nUnknowns_k2f; 00132 if (!nUnknowns_scalars) 00133 { 00134 mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED, true /*cerr*/); 00135 std::cerr << "[RbaEngine::optimize_edges] *Warning*: Skipping optimization since no observation depends on any of the given variables.\n"; 00136 mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL, true /*cerr*/); 00137 return; 00138 } 00139 00140 // k2k edges: 00141 std::vector<typename TSparseBlocksJacobians_dh_dAp::col_t*> dh_dAp(nUnknowns_k2k); 00142 std::vector<k2k_edge_t *> k2k_edge_unknowns(nUnknowns_k2k); 00143 for (size_t i=0;i<nUnknowns_k2k;i++) 00144 { 00145 dh_dAp[i] = &rba_state.lin_system.dh_dAp.getCol( run_k2k_edges[i] ); 00146 k2k_edge_unknowns[i] = 00147 & rba_state.k2k_edges[run_k2k_edges[i]]; 00148 } 00149 00150 // k2f edges: 00151 std::vector<typename TSparseBlocksJacobians_dh_df::col_t*> dh_df(nUnknowns_k2f); 00152 std::vector<TRelativeLandmarkPos*> k2f_edge_unknowns(nUnknowns_k2f); 00153 for (size_t i=0;i<nUnknowns_k2f;i++) 00154 { 00155 const TLandmarkID feat_id = run_feat_ids[i]; 00156 const typename rba_problem_state_t::TLandmarkEntry &lm_e = rba_state.all_lms[feat_id]; 00157 00158 mrpt::utils::map_as_vector<size_t,size_t>::const_iterator it_remap = dh_df_remap.find(feat_id); // O(1) with map_as_vector 00159 ASSERT_(it_remap != dh_df_remap.end()) 00160 00161 dh_df[i] = &rba_state.lin_system.dh_df.getCol( it_remap->second ); 00162 k2f_edge_unknowns[i] = lm_e.rfp; 00163 } 00164 00165 // Unless stated otherwise, take into account ALL the observations involved in each 00166 // unknown (i.e. don't discard any information). 00167 // ------------------------------------------------------------------------------- 00168 DETAILED_PROFILING_ENTER("opt.build_obs_list") 00169 00170 // Mapping betwwen obs. indices in the residuals vector & the global list of all the observations: 00171 std::map<size_t,size_t> obs_global_idx2residual_idx; 00172 00173 std::vector<TObsUsed> involved_obs; // + obs. data 00174 if (in_observation_indices_to_optimize.empty()) 00175 { 00176 // Observations for k2k edges Jacobians 00177 for (size_t i=0;i<nUnknowns_k2k;i++) 00178 { 00179 // For each column, process each nonzero block: 00180 typename TSparseBlocksJacobians_dh_dAp::col_t *col = dh_dAp[i]; 00181 00182 for (typename TSparseBlocksJacobians_dh_dAp::col_t::iterator it=col->begin();it!=col->end();++it) 00183 { 00184 const size_t global_obs_idx = it->first; 00185 const size_t obs_idx = involved_obs.size(); 00186 00187 obs_global_idx2residual_idx[global_obs_idx] = obs_idx; 00188 00189 involved_obs.push_back( TObsUsed (global_obs_idx, 00190 &rba_state.all_observations[global_obs_idx] 00191 ) ); 00192 } 00193 } 00194 // Observations for k2f edges Jacobians 00195 for (size_t i=0;i<nUnknowns_k2f;i++) 00196 { 00197 // For each column, process each nonzero block: 00198 typename TSparseBlocksJacobians_dh_df::col_t *col = dh_df[i]; 00199 00200 for (typename TSparseBlocksJacobians_dh_df::col_t::iterator it=col->begin();it!=col->end();++it) 00201 { 00202 const size_t global_obs_idx = it->first; 00203 // Only add if not already added before: 00204 std::map<size_t,size_t>::const_iterator it_o = obs_global_idx2residual_idx.find(global_obs_idx); 00205 /* TSizeFlag & sf = obs_global_idx2residual_idx[global_obs_idx];*/ 00206 if (it_o == obs_global_idx2residual_idx.end()) 00207 { 00208 const size_t obs_idx = involved_obs.size(); 00209 00210 obs_global_idx2residual_idx[global_obs_idx] = obs_idx; 00211 00212 involved_obs.push_back( TObsUsed( global_obs_idx, 00213 &rba_state.all_observations[global_obs_idx] 00214 ) ); 00215 } 00216 } 00217 } 00218 } 00219 else 00220 { 00221 // use only those observations in "in_observation_indices_to_optimize": 00222 const size_t nInObs = in_observation_indices_to_optimize.size(); 00223 for (size_t i=0;i<nInObs;i++) 00224 { 00225 const size_t global_obs_idx = in_observation_indices_to_optimize[i]; 00226 const size_t obs_idx = involved_obs.size(); 00227 00228 obs_global_idx2residual_idx[global_obs_idx] = obs_idx; 00229 00230 involved_obs.push_back(TObsUsed(global_obs_idx, 00231 &rba_state.all_observations[global_obs_idx] 00232 ) ); 00233 } 00234 } 00235 00236 DETAILED_PROFILING_LEAVE("opt.build_obs_list") 00237 00238 const size_t nObs = involved_obs.size(); 00239 00240 00241 // Make a list of Keyframe IDs whose numeric spanning trees must be updated (so we only update them!) 00242 // ------------------------------------------------------------------------------- 00243 DETAILED_PROFILING_ENTER("opt.prep_list_num_tree_roots") 00244 00245 std::set<TKeyFrameID> kfs_num_spantrees_to_update; 00246 prepare_Jacobians_required_tree_roots(kfs_num_spantrees_to_update, dh_dAp, dh_df); 00247 00248 DETAILED_PROFILING_LEAVE("opt.prep_list_num_tree_roots") 00249 00250 VERBOSE_LEVEL(2) << "[OPT] KF roots whose spantrees need numeric-updates: " << mrpt::system::sprintf_container("%u", kfs_num_spantrees_to_update) <<std::endl; 00251 00252 00253 // Spanning tree: Update numerically only those entries which we really need: 00254 // ------------------------------------------------------------------------------- 00255 DETAILED_PROFILING_ENTER("opt.update_spanning_tree_num") 00256 const size_t count_span_tree_num_update = rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update, false /* don't skip those marked as updated, so update all */); 00257 DETAILED_PROFILING_LEAVE("opt.update_spanning_tree_num") 00258 00259 00260 // Re-evaluate all Jacobians numerically: 00261 // ------------------------------------------------------------------------------- 00262 std::vector<const pose_flag_t*> list_of_required_num_poses; // Filled-in by Jacobian evaluation upon first call. 00263 00264 00265 DETAILED_PROFILING_ENTER("opt.reset_Jacobs_validity") 00266 // Before evaluating Jacobians we must reset as "valid" all the involved observations. 00267 // If needed, they'll be marked as invalid by the Jacobian evaluator if just one of the components 00268 // for one observation leads to an error. 00269 for (size_t i=0;i<nObs;i++) 00270 rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1; 00271 00272 DETAILED_PROFILING_LEAVE("opt.reset_Jacobs_validity") 00273 00274 00275 DETAILED_PROFILING_ENTER("opt.recompute_all_Jacobians") 00276 const size_t count_jacobians = recompute_all_Jacobians(dh_dAp, dh_df, &list_of_required_num_poses ); 00277 DETAILED_PROFILING_LEAVE("opt.recompute_all_Jacobians") 00278 00279 // Mark all required spanning-tree numeric entries as outdated, so an exception will reveal us if 00280 // next time Jacobians are required they haven't been updated as they should: 00281 // ------------------------------------------------------------------------------- 00282 for (size_t i=0;i<list_of_required_num_poses.size();i++) 00283 list_of_required_num_poses[i]->mark_outdated(); 00284 00285 #if 0 // Save a sparse block representation of the Jacobian. 00286 { 00287 static unsigned int dbg_idx = 0; 00288 mrpt::math::CMatrixDouble Jbin; 00289 rba_state.lin_system.dh_dAp.getBinaryBlocksRepresentation(Jbin); 00290 Jbin.saveToTextFile(mrpt::format("sparse_jacobs_blocks_%05u.txt",dbg_idx), mrpt::math::MATRIX_FORMAT_INT ); 00291 rba_state.lin_system.dh_dAp.saveToTextFileAsDense(mrpt::format("sparse_jacobs_%05u.txt",dbg_idx)); 00292 ++dbg_idx; 00293 mrpt::system::pause(); 00294 } 00295 #endif 00296 00297 // ---------------------------------------------------------------------- 00298 // Compute H = J^t * J , exploting the sparse, block representation of J: 00299 // 00300 // Don't add the LevMarq's "\lambda*I" here so "H" needs not to be 00301 // modified between different LevMarq. trials with different lambda values. 00302 // ---------------------------------------------------------------------- 00303 typename hessian_traits_t::TSparseBlocksHessian_Ap HAp; 00304 typename hessian_traits_t::TSparseBlocksHessian_f Hf; 00305 typename hessian_traits_t::TSparseBlocksHessian_Apf HApf; 00306 00307 // This symbolic constructions must be done only ONCE: 00308 DETAILED_PROFILING_ENTER("opt.sparse_hessian_build_symbolic") 00309 sparse_hessian_build_symbolic( 00310 HAp,Hf,HApf, 00311 dh_dAp,dh_df 00312 ); 00313 DETAILED_PROFILING_LEAVE("opt.sparse_hessian_build_symbolic") 00314 00315 if (parameters.srba.compute_sparsity_stats) 00316 { 00317 DETAILED_PROFILING_ENTER("opt.sparsity_stats") 00318 TSparseBlocksJacobians_dh_dAp::getSparsityStats(dh_dAp, out_info.sparsity_dh_dAp_max_size, out_info.sparsity_dh_dAp_nnz ); 00319 TSparseBlocksJacobians_dh_df::getSparsityStats (dh_df , out_info.sparsity_dh_df_max_size, out_info.sparsity_dh_df_nnz ); 00320 HAp.getSparsityStats( out_info.sparsity_HAp_max_size, out_info.sparsity_HAp_nnz ); 00321 Hf.getSparsityStats( out_info.sparsity_Hf_max_size, out_info.sparsity_Hf_nnz ); 00322 HApf.getSparsityStats( out_info.sparsity_HApf_max_size, out_info.sparsity_HApf_nnz ); 00323 DETAILED_PROFILING_LEAVE("opt.sparsity_stats") 00324 } 00325 00326 // and then we only have to do a numeric evaluation upon changes: 00327 size_t nInvalidJacobs = 0; 00328 DETAILED_PROFILING_ENTER("opt.sparse_hessian_update_numeric") 00329 nInvalidJacobs += sparse_hessian_update_numeric(HAp); 00330 nInvalidJacobs += sparse_hessian_update_numeric(Hf); 00331 nInvalidJacobs += sparse_hessian_update_numeric(HApf); 00332 DETAILED_PROFILING_LEAVE("opt.sparse_hessian_update_numeric") 00333 00334 if (nInvalidJacobs) { 00335 mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED); 00336 VERBOSE_LEVEL(1) << "[OPT] " << nInvalidJacobs << " Jacobian blocks ignored for 'invalid'.\n"; 00337 mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL); 00338 } 00339 00340 VERBOSE_LEVEL(2) << "[OPT] Individual Jacobs: " << count_jacobians << " #k2k_edges=" << nUnknowns_k2k << " #k2f_edges=" << nUnknowns_k2f << " #obs=" << nObs << std::endl; 00341 VERBOSE_LEVEL(2) << "[OPT] k2k_edges to optimize: " << mrpt::system::sprintf_container("% u",run_k2k_edges) << std::endl; 00342 VERBOSE_LEVEL(2) << "[OPT] k2f_edges to optimize: " << mrpt::system::sprintf_container("% u",run_feat_ids) << std::endl; 00343 // Extra verbose: display initial value of each optimized pose: 00344 if (m_verbose_level>=2 && !run_k2k_edges.empty()) 00345 { 00346 std::cout << "[OPT] k2k_edges to optimize, initial value(s):\n"; 00347 ASSERT_(k2k_edge_unknowns.size()==run_k2k_edges.size()) 00348 for (size_t i=0;i<run_k2k_edges.size();i++) 00349 std::cout << " k2k_edge: " <<k2k_edge_unknowns[i]->from << "=>" << k2k_edge_unknowns[i]->to << ",inv_pose=" << k2k_edge_unknowns[i]->inv_pose << std::endl; 00350 } 00351 00352 // VERY IMPORTANT: For J^t*J to be invertible, we need a full rank Hessian: 00353 // nObs*OBS_DIMS >= nUnknowns_k2k*POSE_DIMS+nUnknowns_k2f*LM_DIMS 00354 // 00355 ASSERT_ABOVEEQ_(OBS_DIMS*nObs,POSE_DIMS*nUnknowns_k2k+LM_DIMS*nUnknowns_k2f) 00356 00357 // ---------------------------------------------------------------- 00358 // Iterative Levenberg Marquardt (LM) algorithm 00359 // ---------------------------------------------------------------- 00360 // LevMar parameters: 00361 double nu = 2; 00362 const double max_gradient_to_stop = 1e-15; // Stop if the infinity norm of the gradient is smaller than this 00363 double lambda = -1; // initial value. <0 = auto. 00364 00365 // Automatic guess of "lambda" = tau * max(diag(Hessian)) (Hessian=H here) 00366 if (lambda<0) 00367 { 00368 DETAILED_PROFILING_ENTER("opt.guess lambda") 00369 00370 double Hess_diag_max = 0; 00371 for (size_t i=0;i<nUnknowns_k2k;i++) 00372 { 00373 ASSERTDEB_(HAp.getCol(i).find(i)!=HAp.getCol(i).end()) 00374 00375 const double Hii_max = HAp.getCol(i)[i].num.diagonal().maxCoeff(); 00376 mrpt::utils::keep_max(Hess_diag_max, Hii_max); 00377 } 00378 for (size_t i=0;i<nUnknowns_k2f;i++) 00379 { 00380 ASSERTDEB_(Hf.getCol(i).find(i)!=Hf.getCol(i).end()) 00381 00382 const double Hii_max = Hf.getCol(i)[i].num.diagonal().maxCoeff(); 00383 mrpt::utils::keep_max(Hess_diag_max, Hii_max); 00384 } 00385 00386 const double tau = 1e-3; 00387 lambda = tau * Hess_diag_max; 00388 00389 DETAILED_PROFILING_LEAVE("opt.guess lambda") 00390 } 00391 00392 // Compute the reprojection errors: 00393 // residuals = "h(x)-z" (a vector of 2-vectors). 00394 // --------------------------------------------------------------------------------- 00395 vector_residuals_t residuals(nObs); 00396 00397 DETAILED_PROFILING_ENTER("opt.reprojection_residuals") 00398 double total_proj_error = reprojection_residuals( 00399 residuals, // Out 00400 involved_obs // In 00401 ); 00402 DETAILED_PROFILING_LEAVE("opt.reprojection_residuals") 00403 00404 double RMSE = std::sqrt(total_proj_error/nObs); 00405 00406 out_info.num_observations = nObs; 00407 out_info.num_jacobians = count_jacobians; 00408 out_info.num_kf2kf_edges_optimized = run_k2k_edges.size(); 00409 out_info.num_kf2lm_edges_optimized = run_feat_ids.size(); 00410 out_info.num_total_scalar_optimized = nUnknowns_scalars; 00411 out_info.num_span_tree_numeric_updates = count_span_tree_num_update; 00412 out_info.total_sqr_error_init = total_proj_error; 00413 00414 00415 VERBOSE_LEVEL(1) << "[OPT] LM: Initial RMSE=" << RMSE << " #Jcbs=" << count_jacobians << " #k2k_edges=" << nUnknowns_k2k << " #k2f_edges=" << nUnknowns_k2f << " #obs=" << nObs << std::endl; 00416 00417 if (parameters.srba.feedback_user_iteration) 00418 (*parameters.srba.feedback_user_iteration)(0,total_proj_error,RMSE); 00419 00420 // Compute the gradient: "grad = J^t * (h(x)-z)" 00421 // --------------------------------------------------------------------------------- 00422 Eigen::VectorXd minus_grad; // The negative of the gradient. 00423 00424 DETAILED_PROFILING_ENTER("opt.compute_minus_gradient") 00425 compute_minus_gradient(/* Out: */ minus_grad, /* In: */ dh_dAp, dh_df, residuals, obs_global_idx2residual_idx); 00426 DETAILED_PROFILING_LEAVE("opt.compute_minus_gradient") 00427 00428 00429 // Build symbolic structures for Schur complement: 00430 // --------------------------------------------------------------------------------- 00431 my_solver_t my_solver( 00432 m_verbose_level, m_profiler, 00433 HAp,Hf,HApf, // The different symbolic/numeric Hessian 00434 minus_grad, // minus gradient of the Ap part 00435 nUnknowns_k2k, 00436 nUnknowns_k2f); 00437 // Notice: At this point, the constructor of "my_solver_t" might have already built the Schur-complement 00438 // of HAp-HApf into HAp: it's overwritten there (Only if RBA_OPTIONS::solver_t::USE_SCHUR=true). 00439 00440 const double MAX_LAMBDA = this->parameters.srba.max_lambda; 00441 00442 // These are defined here to avoid allocatin/deallocating memory with each iteration: 00443 vector<k2k_edge_t> old_k2k_edge_unknowns; 00444 vector<pose_flag_t> old_span_tree; // In the same order than "list_of_required_num_poses" 00445 vector<TRelativeLandmarkPos> old_k2f_edge_unknowns; 00446 00447 #if SRBA_DETAILED_TIME_PROFILING 00448 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) ); 00449 #endif 00450 00451 // LevMar iterations ------------------------------------- 00452 size_t iter; // Declared here so we can read the final # of iterations out of the "for" loop. 00453 bool stop = false; 00454 for (iter=0; iter<this->parameters.srba.max_iters && !stop; iter++) 00455 { 00456 DETAILED_PROFILING_ENTER(sLabelProfilerLM_iter.c_str()) 00457 00458 // Try with different rho's until a better solution is found: 00459 double rho = 0; 00460 if (lambda>=MAX_LAMBDA) 00461 { 00462 stop=true; 00463 VERBOSE_LEVEL(2) << "[OPT] LM end criterion: lambda too large. " << lambda << ">=" <<MAX_LAMBDA<<endl; 00464 } 00465 if (RMSE < this->parameters.srba.max_error_per_obs_to_stop) 00466 { 00467 stop=true; 00468 VERBOSE_LEVEL(2) << "[OPT] LM end criterion: error too small. " << RMSE << "<" <<this->parameters.srba.max_error_per_obs_to_stop<<endl; 00469 } 00470 00471 while(rho<=0 && !stop) 00472 { 00473 // ------------------------------------------------------------------------- 00474 // Build the matrix (Hessian+ \lambda I) and decompose it with Cholesky: 00475 // ------------------------------------------------------------------------- 00476 if ( !my_solver.solve(lambda) ) 00477 { 00478 // not positive definite so increase lambda and try again 00479 lambda *= nu; 00480 nu *= 2.; 00481 stop = (lambda>MAX_LAMBDA); 00482 00483 VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " NotDefPos in Cholesky. Retrying with lambda=" << lambda << std::endl; 00484 continue; 00485 } 00486 00487 // Make a copy of the old edge values, just in case we need to restore them back... 00488 // ---------------------------------------------------------------------------------- 00489 DETAILED_PROFILING_ENTER("opt.make_backup_copy_edges") 00490 00491 old_k2k_edge_unknowns.resize(nUnknowns_k2k); 00492 for (size_t i=0;i<nUnknowns_k2k;i++) 00493 { 00494 old_k2k_edge_unknowns[i] = *k2k_edge_unknowns[i]; 00495 } 00496 00497 old_k2f_edge_unknowns.resize(nUnknowns_k2f); 00498 for (size_t i=0;i<nUnknowns_k2f;i++) 00499 { 00500 old_k2f_edge_unknowns[i] = *k2f_edge_unknowns[i]; 00501 } 00502 00503 DETAILED_PROFILING_LEAVE("opt.make_backup_copy_edges") 00504 00505 // Add SE(2/3) deltas to the k2k edges: 00506 // ------------------------------------ 00507 DETAILED_PROFILING_ENTER("opt.add_se3_deltas_to_frames") 00508 for (size_t i=0;i<nUnknowns_k2k;i++) 00509 { 00510 // edges_to_optimize: 00511 const pose_t &old_pose = k2k_edge_unknowns[i]->inv_pose; 00512 pose_t new_pose(mrpt::poses::UNINITIALIZED_POSE); 00513 00514 // Use the Lie Algebra methods for the increment: 00515 const mrpt::math::CArrayDouble<POSE_DIMS> incr( & my_solver.delta_eps[POSE_DIMS*i] ); 00516 pose_t incrPose(mrpt::poses::UNINITIALIZED_POSE); 00517 se_traits_t::pseudo_exp(incr,incrPose); // incrPose = exp(incr) (Lie algebra pseudo-exponential map) 00518 00519 //new_pose = old_pose [+] delta 00520 // = exp(delta) (+) old_pose 00521 new_pose.composeFrom(incrPose, old_pose); 00522 00523 VERBOSE_LEVEL(3) << "[OPT] Update k2k_edge[" <<i<< "]: eps=" << incr.transpose() << "\n" << " such that: " << old_pose << " => " << new_pose << "\n"; 00524 00525 // Overwrite problem graph: 00526 k2k_edge_unknowns[i]->inv_pose = new_pose; 00527 } 00528 DETAILED_PROFILING_LEAVE("opt.add_se3_deltas_to_frames") 00529 00530 00531 // Add R^3 deltas to the k2f edges: 00532 // ------------------------------------ 00533 DETAILED_PROFILING_ENTER("opt.add_deltas_to_feats") 00534 for (size_t i=0;i<nUnknowns_k2f;i++) 00535 { 00536 const double *delta_feat = &my_solver.delta_eps[idx_start_f+LM_DIMS*i]; 00537 for (size_t k=0;k<LM_DIMS;k++) 00538 k2f_edge_unknowns[i]->pos[k] += delta_feat[k]; 00539 } 00540 DETAILED_PROFILING_LEAVE("opt.add_deltas_to_feats") 00541 00542 00543 // Update the Spanning tree, making a back-up copy: 00544 // ------------------------------------------------------ 00545 00546 00547 DETAILED_PROFILING_ENTER("opt.make_backup_copy_spntree_num") 00548 // DON'T: old_span_tree = rba_state.spanning_tree.num; // This works but runs in O(n) with the size of the map!! 00549 // Instead: copy just the required entries: 00550 { 00551 const size_t nReqNumPoses = list_of_required_num_poses.size(); 00552 if (old_span_tree.size()!=nReqNumPoses) old_span_tree.resize(nReqNumPoses); 00553 for (size_t i=0;i<nReqNumPoses;i++) 00554 { 00555 old_span_tree[i].pose = list_of_required_num_poses[i]->pose; 00556 } 00557 } 00558 DETAILED_PROFILING_LEAVE("opt.make_backup_copy_spntree_num") 00559 00560 00561 DETAILED_PROFILING_ENTER("opt.update_spanning_tree_num") 00562 for (size_t i=0;i<list_of_required_num_poses.size();i++) 00563 list_of_required_num_poses[i]->mark_outdated(); 00564 00565 rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update, true /* Only those marked as outdated above */); 00566 DETAILED_PROFILING_LEAVE("opt.update_spanning_tree_num") 00567 00568 // Compute new reprojection errors: 00569 // ---------------------------------- 00570 vector_residuals_t new_residuals; 00571 00572 DETAILED_PROFILING_ENTER("opt.reprojection_residuals") 00573 double new_total_proj_error = reprojection_residuals( 00574 new_residuals, // Out 00575 involved_obs // In 00576 ); 00577 DETAILED_PROFILING_LEAVE("opt.reprojection_residuals") 00578 00579 const double new_RMSE = std::sqrt(new_total_proj_error/nObs); 00580 00581 const double error_reduction_ratio = total_proj_error>0 ? (total_proj_error - new_total_proj_error)/total_proj_error : 0; 00582 00583 // is this better or worse? 00584 // ----------------------------- 00585 rho = (total_proj_error - new_total_proj_error)/ (my_solver.delta_eps.array()*(lambda*my_solver.delta_eps + minus_grad).array() ).sum(); 00586 00587 if(rho>0) 00588 { 00589 // Good: Accept new values 00590 00591 // Recalculate Jacobians? 00592 bool do_relinearize = (error_reduction_ratio<0 || error_reduction_ratio> this->parameters.srba.min_error_reduction_ratio_to_relinearize ); 00593 00594 VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " RMSE: " << RMSE << " -> " << new_RMSE << ", rho=" << rho << ", Err.reduc.ratio="<< error_reduction_ratio << " => Relinearize?:" << (do_relinearize ? "YES":"NO") << std::endl; 00595 if (parameters.srba.feedback_user_iteration) 00596 (*parameters.srba.feedback_user_iteration)(iter,new_total_proj_error,new_RMSE); 00597 00598 // Switch variables to the temptative ones, which are now accepted: 00599 // (swap where possible, since it's faster) 00600 // --------------------------------------------------------------------- 00601 residuals.swap( new_residuals ); 00602 00603 total_proj_error = new_total_proj_error; 00604 RMSE = new_RMSE; 00605 00606 if (do_relinearize) 00607 { 00608 DETAILED_PROFILING_ENTER("opt.reset_Jacobs_validity") 00609 // Before evaluating Jacobians we must reset as "valid" all the involved observations. 00610 // If needed, they'll be marked as invalid by the Jacobian evaluator if just one of the components 00611 // for one observation leads to an error. 00612 for (size_t i=0;i<nObs;i++) 00613 rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1; 00614 00615 DETAILED_PROFILING_LEAVE("opt.reset_Jacobs_validity") 00616 00617 DETAILED_PROFILING_ENTER("opt.recompute_all_Jacobians") 00618 recompute_all_Jacobians(dh_dAp, dh_df); 00619 DETAILED_PROFILING_LEAVE("opt.recompute_all_Jacobians") 00620 00621 // Recalculate Hessian: 00622 DETAILED_PROFILING_ENTER("opt.sparse_hessian_update_numeric") 00623 sparse_hessian_update_numeric(HAp); 00624 sparse_hessian_update_numeric(Hf); 00625 sparse_hessian_update_numeric(HApf); 00626 DETAILED_PROFILING_LEAVE("opt.sparse_hessian_update_numeric") 00627 00628 my_solver.realize_relinearized(); 00629 } 00630 00631 // Update gradient: 00632 DETAILED_PROFILING_ENTER("opt.compute_minus_gradient") 00633 compute_minus_gradient(/* Out: */ minus_grad, /* In: */ dh_dAp, dh_df, residuals, obs_global_idx2residual_idx); 00634 DETAILED_PROFILING_LEAVE("opt.compute_minus_gradient") 00635 00636 const double norm_inf_min_grad = mrpt::math::norm_inf(minus_grad); 00637 if (norm_inf_min_grad<=max_gradient_to_stop) 00638 { 00639 VERBOSE_LEVEL(2) << "[OPT] LM end criterion: norm_inf(minus_grad) below threshold: " << norm_inf_min_grad << " <= " <<max_gradient_to_stop<<endl; 00640 stop = true; 00641 } 00642 if (RMSE<this->parameters.srba.max_error_per_obs_to_stop) 00643 { 00644 VERBOSE_LEVEL(2) << "[OPT] LM end criterion: RMSE below threshold: " << RMSE << " < " <<this->parameters.srba.max_error_per_obs_to_stop<<endl; 00645 stop = true; 00646 } 00647 if (rho>this->parameters.srba.max_rho) 00648 { 00649 VERBOSE_LEVEL(2) << "[OPT] LM end criterion: rho above threshold: " << rho << " > " <<this->parameters.srba.max_rho<<endl; 00650 stop = true; 00651 } 00652 // Reset other vars: 00653 lambda *= 1.0/3.0; //std::max(1.0/3.0, 1-std::pow(2*rho-1,3.0) ); 00654 nu = 2.0; 00655 00656 my_solver.realize_lambda_changed(); 00657 } 00658 else 00659 { 00660 DETAILED_PROFILING_ENTER("opt.failedstep_restore_backup") 00661 00662 // Restore old values and retry again with a different lambda: 00663 //DON'T: rba_state.spanning_tree.num = old_span_tree; // NO! Don't do this, since existing pointers will break -> Copy elements one by one: 00664 { 00665 const size_t nReqNumPoses = list_of_required_num_poses.size(); 00666 for (size_t i=0;i<nReqNumPoses;i++) 00667 { 00668 const_cast<pose_flag_t*>(list_of_required_num_poses[i])->pose = old_span_tree[i].pose; 00669 } 00670 } 00671 00672 // Restore old edge values: 00673 for (size_t i=0;i<nUnknowns_k2k;i++) 00674 { 00675 *k2k_edge_unknowns[i] = old_k2k_edge_unknowns[i]; 00676 } 00677 for (size_t i=0;i<nUnknowns_k2f;i++) 00678 { 00679 *k2f_edge_unknowns[i] = old_k2f_edge_unknowns[i]; 00680 } 00681 00682 DETAILED_PROFILING_LEAVE("opt.failedstep_restore_backup") 00683 00684 VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " no update,errs: " << sqrt(total_proj_error/nObs) << " < " << sqrt(new_total_proj_error/nObs) << " lambda=" << lambda <<endl; 00685 lambda *= nu; 00686 nu *= 2.0; 00687 stop = (lambda>MAX_LAMBDA); 00688 00689 my_solver.realize_lambda_changed(); 00690 } 00691 00692 }; // end while rho 00693 00694 DETAILED_PROFILING_LEAVE(sLabelProfilerLM_iter.c_str()) 00695 00696 } // end for LM "iter" 00697 00698 #if 0 00699 std::cout << "residuals" << std::endl; 00700 for( size_t r = 0; r < residuals.size(); ++r ) 00701 { 00702 std::cout << involved_obs[r].k2f->obs.obs.feat_id << "," 00703 << residuals[r][0] << "," 00704 << residuals[r][1] << "," 00705 << residuals[r][2] << "," 00706 << residuals[r][3]; 00707 00708 const double totalres = residuals[r][0]*residuals[r][0]+ 00709 residuals[r][1]*residuals[r][1]+ 00710 residuals[r][2]*residuals[r][2]+ 00711 residuals[r][3]*residuals[r][3]; 00712 00713 if( totalres > 20 ) 00714 std::cout << " <-- spurious( " << totalres << ")"; 00715 00716 std::cout << std::endl; 00717 } 00718 cout << "done" << std::endl; 00719 #endif 00720 00721 // Final output info: 00722 out_info.total_sqr_error_final = total_proj_error; 00723 00724 // Recover information on covariances? 00725 // ---------------------------------------------- 00726 DETAILED_PROFILING_ENTER("opt.cov_recovery") 00727 rba_state.unknown_lms_inf_matrices.clear(); 00728 switch (parameters.srba.cov_recovery) 00729 { 00730 case crpNone: 00731 break; 00732 case crpLandmarksApprox: 00733 { 00734 for (size_t i=0;i<nUnknowns_k2f;i++) 00735 { 00736 if (!my_solver.was_ith_feature_invertible(i)) 00737 continue; 00738 00739 const typename hessian_traits_t::TSparseBlocksHessian_f::col_t & col_i = Hf.getCol(i); 00740 ASSERT_(col_i.rbegin()->first==i) // Make sure the last block matrix is the diagonal term of the upper-triangular matrix. 00741 00742 const typename hessian_traits_t::TSparseBlocksHessian_f::matrix_t & inf_mat_src = col_i.rbegin()->second.num; 00743 typename hessian_traits_t::TSparseBlocksHessian_f::matrix_t & inf_mat_dst = rba_state.unknown_lms_inf_matrices[ run_feat_ids[i] ]; 00744 inf_mat_dst = inf_mat_src; 00745 } 00746 } 00747 break; 00748 default: 00749 throw std::runtime_error("Unknown value found for 'parameters.srba.cov_recovery'"); 00750 } 00751 DETAILED_PROFILING_LEAVE("opt.cov_recovery") 00752 00753 if (parameters.srba.compute_condition_number) 00754 { 00755 DETAILED_PROFILING_ENTER("opt.condition_number") 00756 00757 // Evaluate conditioning number of hessians: 00758 mrpt::math::CMatrixDouble dense_HAp; 00759 HAp.getAsDense(dense_HAp, true /* recover both triangular parts */); 00760 00761 const Eigen::JacobiSVD<mrpt::math::CMatrixDouble::Base> H_svd = dense_HAp.jacobiSvd(); 00762 out_info.HAp_condition_number = H_svd.singularValues().maxCoeff()/H_svd.singularValues().minCoeff(); 00763 00764 DETAILED_PROFILING_LEAVE("opt.condition_number") 00765 } 00766 00767 // Fill in any other extra info from the solver: 00768 DETAILED_PROFILING_ENTER("opt.get_extra_results") 00769 my_solver.get_extra_results(out_info.extra_results); 00770 DETAILED_PROFILING_LEAVE("opt.get_extra_results") 00771 00772 // Extra verbose: display final value of each optimized pose: 00773 if (m_verbose_level>=2 && !run_k2k_edges.empty()) 00774 { 00775 std::cout << "[OPT] k2k_edges to optimize, final value(s):\n"; 00776 ASSERT_(k2k_edge_unknowns.size()==run_k2k_edges.size()) 00777 for (size_t i=0;i<run_k2k_edges.size();i++) 00778 std::cout << " k2k_edge: " <<k2k_edge_unknowns[i]->from << "=>" << k2k_edge_unknowns[i]->to << ",inv_pose=" << k2k_edge_unknowns[i]->inv_pose << std::endl; 00779 } 00780 00781 // Save (quick swap) the list of unknowns to the output structure, 00782 // now that these vectors are not needed anymore: 00783 out_info.optimized_k2k_edge_indices.swap(run_k2k_edges); 00784 out_info.optimized_landmark_indices.swap(run_feat_ids); 00785 00786 m_profiler.leave("opt"); 00787 out_info.obs_rmse = RMSE; 00788 00789 const bool rmse_too_high = (RMSE>parameters.srba.max_rmse_show_red_warning); 00790 if (rmse_too_high && m_verbose_level>=1) mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED); 00791 VERBOSE_LEVEL(1) << "[OPT] Final RMSE=" << RMSE << " #iters=" << iter << "\n"; 00792 if (rmse_too_high && m_verbose_level>=1) mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL); 00793 } 00794 00795 } // End of namespaces 00796