optimal_tf_olae.cpp
Go to the documentation of this file.
1 /* -------------------------------------------------------------------------
2  * A repertory of multi primitive-to-primitive (MP2P) ICP algorithms in C++
3  * Copyright (C) 2018-2024 Jose Luis Blanco, University of Almeria
4  * See LICENSE for license information.
5  * ------------------------------------------------------------------------- */
14 #include <mrpt/core/exceptions.h>
15 #include <mrpt/poses/Lie/SE.h>
16 #include <mrpt/tfest/se3.h>
17 
18 #include <Eigen/Dense>
19 
20 #include "visit_correspondences.h"
21 
22 using namespace mp2p_icp;
23 
24 // Convert to quaternion by normalizing q=[1, optim_rot], then to rot. matrix:
25 static mrpt::poses::CPose3D gibbs2pose(const Eigen::Vector3d& v)
26 {
27  auto x = v[0], y = v[1], z = v[2];
28  const auto r = 1.0 / std::sqrt(1.0 + x * x + y * y + z * z);
29  x *= r;
30  y *= r;
31  z *= r;
32  auto q = mrpt::math::CQuaternionDouble(r, -x, -y, -z);
33 
34  // Quaternion to 3x3 rot matrix:
35  return mrpt::poses::CPose3D(q, .0, .0, .0);
36 }
37 
48 {
49  Eigen::Matrix3d M, Mx, My, Mz;
50  Eigen::Vector3d v, vx, vy, vz;
51 
53  Eigen::Matrix3d B;
54 };
55 
58  const Pairings& in, const WeightParameters& wp,
59  const mrpt::math::TPoint3D& ct_local, const mrpt::math::TPoint3D& ct_global,
60  OutlierIndices& in_out_outliers)
61 {
62  MRPT_START
63 
64  using mrpt::math::TPoint3D;
65  using mrpt::math::TVector3D;
66 
68 
69  // Build the linear system: M g = v
70  res.M = Eigen::Matrix3d::Zero();
71  res.v = Eigen::Vector3d::Zero();
72 
73  // Attitude profile matrix:
74  res.B = Eigen::Matrix3d::Zero();
75 
76  // Lambda: process each pairing:
77  auto lambda_each_pair = [&](const mrpt::math::TVector3D& bi,
78  const mrpt::math::TVector3D& ri,
79  const double wi) {
80 // We will evaluate M from an alternative expression below from the
81 // attitude profile matrix B instead, since it seems to be slightly more
82 // stable, numerically. The original code for M is left here for
83 // reference, though.
84 #if 0
85  // M+=(1/2)* ([s_i]_{x})^2
86  // with: s_i = b_i + r_i
87  const double sx = bi.x + ri.x, sy = bi.y + ri.y, sz = bi.z + ri.z;
88 
89  /* ([s_i]_{x})^2 is:
90  *
91  * ⎡ 2 2 ⎤
92  * ⎢- sy - sz sx⋅sy sx⋅sz ⎥
93  * ⎢ ⎥
94  * ⎢ 2 2 ⎥
95  * ⎢ sx⋅sy - sx - sz sy⋅sz ⎥
96  * ⎢ ⎥
97  * ⎢ 2 2⎥
98  * ⎣ sx⋅sz sy⋅sz - sx - sy ⎦
99  */
100  const double c00 = -sy * sy - sz * sz;
101  const double c11 = -sx * sx - sz * sz;
102  const double c22 = -sx * sx - sy * sy;
103  const double c01 = sx * sy;
104  const double c02 = sx * sz;
105  const double c12 = sy * sz;
106 
107  // clang-format off
108  const auto dM = (Eigen::Matrix3d() <<
109  c00, c01, c02,
110  c01, c11, c12,
111  c02, c12, c22 ).finished();
112  // clang-format on
113 
114  // res.M += wi * dM;
115 
116  // The missing (1/2) from the formulas above:
117  res.M *= 0.5;
118 #endif
119  /* v-= weight * [b_i]_{x} r_i
120  * Each term is:
121  * ⎡by⋅rz - bz⋅ry ⎤ ⎡ B23 - B32 ⎤
122  * ⎢ ⎥ | ⎥
123  * ⎢-bx⋅rz + bz⋅rx⎥ = | B31 - B13 ⎥
124  * ⎢ ⎥ | ⎥
125  * ⎣bx⋅ry - by⋅rx ⎦ ⎣ B12 - B21 ⎦
126  *
127  * B (attitude profile matrix):
128  *
129  * B+= weight * (b_i * r_i')
130  *
131  */
132 
133  // clang-format off
134  const auto dV = (Eigen::Vector3d() <<
135  (bi.y * ri.z - bi.z * ri.y),
136  (-bi.x * ri.z + bi.z * ri.x),
137  (bi.x * ri.y - bi.y * ri.x) ).finished();
138  // clang-format on
139 
140  res.v -= wi * dV;
141 
142  // clang-format off
143  const auto dB = (Eigen::Matrix3d() <<
144  bi.x * ri.x, bi.x * ri.y, bi.x * ri.z,
145  bi.y * ri.x, bi.y * ri.y, bi.y * ri.z,
146  bi.z * ri.x, bi.z * ri.y, bi.z * ri.z).finished();
147  // clang-format on
148  res.B += wi * dB;
149  }; // end lambda for visit_correspondences()
150 
151  // Lambda for the final stage after visiting all corres:
152  auto lambda_final = [&](const double w_sum) {
153  // Normalize weights. OLAE assumes \sum(w_i) = 1.0
154  if (w_sum > .0)
155  {
156  const auto f = (1.0 / w_sum);
157  // res.M *= f;
158  res.v *= f;
159  res.B *= f;
160  }
161  else
162  {
163  // We either had NO input correspondences, or ALL were detected
164  // as outliers... What to do in this case?
165  }
166  };
167 
169  in, wp, ct_local, ct_global, in_out_outliers, lambda_each_pair,
170  lambda_final, true /* DO make unit point vectors for OLAE */);
171 
172  // Now, compute the other three sets of linear systems, corresponding
173  // to the "sequential rotation method" [shuster1981attitude], so we can
174  // later keep the best one (i.e. the one with the largest |M|).
175  {
176  const Eigen::Matrix3d S = res.B + res.B.transpose();
177  const double p = res.B.trace() + 1;
178  const double m = res.B.trace() - 1;
179  // Short cut:
180  const auto& v = res.v;
181 
182  // Set #0: M g=v, without further rotations (the system built above).
183  // clang-format off
184  res.M = (Eigen::Matrix3d() <<
185  S(0,0)-p, S(0,1), S(0,2),
186  S(0,1), S(1,1)-p, S(1,2),
187  S(0,2), S(1,2), S(2,2)-p ).finished();
188  // clang-format on
189 
190  const auto& M0 = res.M; // shortcut
191  const double z1 = v[0], z2 = v[1], z3 = v[2];
192 
193  // Set #1: rotating 180 deg around "x":
194  // clang-format off
195  res.Mx = (Eigen::Matrix3d() <<
196  m , -z3 , z2,
197  -z3 , M0(2,2), -S(1,2),
198  z2 , -S(1,2), M0(1,1)).finished();
199  res.vx = (Eigen::Vector3d() <<
200  -z1, S(0,2), -S(0,1)
201  ).finished();
202  // clang-format on
203 
204  // Set #2: rotating 180 deg around "y":
205  // clang-format off
206  res.My = (Eigen::Matrix3d() <<
207  M0(2,2), z3 , -S(0,2),
208  z3 , m , -z1,
209  -S(0,2) , -z1 , M0(0,0)).finished();
210  res.vy = (Eigen::Vector3d() <<
211  -S(1,2), -z2, S(0,1)
212  ).finished();
213  // clang-format on
214 
215  // Set #3: rotating 180 deg around "z":
216  // clang-format off
217  res.Mz = (Eigen::Matrix3d() <<
218  M0(1,1), -S(0,1), -z2,
219  -S(0,1) , M0(0,0), z1,
220  -z2 , z1 , m).finished();
221  res.vz = (Eigen::Vector3d() <<
222  S(1,2), -S(0,2), -z3
223  ).finished();
224  // clang-format on
225  }
226 
227  return res;
228 
229  MRPT_END
230 }
231 
232 // See .h docs, and associated technical report.
234  const Pairings& in, const WeightParameters& wp, OptimalTF_Result& result)
235 {
236  MRPT_START
237 
238  using mrpt::math::TPoint3D;
239  using mrpt::math::TVector3D;
240 
241  // Note on notation: we are search the relative transformation of
242  // the "other" frame wrt to "this", i.e. "this"="global",
243  // "other"="local":
244  // p_this = pose \oplus p_other
245  // p_A = pose \oplus p_B --> pB = p_A \ominus pose
246 
247  // Reset output to defaults:
248  result = OptimalTF_Result();
249 
250  // Normalize weights for each feature type and for each target (attitude
251  // / translation):
252  ASSERT_(wp.pair_weights.pt2pt >= .0);
253  ASSERT_(wp.pair_weights.ln2ln >= .0);
254  ASSERT_(wp.pair_weights.pl2pl >= .0);
255 
256  // Compute the centroids:
257  auto [ct_local, ct_global] =
258  eval_centroids_robust(in, result.outliers /* empty for now */);
259 
260  // Build the linear system: M g = v
262  in, wp, ct_local, ct_global, result.outliers /* empty for now */);
263 
264  // Re-evaluate the centroids, now that we have a guess on outliers.
265  if (!result.outliers.empty())
266  {
267  // Re-evaluate the centroids:
268  const auto [new_ct_local, new_ct_global] =
269  eval_centroids_robust(in, result.outliers);
270 
271  ct_local = new_ct_local;
272  ct_global = new_ct_global;
273 
274  // And rebuild the linear system with the new values:
275  linsys = olae_build_linear_system(
276  in, wp, ct_local, ct_global, result.outliers);
277  }
278 
279  // We are finding the optimal rotation "g", as a Gibbs vector.
280  // Solve linear system for optimal rotation: M g = v
281 
282  const double detM_orig = std::abs(linsys.M.determinant()),
283  detMx = std::abs(linsys.Mx.determinant()),
284  detMy = std::abs(linsys.My.determinant()),
285  detMz = std::abs(linsys.Mz.determinant());
286 
287 #if 0
288  // clang-format off
289  std::cout << " |M_orig|= " << detM_orig << "\n"
290  " |M_x| = " << detMx << "\n"
291  " |M_t| = " << detMy << "\n"
292  " |M_z| = " << detMz << "\n";
293  // clang-format on
294 #endif
295 
296  if (detM_orig > mrpt::max3(detMx, detMy, detMz))
297  {
298  // original rotation is the best numerically-determined problem:
299  const auto sol0 =
300  gibbs2pose(linsys.M.colPivHouseholderQr().solve(linsys.v));
301  result.optimalPose = sol0;
302 #if 0
303  std::cout << "M : |M|="
304  << mrpt::format("%16.07f", linsys.M.determinant())
305  << " sol: " << sol0.asString() << "\n";
306 #endif
307  }
308  else if (detMx > mrpt::max3(detM_orig, detMy, detMz))
309  {
310  // rotation wrt X is the best choice:
311  auto sol1 =
312  gibbs2pose(linsys.Mx.colPivHouseholderQr().solve(linsys.vx));
313  sol1 = mrpt::poses::CPose3D(0, 0, 0, 0, 0, M_PI) + sol1;
314  result.optimalPose = sol1;
315 #if 0
316  std::cout << "M_x : |M|="
317  << mrpt::format("%16.07f", linsys.Mx.determinant())
318  << " sol: " << sol1.asString() << "\n";
319 #endif
320  }
321  else if (detMy > mrpt::max3(detM_orig, detMx, detMz))
322  {
323  // rotation wrt Y is the best choice:
324  auto sol2 =
325  gibbs2pose(linsys.My.colPivHouseholderQr().solve(linsys.vy));
326  sol2 = mrpt::poses::CPose3D(0, 0, 0, 0, M_PI, 0) + sol2;
327  result.optimalPose = sol2;
328 #if 0
329  std::cout << "M_y : |M|="
330  << mrpt::format("%16.07f", linsys.My.determinant())
331  << " sol: " << sol2.asString() << "\n";
332 #endif
333  }
334  else
335  {
336  // rotation wrt Z is the best choice:
337  auto sol3 =
338  gibbs2pose(linsys.Mz.colPivHouseholderQr().solve(linsys.vz));
339  sol3 = mrpt::poses::CPose3D(0, 0, 0, M_PI, 0, 0) + sol3;
340  result.optimalPose = sol3;
341 #if 0
342  std::cout << "M_z : |M|="
343  << mrpt::format("%16.07f", linsys.Mz.determinant())
344  << " sol: " << sol3.asString() << "\n";
345 #endif
346  }
347 
348  // Use centroids to solve for optimal translation:
349  mrpt::math::TPoint3D pp;
350  result.optimalPose.composePoint(
351  ct_local.x, ct_local.y, ct_local.z, pp.x, pp.y, pp.z);
352  // Scale, if used, was: pp *= s;
353 
354  result.optimalPose.x(ct_global.x - pp.x);
355  result.optimalPose.y(ct_global.y - pp.y);
356  result.optimalPose.z(ct_global.z - pp.z);
357 
358  return true;
359 
360  MRPT_END
361 }
OLAE_LinearSystems
Definition: optimal_tf_olae.cpp:47
mp2p_icp::OptimalTF_Result::optimalPose
mrpt::poses::CPose3D optimalPose
Definition: OptimalTF_Result.h:26
mp2p_icp
Definition: covariance.h:17
OLAE_LinearSystems::My
Eigen::Matrix3d My
Definition: optimal_tf_olae.cpp:49
test.res
res
Definition: test.py:11
OLAE_LinearSystems::vz
Eigen::Vector3d vz
Definition: optimal_tf_olae.cpp:50
OLAE_LinearSystems::Mx
Eigen::Matrix3d Mx
Definition: optimal_tf_olae.cpp:49
kitti-run-seq.f
string f
Definition: kitti-run-seq.py:12
mp2p_icp::Pairings
Definition: Pairings.h:78
OLAE_LinearSystems::vx
Eigen::Vector3d vx
Definition: optimal_tf_olae.cpp:50
optimal_tf_olae.h
OLAE algorithm to find the SE(3) optimal transformation.
OLAE_LinearSystems::vy
Eigen::Vector3d vy
Definition: optimal_tf_olae.cpp:50
test.x
x
Definition: test.py:4
mp2p_icp::OptimalTF_Result
Definition: OptimalTF_Result.h:24
mp2p_icp::WeightParameters::pair_weights
PairWeights pair_weights
See docs for PairWeights.
Definition: WeightParameters.h:46
OLAE_LinearSystems::v
Eigen::Vector3d v
Definition: optimal_tf_olae.cpp:50
mp2p_icp::OutlierIndices::empty
bool empty() const
Definition: Pairings.h:186
mp2p_icp::OptimalTF_Result::outliers
OutlierIndices outliers
Definition: OptimalTF_Result.h:30
OLAE_LinearSystems::B
Eigen::Matrix3d B
Definition: optimal_tf_olae.cpp:53
mp2p_icp::PairWeights::pl2pl
double pl2pl
Weight of plane-to-plane pairs.
Definition: PairWeights.h:38
mp2p_icp::PairWeights::ln2ln
double ln2ln
Weight of line-to-line pairs.
Definition: PairWeights.h:37
mp2p_icp::optimal_tf_olae
bool optimal_tf_olae(const Pairings &in, const WeightParameters &wp, OptimalTF_Result &result)
Definition: optimal_tf_olae.cpp:233
OLAE_LinearSystems::Mz
Eigen::Matrix3d Mz
Definition: optimal_tf_olae.cpp:49
gibbs2pose
static mrpt::poses::CPose3D gibbs2pose(const Eigen::Vector3d &v)
Definition: optimal_tf_olae.cpp:25
mp2p_icp::eval_centroids_robust
std::tuple< mrpt::math::TPoint3D, mrpt::math::TPoint3D > eval_centroids_robust(const Pairings &in, const OutlierIndices &outliers)
Definition: Pairings.cpp:63
olae_build_linear_system
static OLAE_LinearSystems olae_build_linear_system(const Pairings &in, const WeightParameters &wp, const mrpt::math::TPoint3D &ct_local, const mrpt::math::TPoint3D &ct_global, OutlierIndices &in_out_outliers)
Definition: optimal_tf_olae.cpp:57
OLAE_LinearSystems::M
Eigen::Matrix3d M
Definition: optimal_tf_olae.cpp:49
visit_correspondences.h
Template that applies lambdas to unified vector forms of pairings.
mp2p_icp::WeightParameters
Definition: WeightParameters.h:26
mp2p_icp::OutlierIndices
Definition: Pairings.h:180
mp2p_icp::visit_correspondences
void visit_correspondences(const Pairings &in, const WeightParameters &wp, const mrpt::math::TPoint3D &ct_local, const mrpt::math::TPoint3D &ct_global, OutlierIndices &in_out_outliers, LAMBDA lambda_each_pair, LAMBDA2 lambda_final, bool normalize_relative_point_vectors, const mrpt::optional_ref< VisitCorrespondencesStats > &outStats=std::nullopt)
Definition: visit_correspondences.h:31
test.q
q
Definition: test.py:8
mp2p_icp::PairWeights::pt2pt
double pt2pt
Definition: PairWeights.h:32


mp2p_icp
Author(s):
autogenerated on Wed Oct 23 2024 02:45:40