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, const mrpt::math::TPoint3D& ct_local,
59  const mrpt::math::TPoint3D& ct_global, OutlierIndices& in_out_outliers)
60 {
61  MRPT_START
62 
63  using mrpt::math::TPoint3D;
64  using mrpt::math::TVector3D;
65 
67 
68  // Build the linear system: M g = v
69  res.M = Eigen::Matrix3d::Zero();
70  res.v = Eigen::Vector3d::Zero();
71 
72  // Attitude profile matrix:
73  res.B = Eigen::Matrix3d::Zero();
74 
75  // Lambda: process each pairing:
76  auto lambda_each_pair =
77  [&](const mrpt::math::TVector3D& bi, const mrpt::math::TVector3D& ri, const double wi)
78  {
79 // We will evaluate M from an alternative expression below from the
80 // attitude profile matrix B instead, since it seems to be slightly more
81 // stable, numerically. The original code for M is left here for
82 // reference, though.
83 #if 0
84  // M+=(1/2)* ([s_i]_{x})^2
85  // with: s_i = b_i + r_i
86  const double sx = bi.x + ri.x, sy = bi.y + ri.y, sz = bi.z + ri.z;
87 
88  /* ([s_i]_{x})^2 is:
89  *
90  * ⎡ 2 2 ⎤
91  * ⎢- sy - sz sx⋅sy sx⋅sz ⎥
92  * ⎢ ⎥
93  * ⎢ 2 2 ⎥
94  * ⎢ sx⋅sy - sx - sz sy⋅sz ⎥
95  * ⎢ ⎥
96  * ⎢ 2 2⎥
97  * ⎣ sx⋅sz sy⋅sz - sx - sy ⎦
98  */
99  const double c00 = -sy * sy - sz * sz;
100  const double c11 = -sx * sx - sz * sz;
101  const double c22 = -sx * sx - sy * sy;
102  const double c01 = sx * sy;
103  const double c02 = sx * sz;
104  const double c12 = sy * sz;
105 
106  // clang-format off
107  const auto dM = (Eigen::Matrix3d() <<
108  c00, c01, c02,
109  c01, c11, c12,
110  c02, c12, c22 ).finished();
111  // clang-format on
112 
113  // res.M += wi * dM;
114 
115  // The missing (1/2) from the formulas above:
116  res.M *= 0.5;
117 #endif
118  /* v-= weight * [b_i]_{x} r_i
119  * Each term is:
120  * ⎡by⋅rz - bz⋅ry ⎤ ⎡ B23 - B32 ⎤
121  * ⎢ ⎥ | ⎥
122  * ⎢-bx⋅rz + bz⋅rx⎥ = | B31 - B13 ⎥
123  * ⎢ ⎥ | ⎥
124  * ⎣bx⋅ry - by⋅rx ⎦ ⎣ B12 - B21 ⎦
125  *
126  * B (attitude profile matrix):
127  *
128  * B+= weight * (b_i * r_i')
129  *
130  */
131 
132  // clang-format off
133  const auto dV = (Eigen::Vector3d() <<
134  (bi.y * ri.z - bi.z * ri.y),
135  (-bi.x * ri.z + bi.z * ri.x),
136  (bi.x * ri.y - bi.y * ri.x) ).finished();
137  // clang-format on
138 
139  res.v -= wi * dV;
140 
141  // clang-format off
142  const auto dB = (Eigen::Matrix3d() <<
143  bi.x * ri.x, bi.x * ri.y, bi.x * ri.z,
144  bi.y * ri.x, bi.y * ri.y, bi.y * ri.z,
145  bi.z * ri.x, bi.z * ri.y, bi.z * ri.z).finished();
146  // clang-format on
147  res.B += wi * dB;
148  }; // end lambda for visit_correspondences()
149 
150  // Lambda for the final stage after visiting all corres:
151  auto lambda_final = [&](const double w_sum)
152  {
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, lambda_final,
170  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] = eval_centroids_robust(in, result.outliers /* empty for now */);
258 
259  // Build the linear system: M g = v
260  OLAE_LinearSystems linsys =
261  olae_build_linear_system(in, wp, ct_local, ct_global, result.outliers /* empty for now */);
262 
263  // Re-evaluate the centroids, now that we have a guess on outliers.
264  if (!result.outliers.empty())
265  {
266  // Re-evaluate the centroids:
267  const auto [new_ct_local, new_ct_global] = eval_centroids_robust(in, result.outliers);
268 
269  ct_local = new_ct_local;
270  ct_global = new_ct_global;
271 
272  // And rebuild the linear system with the new values:
273  linsys = olae_build_linear_system(in, wp, ct_local, ct_global, result.outliers);
274  }
275 
276  // We are finding the optimal rotation "g", as a Gibbs vector.
277  // Solve linear system for optimal rotation: M g = v
278 
279  const double detM_orig = std::abs(linsys.M.determinant()),
280  detMx = std::abs(linsys.Mx.determinant()),
281  detMy = std::abs(linsys.My.determinant()),
282  detMz = std::abs(linsys.Mz.determinant());
283 
284 #if 0
285  // clang-format off
286  std::cout << " |M_orig|= " << detM_orig << "\n"
287  " |M_x| = " << detMx << "\n"
288  " |M_t| = " << detMy << "\n"
289  " |M_z| = " << detMz << "\n";
290  // clang-format on
291 #endif
292 
293  if (detM_orig > mrpt::max3(detMx, detMy, detMz))
294  {
295  // original rotation is the best numerically-determined problem:
296  const auto sol0 = gibbs2pose(linsys.M.colPivHouseholderQr().solve(linsys.v));
297  result.optimalPose = sol0;
298 #if 0
299  std::cout << "M : |M|="
300  << mrpt::format("%16.07f", linsys.M.determinant())
301  << " sol: " << sol0.asString() << "\n";
302 #endif
303  }
304  else if (detMx > mrpt::max3(detM_orig, detMy, detMz))
305  {
306  // rotation wrt X is the best choice:
307  auto sol1 = gibbs2pose(linsys.Mx.colPivHouseholderQr().solve(linsys.vx));
308  sol1 = mrpt::poses::CPose3D(0, 0, 0, 0, 0, M_PI) + sol1;
309  result.optimalPose = sol1;
310 #if 0
311  std::cout << "M_x : |M|="
312  << mrpt::format("%16.07f", linsys.Mx.determinant())
313  << " sol: " << sol1.asString() << "\n";
314 #endif
315  }
316  else if (detMy > mrpt::max3(detM_orig, detMx, detMz))
317  {
318  // rotation wrt Y is the best choice:
319  auto sol2 = gibbs2pose(linsys.My.colPivHouseholderQr().solve(linsys.vy));
320  sol2 = mrpt::poses::CPose3D(0, 0, 0, 0, M_PI, 0) + sol2;
321  result.optimalPose = sol2;
322 #if 0
323  std::cout << "M_y : |M|="
324  << mrpt::format("%16.07f", linsys.My.determinant())
325  << " sol: " << sol2.asString() << "\n";
326 #endif
327  }
328  else
329  {
330  // rotation wrt Z is the best choice:
331  auto sol3 = gibbs2pose(linsys.Mz.colPivHouseholderQr().solve(linsys.vz));
332  sol3 = mrpt::poses::CPose3D(0, 0, 0, M_PI, 0, 0) + sol3;
333  result.optimalPose = sol3;
334 #if 0
335  std::cout << "M_z : |M|="
336  << mrpt::format("%16.07f", linsys.Mz.determinant())
337  << " sol: " << sol3.asString() << "\n";
338 #endif
339  }
340 
341  // Use centroids to solve for optimal translation:
342  mrpt::math::TPoint3D pp;
343  result.optimalPose.composePoint(ct_local.x, ct_local.y, ct_local.z, pp.x, pp.y, pp.z);
344  // Scale, if used, was: pp *= s;
345 
346  result.optimalPose.x(ct_global.x - pp.x);
347  result.optimalPose.y(ct_global.y - pp.y);
348  result.optimalPose.z(ct_global.z - pp.z);
349 
350  return true;
351 
352  MRPT_END
353 }
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:76
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:178
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:60
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:172
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 Mon May 26 2025 02:45:50