src/tools/lvr2_registration/Main.cpp
Go to the documentation of this file.
1 
35 #include "lvr2/io/ModelFactory.hpp"
36 #include "lvr2/io/IOUtils.hpp"
38 #include "lvr2/io/HDF5IO.hpp"
40 #include "lvr2/io/hdf5/ArrayIO.hpp"
46 
47 
48 #include <boost/program_options.hpp>
49 #include <boost/filesystem.hpp>
50 #include <boost/optional.hpp>
51 #include <iostream>
52 #include <chrono>
53 #include <fstream>
54 
55 using namespace lvr2;
56 using namespace std;
57 using boost::filesystem::path;
58 
59 string format_name(const string& format, int index)
60 {
61  size_t size = snprintf(nullptr, 0, format.c_str(), index) + 1; // Extra space for '\0'
62  char buff[size];
63  snprintf(buff, size, format.c_str(), index);
64  return string(buff);
65 }
66 
67 
68 string map_format(const string& format)
69 {
70  if (format == "uos")
71  {
72  return "scan%03i.3d";
73  }
74  else if (format == "riegl_txt")
75  {
76  return "scan%03i.txt";
77  }
78  else if (format == "ply")
79  {
80  return "scan%03i.ply";
81  }
82  else
83  {
84  throw boost::program_options::error(string("Unknown Output format: ") + format);
85  }
86 }
87 
88 int main(int argc, char** argv)
89 {
90  // =============== parse options ===============
92 
93  path dir;
94  int start = -1;
95  int end = -1;
96  string format = "uos";
97  string pose_format = "pose";
98  bool isHDF = false;
99 
100  bool write_scans = false;
101  string output_format;
102  bool write_pose = false;
103  string output_pose_format;
104  bool no_frames = false;
105  path output_dir;
106 
107  bool help;
108 
109  try
110  {
111  using namespace boost::program_options;
112 
113  options_description general_options("General Options");
114  options_description icp_options("ICP Options");
115  options_description loopclosing_options("Loopclosing Options");
116 
117  general_options.add_options()
118  ("start,s", value<int>(&start)->default_value(start),
119  "The first Scan to process.\n"
120  "-1 (default): Search for first Scan.")
121 
122  ("end,e", value<int>(&end)->default_value(end),
123  "The last Scan to process.\n"
124  "-1 (default): Continue until no more Scans found.")
125 
126  ("format,f", value<string>(&format)->default_value(format),
127  "The format of the Scans in <dir>.\n"
128  "This can be a predefined Format (uos, ply, riegl_txt), or a printf Format String like \"scan%03i.3d\",\n"
129  "containing one %i or %d that will be replaced with the Scan Index.")
130 
131  ("pose-format", value<string>(&pose_format)->default_value(pose_format),
132  "The File extension of the Pose files.\n"
133  "Currently supported are: pose, dat, frames.")
134 
135  ("reduction,r", value<double>(&options.reduction)->default_value(options.reduction),
136  "The Voxel size for Octree based reduction.\n"
137  "-1 (default): No reduction.")
138 
139  ("min,m", value<double>(&options.minDistance)->default_value(options.minDistance),
140  "Ignore all Points closer than <value> to the origin of the Scan.\n"
141  "-1 (default): No filter.")
142 
143  ("max,M", value<double>(&options.maxDistance)->default_value(options.maxDistance),
144  "Ignore all Points farther away than <value> from the origin of the Scan.\n"
145  "-1 (default): No filter.")
146 
147  ("trustPose,p", bool_switch(&options.trustPose),
148  "Use the unmodified Pose for ICP. Useful for GPS Poses or unordered Scans.\n"
149  "false (default): Apply the relative refinement of previous Scans.")
150 
151  ("metascan", bool_switch(&options.metascan),
152  "Match Scans to the combined Pointcloud of all previous Scans instead of just the last Scan.")
153 
154  ("noFrames,F", bool_switch(&no_frames),
155  "Don't write \".frames\" files.")
156 
157  ("writePose,w", value<string>(&output_pose_format)->implicit_value("<pose-format>"),
158  "Write Poses to directory specified by --output.")
159 
160  ("writeScans,W", value<string>(&output_format)->implicit_value("<format>"),
161  "Write Scans to directory specified by --output.")
162 
163  ("output,o", value<path>(&output_dir),
164  "Changes output directory of --writePose and --writeScans. Does not affect \".frames\" files\n"
165  "default: <dir>/output.")
166 
167  ("verbose,v", bool_switch(&options.verbose),
168  "Show more detailed output. Useful for fine-tuning Parameters or debugging.")
169 
170  ("hdf,H", bool_switch(&options.useHDF),
171  "Opens the given hdf5 file. Then registrates all scans in '/raw/scans/'\nthat are named after the scheme: 'position_00001' where '1' is the scans number.\nAfter registration the calculated poses are written to the finalPose dataset in the hdf5 file.\n")
172 
173  ("help,h", bool_switch(&help),
174  "Print this help. Seriously how are you reading this if you don't know the --help Option?")
175  ;
176 
177  icp_options.add_options()
178  ("icpIterations,i", value<int>(&options.icpIterations)->default_value(options.icpIterations),
179  "Number of iterations for ICP.\n"
180  "ICP should ideally converge before this number is met, but this number places an upper Bound on calculation time.")
181 
182  ("icpMaxDistance,d", value<double>(&options.icpMaxDistance)->default_value(options.icpMaxDistance),
183  "The maximum distance between two points during ICP.")
184 
185  ("maxLeafSize", value<int>(&options.maxLeafSize)->default_value(options.maxLeafSize),
186  "The maximum number of Points in a Leaf of a KDTree.")
187 
188  ("epsilon", value<double>(&options.epsilon)->default_value(options.epsilon),
189  "The epsilon difference between ICP-errors for the stop criterion of ICP.")
190  ;
191 
192  loopclosing_options.add_options()
193  ("loopClosing,L", bool_switch(&options.doLoopClosing),
194  "Use simple Loopclosing.\n"
195  "At least one of -L and -G must be specified for Loopclosing to take place.")
196 
197  ("graphSlam,G", bool_switch(&options.doGraphSLAM),
198  "Use complex Loop Closing with GraphSLAM.\n"
199  "At least one of -L and -G must be specified for Loopclosing to take place.")
200 
201  ("closeLoopDistance,c", value<double>(&options.closeLoopDistance)->default_value(options.closeLoopDistance),
202  "The maximum distance between two poses to consider a closed loop in Loopclosing or an Edge in the GraphSLAM Graph.\n"
203  "Mutually exclusive to --closeLoopPairs.")
204 
205  ("closeLoopPairs,C", value<int>(&options.closeLoopPairs)->default_value(options.closeLoopPairs),
206  "The minimum pair overlap between two poses to consider a closed loop in Loopclosing or an Edge in the GraphSLAM Graph.\n"
207  "Mutually exclusive to --closeLoopDistance.\n"
208  "Pairs are judged using slamMaxDistance.\n"
209  "-1 (default): use --closeLoopDistance instead.")
210 
211  ("loopSize,l", value<int>(&options.loopSize)->default_value(options.loopSize),
212  "The minimum number of Scans to be considered a Loop to prevent Loopclosing from triggering on adjacent Scans.\n"
213  "Also used in GraphSLAM when considering other Scans for Edges\n"
214  "For Loopclosing, this value needs to be at least 6, for GraphSLAM at least 1.")
215 
216  ("slamIterations,I", value<int>(&options.slamIterations)->default_value(options.slamIterations),
217  "Number of iterations for SLAM.")
218 
219  ("slamMaxDistance,D", value<double>(&options.slamMaxDistance)->default_value(options.slamMaxDistance),
220  "The maximum distance between two points during SLAM.")
221 
222  ("slamEpsilon", value<double>(&options.slamEpsilon)->default_value(options.slamEpsilon),
223  "The epsilon difference of SLAM corrections for the stop criterion of SLAM.")
224  ;
225 
226  options_description hidden_options("hidden_options");
227  hidden_options.add_options()
228  ("dir", value<path>(&dir))
229  ;
230 
231  positional_options_description pos;
232  pos.add("dir", 1);
233 
234  options_description all_options("options");
235  all_options.add(general_options).add(icp_options).add(loopclosing_options).add(hidden_options);
236 
237  variables_map variables;
238  store(command_line_parser(argc, argv).options(all_options).positional(pos).run(), variables);
239  notify(variables);
240 
241  if (help)
242  {
243  cout << "The Scan Registration Tool" << endl;
244  cout << "Usage: " << endl;
245  cout << "\tlvr2_registration [OPTIONS] <dir>" << endl;
246  cout << endl;
247  general_options.print(cout);
248  cout << endl;
249  icp_options.print(cout);
250  cout << endl;
251  loopclosing_options.print(cout);
252  cout << endl;
253  cout << "<dir> is the directory to search scans in" << endl;
254  return EXIT_SUCCESS;
255  }
256 
257  if (variables.count("dir") != 1)
258  {
259  throw error("Missing <dir> Parameter");
260  }
261 
262  if (variables.count("output") == 0)
263  {
264  output_dir = dir / "output";
265  }
266 
267  if (format.find('%') == string::npos)
268  {
269  format = map_format(format);
270  }
271 
272  if (variables.count("writePose") == 1)
273  {
274  write_pose = true;
275  if (output_pose_format[0] == '<')
276  {
277  output_pose_format = pose_format;
278  }
279  }
280  if (variables.count("writeScans") == 1)
281  {
282  write_scans = true;
283  if (output_format[0] == '<')
284  {
285  output_format = format;
286  }
287  else if (output_format.find('%') == string::npos)
288  {
289  format = map_format(format);
290  }
291  }
292 
293  options.createFrames = !no_frames;
294  }
295  catch (const boost::program_options::error& ex)
296  {
297  std::cerr << ex.what() << endl;
298  std::cerr << endl;
299  std::cerr << "Use '--help' to see the list of possible options" << endl;
300  return EXIT_FAILURE;
301  }
302 
303  using HDF5PCIO = lvr2::Hdf5IO<
309  // contains the hdf5
310  std::shared_ptr<HDF5PCIO> h5_ptr(new HDF5PCIO());
311 
312  if(options.useHDF)
313  {
314  cout << "HDF5 is used!" << endl;
315  ifstream f(dir.c_str());
316  if (f.good())
317  {
318  // create boost::fileystem::path to hdf file location
319  boost::filesystem::path pathToHDF(dir.c_str());
320  h5_ptr->open(pathToHDF.string());
321  }
322  else
323  {
324  cerr << "The given HDF5 file could not be opened! Oben" << endl;
325  return EXIT_FAILURE;
326  }
327  }
328 
329  // =============== search scans ===============
330  if (start == -1)
331  {
332  if (!options.useHDF)
333  {
334  for (int i = 0; i < 100; i++)
335  {
336  path file = dir / format_name(format, i);
337  if (exists(file))
338  {
339  start = i;
340  cout << "First scan: " << file.filename() << endl;
341  break;
342  }
343  }
344  if (start == -1)
345  {
346  cerr << "Could not find a starting scan. are you using the right format?" << endl;
347  return EXIT_FAILURE;
348  }
349  }
350  }
351  if (!options.useHDF)
352  {
353  // make sure all scan and pose files are in the directory
354  for (int i = start; end == -1 || i <= end; i++)
355  {
356  path file = dir / format_name(format, i);
357  if (!exists(file))
358  {
359  if (end != -1 || i == start)
360  {
361  cerr << "Missing scan " << file.filename() << endl;
362  return EXIT_FAILURE;
363  }
364  end = i - 1;
365  cout << "Last scan: \"" << format_name(format, end) << '"' << endl;
366  break;
367  }
368  file.replace_extension(pose_format);
369  if (!exists(file))
370  {
371  cerr << "Missing pose file " << file.filename() << endl;
372  return EXIT_FAILURE;
373  }
374  }
375  }
376 
377  SLAMAlign align(options);
378  vector<SLAMScanPtr> scans;
379 
380  // DEBUG
381  ScanProjectEditMark proj;
382  proj.project = ScanProjectPtr(new ScanProject);
383 
384  int count = end - start + 1;
385 
386  vector<string> numOfScansInHDF;
387  if(h5_ptr->m_hdf5_file)
388  {
389  HighFive::Group hfscans = hdf5util::getGroup(h5_ptr->m_hdf5_file, "raw/scans");
390  numOfScansInHDF = hfscans.listObjectNames();
391  }
392 
393  vector<lvr2::ScanPtr> rawScans;
394  if (options.useHDF)
395  {
396  for (int i = 0; i < numOfScansInHDF.size(); i++)
397  {
398  // create a scan object for each scan in hdf
399  ScanPtr tempScan(new Scan());
400  size_t six;
401  size_t pointsNum;
402  boost::shared_array<float> bb_array = h5_ptr->loadArray<float>("raw/scans/" + numOfScansInHDF[i], "boundingBox", six);
403  BoundingBox<BaseVector<float>> bb(BaseVector<float>(bb_array[0], bb_array[1], bb_array[2]),
404  BaseVector<float>(bb_array[3], bb_array[4], bb_array[5]));
405  // bounding box transfered to object
406  tempScan->boundingBox = bb;
407 
408  boost::shared_array<float> fov_array = h5_ptr->loadArray<float>("raw/scans/" + numOfScansInHDF[i], "fov", six);
409  // fov transfered to object
410 
411  // TODO: min and max angles from new structure
412 
413  boost::shared_array<float> res_array = h5_ptr->loadArray<float>("raw/scans/" + numOfScansInHDF[i], "resolution", six);
414  // resolution transfered
415  tempScan->hResolution = res_array[0];
416  tempScan->vResolution = res_array[1];
417  // point cloud transfered
418  boost::shared_array<float> point_array = h5_ptr->loadArray<float>("raw/scans/"+ numOfScansInHDF[i], "points", pointsNum);
419  // important because x, y, z coords
420  pointsNum = pointsNum / 3;
421  PointBufferPtr pointPointer = PointBufferPtr(new PointBuffer(point_array, pointsNum));
422  tempScan->points = pointPointer;
423  // tempScan->m_points = h5_ptr->loadPointCloud("raw/scans/" + numOfScansInHDF[i]);
424  tempScan->pointsLoaded = true;
425  // pose transfered
426  tempScan->poseEstimation = h5_ptr->loadMatrix<Transformd>("raw/scans/" + numOfScansInHDF[i], "initialPose").get();
427  tempScan->positionNumber = i;
428 
429  tempScan->scanRoot = "raw/scans/" + numOfScansInHDF[i];
430 
431 
432  // sets the finalPose to the identiy matrix
433  tempScan->registration = Transformd::Identity();
434 
435 
436  // DEBUG
437  ScanPosition pos;
438  pos.scans.push_back(tempScan);
439  proj.project->positions.push_back(std::make_shared<ScanPosition>(pos));
440  proj.changed.push_back(false);
441 
442  SLAMScanPtr slamScan = SLAMScanPtr(new SLAMScanWrapper(tempScan));
443 
444  // scans.push_back(slamScan);
445  // align.addScan(slamScan);
446  }
447  // DEBUG
448 
449  cout << "vor Pipe Konstruktor" << endl;
450  ScanProjectEditMarkPtr projPtr = make_shared<ScanProjectEditMark>(proj);
451  RegistrationPipeline pipe(&options, projPtr);
452  pipe.doRegistration();
453  cout << "Nach doRegistration" << endl;
454  for (size_t i = 0; i < projPtr->changed.size(); i++)
455  {
456  cout << "Reconstruct indivcator ans Stelle: " << i << " ist: " << projPtr->changed.at(i)<< endl;
457  }
458 
459  cout << "Eine Pose aus dem Project:" << endl << projPtr->project->positions.at(1)->scans[0]->registration << endl;
460  }
461  else
462  {
463  // case for not using HDF5
464  // TODO: change to ScanDirectoryParser once that is done
465 
466  for (int i = 0; i < count; i++)
467  {
468  path file = dir / format_name(format, start + i);
469  auto model = ModelFactory::readModel(file.string());
470 
471  if (!model)
472  {
473  cerr << "Unable to read Model from: " << file.string() << endl;
474  return EXIT_FAILURE;
475  }
476  if (!model->m_pointCloud)
477  {
478  cerr << "file does not contain Points: " << file.string() << endl;
479  return EXIT_FAILURE;
480  }
481 
482  file.replace_extension(pose_format);
483  Transformd pose = getTransformationFromFile<double>(file);
484 
485  ScanPtr scan = ScanPtr(new Scan());
486  scan->points = model->m_pointCloud;
487  scan->poseEstimation = pose;
488 
489  SLAMScanPtr slamScan = SLAMScanPtr(new SLAMScanWrapper(scan));
490  scans.push_back(slamScan);
491  align.addScan(slamScan);
492  }
493  }
494 
495 
496  auto start_time = chrono::steady_clock::now();
497 
498  align.finish();
499 
500  auto required_time = chrono::steady_clock::now() - start_time;
501  cout << "SLAM finished in " << required_time.count() / 1e9 << " seconds" << endl;
502 
503  if (write_pose || write_scans)
504  {
505  create_directories(output_dir);
506  }
507 
508  path file;
509 
510  if (options.useHDF)
511  {
512  // write poses to hdf
513  for(int i = 0; i < scans.size(); i++)
514  {
515  Transformd pose = scans[i]->pose();
516  cout << "Main:Pose Scan Nummer " << i << endl << pose << endl;
517  // The pose needs to be transposed before writing to hdf,
518  // because the lvr2_viewer expects finalPose in hdf transposed this way.
519  // The initial pose is saved NOT transposed in HDF
520  pose.transposeInPlace();
521  h5_ptr->MatrixIO::save("raw/scans/" + numOfScansInHDF[i], "finalPose", pose);
522  }
523  }
524 
525  for (int i = 0; i < count; i++)
526  {
527  auto& scan = scans[i];
528 
529  if (!no_frames)
530  {
531  file = dir / format_name(format, start + i);
532  file.replace_extension("frames");
533 
534  scan->writeFrames(file.string());
535  }
536 
537  if (write_pose)
538  {
539  file = output_dir / format_name(write_scans ? output_format : format, start + i);
540  file.replace_extension(output_pose_format);
541  ofstream out(file.string());
542 
543  auto pose = scan->pose();
544  for (int y = 0; y < 4; y++)
545  {
546  for (int x = 0; x < 4; x++)
547  {
548  out << pose(y, x);
549  if (x < 3)
550  {
551  out << " ";
552  }
553  }
554  out << endl;
555  }
556  }
557 
558  if (write_scans)
559  {
560  file = output_dir / format_name(output_format, start + i);
561 
562  size_t n = scan->numPoints();
563 
564  auto model = make_shared<Model>();
565  auto pointCloud = make_shared<PointBuffer>();
566  floatArr points = floatArr(new float[n * 3]);
567 
568  #pragma omp parallel for schedule(static)
569  for (size_t i = 0; i < n; i++)
570  {
571  auto point = scan->rawPoint(i);
572  points[i * 3] = point[0];
573  points[i * 3 + 1] = point[1];
574  points[i * 3 + 2] = point[2];
575  }
576 
577  pointCloud->setPointArray(points, n);
578  model->m_pointCloud = pointCloud;
579  ModelFactory::saveModel(model, file.string());
580  }
581  }
582  return EXIT_SUCCESS;
583 }
A class to handle point information with an arbitrarily large number of attribute channels...
Definition: PointBuffer.hpp:51
double minDistance
Ignore all Points closer than to the origin of a scan.
Definition: SLAMOptions.hpp:70
double slamEpsilon
The epsilon difference of SLAM corrections for the stop criterion of SLAM.
double reduction
The Voxel size for Octree based reduction.
Definition: SLAMOptions.hpp:67
bool trustPose
Use the unmodified Pose of new Scans. If false, apply the relative refinement of previous Scans...
Definition: SLAMOptions.hpp:50
const kaboom::Options * options
bool createFrames
Keep track of all previous Transformations of Scans for Animation purposes like &#39;show&#39; from slam6D...
Definition: SLAMOptions.hpp:56
Hdf5IO Feature for handling PointBuffer related IO.
double epsilon
The epsilon difference between ICP-errors for the stop criterion of ICP.
Definition: SLAMOptions.hpp:88
static ModelPtr readModel(std::string filename)
int main(int argc, char **argv)
ScanProjectPtr project
Definition: ScanTypes.hpp:352
std::shared_ptr< Scan > ScanPtr
Shared pointer to scans.
Definition: ScanTypes.hpp:98
A struct to configure SLAMAlign.
Definition: SLAMOptions.hpp:45
std::shared_ptr< PointBuffer > PointBufferPtr
A dynamic bounding box class.
Definition: BoundingBox.hpp:49
Transform< double > Transformd
4x4 double precision transformation matrix
Definition: MatrixTypes.hpp:71
string map_format(const string &format)
bool verbose
Show more detailed output. Useful for fine-tuning Parameters or debugging.
Definition: SLAMOptions.hpp:59
int slamIterations
Number of ICP iterations during Loopclosing and number of GraphSLAM iterations.
bool useHDF
Indicates if a HDF file containing the scans should be used.
Definition: SLAMOptions.hpp:62
A Wrapper around Scan to allow for SLAM usage.
Manager Class for all Hdf5IO components located in hdf5 directory.
boost::shared_array< float > floatArr
Definition: DataStruct.hpp:133
std::shared_ptr< ScanProjectEditMark > ScanProjectEditMarkPtr
Definition: ScanTypes.hpp:357
std::shared_ptr< ScanProject > ScanProjectPtr
Definition: ScanTypes.hpp:344
void finish()
Indicates that no new Scans will be added.
Definition: SLAMAlign.cpp:369
A class to run SLAM on Scans.
Definition: SLAMAlign.hpp:48
void addScan(const SLAMScanPtr &scan, bool match=false)
Adds a new Scan to the SLAM instance.
Definition: SLAMAlign.cpp:77
FILE * file
double slamMaxDistance
The maximum distance between two points during SLAM.
Hdf5IO Feature for handling VariantChannel related IO.
std::vector< ScanPtr > scans
Definition: ScanTypes.hpp:283
KF_EXPORTS void error(const char *error_string, const char *file, const int line, const char *func="")
Error handler. All GPU functions from this subsystem call the function to report an error...
int maxLeafSize
The maximum number of Points in a Leaf of a KDTree.
Definition: SLAMOptions.hpp:85
double icpMaxDistance
The maximum distance between two points during ICP.
Definition: SLAMOptions.hpp:82
std::shared_ptr< SLAMScanWrapper > SLAMScanPtr
bool doGraphSLAM
Use complex Loopclosing with GraphSLAM.
Definition: SLAMOptions.hpp:96
double maxDistance
Ignore all Points farther away than from the origin of a scan.
Definition: SLAMOptions.hpp:73
std::vector< std::string > listObjectNames() const
list all leaf objects name of the node / group
string format_name(const string &format, int index)
bool metascan
Match scans to the combined Pointcloud of all previous Scans instead of just the last Scan...
Definition: SLAMOptions.hpp:53
HighFive::Group getGroup(std::shared_ptr< HighFive::File > hdf5_file, const std::string &groupName, bool create=true)
Definition: Hdf5Util.cpp:58
std::vector< bool > changed
True if scan pose has been changed, one bool for each scan position.
Definition: ScanTypes.hpp:354
bool doLoopClosing
Use simple Loopclosing.
Definition: SLAMOptions.hpp:93
static void saveModel(ModelPtr m, std::string file)
char ** argv


lvr2
Author(s): Thomas Wiemann , Sebastian Pütz , Alexander Mock , Lars Kiesow , Lukas Kalbertodt , Tristan Igelbrink , Johan M. von Behren , Dominik Feldschnieders , Alexander Löhr
autogenerated on Mon Feb 28 2022 22:46:08