Super4PCS Library  V1.1.2(719f5c0)
match4pcsBase.hpp
1 // Copyright 2017 Nicolas Mellado
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 // -------------------------------------------------------------------------- //
16 //
17 // Authors: Dror Aiger, Yoni Weill, Nicolas Mellado
18 //
19 // This file is part of the implementation of the 4-points Congruent Sets (4PCS)
20 // algorithm presented in:
21 //
22 // 4-points Congruent Sets for Robust Surface Registration
23 // Dror Aiger, Niloy J. Mitra, Daniel Cohen-Or
24 // ACM SIGGRAPH 2008 and ACM Transaction of Graphics.
25 //
26 // Given two sets of points in 3-space, P and Q, the algorithm applies RANSAC
27 // in roughly O(n^2) time instead of O(n^3) for standard RANSAC, using an
28 // efficient method based on invariants, to find the set of all 4-points in Q
29 // that can be matched by rigid transformation to a given set of 4-points in P
30 // called a base. This avoids the need to examine all sets of 3-points in Q
31 // against any base of 3-points in P as in standard RANSAC.
32 // The algorithm can use colors and normals to speed-up the matching
33 // and to improve the quality. It can be easily extended to affine/similarity
34 // transformation but then the speed-up is smaller because of the large number
35 // of congruent sets. The algorithm can also limit the range of transformations
36 // when the application knows something on the initial pose but this is not
37 // necessary in general (though can speed the runtime significantly).
38 
39 // Home page of the 4PCS project (containing the paper, presentations and a
40 // demo): http://graphics.stanford.edu/~niloy/research/fpcs/fpcs_sig_08.html
41 // Use google search on "4-points congruent sets" to see many related papers
42 // and applications.
43 
44 #ifndef _SUPER4PCS_ALGO_MATCH_4PCS_BASE_IMPL_
45 #define _SUPER4PCS_ALGO_MATCH_4PCS_BASE_IMPL_
46 
47 #ifndef _SUPER4PCS_ALGO_MATCH_4PCS_BASE_
48 #include "super4pcs/algorithms/match4pcsBase.h"
49 #endif
50 
51 #include <chrono>
52 #include <atomic>
53 
54 #include <Eigen/Geometry> // MatrixBase.homogeneous()
55 #include <Eigen/SVD> // Transform.computeRotationScaling()
56 
57 namespace GlobalRegistration{
58 
59 // The main 4PCS function. Computes the best rigid transformation and transfoms
60 // Q toward P by this transformation.
61 template <typename Sampler, typename Visitor>
63 Match4PCSBase::ComputeTransformation(const std::vector<Point3D>& P,
64  std::vector<Point3D>* Q,
65  Eigen::Ref<MatrixType> transformation,
66  const Sampler& sampler,
67  const Visitor& v) {
68 
69  if (Q == nullptr) return kLargeNumber;
70  if (P.empty() || Q->empty()) return kLargeNumber;
71 
72  init(P, *Q, sampler);
73 
74  if (best_LCP_ != Scalar(1.))
75  Perform_N_steps(number_of_trials_, transformation, Q, v);
76 
77 #ifdef TEST_GLOBAL_TIMINGS
78  Log<LogLevel::Verbose>( "----------- Timings (msec) -------------" );
79  Log<LogLevel::Verbose>( " Total computation time : ", totalTime );
80  Log<LogLevel::Verbose>( " Total verify time : ", verifyTime );
81  Log<LogLevel::Verbose>( " Kdtree query : ", kdTreeTime );
82  Log<LogLevel::Verbose>( "----------------------------------------" );
83 #endif
84 
85  return best_LCP_;
86 }
87 
88 
89 
90 template <typename Sampler>
91 void Match4PCSBase::init(const std::vector<Point3D>& P,
92  const std::vector<Point3D>& Q,
93  const Sampler& sampler){
94 
95 #ifdef TEST_GLOBAL_TIMINGS
96  kdTreeTime = 0;
97  totalTime = 0;
98  verifyTime = 0;
99 #endif
100 
101  const Scalar kSmallError = 0.00001;
102  const int kMinNumberOfTrials = 4;
103  const Scalar kDiameterFraction = 0.3;
104 
105  centroid_P_ = VectorType::Zero();
106  centroid_Q_ = VectorType::Zero();
107 
108  sampled_P_3D_.clear();
109  sampled_Q_3D_.clear();
110 
111  // prepare P
112  if (P.size() > options_.sample_size){
113  sampler(P, options_, sampled_P_3D_);
114  }
115  else
116  {
117  Log<LogLevel::ErrorReport>( "(P) More samples requested than available: use whole cloud" );
118  sampled_P_3D_ = P;
119  }
120 
121 
122 
123  // prepare Q
124  if (Q.size() > options_.sample_size){
125  std::vector<Point3D> uniform_Q;
126  sampler(Q, options_, uniform_Q);
127 
128 
129  std::shuffle(uniform_Q.begin(), uniform_Q.end(), randomGenerator_);
130  size_t nbSamples = std::min(uniform_Q.size(), options_.sample_size);
131  auto endit = uniform_Q.begin(); std::advance(endit, nbSamples );
132  std::copy(uniform_Q.begin(), endit, std::back_inserter(sampled_Q_3D_));
133  }
134  else
135  {
136  Log<LogLevel::ErrorReport>( "(Q) More samples requested than available: use whole cloud" );
137  sampled_Q_3D_ = Q;
138  }
139 
140 
141  // center points around centroids
142  auto centerPoints = [](std::vector<Point3D>&container,
143  VectorType& centroid){
144  for(const auto& p : container) centroid += p.pos();
145  centroid /= Scalar(container.size());
146  for(auto& p : container) p.pos() -= centroid;
147  };
148  centerPoints(sampled_P_3D_, centroid_P_);
149  centerPoints(sampled_Q_3D_, centroid_Q_);
150 
151  initKdTree();
152  // Compute the diameter of P approximately (randomly). This is far from being
153  // Guaranteed close to the diameter but gives good results for most common
154  // objects if they are densely sampled.
155  P_diameter_ = 0.0;
156  for (int i = 0; i < kNumberOfDiameterTrials; ++i) {
157  int at = randomGenerator_() % sampled_Q_3D_.size();
158  int bt = randomGenerator_() % sampled_Q_3D_.size();
159 
160  Scalar l = (sampled_Q_3D_[bt].pos() - sampled_Q_3D_[at].pos()).norm();
161  if (l > P_diameter_) {
162  P_diameter_ = l;
163  }
164  }
165 
166  // Mean distance and a bit more... We increase the estimation to allow for
167  // noise, wrong estimation and non-uniform sampling.
168  P_mean_distance_ = MeanDistance();
169 
170  // Normalize the delta (See the paper) and the maximum base distance.
171  // delta = P_mean_distance_ * delta;
172  max_base_diameter_ = P_diameter_; // * estimated_overlap_;
173 
174  // RANSAC probability and number of needed trials.
175  Scalar first_estimation =
176  std::log(kSmallError) / std::log(1.0 - pow(options_.getOverlapEstimation(),
177  static_cast<Scalar>(kMinNumberOfTrials)));
178  // We use a simple heuristic to elevate the probability to a reasonable value
179  // given that we don't simply sample from P, but instead, we bound the
180  // distance between the points in the base as a fraction of the diameter.
181  number_of_trials_ =
182  static_cast<int>(first_estimation * (P_diameter_ / kDiameterFraction) /
183  max_base_diameter_);
184  if (number_of_trials_ < kMinNumberOfTrials)
185  number_of_trials_ = kMinNumberOfTrials;
186 
187  Log<LogLevel::Verbose>( "norm_max_dist: ", options_.delta );
188  current_trial_ = 0;
189  best_LCP_ = 0.0;
190 
191  Q_copy_ = Q;
192  for (int i = 0; i < 4; ++i) {
193  base_[i] = 0;
194  current_congruent_[i] = 0;
195  }
196  transform_ = Eigen::Matrix<Scalar, 4, 4>::Identity();
197 
198  // call Virtual handler
199  Initialize(P, Q);
200 
201  best_LCP_ = Verify(transform_);
202  Log<LogLevel::Verbose>( "Initial LCP: ", best_LCP_ );
203 }
204 
205 
206 // Performs N RANSAC iterations and compute the best transformation. Also,
207 // transforms the set Q by this optimal transformation.
208 template <typename Visitor>
209 bool
210 Match4PCSBase::Perform_N_steps(int n,
211  Eigen::Ref<MatrixType> transformation,
212  std::vector<Point3D>* Q,
213  const Visitor &v) {
214  using std::chrono::system_clock;
215  if (Q == nullptr) return false;
216 
217 #ifdef TEST_GLOBAL_TIMINGS
218  Timer t (true);
219 #endif
220 
221 
222  // The transformation has been computed between the two point clouds centered
223  // at the origin, we need to recompute the translation to apply it to the original clouds
224  auto getGlobalTransform = [this](Eigen::Ref<MatrixType> transformation){
225  Eigen::Matrix<Scalar, 3, 3> rot, scale;
226  Eigen::Transform<Scalar, 3, Eigen::Affine> (transform_).computeRotationScaling(&rot, &scale);
227  transformation = transform_;
228  transformation.col(3) = (qcentroid1_ + centroid_P_ - ( rot * scale * (qcentroid2_ + centroid_Q_))).homogeneous();
229  };
230 
231  Scalar last_best_LCP = best_LCP_;
232  v(0, best_LCP_, transformation);
233 
234  bool ok = false;
235  std::chrono::time_point<system_clock> t0 = system_clock::now(), end;
236  for (int i = current_trial_; i < current_trial_ + n; ++i) {
237  ok = TryOneBase(v);
238 
239  Scalar fraction_try = Scalar(i) / Scalar(number_of_trials_);
240  Scalar fraction_time =
241  std::chrono::duration_cast<std::chrono::seconds>
242  (system_clock::now() - t0).count() /
243  options_.max_time_seconds;
244  Scalar fraction = std::max(fraction_time, fraction_try);
245 
246  if (v.needsGlobalTransformation()) {
247  getGlobalTransform(transformation);
248  } else {
249  transformation = transform_;
250  }
251 
252  v(fraction, best_LCP_, transformation);
253 
254  // ok means that we already have the desired LCP.
255  if (ok || i > number_of_trials_ || fraction >= 0.99 || best_LCP_ == 1.0) break;
256  }
257 
258  current_trial_ += n;
259  if (best_LCP_ > last_best_LCP) {
260  *Q = Q_copy_;
261 
262  getGlobalTransform(transformation);
263 
264  // Transforms Q by the new transformation.
265  for (size_t i = 0; i < Q->size(); ++i) {
266  (*Q)[i].pos() = (transformation * (*Q)[i].pos().homogeneous()).head<3>();
267  }
268  }
269 #ifdef TEST_GLOBAL_TIMINGS
270  totalTime += Scalar(t.elapsed().count()) / Scalar(CLOCKS_PER_SEC);
271 #endif
272 
273  return ok || current_trial_ >= number_of_trials_;
274 }
275 
276 
277 
278 // Pick one base, finds congruent 4-points in Q, verifies for all
279 // transformations, and retains the best transformation and LCP. This is
280 // a complete RANSAC iteration.
281 template<typename Visitor>
282 bool Match4PCSBase::TryOneBase(const Visitor &v) {
283  Scalar invariant1, invariant2;
284  int base_id1, base_id2, base_id3, base_id4;
285 
286 //#define STATIC_BASE
287 
288 #ifdef STATIC_BASE
289  static bool first_time = true;
290 
291  if (first_time){
292  base_id1 = 0;
293  base_id2 = 3;
294  base_id3 = 1;
295  base_id4 = 4;
296 
297  base_3D_[0] = sampled_P_3D_ [base_id1];
298  base_3D_[1] = sampled_P_3D_ [base_id2];
299  base_3D_[2] = sampled_P_3D_ [base_id3];
300  base_3D_[3] = sampled_P_3D_ [base_id4];
301 
302  TryQuadrilateral(&invariant1, &invariant2, base_id1, base_id2, base_id3, base_id4);
303 
304  first_time = false;
305  }
306  else
307  return false;
308 
309 #else
310 
311  if (!SelectQuadrilateral(invariant1, invariant2, base_id1, base_id2,
312  base_id3, base_id4)) {
313  return false;
314  }
315 #endif
316 
317  // Computes distance between pairs.
318  const Scalar distance1 = (base_3D_[0].pos()- base_3D_[1].pos()).norm();
319  const Scalar distance2 = (base_3D_[2].pos()- base_3D_[3].pos()).norm();
320 
321  std::vector<std::pair<int, int>> pairs1, pairs2;
322  std::vector<Quadrilateral> congruent_quads;
323 
324  // Compute normal angles.
325  const Scalar normal_angle1 = (base_3D_[0].normal() - base_3D_[1].normal()).norm();
326  const Scalar normal_angle2 = (base_3D_[2].normal() - base_3D_[3].normal()).norm();
327 
328  ExtractPairs(distance1, normal_angle1, distance_factor * options_.delta, 0,
329  1, &pairs1);
330  ExtractPairs(distance2, normal_angle2, distance_factor * options_.delta, 2,
331  3, &pairs2);
332 
333 // Log<LogLevel::Verbose>( "Pair creation ouput: ", pairs1.size(), " - ", pairs2.size());
334 
335  if (pairs1.size() == 0 || pairs2.size() == 0) {
336  return false;
337  }
338 
339 
340  if (!FindCongruentQuadrilaterals(invariant1, invariant2,
341  distance_factor * options_.delta,
342  distance_factor * options_.delta,
343  pairs1,
344  pairs2,
345  &congruent_quads)) {
346  return false;
347  }
348 
349  size_t nb = 0;
350 
351  bool match = TryCongruentSet(base_id1, base_id2, base_id3, base_id4,
352  congruent_quads,
353  v,
354  nb);
355 
356  //if (nb != 0)
357  // Log<LogLevel::Verbose>( "Congruent quads: (", nb, ") " );
358 
359  return match;
360 }
361 
362 
363 template <typename Visitor>
364 bool Match4PCSBase::TryCongruentSet(
365  int base_id1,
366  int base_id2,
367  int base_id3,
368  int base_id4,
369  const std::vector<Quadrilateral>& congruent_quads,
370  const Visitor& v,
371  size_t &nbCongruent){
372  static const double pi = std::acos(-1);
373 
374  // get references to the basis coordinates
375  const Point3D& b1 = sampled_P_3D_[base_id1];
376  const Point3D& b2 = sampled_P_3D_[base_id2];
377  const Point3D& b3 = sampled_P_3D_[base_id3];
378  const Point3D& b4 = sampled_P_3D_[base_id4];
379 
380  // set the basis coordinates in the congruent quad array
381  const std::array<Point3D, 4> congruent_base {{b1, b2, b3, b4}};
382 
383 
384  // Centroid of the basis, computed once and using only the three first points
385  Eigen::Matrix<Scalar, 3, 1> centroid1 = (b1.pos() + b2.pos() + b3.pos()) / Scalar(3);
386 
387 
388  std::atomic<size_t> nbCongruentAto(0);
389 
390 #ifdef SUPER4PCS_USE_OPENMP
391 #pragma omp parallel for num_threads(omp_nthread_congruent_)
392 #endif
393  for (int i = 0; i < int(congruent_quads.size()); ++i) {
394  std::array<Point3D, 4> congruent_candidate;
395 
396  Eigen::Matrix<Scalar, 4, 4> transform;
397 
398  // Centroid of the sets, computed in the loop using only the three first points
399  Eigen::Matrix<Scalar, 3, 1> centroid2;
400 
401  const int a = congruent_quads[i].vertices[0];
402  const int b = congruent_quads[i].vertices[1];
403  const int c = congruent_quads[i].vertices[2];
404  const int d = congruent_quads[i].vertices[3];
405  congruent_candidate[0] = sampled_Q_3D_[a];
406  congruent_candidate[1] = sampled_Q_3D_[b];
407  congruent_candidate[2] = sampled_Q_3D_[c];
408  congruent_candidate[3] = sampled_Q_3D_[d];
409 
410  #ifdef STATIC_BASE
411  Log<LogLevel::Verbose>( "Ids: ", base_id1, "\t", base_id2, "\t", base_id3, "\t", base_id4);
412  Log<LogLevel::Verbose>( " ", a, "\t", b, "\t", c, "\t", d);
413  #endif
414 
415  centroid2 = (congruent_candidate[0].pos() +
416  congruent_candidate[1].pos() +
417  congruent_candidate[2].pos()) / Scalar(3.);
418 
419  Scalar rms = -1;
420 
421  const bool ok =
422  ComputeRigidTransformation(congruent_base, // input congruent quad
423  congruent_candidate,// tested congruent quad
424  centroid1, // input: basis centroid
425  centroid2, // input: candidate quad centroid
426  options_.max_angle * pi / 180.0, // maximum per-dimension angle, check return value to detect invalid cases
427  transform, // output: transformation
428  rms, // output: rms error of the transformation between the basis and the congruent quad
429  #ifdef MULTISCALE
430  true
431  #else
432  false
433  #endif
434  ); // state: compute scale ratio ?
435 
436  if (ok && rms >= Scalar(0.)) {
437 
438  // We give more tolerantz in computing the best rigid transformation.
439  if (rms < distance_factor * options_.delta) {
440 
441  nbCongruentAto++;
442  // The transformation is computed from the point-clouds centered inn [0,0,0]
443 
444  // Verify the rest of the points in Q against P.
445  Scalar lcp = Verify(transform);
446 
447  // transformation has been computed between the two point clouds centered
448  // at the origin, we need to recompute the translation to apply it to the original clouds
449  auto getGlobalTransform =
450  [this, transform, centroid1, centroid2]
451  (Eigen::Ref<MatrixType> transformation){
452  Eigen::Matrix<Scalar, 3, 3> rot, scale;
453  Eigen::Transform<Scalar, 3, Eigen::Affine> (transform).computeRotationScaling(&rot, &scale);
454  transformation = transform;
455  transformation.col(3) = (centroid1 + centroid_P_ - ( rot * scale * (centroid2 + centroid_Q_))).homogeneous();
456  };
457 
458  if (v.needsGlobalTransformation())
459  {
460  Eigen::Matrix<Scalar, 4, 4> transformation = transform;
461  getGlobalTransform(transformation);
462  v(-1, lcp, transformation);
463  }
464  else
465  v(-1, lcp, transform);
466 
467 #pragma omp critical
468  if (lcp > best_LCP_) {
469  // Retain the best LCP and transformation.
470  base_[0] = base_id1;
471  base_[1] = base_id2;
472  base_[2] = base_id3;
473  base_[3] = base_id4;
474 
475  current_congruent_[0] = a;
476  current_congruent_[1] = b;
477  current_congruent_[2] = c;
478  current_congruent_[3] = d;
479 
480  best_LCP_ = lcp;
481  transform_ = transform;
482  qcentroid1_ = centroid1;
483  qcentroid2_ = centroid2;
484  }
485  // Terminate if we have the desired LCP already.
486  if (best_LCP_ > options_.getTerminateThreshold()){
487  continue;
488  }
489  }
490  }
491  }
492 
493  nbCongruent = nbCongruentAto;
494 
495  // If we reached here we do not have yet the desired LCP.
496  return best_LCP_ > options_.getTerminateThreshold() /*false*/;
497 }
498 
499 
500 } // namespace Super4PCS
501 
502 #endif
size_t sample_size
The number of points in the sample. We sample this number of points uniformly from P and Q...
Definition: shared4pcs.h:165
static constexpr int kNumberOfDiameterTrials
Definition: match4pcsBase.h:79
Scalar ComputeTransformation(const std::vector< Point3D > &P, std::vector< Point3D > *Q, Eigen::Ref< MatrixType > transformation, const Sampler &sampler=Sampler(), const Visitor &v=Visitor())
Computes an approximation of the best LCP (directional) from Q to P and the rigid transformation that...
Definition: match4pcsBase.hpp:63
Scalar getOverlapEstimation() const
Definition: shared4pcs.h:180
Definition: bbox.h:54
The basic 3D point structure. A point potentially contains also directional information and color...
Definition: shared4pcs.h:61
int max_time_seconds
Maximum time we allow the computation to take. This makes the algorithm an ANY TIME algorithm that ca...
Definition: shared4pcs.h:169
typename Point3D::VectorType VectorType
Definition: match4pcsBase.h:70
Scalar delta
The delta for the LCP (see the paper).
Definition: shared4pcs.h:153
Scalar getTerminateThreshold() const
Definition: shared4pcs.h:179
Scalar max_angle
Maximum rotation angle. Set negative to ignore.
Definition: shared4pcs.h:160
static constexpr Scalar distance_factor
Definition: match4pcsBase.h:81
VectorType & pos()
Definition: shared4pcs.h:77
typename Point3D::Scalar Scalar
Definition: match4pcsBase.h:69
static constexpr Scalar kLargeNumber
Definition: match4pcsBase.h:80