ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
GlobalOptimization.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <Eigen.h>
11 #include <Logging.h>
12 #include <Timer.h>
13 
14 #include <Eigen/Dense>
15 #include <Eigen/Sparse>
16 #include <tuple>
17 #include <vector>
18 
22 
23 namespace cloudViewer {
24 namespace pipelines {
25 namespace registration {
26 
37 
38 // clang-format off
39 const std::vector<Eigen::Matrix4d, utility::Matrix4d_allocator>
41  // for alpha
42  (Eigen::Matrix4d() << 0, 0, 0, 0,
43  0, 0, -1, 0,
44  0, 1, 0, 0,
45  0, 0, 0, 0).finished(),
46  // for beta
47  (Eigen::Matrix4d() << 0, 0, 1, 0,
48  0, 0, 0, 0,
49  -1, 0, 0, 0,
50  0, 0, 0, 0).finished(),
51  // for gamma
52  (Eigen::Matrix4d() << 0, -1, 0, 0,
53  1, 0, 0, 0,
54  0, 0, 0, 0,
55  0, 0, 0, 0).finished(),
56  // for a
57  (Eigen::Matrix4d() << 0, 0, 0, 1,
58  0, 0, 0, 0,
59  0, 0, 0, 0,
60  0, 0, 0, 0).finished(),
61  // for b
62  (Eigen::Matrix4d() << 0, 0, 0, 0,
63  0, 0, 0, 1,
64  0, 0, 0, 0,
65  0, 0, 0, 0).finished(),
66  // for c
67  (Eigen::Matrix4d() << 0, 0, 0, 0,
68  0, 0, 0, 0,
69  0, 0, 0, 1,
70  0, 0, 0, 0).finished()};
71 // clang-format on
72 
78 inline Eigen::Vector6d GetLinearized6DVector(const Eigen::Matrix4d &input) {
79  Eigen::Vector6d output;
80  output(0) = (-input(1, 2) + input(2, 1)) / 2.0;
81  output(1) = (-input(2, 0) + input(0, 2)) / 2.0;
82  output(2) = (-input(0, 1) + input(1, 0)) / 2.0;
83  output.block<3, 1>(3, 0) = input.block<3, 1>(0, 3);
84  return output;
85 }
86 
87 inline Eigen::Vector6d GetMisalignmentVector(const Eigen::Matrix4d &X_inv,
88  const Eigen::Matrix4d &Ts,
89  const Eigen::Matrix4d &Tt_inv) {
90  Eigen::Matrix4d temp;
91  temp.noalias() = X_inv * Tt_inv * Ts;
92  return GetLinearized6DVector(temp);
93 }
94 
95 inline std::tuple<Eigen::Matrix4d, Eigen::Matrix4d, Eigen::Matrix4d>
96 GetRelativePoses(const PoseGraph &pose_graph, int edge_id) {
97  const PoseGraphEdge &te = pose_graph.edges_[edge_id];
98  const PoseGraphNode &ts = pose_graph.nodes_[te.source_node_id_];
99  const PoseGraphNode &tt = pose_graph.nodes_[te.target_node_id_];
100  Eigen::Matrix4d X_inv = te.transformation_.inverse();
101  Eigen::Matrix4d Ts = ts.pose_;
102  Eigen::Matrix4d Tt_inv = tt.pose_.inverse();
103  return std::make_tuple(std::move(X_inv), std::move(Ts), std::move(Tt_inv));
104 }
105 
106 std::tuple<Eigen::Matrix6d, Eigen::Matrix6d> GetJacobian(
107  const Eigen::Matrix4d &X_inv,
108  const Eigen::Matrix4d &Ts,
109  const Eigen::Matrix4d &Tt_inv) {
110  Eigen::Matrix6d Js = Eigen::Matrix6d::Zero();
111  for (int i = 0; i < 6; i++) {
112  Eigen::Matrix4d temp = X_inv * Tt_inv * jacobian_operator[i] * Ts;
113  Js.block<6, 1>(0, i) = GetLinearized6DVector(temp);
114  }
115  Eigen::Matrix6d Jt = Eigen::Matrix6d::Zero();
116  for (int i = 0; i < 6; i++) {
117  Eigen::Matrix4d temp = X_inv * Tt_inv * -jacobian_operator[i] * Ts;
118  Jt.block<6, 1>(0, i) = GetLinearized6DVector(temp);
119  }
120  return std::make_tuple(std::move(Js), std::move(Jt));
121 }
122 
125 int UpdateConfidence(PoseGraph &pose_graph,
126  const Eigen::VectorXd &zeta,
127  const double line_process_weight,
128  const GlobalOptimizationOption &option) {
129  int n_edges = (int)pose_graph.edges_.size();
130  int valid_edges_num = 0;
131  for (int iter_edge = 0; iter_edge < n_edges; iter_edge++) {
132  PoseGraphEdge &t = pose_graph.edges_[iter_edge];
133  if (t.uncertain_) {
134  Eigen::Vector6d e = zeta.block<6, 1>(iter_edge * 6, 0);
135  double residual_square = e.transpose() * t.information_ * e;
136  double temp = line_process_weight /
137  (line_process_weight + residual_square);
138  double temp2 = temp * temp;
139  t.confidence_ = temp2;
140  if (temp2 > option.edge_prune_threshold_) valid_edges_num++;
141  }
142  }
143  return valid_edges_num;
144 }
145 
147 double ComputeResidual(const PoseGraph &pose_graph,
148  const Eigen::VectorXd &zeta,
149  const double line_process_weight,
150  const GlobalOptimizationOption &option) {
151  int n_edges = (int)pose_graph.edges_.size();
152  double residual = 0.0;
153  for (int iter_edge = 0; iter_edge < n_edges; iter_edge++) {
154  const PoseGraphEdge &te = pose_graph.edges_[iter_edge];
155  double line_process_iter = te.confidence_;
156  Eigen::Vector6d e = zeta.block<6, 1>(iter_edge * 6, 0);
157  residual += line_process_iter * e.transpose() * te.information_ * e +
158  line_process_weight * pow(sqrt(line_process_iter) - 1, 2.0);
159  }
160  return residual;
161 }
162 
164 Eigen::VectorXd ComputeZeta(const PoseGraph &pose_graph) {
165  int n_edges = (int)pose_graph.edges_.size();
166  Eigen::VectorXd output(n_edges * 6);
167  for (int iter_edge = 0; iter_edge < n_edges; iter_edge++) {
168  Eigen::Matrix4d X_inv, Ts, Tt_inv;
169  std::tie(X_inv, Ts, Tt_inv) = GetRelativePoses(pose_graph, iter_edge);
170  Eigen::Vector6d e = GetMisalignmentVector(X_inv, Ts, Tt_inv);
171  output.block<6, 1>(iter_edge * 6, 0) = e;
172  }
173  return output;
174 }
175 
189 std::tuple<Eigen::MatrixXd, Eigen::VectorXd> ComputeLinearSystem(
190  const PoseGraph &pose_graph, const Eigen::VectorXd &zeta) {
191  int n_nodes = (int)pose_graph.nodes_.size();
192  int n_edges = (int)pose_graph.edges_.size();
193  Eigen::MatrixXd H(n_nodes * 6, n_nodes * 6);
194  Eigen::VectorXd b(n_nodes * 6);
195  H.setZero();
196  b.setZero();
197 
198  for (int iter_edge = 0; iter_edge < n_edges; iter_edge++) {
199  const PoseGraphEdge &t = pose_graph.edges_[iter_edge];
200  Eigen::Vector6d e = zeta.block<6, 1>(iter_edge * 6, 0);
201 
202  Eigen::Matrix4d X_inv, Ts, Tt_inv;
203  std::tie(X_inv, Ts, Tt_inv) = GetRelativePoses(pose_graph, iter_edge);
204 
205  Eigen::Matrix6d Js, Jt;
206  std::tie(Js, Jt) = GetJacobian(X_inv, Ts, Tt_inv);
207  Eigen::Matrix6d JsT_Info = Js.transpose() * t.information_;
208  Eigen::Matrix6d JtT_Info = Jt.transpose() * t.information_;
209  Eigen::Vector6d eT_Info = e.transpose() * t.information_;
210  double line_process_iter = t.confidence_;
211 
212  int id_i = t.source_node_id_ * 6;
213  int id_j = t.target_node_id_ * 6;
214  H.block<6, 6>(id_i, id_i).noalias() +=
215  line_process_iter * JsT_Info * Js;
216  H.block<6, 6>(id_i, id_j).noalias() +=
217  line_process_iter * JsT_Info * Jt;
218  H.block<6, 6>(id_j, id_i).noalias() +=
219  line_process_iter * JtT_Info * Js;
220  H.block<6, 6>(id_j, id_j).noalias() +=
221  line_process_iter * JtT_Info * Jt;
222  b.block<6, 1>(id_i, 0).noalias() -=
223  line_process_iter * eT_Info.transpose() * Js;
224  b.block<6, 1>(id_j, 0).noalias() -=
225  line_process_iter * eT_Info.transpose() * Jt;
226  }
227  return std::make_tuple(std::move(H), std::move(b));
228 }
229 
230 Eigen::VectorXd UpdatePoseVector(const PoseGraph &pose_graph) {
231  int n_nodes = (int)pose_graph.nodes_.size();
232  Eigen::VectorXd output(n_nodes * 6);
233  for (int iter_node = 0; iter_node < n_nodes; iter_node++) {
235  pose_graph.nodes_[iter_node].pose_);
236  output.block<6, 1>(iter_node * 6, 0) = output_iter;
237  }
238  return output;
239 }
240 
241 std::shared_ptr<PoseGraph> UpdatePoseGraph(const PoseGraph &pose_graph,
242  const Eigen::VectorXd delta) {
243  std::shared_ptr<PoseGraph> pose_graph_updated =
244  std::make_shared<PoseGraph>();
245  *pose_graph_updated = pose_graph;
246  int n_nodes = (int)pose_graph.nodes_.size();
247  for (int iter_node = 0; iter_node < n_nodes; iter_node++) {
248  Eigen::Vector6d delta_iter = delta.block<6, 1>(iter_node * 6, 0);
249  pose_graph_updated->nodes_[iter_node].pose_ =
251  pose_graph_updated->nodes_[iter_node].pose_;
252  }
253  return pose_graph_updated;
254 }
255 
256 bool CheckRightTerm(const Eigen::VectorXd &right_term,
257  const GlobalOptimizationConvergenceCriteria &criteria) {
258  if (right_term.maxCoeff() < criteria.min_right_term_) {
259  utility::LogDebug("Maximum coefficient of right term < {:e}",
260  criteria.min_right_term_);
261  return true;
262  }
263  return false;
264 }
265 
267  const Eigen::VectorXd &delta,
268  const Eigen::VectorXd &x,
269  const GlobalOptimizationConvergenceCriteria &criteria) {
270  if (delta.norm() < criteria.min_relative_increment_ *
271  (x.norm() + criteria.min_relative_increment_)) {
272  utility::LogDebug("Delta.norm() < {:e} * (x.norm() + {:e})",
273  criteria.min_relative_increment_,
274  criteria.min_relative_increment_);
275  return true;
276  }
277  return false;
278 }
279 
281  double current_residual,
282  double new_residual,
283  const GlobalOptimizationConvergenceCriteria &criteria) {
284  if (current_residual - new_residual <
285  criteria.min_relative_residual_increment_ * current_residual) {
287  "Current_residual - new_residual < {:e} * current_residual",
289  return true;
290  }
291  return false;
292 }
293 
294 bool CheckResidual(double residual,
295  const GlobalOptimizationConvergenceCriteria &criteria) {
296  if (residual < criteria.min_residual_) {
297  utility::LogDebug("Current_residual < {:e}", criteria.min_residual_);
298  return true;
299  }
300  return false;
301 }
302 
303 bool CheckMaxIteration(int iteration,
304  const GlobalOptimizationConvergenceCriteria &criteria) {
305  if (iteration >= criteria.max_iteration_) {
306  utility::LogDebug("Reached maximum number of iterations ({:d})",
307  criteria.max_iteration_);
308  return true;
309  }
310  return false;
311 }
312 
314  int iteration, const GlobalOptimizationConvergenceCriteria &criteria) {
315  if (iteration >= criteria.max_iteration_lm_) {
316  utility::LogDebug("Reached maximum number of iterations ({:d})",
317  criteria.max_iteration_lm_);
318  return true;
319  }
320  return false;
321 }
322 
323 double ComputeLineProcessWeight(const PoseGraph &pose_graph,
324  const GlobalOptimizationOption &option) {
325  int n_edges = (int)pose_graph.edges_.size();
326  double average_number_of_correspondences = 0.0;
327  for (int iter_edge = 0; iter_edge < n_edges; iter_edge++) {
328  double number_of_correspondences =
329  pose_graph.edges_[iter_edge].information_(5, 5);
330  average_number_of_correspondences += number_of_correspondences;
331  }
332  if (n_edges > 0) {
333  // see Section 5 in [Choi et al 2015]
334  average_number_of_correspondences /= (double)n_edges;
335  double line_process_weight =
336  option.preference_loop_closure_ *
337  pow(option.max_correspondence_distance_, 2) *
338  average_number_of_correspondences;
339  return line_process_weight;
340  } else {
341  return 0.0;
342  }
343 }
344 
346  const PoseGraph &pose_graph_orig,
347  int reference_node) {
348  utility::LogDebug("CompensateReferencePoseGraphNode : reference : {:d}",
349  reference_node);
350  int n_nodes = (int)pose_graph_new.nodes_.size();
351  if (reference_node < 0 || reference_node >= n_nodes) {
352  return;
353  } else {
354  Eigen::Matrix4d compensation =
355  pose_graph_orig.nodes_[reference_node].pose_ *
356  pose_graph_new.nodes_[reference_node].pose_.inverse();
357  for (int i = 0; i < n_nodes; i++) {
358  pose_graph_new.nodes_[i].pose_ =
359  compensation * pose_graph_new.nodes_[i].pose_;
360  }
361  }
362 }
363 
365  bool ignore_uncertain_edges = false) {
366  size_t n_nodes = pose_graph.nodes_.size();
367  size_t n_edges = pose_graph.edges_.size();
368 
369  // Test if the connected component containing the first node is the entire
370  // graph
371  std::vector<int> nodes_to_explore{};
372  std::vector<int> component{};
373  if (n_nodes > 0) {
374  nodes_to_explore.push_back(0);
375  component.push_back(0);
376  }
377  while (!nodes_to_explore.empty()) {
378  int i = nodes_to_explore.back();
379  nodes_to_explore.pop_back();
380  for (size_t j = 0; j < n_edges; j++) {
381  const PoseGraphEdge &t = pose_graph.edges_[j];
382  if (ignore_uncertain_edges && t.uncertain_) {
383  continue;
384  }
385  int adjacent_node{-1};
386  if (t.source_node_id_ == i) {
387  adjacent_node = t.target_node_id_;
388  } else if (t.target_node_id_ == i) {
389  adjacent_node = t.source_node_id_;
390  }
391  if (adjacent_node != -1) {
392  auto find_result = std::find(component.begin(), component.end(),
393  adjacent_node);
394  if (find_result == component.end()) {
395  nodes_to_explore.push_back(adjacent_node);
396  component.push_back(adjacent_node);
397  }
398  }
399  }
400  }
401  return component.size() == n_nodes;
402 }
403 
404 bool ValidatePoseGraph(const PoseGraph &pose_graph) {
405  int n_nodes = (int)pose_graph.nodes_.size();
406  int n_edges = (int)pose_graph.edges_.size();
407 
408  if (!ValidatePoseGraphConnectivity(pose_graph, false)) {
409  utility::LogWarning("Invalid PoseGraph - graph is not connected.");
410  return false;
411  }
412 
413  if (!ValidatePoseGraphConnectivity(pose_graph, true)) {
415  "Certain-edge subset of PoseGraph is not connected.");
416  }
417 
418  for (int j = 0; j < n_edges; j++) {
419  bool valid = false;
420  const PoseGraphEdge &t = pose_graph.edges_[j];
421  if (t.source_node_id_ >= 0 && t.source_node_id_ < n_nodes &&
422  t.target_node_id_ >= 0 && t.target_node_id_ < n_nodes)
423  valid = true;
424  if (!valid) {
426  "Invalid PoseGraph - an edge references an invalide "
427  "node.");
428  return false;
429  }
430  }
431  for (int j = 0; j < n_edges; j++) {
432  const PoseGraphEdge &t = pose_graph.edges_[j];
433  if (!t.uncertain_ && t.confidence_ != 1.0) {
435  "Invalid PoseGraph - the certain edge does not have 1.0 as "
436  "a confidence.");
437  return false;
438  }
439  }
440  utility::LogDebug("Validating PoseGraph - finished.");
441  return true;
442 }
443 
444 std::shared_ptr<PoseGraph> CreatePoseGraphWithoutInvalidEdges(
445  const PoseGraph &pose_graph, const GlobalOptimizationOption &option) {
446  std::shared_ptr<PoseGraph> pose_graph_pruned =
447  std::make_shared<PoseGraph>();
448 
449  int n_nodes = (int)pose_graph.nodes_.size();
450  for (int iter_node = 0; iter_node < n_nodes; iter_node++) {
451  const PoseGraphNode &t = pose_graph.nodes_[iter_node];
452  pose_graph_pruned->nodes_.push_back(t);
453  }
454  int n_edges = (int)pose_graph.edges_.size();
455  for (int iter_edge = 0; iter_edge < n_edges; iter_edge++) {
456  const PoseGraphEdge &t = pose_graph.edges_[iter_edge];
457  if (t.uncertain_) {
458  if (t.confidence_ > option.edge_prune_threshold_) {
459  pose_graph_pruned->edges_.push_back(t);
460  }
461  } else {
462  pose_graph_pruned->edges_.push_back(t);
463  }
464  }
465  return pose_graph_pruned;
466 }
467 
469  PoseGraph &pose_graph,
471  const GlobalOptimizationOption &option) const {
472  int n_nodes = (int)pose_graph.nodes_.size();
473  int n_edges = (int)pose_graph.edges_.size();
474  double line_process_weight = ComputeLineProcessWeight(pose_graph, option);
475 
477  "[GlobalOptimizationGaussNewton] Optimizing PoseGraph having {:d} "
478  "nodes and {:d} edges.",
479  n_nodes, n_edges);
480  utility::LogDebug("Line process weight : {:f}", line_process_weight);
481 
482  Eigen::VectorXd zeta = ComputeZeta(pose_graph);
483  double current_residual, new_residual;
484  new_residual =
485  ComputeResidual(pose_graph, zeta, line_process_weight, option);
486  current_residual = new_residual;
487 
488  int valid_edges_num;
489  valid_edges_num =
490  UpdateConfidence(pose_graph, zeta, line_process_weight, option);
491 
492  Eigen::MatrixXd H;
493  Eigen::VectorXd b;
494  Eigen::VectorXd x = UpdatePoseVector(pose_graph);
495 
496  std::tie(H, b) = ComputeLinearSystem(pose_graph, zeta);
497 
498  utility::LogDebug("[Initial ] residual : {:e}", current_residual);
499 
500  bool stop = false;
501  if (CheckRightTerm(b, criteria)) return;
502 
503  utility::Timer timer_overall;
504  timer_overall.Start();
505  int iter;
506  for (iter = 0; !stop; iter++) {
507  utility::Timer timer_iter;
508  timer_iter.Start();
509 
510  Eigen::VectorXd delta(H.cols());
511  bool solver_success = false;
512 
513  // Solve H_LM @ delta == b using a sparse solver
514  std::tie(solver_success, delta) = utility::SolveLinearSystemPSD(
515  H, b, /*prefer_sparse=*/true, /*check_symmetric=*/false,
516  /*check_det=*/false, /*check_psd=*/false);
517 
518  stop = stop || CheckRelativeIncrement(delta, x, criteria);
519  if (stop) {
520  break;
521  } else {
522  std::shared_ptr<PoseGraph> pose_graph_new =
523  UpdatePoseGraph(pose_graph, delta);
524 
525  Eigen::VectorXd zeta_new;
526  zeta_new = ComputeZeta(*pose_graph_new);
527  new_residual = ComputeResidual(pose_graph, zeta_new,
528  line_process_weight, option);
529  stop = stop || CheckRelativeResidualIncrement(
530  current_residual, new_residual, criteria);
531  if (stop) break;
532  current_residual = new_residual;
533 
534  zeta = zeta_new;
535  pose_graph = *pose_graph_new;
536  x = UpdatePoseVector(pose_graph);
537  valid_edges_num = UpdateConfidence(pose_graph, zeta,
538  line_process_weight, option);
539  std::tie(H, b) = ComputeLinearSystem(pose_graph, zeta);
540 
541  stop = stop || CheckRightTerm(b, criteria);
542  if (stop) break;
543  }
544  timer_iter.Stop();
546  "[Iteration {:02d}] residual : {:e}, valid edges : {:d}, time "
547  ": {:.3f} "
548  "sec.",
549  iter, current_residual, valid_edges_num,
550  timer_iter.GetDurationInMillisecond() / 1000.0);
551  stop = stop || CheckResidual(current_residual, criteria) ||
552  CheckMaxIteration(iter, criteria);
553  } // end for
554  timer_overall.Stop();
556  "[GlobalOptimizationGaussNewton] total time : {:.3f} sec.",
557  timer_overall.GetDurationInMillisecond() / 1000.0);
558 }
559 
561  PoseGraph &pose_graph,
563  const GlobalOptimizationOption &option) const {
564  int n_nodes = (int)pose_graph.nodes_.size();
565  int n_edges = (int)pose_graph.edges_.size();
566  double line_process_weight = ComputeLineProcessWeight(pose_graph, option);
567 
569  "[GlobalOptimizationLM] Optimizing PoseGraph having {:d} nodes and "
570  "{:d} edges.",
571  n_nodes, n_edges);
572  utility::LogDebug("Line process weight : {:f}", line_process_weight);
573 
574  Eigen::VectorXd zeta = ComputeZeta(pose_graph);
575  double current_residual, new_residual;
576  new_residual =
577  ComputeResidual(pose_graph, zeta, line_process_weight, option);
578  current_residual = new_residual;
579 
580  int valid_edges_num =
581  UpdateConfidence(pose_graph, zeta, line_process_weight, option);
582 
583  Eigen::MatrixXd H_I = Eigen::MatrixXd::Identity(n_nodes * 6, n_nodes * 6);
584  Eigen::MatrixXd H;
585  Eigen::VectorXd b;
586  Eigen::VectorXd x = UpdatePoseVector(pose_graph);
587 
588  std::tie(H, b) = ComputeLinearSystem(pose_graph, zeta);
589 
590  Eigen::VectorXd H_diag = H.diagonal();
591  double tau = 1e-5;
592  double current_lambda = tau * H_diag.maxCoeff();
593  double ni = 2.0;
594  double rho = 0.0;
595 
596  utility::LogDebug("[Initial ] residual : {:e}, lambda : {:e}",
597  current_residual, current_lambda);
598 
599  bool stop = false;
600  stop = stop || CheckRightTerm(b, criteria);
601  if (stop) return;
602 
603  utility::Timer timer_overall;
604  timer_overall.Start();
605  for (int iter = 0; !stop; iter++) {
606  utility::Timer timer_iter;
607  timer_iter.Start();
608  int lm_count = 0;
609  do {
610  Eigen::MatrixXd H_LM = H + current_lambda * H_I;
611  Eigen::VectorXd delta(H_LM.cols());
612  bool solver_success = false;
613 
614  // Solve H_LM @ delta == b using a sparse solver
615  std::tie(solver_success, delta) = utility::SolveLinearSystemPSD(
616  H_LM, b, /*prefer_sparse=*/true, /*check_symmetric=*/false,
617  /*check_det=*/false, /*check_psd=*/false);
618 
619  stop = stop || CheckRelativeIncrement(delta, x, criteria);
620  if (!stop) {
621  std::shared_ptr<PoseGraph> pose_graph_new =
622  UpdatePoseGraph(pose_graph, delta);
623 
624  Eigen::VectorXd zeta_new;
625  zeta_new = ComputeZeta(*pose_graph_new);
626  new_residual = ComputeResidual(pose_graph, zeta_new,
627  line_process_weight, option);
628  rho = (current_residual - new_residual) /
629  (delta.dot(current_lambda * delta + b) + 1e-3);
630  if (rho > 0) {
631  stop = stop ||
633  current_residual, new_residual, criteria);
634  if (stop) break;
635  double alpha = 1. - pow((2 * rho - 1), 3);
636  alpha = (std::min)(alpha, criteria.upper_scale_factor_);
637  double scaleFactor =
638  (std::max)(criteria.lower_scale_factor_, alpha);
639  current_lambda *= scaleFactor;
640  ni = 2;
641  current_residual = new_residual;
642 
643  zeta = zeta_new;
644  pose_graph = *pose_graph_new;
645  x = UpdatePoseVector(pose_graph);
646  valid_edges_num = UpdateConfidence(
647  pose_graph, zeta, line_process_weight, option);
648  std::tie(H, b) = ComputeLinearSystem(pose_graph, zeta);
649 
650  stop = stop || CheckRightTerm(b, criteria);
651  if (stop) break;
652  } else {
653  current_lambda *= ni;
654  ni *= 2;
655  }
656  }
657  lm_count++;
658  stop = stop || CheckMaxIterationLM(lm_count, criteria);
659  } while (!((rho > 0) || stop));
660  timer_iter.Stop();
661  if (!stop) {
663  "[Iteration {:02d}] residual : {:e}, valid edges : {:d}, "
664  "time : "
665  "{:.3f} sec.",
666  iter, current_residual, valid_edges_num,
667  timer_iter.GetDurationInMillisecond() / 1000.0);
668  }
669  stop = stop || CheckResidual(current_residual, criteria) ||
670  CheckMaxIteration(iter, criteria);
671  } // end for
672  timer_overall.Stop();
673  utility::LogDebug("[GlobalOptimizationLM] total time : {:.3f} sec.",
674  timer_overall.GetDurationInMillisecond() / 1000.0);
675 }
676 
677 void GlobalOptimization(PoseGraph &pose_graph,
678  const GlobalOptimizationMethod &method
679  /* = GlobalOptimizationLevenbergMarquardt() */,
681  /* = GlobalOptimizationConvergenceCriteria() */,
682  const GlobalOptimizationOption &option
683  /* = GlobalOptimizationOption() */) {
684  if (!ValidatePoseGraph(pose_graph)) return;
685  std::shared_ptr<PoseGraph> pose_graph_pre = std::make_shared<PoseGraph>();
686  *pose_graph_pre = pose_graph;
687  method.OptimizePoseGraph(*pose_graph_pre, criteria, option);
688  auto pose_graph_pre_pruned =
689  CreatePoseGraphWithoutInvalidEdges(*pose_graph_pre, option);
690  method.OptimizePoseGraph(*pose_graph_pre_pruned, criteria, option);
691  auto pose_graph_pre_pruned_2 =
692  CreatePoseGraphWithoutInvalidEdges(*pose_graph_pre_pruned, option);
693  CompensateReferencePoseGraphNode(*pose_graph_pre_pruned_2, pose_graph,
694  option.reference_node_);
695  pose_graph = *pose_graph_pre_pruned_2;
696 }
697 
698 } // namespace registration
699 } // namespace pipelines
700 } // namespace cloudViewer
void OptimizePoseGraph(PoseGraph &pose_graph, const GlobalOptimizationConvergenceCriteria &criteria, const GlobalOptimizationOption &option) const override
Run pose graph optimization method.
void OptimizePoseGraph(PoseGraph &pose_graph, const GlobalOptimizationConvergenceCriteria &criteria, const GlobalOptimizationOption &option) const override
Run pose graph optimization method.
virtual void OptimizePoseGraph(PoseGraph &pose_graph, const GlobalOptimizationConvergenceCriteria &criteria, const GlobalOptimizationOption &option) const =0
Run pose graph optimization method.
int reference_node_
The pose of this node is unchanged after optimization.
Eigen::Matrix4d_u transformation_
Transformation matrix.
Definition: PoseGraph.h:76
int target_node_id_
Target PoseGraphNode id.
Definition: PoseGraph.h:74
int source_node_id_
Source PoseGraphNode id.
Definition: PoseGraph.h:72
bool uncertain_
Whether the edge is uncertain.
Definition: PoseGraph.h:83
Eigen::Matrix6d_u information_
Information matrix.
Definition: PoseGraph.h:78
double confidence_
Confidence value of the edge.
Definition: PoseGraph.h:90
Data structure defining the pose graph.
Definition: PoseGraph.h:96
std::vector< PoseGraphNode > nodes_
List of PoseGraphNode.
Definition: PoseGraph.h:108
std::vector< PoseGraphEdge > edges_
List of PoseGraphEdge.
Definition: PoseGraph.h:111
double GetDurationInMillisecond() const
Definition: Timer.cpp:36
#define LogWarning(...)
Definition: Logging.h:72
#define LogDebug(...)
Definition: Logging.h:90
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
Eigen::Matrix< double, 6, 1 > Vector6d
Definition: Eigen.h:107
Eigen::Matrix< double, 6, 6 > Matrix6d
Extending Eigen namespace by adding frequently used matrix type.
Definition: Eigen.h:106
Eigen::Vector6d GetLinearized6DVector(const Eigen::Matrix4d &input)
bool ValidatePoseGraphConnectivity(const PoseGraph &pose_graph, bool ignore_uncertain_edges=false)
std::tuple< Eigen::Matrix6d, Eigen::Matrix6d > GetJacobian(const Eigen::Matrix4d &X_inv, const Eigen::Matrix4d &Ts, const Eigen::Matrix4d &Tt_inv)
Eigen::VectorXd UpdatePoseVector(const PoseGraph &pose_graph)
std::tuple< Eigen::Matrix4d, Eigen::Matrix4d, Eigen::Matrix4d > GetRelativePoses(const PoseGraph &pose_graph, int edge_id)
bool CheckResidual(double residual, const GlobalOptimizationConvergenceCriteria &criteria)
bool CheckRightTerm(const Eigen::VectorXd &right_term, const GlobalOptimizationConvergenceCriteria &criteria)
double ComputeResidual(const PoseGraph &pose_graph, const Eigen::VectorXd &zeta, const double line_process_weight, const GlobalOptimizationOption &option)
Function to compute residual defined in [Choi et al 2015] See Eq (9).
bool CheckRelativeIncrement(const Eigen::VectorXd &delta, const Eigen::VectorXd &x, const GlobalOptimizationConvergenceCriteria &criteria)
std::shared_ptr< PoseGraph > UpdatePoseGraph(const PoseGraph &pose_graph, const Eigen::VectorXd delta)
const std::vector< Eigen::Matrix4d, utility::Matrix4d_allocator > jacobian_operator
bool CheckMaxIteration(int iteration, const GlobalOptimizationConvergenceCriteria &criteria)
void GlobalOptimization(PoseGraph &pose_graph, const GlobalOptimizationMethod &method, const GlobalOptimizationConvergenceCriteria &criteria, const GlobalOptimizationOption &option)
bool ValidatePoseGraph(const PoseGraph &pose_graph)
std::shared_ptr< PoseGraph > CreatePoseGraphWithoutInvalidEdges(const PoseGraph &pose_graph, const GlobalOptimizationOption &option)
void CompensateReferencePoseGraphNode(PoseGraph &pose_graph_new, const PoseGraph &pose_graph_orig, int reference_node)
double ComputeLineProcessWeight(const PoseGraph &pose_graph, const GlobalOptimizationOption &option)
Eigen::VectorXd ComputeZeta(const PoseGraph &pose_graph)
Function to compute residual defined in [Choi et al 2015] See Eq (6).
bool CheckMaxIterationLM(int iteration, const GlobalOptimizationConvergenceCriteria &criteria)
Eigen::Vector6d GetMisalignmentVector(const Eigen::Matrix4d &X_inv, const Eigen::Matrix4d &Ts, const Eigen::Matrix4d &Tt_inv)
std::tuple< Eigen::MatrixXd, Eigen::VectorXd > ComputeLinearSystem(const PoseGraph &pose_graph, const Eigen::VectorXd &zeta)
int UpdateConfidence(PoseGraph &pose_graph, const Eigen::VectorXd &zeta, const double line_process_weight, const GlobalOptimizationOption &option)
bool CheckRelativeResidualIncrement(double current_residual, double new_residual, const GlobalOptimizationConvergenceCriteria &criteria)
std::tuple< bool, Eigen::VectorXd > SolveLinearSystemPSD(const Eigen::MatrixXd &A, const Eigen::VectorXd &b, bool prefer_sparse=false, bool check_symmetric=false, bool check_det=false, bool check_psd=false)
Function to solve Ax=b.
Definition: Eigen.cpp:21
Eigen::Vector6d TransformMatrix4dToVector6d(const Eigen::Matrix4d &input)
Definition: Eigen.cpp:88
Eigen::Matrix4d TransformVector6dToMatrix4d(const Eigen::Vector6d &input)
Definition: Eigen.cpp:76
Generic file read and write utility for python interface.