Main MRPT website > C++ reference
MRPT logo
optimize_edges.h
Go to the documentation of this file.
1 /* +---------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2014, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +---------------------------------------------------------------------------+ */
9 
10 #pragma once
11 
12 #include <mrpt/math/ops_containers.h> // norm_inf()
13 
14 namespace mrpt { namespace srba {
15 
16 using namespace mrpt;
17 using namespace mrpt::math;
18 using namespace std;
19 
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.
22 #endif
23 
24 // Macros:
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);
28 #else
29 # define DETAILED_PROFILING_ENTER(_STR)
30 # define DETAILED_PROFILING_LEAVE(_STR)
31 #endif
32 
33 namespace internal
34 {
35  /** Generic solver declaration */
36  template <bool USE_SCHUR, bool DENSE_CHOL,class RBA_ENGINE>
37  struct solver_engine;
38 
39  // Implemented in lev-marq_solvers.h
40 }
41 
42 
43 // ------------------------------------------
44 // optimize_edges
45 // (See header for docs)
46 // ------------------------------------------
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,
51  TOptimizeExtraOutputInfo & out_info,
52  const std::vector<size_t> & in_observation_indices_to_optimize
53  )
54 {
55  // This method deals with many common tasks to any optimizer: update Jacobians, prepare Hessians, etc.
56  // The specific solver method details are implemented in "my_solver_t":
58 
59  m_profiler.enter("opt");
60 
61  // Problem dimensions:
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;
65 
66 
67  // Filter out those unknowns which for sure do not have any observation,
68  // which can be checked by looking for empty columns in the sparse Jacobian.
69  // -------------------------------------------------------------------------------
70  DETAILED_PROFILING_ENTER("opt.filter_unknowns")
71 
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());
74 
75  for (size_t i=0;i<run_k2k_edges_in.size();i++)
76  {
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] );
79  else
80  {
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;
84 #else
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;
87 #endif
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";
89  }
90  }
91 
92  const mrpt::utils::map_as_vector<size_t,size_t> & dh_df_remap = rba_state.lin_system.dh_df.getColInverseRemappedIndices();
93  for (size_t i=0;i<run_feat_ids_in.size();i++)
94  {
95  const TLandmarkID feat_id = run_feat_ids_in[i];
96  ASSERT_(feat_id<rba_state.all_lms.size())
97 
98  const typename rba_problem_state_t::TLandmarkEntry &lm_e = rba_state.all_lms[feat_id];
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")
101 
102  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
103  ASSERT_(it_remap != dh_df_remap.end())
104 
105  const typename TSparseBlocksJacobians_dh_df::col_t & col_i = rba_state.lin_system.dh_df.getCol( it_remap->second );
106 
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"; }
109  }
110 
111  DETAILED_PROFILING_LEAVE("opt.filter_unknowns")
112 
113  // Build list of unknowns, and their corresponding columns in the Sparse Jacobian:
114  // -------------------------------------------------------------------------------
115  const size_t nUnknowns_k2k = run_k2k_edges.size();
116  const size_t nUnknowns_k2f = run_feat_ids.size();
117 
118  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)
119  const size_t nUnknowns_scalars = POSE_DIMS*nUnknowns_k2k + LM_DIMS*nUnknowns_k2f;
120  ASSERT_(nUnknowns_scalars>=1)
121 
122  // k2k edges:
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++)
126  {
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();
131 #else
132  & rba_state.k2k_edges[run_k2k_edges[i]];
133 #endif
134  }
135 
136  // k2f edges:
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++)
140  {
141  const TLandmarkID feat_id = run_feat_ids[i];
142  const typename rba_problem_state_t::TLandmarkEntry &lm_e = rba_state.all_lms[feat_id];
143 
144  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
145  ASSERT_(it_remap != dh_df_remap.end())
146 
147  dh_df[i] = &rba_state.lin_system.dh_df.getCol( it_remap->second );
148  k2f_edge_unknowns[i] = lm_e.rfp;
149  }
150 
151  // Unless stated otherwise, take into account ALL the observations involved in each
152  // unknown (i.e. don't discard any information).
153  // -------------------------------------------------------------------------------
154  DETAILED_PROFILING_ENTER("opt.build_obs_list")
155 
156  // Mapping betwwen obs. indices in the residuals vector & the global list of all the observations:
157  std::map<size_t,size_t> obs_global_idx2residual_idx;
158 
159  std::vector<TObsUsed> involved_obs; // + obs. data
160  if (in_observation_indices_to_optimize.empty())
161  {
162  // Observations for k2k edges Jacobians
163  for (size_t i=0;i<nUnknowns_k2k;i++)
164  {
165  // For each column, process each nonzero block:
166  typename TSparseBlocksJacobians_dh_dAp::col_t *col = dh_dAp[i];
167 
168  for (typename TSparseBlocksJacobians_dh_dAp::col_t::iterator it=col->begin();it!=col->end();++it)
169  {
170  const size_t global_obs_idx = it->first;
171  const size_t obs_idx = involved_obs.size();
172 
173  obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
174 
175  involved_obs.push_back( TObsUsed (global_obs_idx,
176 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
177  &(*rba_state.all_observations[global_obs_idx])
178 #else
179  &rba_state.all_observations[global_obs_idx]
180 #endif
181  ) );
182  }
183  }
184  // Observations for k2f edges Jacobians
185  for (size_t i=0;i<nUnknowns_k2f;i++)
186  {
187  // For each column, process each nonzero block:
188  typename TSparseBlocksJacobians_dh_df::col_t *col = dh_df[i];
189 
190  for (typename TSparseBlocksJacobians_dh_df::col_t::iterator it=col->begin();it!=col->end();++it)
191  {
192  const size_t global_obs_idx = it->first;
193  // Only add if not already added before:
194  std::map<size_t,size_t>::const_iterator it_o = obs_global_idx2residual_idx.find(global_obs_idx);
195  /* TSizeFlag & sf = obs_global_idx2residual_idx[global_obs_idx];*/
196  if (it_o == obs_global_idx2residual_idx.end())
197  {
198  const size_t obs_idx = involved_obs.size();
199 
200  obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
201 
202  involved_obs.push_back( TObsUsed( global_obs_idx,
203 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
204  &(*rba_state.all_observations[global_obs_idx])
205 #else
206  &rba_state.all_observations[global_obs_idx]
207 #endif
208  ) );
209  }
210  }
211  }
212  }
213  else
214  {
215  // use only those observations in "in_observation_indices_to_optimize":
216  const size_t nInObs = in_observation_indices_to_optimize.size();
217  for (size_t i=0;i<nInObs;i++)
218  {
219  const size_t global_obs_idx = in_observation_indices_to_optimize[i];
220  const size_t obs_idx = involved_obs.size();
221 
222  obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
223 
224  involved_obs.push_back(TObsUsed(global_obs_idx,
225 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
226  &(*rba_state.all_observations[global_obs_idx])
227 #else
228  &rba_state.all_observations[global_obs_idx]
229 #endif
230  ) );
231  }
232  }
233 
234  DETAILED_PROFILING_LEAVE("opt.build_obs_list")
235 
236  const size_t nObs = involved_obs.size();
237 
238 
239  // Make a list of Keyframe IDs whose numeric spanning trees must be updated (so we only update them!)
240  // -------------------------------------------------------------------------------
241  DETAILED_PROFILING_ENTER("opt.prep_list_num_tree_roots")
242 
243  std::set<TKeyFrameID> kfs_num_spantrees_to_update;
244  prepare_Jacobians_required_tree_roots(kfs_num_spantrees_to_update, dh_dAp, dh_df);
245 
246  DETAILED_PROFILING_LEAVE("opt.prep_list_num_tree_roots")
247 
248  VERBOSE_LEVEL(2) << "[OPT] KF roots whose spantrees need numeric-updates: " << mrpt::system::sprintf_container("%u", kfs_num_spantrees_to_update) <<endl;
249 
250 
251  // Spanning tree: Update numerically only those entries which we really need:
252  // -------------------------------------------------------------------------------
253  DETAILED_PROFILING_ENTER("opt.update_spanning_tree_num")
254  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 */);
255  DETAILED_PROFILING_LEAVE("opt.update_spanning_tree_num")
256 
257 
258  // Re-evaluate all Jacobians numerically:
259  // -------------------------------------------------------------------------------
260  std::vector<const pose_flag_t*> list_of_required_num_poses; // Filled-in by Jacobian evaluation upon first call.
261 
262 
263  DETAILED_PROFILING_ENTER("opt.reset_Jacobs_validity")
264  // Before evaluating Jacobians we must reset as "valid" all the involved observations.
265  // If needed, they'll be marked as invalid by the Jacobian evaluator if just one of the components
266  // for one observation leads to an error.
267  for (size_t i=0;i<nObs;i++)
268  rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1;
269 
270  DETAILED_PROFILING_LEAVE("opt.reset_Jacobs_validity")
271 
272 
273  DETAILED_PROFILING_ENTER("opt.recompute_all_Jacobians")
274  const size_t count_jacobians = recompute_all_Jacobians(dh_dAp, dh_df, &list_of_required_num_poses );
275  DETAILED_PROFILING_LEAVE("opt.recompute_all_Jacobians")
276 
277  // Mark all required spanning-tree numeric entries as outdated, so an exception will reveal us if
278  // next time Jacobians are required they haven't been updated as they should:
279  // -------------------------------------------------------------------------------
280  for (size_t i=0;i<list_of_required_num_poses.size();i++)
281  list_of_required_num_poses[i]->mark_outdated();
282 
283 #if 0 // Save a sparse block representation of the Jacobian.
284  {
285  CMatrixDouble Jbin;
286  rba_state.lin_system.dh_dAp.getBinaryBlocksRepresentation(Jbin);
287  Jbin.saveToTextFile("sparse_jacobs_blocks.txt", mrpt::math::MATRIX_FORMAT_INT );
288  rba_state.lin_system.dh_dAp.saveToTextFileAsDense("sparse_jacobs.txt");
290  }
291 #endif
292 
293  // ----------------------------------------------------------------------
294  // Compute H = J^t * J , exploting the sparse, block representation of J:
295  //
296  // Don't add the LevMarq's "\lambda*I" here so "H" needs not to be
297  // modified between different LevMarq. trials with different lambda values.
298  // ----------------------------------------------------------------------
302 
303  // This symbolic constructions must be done only ONCE:
304  DETAILED_PROFILING_ENTER("opt.sparse_hessian_build_symbolic")
305  sparse_hessian_build_symbolic(
306  HAp,Hf,HApf,
307  dh_dAp,dh_df
308  );
309  DETAILED_PROFILING_LEAVE("opt.sparse_hessian_build_symbolic")
310 
311  // and then we only have to do a numeric evaluation upon changes:
312  size_t nInvalidJacobs = 0;
313  DETAILED_PROFILING_ENTER("opt.sparse_hessian_update_numeric")
314  nInvalidJacobs += sparse_hessian_update_numeric(HAp);
315  nInvalidJacobs += sparse_hessian_update_numeric(Hf);
316  nInvalidJacobs += sparse_hessian_update_numeric(HApf);
317  DETAILED_PROFILING_LEAVE("opt.sparse_hessian_update_numeric")
318 
319  if (nInvalidJacobs)
320  VERBOSE_LEVEL(1) << "[OPT] " << nInvalidJacobs << " Jacobian blocks ignored for 'invalid'.\n";
321 
322  VERBOSE_LEVEL(2) << "[OPT] Individual Jacobs: " << count_jacobians << " #k2k_edges=" << nUnknowns_k2k << " #k2f_edges=" << nUnknowns_k2f << " #obs=" << nObs << endl;
323  VERBOSE_LEVEL(2) << "[OPT] k2k_edges to optimize: " << mrpt::system::sprintf_container("% u",run_k2k_edges) << endl;
324  VERBOSE_LEVEL(2) << "[OPT] k2f_edges to optimize: " << mrpt::system::sprintf_container("% u",run_feat_ids) << endl;
325  // Extra verbose: display initial value of each optimized pose:
326  if (m_verbose_level>=2 && !run_k2k_edges.empty())
327  {
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;
332  }
333 
334  // VERY IMPORTANT: For J^t*J to be invertible, we need a full rank Hessian:
335  // nObs*OBS_DIMS >= nUnknowns_k2k*POSE_DIMS+nUnknowns_k2f*LM_DIMS
336  //
337  ASSERT_ABOVEEQ_(OBS_DIMS*nObs,POSE_DIMS*nUnknowns_k2k+LM_DIMS*nUnknowns_k2f)
338 
339  // ----------------------------------------------------------------
340  // Iterative Levenberg Marquardt (LM) algorithm
341  // ----------------------------------------------------------------
342  // LevMar parameters:
343  double nu = 2;
344  const double max_gradient_to_stop = 1e-15; // Stop if the infinity norm of the gradient is smaller than this
345  double lambda = -1; // initial value. <0 = auto.
346 
347  // Automatic guess of "lambda" = tau * max(diag(Hessian)) (Hessian=H here)
348  if (lambda<0)
349  {
350  DETAILED_PROFILING_ENTER("opt.guess lambda")
351 
352  double Hess_diag_max = 0;
353  for (size_t i=0;i<nUnknowns_k2k;i++)
354  {
355  ASSERTDEB_(HAp.getCol(i).find(i)!=HAp.getCol(i).end())
356 
357  const double Hii_max = HAp.getCol(i)[i].num.diagonal().maxCoeff();
358  mrpt::utils::keep_max(Hess_diag_max, Hii_max);
359  }
360  for (size_t i=0;i<nUnknowns_k2f;i++)
361  {
362  ASSERTDEB_(Hf.getCol(i).find(i)!=Hf.getCol(i).end())
363 
364  const double Hii_max = Hf.getCol(i)[i].num.diagonal().maxCoeff();
365  mrpt::utils::keep_max(Hess_diag_max, Hii_max);
366  }
367 
368  const double tau = 1e-3;
369  lambda = tau * Hess_diag_max;
370 
371  DETAILED_PROFILING_LEAVE("opt.guess lambda")
372  }
373 
374  // Compute the reprojection errors:
375  // residuals = "h(x)-z" (a vector of 2-vectors).
376  // ---------------------------------------------------------------------------------
377  vector_residuals_t residuals(nObs);
378 
379  DETAILED_PROFILING_ENTER("opt.reprojection_residuals")
380  double total_proj_error = reprojection_residuals(
381  residuals, // Out
382  involved_obs // In
383  );
384  DETAILED_PROFILING_LEAVE("opt.reprojection_residuals")
385 
386  double RMSE = std::sqrt(total_proj_error/nObs);
387 
388  out_info.num_observations = nObs;
389  out_info.num_jacobians = count_jacobians;
390  out_info.num_kf2kf_edges_optimized = run_k2k_edges.size();
391  out_info.num_kf2lm_edges_optimized = run_feat_ids.size();
392  out_info.num_total_scalar_optimized = nUnknowns_scalars;
393  out_info.num_span_tree_numeric_updates = count_span_tree_num_update;
394  out_info.total_sqr_error_init = total_proj_error;
395 
396 
397  VERBOSE_LEVEL(1) << "[OPT] LM: Initial RMSE=" << RMSE << " #Jcbs=" << count_jacobians << " #k2k_edges=" << nUnknowns_k2k << " #k2f_edges=" << nUnknowns_k2f << " #obs=" << nObs << endl;
398 
399  if (parameters.srba.feedback_user_iteration)
400  (*parameters.srba.feedback_user_iteration)(0,total_proj_error,RMSE);
401 
402  // Compute the gradient: "grad = J^t * (h(x)-z)"
403  // ---------------------------------------------------------------------------------
404  Eigen::VectorXd minus_grad; // The negative of the gradient.
405 
406  DETAILED_PROFILING_ENTER("opt.compute_minus_gradient")
407  compute_minus_gradient(/* Out: */ minus_grad, /* In: */ dh_dAp, dh_df, residuals, obs_global_idx2residual_idx);
408  DETAILED_PROFILING_LEAVE("opt.compute_minus_gradient")
409 
410 
411  // Build symbolic structures for Schur complement:
412  // ---------------------------------------------------------------------------------
413  my_solver_t my_solver(
414  m_verbose_level, m_profiler,
415  HAp,Hf,HApf, // The different symbolic/numeric Hessian
416  minus_grad, // minus gradient of the Ap part
417  nUnknowns_k2k,
418  nUnknowns_k2f);
419 
420  const double MAX_LAMBDA = this->parameters.srba.max_lambda;
421 
422  // These are defined here to avoid allocatin/deallocating memory with each iteration:
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; // In the same order than "list_of_required_num_poses"
426  vector<stlplus::smart_ptr<TRelativeLandmarkPos> > old_k2f_edge_unknowns;
427 #else
428  vector<k2k_edge_t> old_k2k_edge_unknowns;
429  vector<pose_flag_t> old_span_tree; // In the same order than "list_of_required_num_poses"
430  vector<TRelativeLandmarkPos> old_k2f_edge_unknowns;
431 #endif
432 
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) );
434 
435  // LevMar iterations -------------------------------------
436  size_t iter; // Declared here so we can read the final # of iterations out of the "for" loop.
437  bool stop = false;
438  for (iter=0; iter<this->parameters.srba.max_iters && !stop; iter++)
439  {
440  DETAILED_PROFILING_ENTER(sLabelProfilerLM_iter.c_str())
441 
442  // Try with different rho's until a better solution is found:
443  double rho = 0;
444  if (lambda>=MAX_LAMBDA)
445  {
446  stop=true;
447  VERBOSE_LEVEL(2) << "[OPT] LM end criterion: lambda too large. " << lambda << ">=" <<MAX_LAMBDA<<endl;
448  }
449  if (RMSE < this->parameters.srba.max_error_per_obs_to_stop)
450  {
451  stop=true;
452  VERBOSE_LEVEL(2) << "[OPT] LM end criterion: error too small. " << RMSE << "<" <<this->parameters.srba.max_error_per_obs_to_stop<<endl;
453  }
454 
455  while(rho<=0 && !stop)
456  {
457  // -------------------------------------------------------------------------
458  // Build the matrix (Hessian+ \lambda I) and decompose it with Cholesky:
459  // -------------------------------------------------------------------------
460  if ( !my_solver.solve(lambda) )
461  {
462  // not positive definite so increase lambda and try again
463  lambda *= nu;
464  nu *= 2.;
465  stop = (lambda>MAX_LAMBDA);
466 
467  VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " NotDefPos in Cholesky. Retrying with lambda=" << lambda << endl;
468  continue;
469  }
470 
471  // Make a copy of the old edge values, just in case we need to restore them back...
472  // ----------------------------------------------------------------------------------
473  DETAILED_PROFILING_ENTER("opt.make_backup_copy_edges")
474 
475  old_k2k_edge_unknowns.resize(nUnknowns_k2k);
476  for (size_t i=0;i<nUnknowns_k2k;i++)
477  {
478 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
479  old_k2k_edge_unknowns[i] = stlplus::smart_ptr<k2k_edge_t>( new k2k_edge_t(*k2k_edge_unknowns[i] ) );
480 #else
481  old_k2k_edge_unknowns[i] = *k2k_edge_unknowns[i];
482 #endif
483  }
484 
485  old_k2f_edge_unknowns.resize(nUnknowns_k2f);
486  for (size_t i=0;i<nUnknowns_k2f;i++)
487  {
488 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
489  old_k2f_edge_unknowns[i] = stlplus::smart_ptr<TRelativeLandmarkPos>(new TRelativeLandmarkPos(*k2f_edge_unknowns[i]));
490 #else
491  old_k2f_edge_unknowns[i] = *k2f_edge_unknowns[i];
492 #endif
493  }
494 
495  DETAILED_PROFILING_LEAVE("opt.make_backup_copy_edges")
496 
497  // Add SE(2/3) deltas to the k2k edges:
498  // ------------------------------------
499  DETAILED_PROFILING_ENTER("opt.add_se3_deltas_to_frames")
500  for (size_t i=0;i<nUnknowns_k2k;i++)
501  {
502  // edges_to_optimize:
503  const pose_t &old_pose = k2k_edge_unknowns[i]->inv_pose;
505 
506  // Use the Lie Algebra methods for the increment:
507  const CArrayDouble<POSE_DIMS> incr( & my_solver.delta_eps[POSE_DIMS*i] );
509  se_traits_t::pseudo_exp(incr,incrPose); // incrPose = exp(incr) (Lie algebra pseudo-exponential map)
510 
511  //new_pose = old_pose [+] delta
512  // = exp(delta) (+) old_pose
513  new_pose.composeFrom(incrPose, old_pose);
514 
515  VERBOSE_LEVEL(3) << "[OPT] Update k2k_edge[" <<i<< "]: eps=" << incr.transpose() << "\n" << " such that: " << old_pose << " => " << new_pose << "\n";
516 
517  // Overwrite problem graph:
518  k2k_edge_unknowns[i]->inv_pose = new_pose;
519  }
520  DETAILED_PROFILING_LEAVE("opt.add_se3_deltas_to_frames")
521 
522 
523  // Add R^3 deltas to the k2f edges:
524  // ------------------------------------
525  DETAILED_PROFILING_ENTER("opt.add_deltas_to_feats")
526  for (size_t i=0;i<nUnknowns_k2f;i++)
527  {
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];
531  }
532  DETAILED_PROFILING_LEAVE("opt.add_deltas_to_feats")
533 
534 
535  // Update the Spanning tree, making a back-up copy:
536  // ------------------------------------------------------
537 
538 
539  DETAILED_PROFILING_ENTER("opt.make_backup_copy_spntree_num")
540  // DON'T: old_span_tree = rba_state.spanning_tree.num; // This works but runs in O(n) with the size of the map!!
541  // Instead: copy just the required entries:
542  {
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++)
546  {
547 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
548  old_span_tree[i] = stlplus::smart_ptr<pose_flag_t>(new pose_flag_t);
549  old_span_tree[i]->pose = list_of_required_num_poses[i]->pose;
550 #else
551  old_span_tree[i].pose = list_of_required_num_poses[i]->pose;
552 #endif
553  }
554  }
555  DETAILED_PROFILING_LEAVE("opt.make_backup_copy_spntree_num")
556 
557 
558  DETAILED_PROFILING_ENTER("opt.update_spanning_tree_num")
559  for (size_t i=0;i<list_of_required_num_poses.size();i++)
560  list_of_required_num_poses[i]->mark_outdated();
561 
562  rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update, true /* Only those marked as outdated above */);
563  DETAILED_PROFILING_LEAVE("opt.update_spanning_tree_num")
564 
565  // Compute new reprojection errors:
566  // ----------------------------------
567  vector_residuals_t new_residuals;
568 
569  DETAILED_PROFILING_ENTER("opt.reprojection_residuals")
570  double new_total_proj_error = reprojection_residuals(
571  new_residuals, // Out
572  involved_obs // In
573  );
574  DETAILED_PROFILING_LEAVE("opt.reprojection_residuals")
575 
576  const double new_RMSE = std::sqrt(new_total_proj_error/nObs);
577 
578  const double error_reduction_ratio = total_proj_error>0 ? (total_proj_error - new_total_proj_error)/total_proj_error : 0;
579 
580  // is this better or worse?
581  // -----------------------------
582  rho = (total_proj_error - new_total_proj_error)/ (my_solver.delta_eps.array()*(lambda*my_solver.delta_eps + minus_grad).array() ).sum();
583 
584  if(rho>0)
585  {
586  // Good: Accept new values
587 
588  // Recalculate Jacobians?
589  bool do_relinearize = (error_reduction_ratio<0 || error_reduction_ratio> this->parameters.srba.min_error_reduction_ratio_to_relinearize );
590 
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);
594 
595  // Switch variables to the temptative ones, which are now accepted:
596  // (swap where possible, since it's faster)
597  // ---------------------------------------------------------------------
598  residuals.swap( new_residuals );
599 
600  total_proj_error = new_total_proj_error;
601  RMSE = new_RMSE;
602 
603  if (do_relinearize)
604  {
605  DETAILED_PROFILING_ENTER("opt.reset_Jacobs_validity")
606  // Before evaluating Jacobians we must reset as "valid" all the involved observations.
607  // If needed, they'll be marked as invalid by the Jacobian evaluator if just one of the components
608  // for one observation leads to an error.
609  for (size_t i=0;i<nObs;i++)
610  rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1;
611 
612  DETAILED_PROFILING_LEAVE("opt.reset_Jacobs_validity")
613 
614  DETAILED_PROFILING_ENTER("opt.recompute_all_Jacobians")
615  recompute_all_Jacobians(dh_dAp, dh_df);
616  DETAILED_PROFILING_LEAVE("opt.recompute_all_Jacobians")
617 
618  // Recalculate Hessian:
619  DETAILED_PROFILING_ENTER("opt.sparse_hessian_update_numeric")
620  sparse_hessian_update_numeric(HAp);
621  sparse_hessian_update_numeric(Hf);
622  sparse_hessian_update_numeric(HApf);
623  DETAILED_PROFILING_LEAVE("opt.sparse_hessian_update_numeric")
624 
625  my_solver.realize_relinearized();
626  }
627 
628  // Update gradient:
629  DETAILED_PROFILING_ENTER("opt.compute_minus_gradient")
630  compute_minus_gradient(/* Out: */ minus_grad, /* In: */ dh_dAp, dh_df, residuals, obs_global_idx2residual_idx);
631  DETAILED_PROFILING_LEAVE("opt.compute_minus_gradient")
632 
633  const double norm_inf_min_grad = norm_inf(minus_grad);
634  if (norm_inf_min_grad<=max_gradient_to_stop)
635  {
636  VERBOSE_LEVEL(2) << "[OPT] LM end criterion: norm_inf(minus_grad) below threshold: " << norm_inf_min_grad << " <= " <<max_gradient_to_stop<<endl;
637  stop = true;
638  }
639  if (RMSE<this->parameters.srba.max_error_per_obs_to_stop)
640  {
641  VERBOSE_LEVEL(2) << "[OPT] LM end criterion: RMSE below threshold: " << RMSE << " < " <<this->parameters.srba.max_error_per_obs_to_stop<<endl;
642  stop = true;
643  }
644  if (rho>this->parameters.srba.max_rho)
645  {
646  VERBOSE_LEVEL(2) << "[OPT] LM end criterion: rho above threshold: " << rho << " > " <<this->parameters.srba.max_rho<<endl;
647  stop = true;
648  }
649  // Reset other vars:
650  lambda *= 1.0/3.0; //std::max(1.0/3.0, 1-std::pow(2*rho-1,3.0) );
651  nu = 2.0;
652 
653  my_solver.realize_lambda_changed();
654  }
655  else
656  {
657  DETAILED_PROFILING_ENTER("opt.failedstep_restore_backup")
658 
659  // Restore old values and retry again with a different lambda:
660  //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:
661  {
662  const size_t nReqNumPoses = list_of_required_num_poses.size();
663  for (size_t i=0;i<nReqNumPoses;i++)
664  {
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;
667 #else
668  const_cast<pose_flag_t*>(list_of_required_num_poses[i])->pose = old_span_tree[i].pose;
669 #endif
670  }
671  }
672 
673  // Restore old edge values:
674  for (size_t i=0;i<nUnknowns_k2k;i++)
675  {
676  *k2k_edge_unknowns[i] =
677 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
678  *old_k2k_edge_unknowns[i];
679 #else
680  old_k2k_edge_unknowns[i];
681 #endif
682  }
683  for (size_t i=0;i<nUnknowns_k2f;i++)
684  {
685  *k2f_edge_unknowns[i] =
686 #ifdef SRBA_WORKAROUND_MSVC9_DEQUE_BUG
687  *old_k2f_edge_unknowns[i];
688 #else
689  old_k2f_edge_unknowns[i];
690 #endif
691  }
692 
693  DETAILED_PROFILING_LEAVE("opt.failedstep_restore_backup")
694 
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;
696  lambda *= nu;
697  nu *= 2.0;
698  stop = (lambda>MAX_LAMBDA);
699 
700  my_solver.realize_lambda_changed();
701  }
702 
703  }; // end while rho
704 
705  DETAILED_PROFILING_LEAVE(sLabelProfilerLM_iter.c_str())
706 
707  } // end for LM "iter"
708 
709 #if 0
710  cout << "residuals" << endl;
711  for( size_t r = 0; r < residuals.size(); ++r )
712  {
713  cout << involved_obs[r].k2f->obs.obs.feat_id << ","
714  << residuals[r][0] << ","
715  << residuals[r][1] << ","
716  << residuals[r][2] << ","
717  << residuals[r][3];
718 
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];
723 
724  if( totalres > 20 )
725  cout << " <-- spurious( " << totalres << ")";
726 
727  cout << endl;
728  }
729  cout << "done" << endl;
730 #endif
731 
732  // Final output info:
733  out_info.total_sqr_error_final = total_proj_error;
734 
735  // Recover information on covariances?
736  // ----------------------------------------------
737  DETAILED_PROFILING_ENTER("opt.cov_recovery")
738  rba_state.unknown_lms_inf_matrices.clear();
739  switch (parameters.srba.cov_recovery)
740  {
741  case crpNone:
742  break;
743  case crpLandmarksApprox:
744  {
745  for (size_t i=0;i<nUnknowns_k2f;i++)
746  {
747  if (!my_solver.was_ith_feature_invertible(i))
748  continue;
749 
750  const typename hessian_traits_t::TSparseBlocksHessian_f::col_t & col_i = Hf.getCol(i);
751  ASSERT_(col_i.rbegin()->first==i) // Make sure the last block matrix is the diagonal term of the upper-triangular matrix.
752 
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;
756  }
757  }
758  break;
759  default:
760  throw std::runtime_error("Unknown value found for 'parameters.srba.cov_recovery'");
761  }
762  DETAILED_PROFILING_LEAVE("opt.cov_recovery")
763 
764  if (parameters.srba.compute_condition_number)
765  {
766  DETAILED_PROFILING_ENTER("opt.condition_number")
767 
768  // Evaluate conditioning number of hessians:
769  mrpt::math::CMatrixDouble dense_HAp;
770  HAp.getAsDense(dense_HAp, true /* recover both triangular parts */);
771 
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();
774 
775  DETAILED_PROFILING_LEAVE("opt.condition_number")
776  }
777 
778  // Fill in any other extra info from the solver:
779  DETAILED_PROFILING_ENTER("opt.get_extra_results")
780  my_solver.get_extra_results(out_info.extra_results);
781  DETAILED_PROFILING_LEAVE("opt.get_extra_results")
782 
783  // Extra verbose: display final value of each optimized pose:
784  if (m_verbose_level>=2 && !run_k2k_edges.empty())
785  {
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;
790  }
791 
792  // Save (quick swap) the list of unknowns to the output structure,
793  // now that these vectors are not needed anymore:
794  out_info.optimized_k2k_edge_indices.swap(run_k2k_edges);
795  out_info.optimized_landmark_indices.swap(run_feat_ids);
796 
797  m_profiler.leave("opt");
798  out_info.obs_rmse = RMSE;
799 
800  VERBOSE_LEVEL(1) << "[OPT] Final RMSE=" << RMSE << " #iters=" << iter << "\n";
801 }
802 
803 
804 } } // end namespaces
805 
size_t num_observations
Number of individual feature observations taken into account in the optimization. ...
Definition: RbaEngine.h:126
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
Definition: RbaEngine.h:109
size_t num_span_tree_numeric_updates
Number of poses updated in the spanning tree numeric-update stage.
Definition: RbaEngine.h:131
double obs_rmse
RMSE for each observation after optimization.
Definition: RbaEngine.h:132
#define DETAILED_PROFILING_ENTER(_STR)
vec_t::const_iterator const_iterator
Definition: map_as_vector.h:56
uint64_t TLandmarkID
Numeric IDs for landmarks.
Definition: srba_types.h:32
iterator find(const size_t i)
Constant-time find, returning an iterator to the pair or to end() if not found (that is...
double total_sqr_error_final
Initial and final total squared error for all the observations.
Definition: RbaEngine.h:133
Scalar * iterator
Definition: eigen_plugins.h:23
This file implements several operations that operate element-wise on individual or pairs of container...
Don't recover any covariance information.
Definition: srba_types.h:178
STL namespace.
const Scalar * const_iterator
Definition: eigen_plugins.h:24
Each of the observations used during the optimization.
Definition: RbaEngine.h:763
std::vector< size_t > optimized_k2k_edge_indices
The 0-based indices of all kf-to-kf edges which were considered in the optimization.
Definition: RbaEngine.h:136
kf2kf_pose_traits_t::pose_flag_t pose_flag_t
Definition: RbaEngine.h:96
KF2KF_POSE_TYPE::pose_t pose_t
The type of relative poses (e.g. mrpt::poses::CPose3D)
Definition: RbaEngine.h:89
Keyframe-to-keyframe edge: an unknown of the problem.
Definition: srba_types.h:82
double HAp_condition_number
To be computed only if enabled in parameters.compute_condition_number.
Definition: RbaEngine.h:134
landmark_traits< LM_TYPE >::TLandmarkEntry TLandmarkEntry
Definition: srba_types.h:482
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...
size_t num_jacobians
Number of Jacobian blocks which had been to be evaluated for each relinearization step...
Definition: RbaEngine.h:127
This base provides a set of functions for maths stuff.
Definition: CArray.h:18
size_t num_total_scalar_optimized
The total number of dimensions (scalar values) in all the optimized unknowns.
Definition: RbaEngine.h:130
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.
RBA_OPTIONS::solver_t::extra_results_t extra_results
Other solver-specific output information.
Definition: RbaEngine.h:140
landmark_traits_t::TRelativeLandmarkPos TRelativeLandmarkPos
One landmark position (relative to its base KF)
Definition: RbaEngine.h:100
std::vector< size_t > optimized_landmark_indices
The 0-based indices of all landmarks whose relative positions were considered as unknowns in the opti...
Definition: RbaEngine.h:137
#define DETAILED_PROFILING_LEAVE(_STR)
A templated column-indexed efficient storage of block-sparse Jacobian or Hessian matrices, together with other arbitrary information.
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)
Definition: RbaEngine.h:25
All the information returned by the local area optimizer.
Definition: RbaEngine.h:119
#define ASSERT_(f)
A partial specialization of CArrayNumeric for double numbers.
Definition: CArrayNumeric.h:74
size_t num_kf2kf_edges_optimized
Number of solved unknowns of type "kf-to-kf edge".
Definition: RbaEngine.h:128
Approximate covariances of landmarks as the inverse of the hessian diagonal blocks.
Definition: srba_types.h:180
#define ASSERTMSG_(f, __ERROR_MSG)
size_t num_kf2lm_edges_optimized
Number of solved unknowns of type "kf-to-landmark".
Definition: RbaEngine.h:129
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...
Definition: string_utils.h:115
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)
Definition: srba_types.h:31



Page generated by Doxygen 1.8.8 for MRPT 1.2.2 SVN:Unversioned directory at Tue Oct 14 02:14:08 UTC 2014