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