optimal_tf_gauss_newton.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  * ------------------------------------------------------------------------- */
13 #include <mp2p_icp/errorTerms.h>
16 #include <mrpt/poses/Lie/SE.h>
17 
18 #include <Eigen/Dense>
19 #include <iostream>
20 
21 #if defined(MP2P_HAS_TBB)
22 #include <tbb/blocked_range.h>
23 #include <tbb/parallel_reduce.h>
24 #endif
25 
26 using namespace mp2p_icp;
27 
29  const Pairings& in, OptimalTF_Result& result,
30  const OptimalTF_GN_Parameters& gnParams)
31 {
32  using std::size_t;
33 
34  MRPT_START
35 
36  // Run Gauss-Newton steps, using SE(3) relinearization at the current
37  // solution:
38  ASSERTMSG_(
39  gnParams.linearizationPoint.has_value(),
40  "This method requires a linearization point");
41 
42  result.optimalPose = gnParams.linearizationPoint.value();
43 
44  const robust_sqrt_weight_func_t robustSqrtWeightFunc =
46 
47  const auto nPt2Pt = in.paired_pt2pt.size();
48  const auto nPt2Ln = in.paired_pt2ln.size();
49  const auto nPt2Pl = in.paired_pt2pl.size();
50  const auto nPl2Pl = in.paired_pl2pl.size();
51  const auto nLn2Ln = in.paired_ln2ln.size();
52 
53  // Note: Using Matrix<N,1> instead of Vector<N> for compatibility
54  // with Eigen<=3.4 in ROS Noetic.
55  Eigen::Matrix<double, 6, 1> g = Eigen::Matrix<double, 6, 1>::Zero();
56  Eigen::Matrix<double, 6, 6> H = Eigen::Matrix<double, 6, 6>::Zero();
57 
58  auto w = gnParams.pairWeights;
59 
60  const bool has_per_pt_weight = !in.point_weights.empty();
61  auto cur_point_block_weights = in.point_weights.begin();
62  std::size_t cur_point_block_start = 0;
63 
64  for (size_t iter = 0; iter < gnParams.maxInnerLoopIterations; iter++)
65  {
66  // (12x6 Jacobian)
67  const auto dDexpe_de =
68  mrpt::poses::Lie::SE<3>::jacob_dDexpe_de(result.optimalPose);
69 
70  double errNormSqr = 0;
71 
72 #if defined(MP2P_HAS_TBB)
73  // For the TBB lambdas:
74  // TBB call structure based on the beautiful implementation in KISS-ICP.
75  struct Result
76  {
77  Result()
78  {
79  H.setZero();
80  g.setZero();
81  }
82 
83  Result operator+(const Result& other)
84  {
85  H += other.H;
86  g += other.g;
87  return *this;
88  }
89 
90  Eigen::Matrix<double, 6, 6> H;
91  Eigen::Matrix<double, 6, 1> g;
92  };
93 
94  const auto& [H_tbb_pt2pt, g_tbb_pt2pt] = tbb::parallel_reduce(
95  // Range
96  tbb::blocked_range<size_t>{0, nPt2Pt},
97  // Identity
98  Result(),
99  // 1st lambda: Parallel computation
100  [&](const tbb::blocked_range<size_t>& r, Result res) -> Result
101  {
102  auto& [H_local, g_local] = res;
103  for (size_t idx_pt = r.begin(); idx_pt < r.end(); idx_pt++)
104  {
105  // Error:
106  const auto& p = in.paired_pt2pt[idx_pt];
107  mrpt::math::CMatrixFixed<double, 3, 12> J1;
108  mrpt::math::CVectorFixedDouble<3> ret =
110 
111  // Get point weight:
112  if (has_per_pt_weight)
113  {
114  if (idx_pt >= cur_point_block_start +
115  cur_point_block_weights->first)
116  {
117  ASSERT_(
118  cur_point_block_weights !=
119  in.point_weights.end());
120  ++cur_point_block_weights; // move to next block
121  cur_point_block_start = idx_pt;
122  }
123  w.pt2pt = cur_point_block_weights->second;
124  }
125 
126  // Apply robust kernel?
127  double weight = w.pt2pt,
128  retSqrNorm = ret.asEigen().squaredNorm();
129  if (robustSqrtWeightFunc)
130  weight *= robustSqrtWeightFunc(retSqrNorm);
131 
132  // Error and Jacobian:
133  const Eigen::Vector3d err_i = ret.asEigen();
134  errNormSqr += weight * retSqrNorm;
135 
136  const Eigen::Matrix<double, 3, 6> Ji =
137  J1.asEigen() * dDexpe_de.asEigen();
138  g_local.noalias() += weight * Ji.transpose() * err_i;
139  H_local.noalias() += weight * Ji.transpose() * Ji;
140  }
141  return res;
142  },
143  // 2nd lambda: Parallel reduction
144  [](Result a, const Result& b) -> Result { return a + b; });
145 
146  H = std::move(H_tbb_pt2pt);
147  g = std::move(g_tbb_pt2pt);
148 #else
149  // Point-to-point:
150  for (size_t idx_pt = 0; idx_pt < nPt2Pt; idx_pt++)
151  {
152  // Error:
153  const auto& p = in.paired_pt2pt[idx_pt];
154  mrpt::math::CMatrixFixed<double, 3, 12> J1;
155  mrpt::math::CVectorFixedDouble<3> ret =
157 
158  // Get point weight:
159  if (has_per_pt_weight)
160  {
161  if (idx_pt >=
162  cur_point_block_start + cur_point_block_weights->first)
163  {
164  ASSERT_(cur_point_block_weights != in.point_weights.end());
165  ++cur_point_block_weights; // move to next block
166  cur_point_block_start = idx_pt;
167  }
168  w.pt2pt = cur_point_block_weights->second;
169  }
170 
171  // Apply robust kernel?
172  double weight = w.pt2pt, retSqrNorm = ret.asEigen().squaredNorm();
173  if (robustSqrtWeightFunc)
174  weight *= robustSqrtWeightFunc(retSqrNorm);
175 
176  // Error and Jacobian:
177  const Eigen::Vector3d err_i = ret.asEigen();
178  errNormSqr += weight * retSqrNorm;
179 
180  const Eigen::Matrix<double, 3, 6> Ji =
181  J1.asEigen() * dDexpe_de.asEigen();
182  g.noalias() += weight * Ji.transpose() * err_i;
183  H.noalias() += weight * Ji.transpose() * Ji;
184  }
185 #endif
186 
187  // Point-to-line
188  for (size_t idx_pt = 0; idx_pt < nPt2Ln; idx_pt++)
189  {
190  // Error
191  const auto& p = in.paired_pt2ln[idx_pt];
192  mrpt::math::CMatrixFixed<double, 3, 12> J1;
193  mrpt::math::CVectorFixedDouble<3> ret =
195 
196  // Apply robust kernel?
197  double weight = w.pt2ln, retSqrNorm = ret.asEigen().squaredNorm();
198  if (robustSqrtWeightFunc)
199  weight *= robustSqrtWeightFunc(retSqrNorm);
200 
201  // Error and Jacobian:
202  const Eigen::Vector3d err_i = ret.asEigen();
203  errNormSqr += weight * weight * retSqrNorm;
204 
205  const Eigen::Matrix<double, 3, 6> Ji =
206  J1.asEigen() * dDexpe_de.asEigen();
207  g.noalias() += weight * Ji.transpose() * err_i;
208  H.noalias() += weight * Ji.transpose() * Ji;
209  }
210 
211  // Line-to-Line
212  // Minimum angle to approach zero
213  for (size_t idx_ln = 0; idx_ln < nLn2Ln; idx_ln++)
214  {
215  const auto& p = in.paired_ln2ln[idx_ln];
216  mrpt::math::CMatrixFixed<double, 4, 12> J1;
217  mrpt::math::CVectorFixedDouble<4> ret =
218  mp2p_icp::error_line2line(p, result.optimalPose, J1);
219 
220  // Apply robust kernel?
221  double weight = w.ln2ln, retSqrNorm = ret.asEigen().squaredNorm();
222  if (robustSqrtWeightFunc)
223  weight *= robustSqrtWeightFunc(retSqrNorm);
224 
225  // Error and Jacobian:
226  const Eigen::Vector4d err_i = ret.asEigen();
227  errNormSqr += weight * weight * retSqrNorm;
228 
229  const Eigen::Matrix<double, 4, 6> Ji =
230  J1.asEigen() * dDexpe_de.asEigen();
231  g.noalias() += weight * Ji.transpose() * err_i;
232  H.noalias() += weight * Ji.transpose() * Ji;
233  }
234 
235 #if defined(MP2P_HAS_TBB)
236  // Point-to-plane:
237  const auto& [H_tbb_pt2pl, g_tbb_pt2pl] = tbb::parallel_reduce(
238  // Range
239  tbb::blocked_range<size_t>{0, nPt2Pl},
240  // Identity
241  Result(),
242  // 1st lambda: Parallel computation
243  [&](const tbb::blocked_range<size_t>& r, Result res) -> Result
244  {
245  auto& [H_local, g_local] = res;
246  for (size_t idx_pl = r.begin(); idx_pl < r.end(); idx_pl++)
247  {
248  // Error:
249  const auto& p = in.paired_pt2pl[idx_pl];
250  mrpt::math::CMatrixFixed<double, 3, 12> J1;
251  mrpt::math::CVectorFixedDouble<3> ret =
253 
254  // Apply robust kernel?
255  double weight = w.pt2pl,
256  retSqrNorm = ret.asEigen().squaredNorm();
257  if (robustSqrtWeightFunc)
258  weight *= robustSqrtWeightFunc(retSqrNorm);
259 
260  // Error and Jacobian:
261  const Eigen::Vector3d err_i = ret.asEigen();
262  errNormSqr += weight * retSqrNorm;
263 
264  const Eigen::Matrix<double, 3, 6> Ji =
265  J1.asEigen() * dDexpe_de.asEigen();
266  g_local.noalias() += weight * Ji.transpose() * err_i;
267  H_local.noalias() += weight * Ji.transpose() * Ji;
268  }
269  return res;
270  },
271  // 2nd lambda: Parallel reduction
272  [](Result a, const Result& b) -> Result { return a + b; });
273 
274  H += H_tbb_pt2pl;
275  g += g_tbb_pt2pl;
276 #else
277  // Point-to-plane:
278  for (size_t idx_pl = 0; idx_pl < nPt2Pl; idx_pl++)
279  {
280  // Error:
281  const auto& p = in.paired_pt2pl[idx_pl];
282  mrpt::math::CMatrixFixed<double, 3, 12> J1;
283  mrpt::math::CVectorFixedDouble<3> ret =
285 
286  // Apply robust kernel?
287  double weight = w.pt2pl, retSqrNorm = ret.asEigen().squaredNorm();
288  if (robustSqrtWeightFunc)
289  weight *= robustSqrtWeightFunc(retSqrNorm);
290 
291  // Error and Jacobian:
292  const Eigen::Vector3d err_i = ret.asEigen();
293  errNormSqr += weight * weight * retSqrNorm;
294 
295  const Eigen::Matrix<double, 3, 6> Ji =
296  J1.asEigen() * dDexpe_de.asEigen();
297  g.noalias() += weight * Ji.transpose() * err_i;
298  H.noalias() += weight * Ji.transpose() * Ji;
299  }
300 #endif
301 
302  // Plane-to-plane (only direction of normal vectors):
303  for (size_t idx_pl = 0; idx_pl < nPl2Pl; idx_pl++)
304  {
305  // Error term:
306  const auto& p = in.paired_pl2pl[idx_pl];
307  mrpt::math::CMatrixFixed<double, 3, 12> J1;
308  mrpt::math::CVectorFixedDouble<3> ret =
310 
311  // Apply robust kernel?
312  double weight = w.pl2pl, retSqrNorm = ret.asEigen().squaredNorm();
313  if (robustSqrtWeightFunc)
314  weight *= robustSqrtWeightFunc(retSqrNorm);
315 
316  const Eigen::Vector3d err_i = ret.asEigen();
317  errNormSqr += weight * weight * retSqrNorm;
318 
319  const Eigen::Matrix<double, 3, 6> Ji =
320  J1.asEigen() * dDexpe_de.asEigen();
321  g.noalias() += weight * Ji.transpose() * err_i;
322  H.noalias() += weight * Ji.transpose() * Ji;
323  }
324 
325  // Prior guess term:
326  if (gnParams.prior.has_value())
327  {
328  const auto& priorMean = gnParams.prior->mean;
329  const auto& priorInf = gnParams.prior->cov_inv;
330 
331  // Compute the residual pose error of these pair of nodes + its
332  // constraint:
333  // SE(3) error = inv(P_prior) * P_current
334  // = (P_current \ominus P_prior)
335 
336  const mrpt::poses::CPose3D P1invP2 = result.optimalPose - priorMean;
337  const auto err_i = mrpt::poses::Lie::SE<3>::log(P1invP2);
338 
339  mrpt::math::CMatrixDouble66 df_de2;
340 
341  mrpt::poses::Lie::SE<3>::jacob_dDinvP1invP2_de1e2(
342  // edge between the two poses:in this case, both should coincide
343  mrpt::poses::CPose3D::Identity(),
344  // P1:
345  priorMean,
346  // P2:
347  result.optimalPose,
348  // df_de1
349  std::nullopt,
350  // df_de2
351  df_de2);
352 
353  g.noalias() +=
354  (df_de2.transpose() * priorInf.asEigen()) * err_i.asEigen();
355 
356  H.noalias() +=
357  (df_de2.transpose() * priorInf.asEigen()) * df_de2.asEigen();
358  }
359 
360  // Target error?
361  const double errNorm = std::sqrt(errNormSqr);
362 
363  if (errNorm <= gnParams.maxCost) break;
364 
365  // 3) Solve Gauss-Newton:
366  // g = J.transpose() * err;
367  // H = J.transpose() * J;
368  const Eigen::Matrix<double, 6, 1> delta = -H.ldlt().solve(g);
369 
370  // 4) add SE(3) increment:
371  const auto dE = mrpt::poses::Lie::SE<3>::exp(
372  mrpt::math::CVectorFixed<double, 6>(delta));
373 
374  result.optimalPose = result.optimalPose + dE;
375 
376  if (gnParams.verbose)
377  {
378  std::cout << "[P2P GN] iter:" << iter << " err:" << errNorm
379  << " delta:" << delta.transpose() << "\n";
380  }
381 
382  // Simple convergence test:
383  if (delta.norm() < gnParams.minDelta) break;
384 
385  } // for each iteration
386 
387  return true;
388 
389  MRPT_END
390 }
mp2p_icp::OptimalTF_GN_Parameters
Definition: optimal_tf_gauss_newton.h:24
mp2p_icp::Pairings::paired_ln2ln
MatchedLineList paired_ln2ln
Definition: Pairings.h:91
mp2p_icp::optimal_tf_gauss_newton
bool optimal_tf_gauss_newton(const Pairings &in, OptimalTF_Result &result, const OptimalTF_GN_Parameters &gnParams=OptimalTF_GN_Parameters())
Definition: optimal_tf_gauss_newton.cpp:28
mp2p_icp::OptimalTF_Result::optimalPose
mrpt::poses::CPose3D optimalPose
Definition: OptimalTF_Result.h:26
mp2p_icp::OptimalTF_GN_Parameters::kernelParam
double kernelParam
Definition: optimal_tf_gauss_newton.h:50
mp2p_icp
Definition: covariance.h:17
test.res
res
Definition: test.py:11
mp2p_icp::robust_sqrt_weight_func_t
std::function< double(double)> robust_sqrt_weight_func_t
Definition: robust_kernels.h:37
mp2p_icp::Pairings
Definition: Pairings.h:78
mp2p_icp::OptimalTF_GN_Parameters::verbose
bool verbose
Definition: optimal_tf_gauss_newton.h:52
mp2p_icp::OptimalTF_GN_Parameters::kernel
RobustKernel kernel
Definition: optimal_tf_gauss_newton.h:49
mp2p_icp::OptimalTF_Result
Definition: OptimalTF_Result.h:24
mp2p_icp::OptimalTF_GN_Parameters::maxCost
double maxCost
Definition: optimal_tf_gauss_newton.h:42
optimal_tf_gauss_newton.h
Simple non-linear optimizer to find the SE(3) optimal transformation.
mp2p_icp::Pairings::point_weights
std::vector< std::pair< std::size_t, double > > point_weights
Definition: Pairings.h:104
errorTerms.h
mp2p_icp::error_plane2plane
mrpt::math::CVectorFixedDouble< 3 > error_plane2plane(const mp2p_icp::matched_plane_t &pairing, const mrpt::poses::CPose3D &relativePose, mrpt::optional_ref< mrpt::math::CMatrixFixed< double, 3, 12 >> jacobian=std::nullopt)
Definition: errorTerms.cpp:337
mp2p_icp::error_point2plane
mrpt::math::CVectorFixedDouble< 3 > error_point2plane(const mp2p_icp::point_plane_pair_t &pairing, const mrpt::poses::CPose3D &relativePose, mrpt::optional_ref< mrpt::math::CMatrixFixed< double, 3, 12 >> jacobian=std::nullopt)
Definition: errorTerms.cpp:109
mp2p_icp::OptimalTF_GN_Parameters::prior
std::optional< mrpt::poses::CPose3DPDFGaussianInf > prior
Definition: optimal_tf_gauss_newton.h:36
mp2p_icp::create_robust_kernel
robust_sqrt_weight_func_t create_robust_kernel(const RobustKernel kernel, const double kernelParam)
Definition: robust_kernels.h:49
mp2p_icp::OptimalTF_GN_Parameters::maxInnerLoopIterations
uint32_t maxInnerLoopIterations
Definition: optimal_tf_gauss_newton.h:47
mp2p_icp::OptimalTF_GN_Parameters::pairWeights
PairWeights pairWeights
Definition: optimal_tf_gauss_newton.h:44
mp2p_icp::OptimalTF_GN_Parameters::linearizationPoint
std::optional< mrpt::poses::CPose3D > linearizationPoint
Definition: optimal_tf_gauss_newton.h:29
mp2p_icp::error_point2point
mrpt::math::CVectorFixedDouble< 3 > error_point2point(const mrpt::tfest::TMatchingPair &pairing, const mrpt::poses::CPose3D &relativePose, mrpt::optional_ref< mrpt::math::CMatrixFixed< double, 3, 12 >> jacobian=std::nullopt)
Definition: errorTerms.cpp:28
mp2p_icp::Pairings::paired_pl2pl
MatchedPlaneList paired_pl2pl
Definition: Pairings.h:92
robust_kernels.h
Robust kernel types and functions, for common use in all solvers.
mp2p_icp::Pairings::paired_pt2ln
MatchedPointLineList paired_pt2ln
Definition: Pairings.h:89
operator+
TF2SIMD_FORCE_INLINE Vector3 operator+(const Vector3 &v1, const Vector3 &v2)
mp2p_icp::OptimalTF_GN_Parameters::minDelta
double minDelta
Definition: optimal_tf_gauss_newton.h:39
mp2p_icp::Pairings::paired_pt2pt
mrpt::tfest::TMatchingPairList paired_pt2pt
Definition: Pairings.h:88
mp2p_icp::error_point2line
mrpt::math::CVectorFixedDouble< 3 > error_point2line(const mp2p_icp::point_line_pair_t &pairing, const mrpt::poses::CPose3D &relativePose, mrpt::optional_ref< mrpt::math::CMatrixFixed< double, 3, 12 >> jacobian=std::nullopt)
Definition: errorTerms.cpp:61
mp2p_icp::Pairings::paired_pt2pl
MatchedPointPlaneList paired_pt2pl
Definition: Pairings.h:90
mp2p_icp::error_line2line
mrpt::math::CVectorFixedDouble< 4 > error_line2line(const mp2p_icp::matched_line_t &pairing, const mrpt::poses::CPose3D &relativePose, mrpt::optional_ref< mrpt::math::CMatrixFixed< double, 4, 12 >> jacobian=std::nullopt)
Definition: errorTerms.cpp:167


mp2p_icp
Author(s):
autogenerated on Thu Dec 26 2024 03:48:12