test-mp2p_icp_algos.cpp
Go to the documentation of this file.
1 /* -------------------------------------------------------------------------
2  * A Modular Optimization framework for Localization and mApping (MOLA)
3  * Copyright (C) 2018-2024 Jose Luis Blanco, University of Almeria
4  * See LICENSE for license information.
5  * ------------------------------------------------------------------------- */
6 
18 #include <mp2p_icp/Solver_Horn.h>
19 #include <mp2p_icp/Solver_OLAE.h>
20 #include <mp2p_icp/load_xyz_file.h>
21 #include <mrpt/core/exceptions.h>
22 #include <mrpt/core/get_env.h>
23 #include <mrpt/maps/CSimplePointsMap.h>
24 #include <mrpt/poses/CPose3D.h>
25 #include <mrpt/poses/CPose3DQuat.h>
26 #include <mrpt/poses/Lie/SE.h>
27 #include <mrpt/poses/Lie/SO.h>
28 #include <mrpt/random.h>
29 #include <mrpt/system/CTimeLogger.h>
30 #include <mrpt/system/filesystem.h>
31 #include <mrpt/version.h>
32 
33 #include <Eigen/Dense>
34 #include <iostream>
35 
36 namespace
37 {
38 
39 int NUM_REPS = mrpt::get_env<int>("NUM_REPS", 3);
40 bool DO_SAVE_STAT_FILES = mrpt::get_env<bool>("DO_SAVE_STAT_FILES", false);
41 bool DO_PRINT_ALL = mrpt::get_env<bool>("DO_PRINT_ALL", false);
42 
43 const std::string datasetDir = MP2P_DATASET_DIR;
44 
45 void test_icp(
46  const std::string& inFile, const std::string& icpClassName, const std::string& solverName,
47  const std::string& matcherName, int pointDecimation)
48 {
49  using namespace mrpt::poses::Lie;
50 
51  const auto fileFullPath = datasetDir + inFile;
52 
53  mrpt::maps::CSimplePointsMap::Ptr pts = mp2p_icp::load_xyz_file(fileFullPath);
54 
55  if (pointDecimation > 1)
56  {
57  auto decimPts = mrpt::maps::CSimplePointsMap::Create();
58  for (size_t i = 0; i < pts->size(); i += pointDecimation)
59  {
60  float x, y, z;
61  pts->getPoint(i, x, y, z);
62  decimPts->insertPoint(x, y, z);
63  }
64  pts = decimPts;
65  }
66 
67  std::cout << "Running " << icpClassName << "|" << solverName << "|" << matcherName
68  << " test on: " << inFile << " with " << pts->size() << " points\n";
69 
70  double outliers_ratio = 0;
71  bool use_robust = true;
72 
73  const std::string tstName = mrpt::format(
74  "test_icp_Model=%s_Algo=%s_%s_%s_outliers=%06.03f_robust=%i", inFile.c_str(),
75  icpClassName.c_str(), solverName.c_str(), matcherName.c_str(), outliers_ratio,
76  use_robust ? 1 : 0);
77 
78  const auto bbox = pts->boundingBox();
79  const auto bbox_size = bbox.max - bbox.min;
80 
81  const double max_dim = mrpt::max3(bbox_size.x, bbox_size.y, bbox_size.z);
82 
83  const double f = 0.15;
84 
85  mrpt::system::CTicTac timer;
86 
87  // Collect stats: columns are (see write TXT to file code at the bottom)
88  mrpt::math::CMatrixDouble stats(NUM_REPS, 2 + 2 + 1);
89 
90  for (int rep = 0; rep < NUM_REPS; rep++)
91  {
92  // Create transformed (and corrupted?) point cloud:
93  auto& rnd = mrpt::random::getRandomGenerator();
94  const double Dx = rnd.drawUniform(-f * bbox_size.x, f * bbox_size.x);
95  const double Dy = rnd.drawUniform(-f * bbox_size.y, f * bbox_size.y);
96  const double Dz = rnd.drawUniform(-f * bbox_size.z, f * bbox_size.z);
97  const double yaw = mrpt::DEG2RAD(rnd.drawUniform(-10.0, 10.0));
98  const double pitch = mrpt::DEG2RAD(rnd.drawUniform(-10.0, 10.0));
99  const double roll = mrpt::DEG2RAD(rnd.drawUniform(-10.0, 10.0));
100 
101  const auto gt_pose = mrpt::poses::CPose3D(Dx, Dy, Dz, yaw, pitch, roll);
102 
103  stats(rep, 0) = SO<3>::log(gt_pose.getRotationMatrix()).norm();
104  stats(rep, 1) = gt_pose.norm();
105 
106  auto pts_reg = mrpt::maps::CSimplePointsMap::Create();
107  pts_reg->changeCoordinatesReference(*pts, -gt_pose);
108 
109  // Point clouds: reference and modified:
110  mp2p_icp::metric_map_t pc_ref, pc_mod;
111  pc_ref.layers["raw"] = pts;
112  pc_mod.layers["raw"] = pts_reg;
113 
114  const auto init_guess = mrpt::math::TPose3D::Identity();
115 
116  mp2p_icp::ICP::Ptr icp =
117  std::dynamic_pointer_cast<mp2p_icp::ICP>(mrpt::rtti::classFactory(icpClassName));
118 
119  if (!icp)
120  THROW_EXCEPTION_FMT(
121  "Could not create object of type `%s`, is it registered?", icpClassName.c_str());
122 
123  // Initialize solvers:
124  if (!solverName.empty())
125  {
126  mp2p_icp::Solver::Ptr solver =
127  std::dynamic_pointer_cast<mp2p_icp::Solver>(mrpt::rtti::classFactory(solverName));
128 
129  if (!solver)
130  THROW_EXCEPTION_FMT(
131  "Could not create Solver of type `%s`, is it registered?", solverName.c_str());
132 
133  icp->solvers().clear();
134  icp->solvers().push_back(solver);
135  }
136 
137  // Initialize matchers:
138  if (!matcherName.empty())
139  {
140  mp2p_icp::Matcher::Ptr matcher =
141  std::dynamic_pointer_cast<mp2p_icp::Matcher>(mrpt::rtti::classFactory(matcherName));
142 
143  if (!matcher)
144  THROW_EXCEPTION_FMT(
145  "Could not create Matcher of type `%s`, is it registered?",
146  matcherName.c_str());
147 
148  icp->matchers().push_back(matcher);
149  }
150 
151  // Special parameters:
152  if (!icp->matchers().empty())
153  {
154  if (auto m = std::dynamic_pointer_cast<mp2p_icp::Matcher_Points_DistanceThreshold>(
155  icp->matchers().at(0));
156  m)
157  {
158  mrpt::containers::yaml ps;
159  ps["threshold"] = 0.40 * max_dim;
160  ps["thresholdAngularDeg"] = 0;
161  m->initialize(ps);
162  }
163 
164  if (auto m =
165  std::dynamic_pointer_cast<mp2p_icp::Matcher_Point2Plane>(icp->matchers().at(0));
166  m)
167  {
168  mrpt::containers::yaml ps;
169  ps["distanceThreshold"] = 0.05 * max_dim;
170  ps["searchRadius"] = 0.20 * max_dim;
171  ps["planeEigenThreshold"] = 10.0;
172  ps["minimumPlanePoints"] = 5;
173  ps["knn"] = 5;
174 
175  m->initialize(ps);
176  }
177  }
178 
179  // ICP test itself:
180  mp2p_icp::Parameters icp_params;
181  mp2p_icp::Results icp_results;
182 
183  icp_params.maxIterations = 100;
184 
185  timer.Tic();
186 
187  icp->align(pc_mod, pc_ref, init_guess, icp_params, icp_results);
188 
189  const double dt = timer.Tac();
190 
191  const auto pos_error = gt_pose - icp_results.optimal_tf.mean;
192  const auto err_log_n = SO<3>::log(pos_error.getRotationMatrix()).norm();
193  const auto err_xyz = pos_error.norm();
194  const auto err_se3 = SE<3>::log(pos_error).norm();
195 
196  stats(rep, 2 + 0) = err_log_n;
197  stats(rep, 2 + 1) = err_xyz;
198  stats(rep, 2 + 2) = dt;
199 
200  if (DO_PRINT_ALL)
201  {
202  std::cout << "GT pose : " << gt_pose.asString() << "\n";
203  std::cout << "ICP pose : " << icp_results.optimal_tf.mean.asString() << "\n";
204  std::cout << "Error SE(3) : " << err_se3 << "\n";
205  std::cout << "ICP pose stddev: "
206  << icp_results.optimal_tf.cov.asEigen()
207  .diagonal()
208  .array()
209  .sqrt()
210  .matrix()
211  .transpose()
212  << "\n";
213  std::cout << "ICP quality : " << icp_results.quality << "\n";
214  std::cout << "ICP iterations : " << icp_results.nIterations << "\n";
215  }
216  ASSERT_LT_(err_se3, 0.1);
217 
218  } // for reps
219 
220  if (DO_SAVE_STAT_FILES)
221  stats.saveToTextFile(
222  mrpt::system::fileNameStripInvalidChars(tstName) + std::string(".txt"),
223  mrpt::math::MATRIX_FORMAT_ENG, true,
224  "% Columns: norm GT_rot, norm_GT_XYZ, norm(SO3_error) "
225  "norm(XYZ_error) icp_time\n\n");
226 }
227 
228 } // namespace
229 
230 int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
231 {
232  try
233  {
234  auto& rnd = mrpt::random::getRandomGenerator();
235  rnd.randomize(1234); // for reproducible tests
236 
237  const std::vector<const char*> lst_files{
238  {"bunny_decim.xyz.gz", "happy_buddha_decim.xyz.gz"}};
239 
240  using lst_algos_t =
241  std::vector<std::tuple<const char*, const char*, const char*, int /*decimation*/>>;
242  // clang-format off
243  lst_algos_t lst_algos = {
244  {"mp2p_icp::ICP", "mp2p_icp::Solver_Horn", "mp2p_icp::Matcher_Points_DistanceThreshold", 10},
245  {"mp2p_icp::ICP", "mp2p_icp::Solver_Horn", "mp2p_icp::Matcher_Points_InlierRatio", 10},
246  //{"mp2p_icp::ICP", "mp2p_icp::Solver_Horn", "mp2p_icp::Matcher_Point2Plane", 10},
247 
248  {"mp2p_icp::ICP", "mp2p_icp::Solver_OLAE", "mp2p_icp::Matcher_Points_DistanceThreshold", 10},
249  {"mp2p_icp::ICP", "mp2p_icp::Solver_OLAE", "mp2p_icp::Matcher_Points_InlierRatio", 10},
250  //{"mp2p_icp::ICP", "mp2p_icp::Solver_OLAE", "mp2p_icp::Matcher_Point2Plane", 10},
251 
252  {"mp2p_icp::ICP", "mp2p_icp::Solver_GaussNewton", "mp2p_icp::Matcher_Points_DistanceThreshold", 10},
253  {"mp2p_icp::ICP", "mp2p_icp::Solver_GaussNewton", "mp2p_icp::Matcher_Points_InlierRatio", 10},
254  //{"mp2p_icp::ICP", "mp2p_icp::Solver_GaussNewton", "mp2p_icp::Matcher_Point2Plane", 10},
255  };
256  // clang-format on
257 
258  // Optional methods:
259 #if 0 // Disabled for now: we now need to initialize from a YAML file:
261  lst_algos.push_back({"mp2p_icp::ICP_LibPointmatcher", "", "", 1});
262 #endif
263 
264  for (const auto& algo : lst_algos)
265  for (const auto& fil : lst_files)
266  test_icp(
267  fil, std::get<0>(algo), std::get<1>(algo), std::get<2>(algo),
268  std::get<3>(algo));
269  }
270  catch (std::exception& e)
271  {
272  std::cerr << mrpt::exception_to_str(e) << "\n";
273  return 1;
274  }
275 }
DO_PRINT_ALL
static bool DO_PRINT_ALL
Definition: test-mp2p_optimal_tf_algos.cpp:34
timer
Definition: helpers.h:128
kitti-run-seq.f
string f
Definition: kitti-run-seq.py:12
ICP_LibPointmatcher.h
ICP wrapper on libpointmatcher.
main
int main([[maybe_unused]] int argc, [[maybe_unused]] char **argv)
Definition: test-mp2p_icp_algos.cpp:230
mp2p_icp::Results::optimal_tf
mrpt::poses::CPose3DPDFGaussian optimal_tf
Definition: Results.h:25
test.x
x
Definition: test.py:4
testing::internal::string
::std::string string
Definition: gtest.h:1979
mp2p_icp::Results::nIterations
size_t nIterations
Definition: Results.h:31
mp2p_icp::Parameters::maxIterations
uint32_t maxIterations
Definition: Parameters.h:34
rnd
auto & rnd
Definition: test-mp2p_error_terms_jacobians.cpp:28
mp2p_icp::ICP_LibPointmatcher::methodAvailable
static bool methodAvailable()
Definition: ICP_LibPointmatcher.cpp:35
Matcher_Points_DistanceThreshold.h
Pointcloud matcher: fixed distance thresholds.
Solver_Horn.h
ICP solver for pointclouds split in different "layers".
Solver_OLAE.h
ICP registration for points and planes.
PointMatcherSupport::timer
boost::timer timer
Definition: Timer.h:80
Solver_GaussNewton.h
ICP registration for points and planes.
mp2p_icp::load_xyz_file
mrpt::maps::CSimplePointsMap::Ptr load_xyz_file(const std::string &fil)
Definition: load_xyz_file.cpp:21
DO_SAVE_STAT_FILES
static bool DO_SAVE_STAT_FILES
Definition: test-mp2p_optimal_tf_algos.cpp:33
mp2p_icp::metric_map_t
Generic container of pointcloud(s), extracted features and other maps.
Definition: metricmap.h:55
mp2p_icp::Results
Definition: Results.h:21
mp2p_icp::Parameters
Definition: Parameters.h:26
load_xyz_file.h
Unit tests common utilities.
mp2p_icp::metric_map_t::layers
std::map< layer_name_t, mrpt::maps::CMetricMap::Ptr > layers
Definition: metricmap.h:82
Matcher_Point2Plane.h
Pointcloud matcher: point to plane-fit of nearby points.
mp2p_icp::Results::quality
double quality
Definition: Results.h:38


mp2p_icp
Author(s):
autogenerated on Mon May 26 2025 02:45:50