GraphSLAM.cpp
Go to the documentation of this file.
1 
35 
36 #include <Eigen/SparseCholesky>
37 
38 #include <math.h>
39 
40 using namespace std;
41 using namespace Eigen;
42 
43 namespace lvr2
44 {
45 
53 bool findCloseScans(const vector<SLAMScanPtr>& scans, size_t scan, const SLAMOptions& options, vector<size_t>& output)
54 {
55  if (scan < options.loopSize)
56  {
57  return false;
58  }
59 
60  const SLAMScanPtr& cur = scans[scan];
61 
62  // closeLoopPairs not specified => use closeLoopDistance
63  if (options.closeLoopPairs < 0)
64  {
65  double maxDist = std::pow(options.closeLoopDistance, 2);
66  Vector3d pos = cur->getPosition();
67  for (size_t other = 0; other < scan - options.loopSize; other++)
68  {
69  if ((scans[other]->getPosition() - pos).squaredNorm() < maxDist)
70  {
71  output.push_back(other);
72  }
73  }
74  }
75  else
76  {
77  // convert current Scan to KDTree for Pair search
78  auto tree = KDTree::create(cur, options.maxLeafSize);
79 
80  size_t maxLen = 0;
81  for (size_t other = 0; other < scan - options.loopSize; other++)
82  {
83  maxLen = max(maxLen, scans[other]->numPoints());
84  }
85  KDTree::Neighbor* neighbors = new KDTree::Neighbor[maxLen];
86 
87  for (size_t other = 0; other < scan - options.loopSize; other++)
88  {
89  size_t count = KDTree::nearestNeighbors(tree, scans[other], neighbors, options.slamMaxDistance);
90  if (count >= options.closeLoopPairs)
91  {
92  output.push_back(other);
93  }
94  }
95 
96  delete[] neighbors;
97  }
98 
99  return !output.empty();
100 }
101 
102 
108 void EulerToMatrix4(const Vector3d& pos, const Vector3d& theta, Matrix4d& mat);
109 
115 void Matrix4ToEuler(const Matrix4d mat, Vector3d& rPosTheta, Vector3d& rPos);
116 
117 GraphSLAM::GraphSLAM(const SLAMOptions* options)
118  : m_options(options)
119 {
120 }
121 
122 void GraphSLAM::doGraphSLAM(const vector<SLAMScanPtr>& scans, size_t last, const std::vector<bool>& new_scans) const
123 {
124  // ignore first scan, keep last scan => n = last - 1 + 1
125  size_t n = last;
126 
127  Graph graph;
128  graph.reserve(n * n / 2);
129 
130  GraphMatrix A(6 * n, 6 * n);
131  GraphVector B(6 * n);
132  GraphVector X(6 * n);
133 
134  for (size_t iteration = 0;
135  iteration < m_options->slamIterations;
136  iteration++)
137  {
138  cout << "GraphSLAM Iteration " << iteration << " of " << m_options->slamIterations << endl;
139 
140  createGraph(scans, last, graph);
141 
142  // Construct the linear equation system A * X = B..
143  fillEquation(scans, graph, A, B);
144 
145  graph.clear();
146 
147  X = SimplicialCholesky<GraphMatrix>().compute(A).solve(B);
148 
149  double sum_position_diff = 0.0;
150 
151  // Start with second Scan
152  #pragma omp parallel for reduction(+:sum_position_diff) schedule(static)
153  for (size_t i = 1; i <= last; i++)
154  {
155  if (new_scans.empty() || new_scans.at(i))
156  {
157  const SLAMScanPtr& scan = scans[i];
158 
159  // Now update the Poses
160  Matrix6d Ha = Matrix6d::Identity();
161 
162  Matrix4d initialPose = scan->pose();
163  Vector3d pos, theta;
164  Matrix4ToEuler(initialPose, theta, pos);
165  if (m_options->verbose)
166  {
167  cout << "Start of " << i << ": " << pos.transpose() << ", " << theta.transpose() << endl;
168  }
169 
170  double ctx, stx, cty, sty;
171 
172 #ifndef __APPLE__
173  sincos(theta.x(), &stx, &ctx);
174  sincos(theta.y(), &sty, &cty);
175 #else
176  __sincos(theta.x(), &stx, &ctx);
177  __sincos(theta.y(), &sty, &cty);
178 #endif
179 
180  // Fill Ha
181  Ha(0, 4) = -pos.z() * ctx + pos.y() * stx;
182  Ha(0, 5) = pos.y() * cty * ctx + pos.z() * stx * cty;
183 
184  Ha(1, 3) = pos.z();
185  Ha(1, 4) = -pos.x() * stx;
186  Ha(1, 5) = -pos.x() * ctx * cty + pos.z() * sty;
187 
188 
189  Ha(2, 3) = -pos.y();
190  Ha(2, 4) = pos.x() * ctx;
191  Ha(2, 5) = -pos.x() * cty * stx - pos.y() * sty;
192 
193  Ha(3, 5) = sty;
194 
195  Ha(4, 4) = stx;
196  Ha(4, 5) = ctx * cty;
197 
198  Ha(5, 4) = ctx;
199  Ha(5, 5) = -stx * cty;
200 
201  // Correct pose estimate
202  Vector6d result = Ha.inverse() * X.block<6, 1>((i - 1) * 6, 0);
203 
204  // Update the Pose
205  pos -= result.block<3, 1>(0, 0);
206  theta -= result.block<3, 1>(3, 0);
208  EulerToMatrix4(pos, theta, transform);
209 
210  if (m_options->verbose)
211  {
212  cout << "End: pos: " << pos.transpose() << "," << endl << "theta: " << theta.transpose() << endl;
213  }
214 
215  transform = transform * initialPose.inverse();
216 
218 
219  sum_position_diff += result.block<3, 1>(0, 0).norm();
220  }
221  }
222 
223  if (m_options->createFrames)
224  {
225  // add Frames to unused Scans
226  scans[0]->addFrame(FrameUse::GRAPHSLAM);
227  for (size_t i = last + 1; i < scans.size(); i++)
228  {
229  scans[i]->addFrame(FrameUse::INVALID);
230  }
231  }
232 
233  cout << "Sum of Position differences = " << sum_position_diff << endl;
234 
235  double avg = sum_position_diff / n;
236  if (avg < m_options->slamEpsilon)
237  {
238  break;
239  }
240  }
241 }
242 
243 void GraphSLAM::createGraph(const vector<SLAMScanPtr>& scans, size_t last, Graph& graph) const
244 {
245  graph.clear();
246 
247  for (size_t i = 1; i <= last; i++)
248  {
249  graph.push_back(make_pair(i - 1, i));
250  }
251 
252  vector<size_t> others;
253  for (size_t i = m_options->loopSize; i <= last; i++)
254  {
255  findCloseScans(scans, i, *m_options, others);
256 
257  for (size_t other : others)
258  {
259  graph.push_back(make_pair(other, i));
260  }
261 
262  others.clear();
263  }
264 }
265 
266 void GraphSLAM::fillEquation(const vector<SLAMScanPtr>& scans, const Graph& graph, GraphMatrix& mat, GraphVector& vec) const
267 {
268  // Cache all KDTrees
269  map<size_t, KDTreePtr> trees;
270  for (size_t i = 0; i < graph.size(); i++)
271  {
272  size_t a = graph[i].first;
273  if (trees.find(a) == trees.end())
274  {
275  auto tree = KDTree::create(scans[a], m_options->maxLeafSize);
276  trees.insert(make_pair(a, tree));
277  }
278  }
279 
280  vector<pair<Matrix6d, Vector6d>> coeff(graph.size());
281 
282  #pragma omp parallel for schedule(dynamic)
283  for (size_t i = 0; i < graph.size(); i++)
284  {
285  int a, b;
286  std::tie(a, b) = graph[i];
287 
288  KDTreePtr tree = trees[a];
289  SLAMScanPtr scan = scans[b];
290 
291  Matrix6d coeffMat;
292  Vector6d coeffVec;
293  eulerCovariance(tree, scan, coeffMat, coeffVec);
294 
295  coeff[i] = make_pair(coeffMat, coeffVec);
296  }
297 
298  trees.clear();
299 
300  map<pair<int, int>, Matrix6d> result;
301 
302  mat.setZero();
303  vec.setZero();
304 
305  for (size_t i = 0; i < graph.size(); i++)
306  {
307  int a, b;
308  std::tie(a, b) = graph[i];
309 
310  Matrix6d coeffMat;
311  Vector6d coeffVec;
312  std::tie(coeffMat, coeffVec) = coeff[i];
313 
314  // first scan is not part of Matrix => ignore any a or b of 0
315 
316  int offsetA = (a - 1) * 6;
317  int offsetB = (b - 1) * 6;
318 
319  if (offsetA >= 0)
320  {
321  vec.block<6, 1>(offsetA, 0) += coeffVec;
322  auto key = make_pair(offsetA, offsetA);
323  auto found = result.find(key);
324  if (found == result.end())
325  {
326  result.insert(make_pair(key, coeffMat));
327  }
328  else
329  {
330  found->second += coeffMat;
331  }
332  }
333  if (offsetB >= 0)
334  {
335  vec.block<6, 1>(offsetB, 0) -= coeffVec;
336  auto key = make_pair(offsetB, offsetB);
337  auto found = result.find(key);
338  if (found == result.end())
339  {
340  result.insert(make_pair(key, coeffMat));
341  }
342  else
343  {
344  found->second += coeffMat;
345  }
346  }
347  if (offsetA >= 0 && offsetB >= 0)
348  {
349  auto key = make_pair(offsetA, offsetB);
350  auto found = result.find(key);
351  if (found == result.end())
352  {
353  result.insert(make_pair(key, -coeffMat));
354  }
355  else
356  {
357  found->second -= coeffMat;
358  }
359 
360  key = make_pair(offsetB, offsetA);
361  found = result.find(key);
362  if (found == result.end())
363  {
364  result.insert(make_pair(key, -coeffMat));
365  }
366  else
367  {
368  found->second -= coeffMat;
369  }
370  }
371  }
372 
373  vector<Triplet<double>> triplets;
374  triplets.reserve(result.size() * 6 * 6);
375 
376  int x, y;
377  for (auto& e : result)
378  {
379  tie(x, y) = e.first;
380  Matrix6d& m = e.second;
381  for (int dx = 0; dx < 6; dx++)
382  {
383  for (int dy = 0; dy < 6; dy++)
384  {
385  triplets.push_back(Triplet<double>(x + dx, y + dy, m(dx, dy)));
386  }
387  }
388  }
389  mat.setFromTriplets(triplets.begin(), triplets.end());
390 }
391 
392 void GraphSLAM::eulerCovariance(KDTreePtr tree, SLAMScanPtr scan, Matrix6d& outMat, Vector6d& outVec) const
393 {
394  size_t n = scan->numPoints();
395 
396  KDTree::Neighbor* results = new KDTree::Neighbor[n];
397 
398  size_t pairs = KDTree::nearestNeighbors(tree, scan, results, m_options->slamMaxDistance);
399 
400  Vector6d mz = Vector6d::Zero();
401  Vector3d sum = Vector3d::Zero();
402  double xy, yz, xz, ypz, xpz, xpy;
403  xy = yz = xz = ypz = xpz = xpy = 0.0;
404 
405  for (size_t i = 0; i < n; i++)
406  {
407  if (results[i] == nullptr)
408  {
409  continue;
410  }
411 
412  Vector3d p = scan->point(i).cast<double>();
413  Vector3d r = results[i]->cast<double>();
414 
415  Vector3d mid = (p + r) / 2.0;
416  Vector3d d = r - p;
417 
418  double x = mid.x(), y = mid.y(), z = mid.z();
419 
420  sum += mid;
421 
422  xpy += x * x + y * y;
423  xpz += x * x + z * z;
424  ypz += y * y + z * z;
425 
426  xy += x * y;
427  xz += x * z;
428  yz += y * z;
429 
430  mz.block<3, 1>(0, 0) += d;
431 
432  mz(3) += -z * d.y() + y * d.z();
433  mz(4) += -y * d.x() + x * d.y();
434  mz(5) += z * d.x() - x * d.z();
435  }
436 
437  Matrix6d mm = Matrix6d::Zero();
438  mm(0, 0) = mm(1, 1) = mm(2, 2) = pairs;
439  mm(3, 3) = ypz;
440  mm(4, 4) = xpy;
441  mm(5, 5) = xpz;
442 
443  mm(0, 4) = mm(4, 0) = -sum.y();
444  mm(0, 5) = mm(5, 0) = sum.z();
445  mm(1, 3) = mm(3, 1) = -sum.z();
446  mm(1, 4) = mm(4, 1) = sum.x();
447  mm(2, 3) = mm(3, 2) = sum.y();
448  mm(2, 5) = mm(5, 2) = -sum.x();
449 
450  mm(3, 4) = mm(4, 3) = -xz;
451  mm(3, 5) = mm(5, 3) = -xy;
452  mm(4, 5) = mm(5, 4) = -yz;
453 
454  Vector6d d = mm.inverse() * mz;
455 
456  double ss = 0.0;
457 
458  for (size_t i = 0; i < n; i++)
459  {
460  if (results[i] == nullptr)
461  {
462  continue;
463  }
464 
465  Vector3d p = scan->point(i).cast<double>();
466  Vector3d r = results[i]->cast<double>();
467 
468  Vector3d mid = (p + r) / 2.0;
469  Vector3d delta = r - p;
470 
471  ss += pow(delta.x() + (d(0) - mid.y() * d(4) + mid.z() * d(5)), 2.0)
472  + pow(delta.y() + (d(1) - mid.z() * d(3) + mid.x() * d(4)), 2.0)
473  + pow(delta.z() + (d(2) + mid.y() * d(3) - mid.x() * d(5)), 2.0);
474  }
475 
476  delete[] results;
477 
478  ss = ss / (2.0 * pairs - 3.0);
479 
480  ss = 1.0 / ss;
481 
482  outMat = mm * ss;
483  outVec = mz * ss;
484 }
485 
486 void EulerToMatrix4(const Vector3d& pos,
487  const Vector3d& theta,
488  Matrix4d& mat)
489 {
490  double sx = sin(theta[0]);
491  double cx = cos(theta[0]);
492  double sy = sin(theta[1]);
493  double cy = cos(theta[1]);
494  double sz = sin(theta[2]);
495  double cz = cos(theta[2]);
496 
497  mat << cy* cz,
498  sx* sy* cz + cx* sz,
499  -cx* sy* cz + sx* sz,
500  0.0,
501  -cy* sz,
502  -sx* sy* sz + cx* cz,
503  cx* sy* sz + sx* cz,
504  0.0,
505  sy,
506  -sx* cy,
507  cx* cy,
508 
509  0.0,
510 
511  pos[0],
512  pos[1],
513  pos[2],
514  1;
515 
516  mat.transposeInPlace();
517 }
518 
519 void Matrix4ToEuler(const Matrix4d inputMat,
520  Vector3d& rPosTheta,
521  Vector3d& rPos)
522 {
523  Matrix4d mat = inputMat.transpose();
524 
525  double _trX, _trY;
526 
527  // Calculate Y-axis angle
528  rPosTheta[1] = asin(max(-1.0, min(1.0, mat(2, 0)))); // asin returns nan for any number outside [-1, 1]
529  if (mat(0, 0) <= 0.0)
530  {
531  rPosTheta[1] = M_PI - rPosTheta[1];
532  }
533 
534  double C = cos(rPosTheta[1]);
535  if (fabs( C ) > 0.005) // Gimbal lock?
536  {
537  _trX = mat(2, 2) / C; // No, so get X-axis angle
538  _trY = -mat(2, 1) / C;
539  rPosTheta[0] = atan2( _trY, _trX );
540  _trX = mat(0, 0) / C; // Get Z-axis angle
541  _trY = -mat(1, 0) / C;
542  rPosTheta[2] = atan2( _trY, _trX );
543  }
544  else // Gimbal lock has occurred
545  {
546  rPosTheta[0] = 0.0; // Set X-axis angle to zero
547  _trX = mat(1, 1); //1 // And calculate Z-axis angle
548  _trY = mat(0, 1); //2
549  rPosTheta[2] = atan2( _trY, _trX );
550  }
551 
552  rPos = inputMat.block<3, 1>(0, 3);
553 }
554 
555 } /* namespace lvr2 */
lvr2::Matrix6d
Eigen::Matrix< double, 6, 6 > Matrix6d
6D matrix double precision
Definition: MatrixTypes.hpp:159
lvr2::GraphSLAM::fillEquation
void fillEquation(const std::vector< SLAMScanPtr > &scans, const Graph &graph, GraphMatrix &mat, GraphVector &vec) const
A function to fill the linear system mat * x = vec.
Definition: GraphSLAM.cpp:266
lvr2::SLAMOptions::createFrames
bool createFrames
Keep track of all previous Transformations of Scans for Animation purposes like 'show' from slam6D.
Definition: SLAMOptions.hpp:56
lvr2::FrameUse::GRAPHSLAM
@ GRAPHSLAM
The Scan was part of a GraphSLAM Iteration.
transform
Definition: src/tools/lvr2_transform/Options.cpp:44
lvr2::Matrix4ToEuler
void Matrix4ToEuler(const Matrix4d mat, Vector3d &rPosTheta, Vector3d &rPos)
Conversion from Matrix to Pose representation.
Definition: GraphSLAM.cpp:519
p
SharedPointer p
Definition: ConvertShared.hpp:42
lvr2::GraphSLAM::eulerCovariance
void eulerCovariance(KDTreePtr tree, SLAMScanPtr scan, Matrix6d &outMat, Vector6d &outVec) const
Definition: GraphSLAM.cpp:392
lvr2::FrameUse::INVALID
@ INVALID
The Scan has not been registered yet.
lvr2::GraphSLAM::Graph
std::vector< std::pair< int, int > > Graph
Definition: GraphSLAM.hpp:67
lvr2::EulerToMatrix4
void EulerToMatrix4(const Vector3d &pos, const Vector3d &theta, Matrix4d &mat)
Conversion from Pose to Matrix representation.
Definition: GraphSLAM.cpp:486
lvr2::GraphSLAM::GraphVector
Eigen::VectorXd GraphVector
Definition: GraphSLAM.hpp:66
M_PI
#define M_PI
Definition: Matrix4.hpp:52
lvr2::SLAMOptions::loopSize
int loopSize
Definition: SLAMOptions.hpp:109
lvr2::KDTree::create
static std::shared_ptr< KDTree > create(SLAMScanPtr scan, int maxLeafSize=20)
Creates a new KDTree from the given Scan.
Definition: KDTree.cpp:153
lvr2::findCloseScans
bool findCloseScans(const vector< SLAMScanPtr > &scans, size_t scan, const SLAMOptions &options, vector< size_t > &output)
Lists all numbers of scans near to the scan.
Definition: GraphSLAM.cpp:53
options
const kaboom::Options * options
Definition: src/tools/lvr2_kaboom/Main.cpp:45
lvr2::GraphSLAM::doGraphSLAM
void doGraphSLAM(const std::vector< SLAMScanPtr > &scans, size_t last, const std::vector< bool > &new_scans=std::vector< bool >()) const
runs the GraphSLAM algorithm
Definition: GraphSLAM.cpp:122
lvr2::GraphSLAM::m_options
const SLAMOptions * m_options
Definition: GraphSLAM.hpp:102
lvr2::SLAMOptions::verbose
bool verbose
Show more detailed output. Useful for fine-tuning Parameters or debugging.
Definition: SLAMOptions.hpp:59
lvr2::KDTreePtr
std::shared_ptr< KDTree > KDTreePtr
Definition: KDTree.hpp:135
lvr2::Matrix4d
Eigen::Matrix4d Matrix4d
Eigen 4x4 matrix, double precision.
Definition: MatrixTypes.hpp:150
lvr2::KDTree::nearestNeighbors
static size_t nearestNeighbors(KDTreePtr tree, SLAMScanPtr scan, Neighbor *neighbors, double maxDistance, Vector3d &centroid_m, Vector3d &centroid_d)
Finds the nearest neighbors of all points in a Scan using a pre-generated KDTree.
Definition: KDTree.cpp:176
lvr2::SLAMScanPtr
std::shared_ptr< SLAMScanWrapper > SLAMScanPtr
Definition: SLAMScanWrapper.hpp:221
lvr2::SLAMOptions::slamMaxDistance
double slamMaxDistance
The maximum distance between two points during SLAM.
Definition: SLAMOptions.hpp:115
lvr2::qttf::transform
PointBufferPtr transform(PointBufferPtr pc_in, const Transformd &T)
Definition: qttf.cpp:32
lvr2::Vector3d
Eigen::Vector3d Vector3d
Eigen 3D vector, double precision.
Definition: MatrixTypes.hpp:121
lvr2::Vector6d
Eigen::Matrix< double, 6, 1 > Vector6d
6D vector double precision
Definition: MatrixTypes.hpp:162
std
Definition: HalfEdge.hpp:124
lvr2
Definition: BaseBufferManipulators.hpp:39
lvr2::GraphSLAM::GraphMatrix
Eigen::SparseMatrix< double > GraphMatrix
Definition: GraphSLAM.hpp:65
kfusion::device::norm
__kf_device__ float norm(const float3 &v)
Definition: temp_utils.hpp:87
lvr2::SLAMOptions::slamIterations
int slamIterations
Number of ICP iterations during Loopclosing and number of GraphSLAM iterations.
Definition: SLAMOptions.hpp:112
lvr2::GraphSLAM::createGraph
void createGraph(const std::vector< SLAMScanPtr > &scans, size_t last, Graph &graph) const
Creates a graph. An edge between nodes(scans) means posible overlap.
Definition: GraphSLAM.cpp:243
lvr2::KDTree::Neighbor
Point * Neighbor
Definition: KDTree.hpp:56
lvr2::SLAMOptions
A struct to configure SLAMAlign.
Definition: SLAMOptions.hpp:45
lvr2::findCloseScans
bool findCloseScans(const std::vector< SLAMScanPtr > &scans, size_t scan, const SLAMOptions &options, std::vector< size_t > &output)
finds Scans that are "close" to a Scan as determined by a Loopclosing strategy
lvr2::SLAMOptions::maxLeafSize
int maxLeafSize
The maximum number of Points in a Leaf of a KDTree.
Definition: SLAMOptions.hpp:85
GraphSLAM.hpp


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 Wed Mar 2 2022 00:37:23