Point Cloud Library (PCL) 1.15.1-dev
Loading...
Searching...
No Matches
ia_fpcs.hpp
1/*
2 * Software License Agreement (BSD License)
3 *
4 * Point Cloud Library (PCL) - www.pointclouds.org
5 * Copyright (c) 2014-, Open Perception, Inc.
6 * Copyright (C) 2008 Ben Gurion University of the Negev, Beer Sheva, Israel.
7 *
8 * All rights reserved
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met
12 *
13 * * Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
19 * * Neither the name of the copyright holder(s) nor the names of its
20 * contributors may be used to endorse or promote products derived
21 * from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 *
36 */
37
38#ifndef PCL_REGISTRATION_IMPL_IA_FPCS_H_
39#define PCL_REGISTRATION_IMPL_IA_FPCS_H_
40
42#include <pcl/common/time.h>
43#include <pcl/common/utils.h>
44#include <pcl/filters/random_sample.h>
45#include <pcl/registration/ia_fpcs.h>
46#include <pcl/registration/transformation_estimation_3point.h>
47#include <pcl/sample_consensus/sac_model_plane.h>
48#include <pcl/search/auto.h>
49
50#include <limits>
51
52///////////////////////////////////////////////////////////////////////////////////////////
53template <typename PointT>
54inline float
56 float max_dist,
57 int nr_threads)
58{
59 const float max_dist_sqr = max_dist * max_dist;
60 const std::size_t s = cloud->size();
61
62 typename pcl::search::Search<PointT>::Ptr tree(pcl::search::autoSelectMethod<PointT>(
64
65 float mean_dist = 0.f;
66 int num = 0;
67 pcl::Indices ids(2);
68 std::vector<float> dists_sqr(2);
69
70 pcl::utils::ignore(nr_threads);
71#pragma omp parallel for default(none) shared(tree, cloud) \
72 firstprivate(ids, dists_sqr) reduction(+ : mean_dist, num) \
73 firstprivate(s, max_dist_sqr) num_threads(nr_threads)
74 for (int i = 0; i < 1000; i++) {
75 tree->nearestKSearch((*cloud)[rand() % s], 2, ids, dists_sqr);
76 if (dists_sqr[1] < max_dist_sqr) {
77 mean_dist += std::sqrt(dists_sqr[1]);
78 num++;
79 }
80 }
81
82 return (mean_dist / num);
83};
84
85///////////////////////////////////////////////////////////////////////////////////////////
86template <typename PointT>
87inline float
89 const pcl::Indices& indices,
90 float max_dist,
91 int nr_threads)
92{
93 const float max_dist_sqr = max_dist * max_dist;
94 const std::size_t s = indices.size();
95
96 typename pcl::search::Search<PointT>::Ptr tree(pcl::search::autoSelectMethod<PointT>(
98
99 float mean_dist = 0.f;
100 int num = 0;
101 pcl::Indices ids(2);
102 std::vector<float> dists_sqr(2);
103
104 pcl::utils::ignore(nr_threads);
105#if OPENMP_LEGACY_CONST_DATA_SHARING_RULE
106#pragma omp parallel for default(none) shared(tree, cloud, indices) \
107 firstprivate(ids, dists_sqr) reduction(+ : mean_dist, num) num_threads(nr_threads)
108#else
109#pragma omp parallel for default(none) shared(tree, cloud, indices, s, max_dist_sqr) \
110 firstprivate(ids, dists_sqr) reduction(+ : mean_dist, num) num_threads(nr_threads)
111#endif
112 for (int i = 0; i < 1000; i++) {
113 tree->nearestKSearch((*cloud)[indices[rand() % s]], 2, ids, dists_sqr);
114 if (dists_sqr[1] < max_dist_sqr) {
115 mean_dist += std::sqrt(dists_sqr[1]);
116 num++;
117 }
118 }
119
120 return (mean_dist / num);
121};
122
123///////////////////////////////////////////////////////////////////////////////////////////
124template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
127: source_normals_()
128, target_normals_()
129, score_threshold_(std::numeric_limits<float>::max())
130, fitness_score_(std::numeric_limits<float>::max())
131{
132 reg_name_ = "pcl::registration::FPCSInitialAlignment";
133 max_iterations_ = 0;
134 ransac_iterations_ = 1000;
137}
138
139///////////////////////////////////////////////////////////////////////////////////////////
140template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
141void
143 computeTransformation(PointCloudSource& output, const Eigen::Matrix4f& guess)
144{
145 if (!initCompute())
146 return;
147
148 final_transformation_ = guess;
149 bool abort = false;
150 std::vector<MatchingCandidates> all_candidates(max_iterations_);
151 pcl::StopWatch timer;
152
153#pragma omp parallel default(none) shared(abort, all_candidates, timer) \
154 num_threads(nr_threads_)
155 {
156#ifdef _OPENMP
157 const unsigned int seed =
158 static_cast<unsigned int>(std::time(nullptr)) ^ omp_get_thread_num();
159 std::srand(seed);
160 PCL_DEBUG("[%s::computeTransformation] Using seed=%u\n", reg_name_.c_str(), seed);
161#pragma omp for schedule(dynamic)
162#endif
163 for (int i = 0; i < max_iterations_; i++) {
164#pragma omp flush(abort)
165
166 MatchingCandidates candidates(1);
167 pcl::Indices base_indices(4);
168 all_candidates[i] = candidates;
169
170 if (!abort) {
171 float ratio[2];
172 // select four coplanar point base
173 if (selectBase(base_indices, ratio) == 0) {
174 // calculate candidate pair correspondences using diagonal lengths of base
175 pcl::Correspondences pairs_a, pairs_b;
176 if (bruteForceCorrespondences(base_indices[0], base_indices[1], pairs_a) ==
177 0 &&
178 bruteForceCorrespondences(base_indices[2], base_indices[3], pairs_b) ==
179 0) {
180 // determine candidate matches by combining pair correspondences based on
181 // segment distances
182 std::vector<pcl::Indices> matches;
183 if (determineBaseMatches(base_indices, matches, pairs_a, pairs_b, ratio) ==
184 0) {
185 // check and evaluate candidate matches and store them
186 handleMatches(base_indices, matches, candidates);
187 if (!candidates.empty())
188 all_candidates[i] = candidates;
189 }
190 }
191 }
192
193 // check terminate early (time or fitness_score threshold reached)
194 if (!candidates.empty() && candidates[0].fitness_score < score_threshold_) {
195 PCL_DEBUG("[%s::computeTransformation] Terminating because fitness score "
196 "(%g) is below threshold (%g).\n",
197 reg_name_.c_str(),
198 candidates[0].fitness_score,
199 score_threshold_);
200 abort = true;
201 }
202 else if (timer.getTimeSeconds() > max_runtime_) {
203 PCL_DEBUG("[%s::computeTransformation] Terminating because time exceeded.\n",
204 reg_name_.c_str());
205 abort = true;
206 }
207
208#pragma omp flush(abort)
209 }
210 }
211 }
212
213 // determine best match over all tries
214 finalCompute(all_candidates);
215
216 // apply the final transformation
217 pcl::transformPointCloud(*input_, output, final_transformation_);
218
219 deinitCompute();
220}
221
222///////////////////////////////////////////////////////////////////////////////////////////
223template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
224bool
227{
228 const unsigned int seed = std::time(nullptr);
229 std::srand(seed);
230 PCL_DEBUG("[%s::initCompute] Using seed=%u\n", reg_name_.c_str(), seed);
231
232 // basic pcl initialization
234 return (false);
235
236 // check if source and target are given
237 if (!input_ || !target_) {
238 PCL_ERROR("[%s::initCompute] Source or target dataset not given!\n",
239 reg_name_.c_str());
240 return (false);
241 }
242
243 if (!target_indices_ || target_indices_->empty()) {
244 target_indices_.reset(new pcl::Indices(target_->size()));
245 int index = 0;
246 for (auto& target_index : *target_indices_)
247 target_index = index++;
248 target_cloud_updated_ = true;
249 }
250
251 // if a sample size for the point clouds is given; preferably no sampling of target
252 // cloud
253 if (nr_samples_ > 0 && static_cast<std::size_t>(nr_samples_) < indices_->size()) {
254 source_indices_ = pcl::IndicesPtr(new pcl::Indices);
255 pcl::RandomSample<PointSource> random_sampling;
256 random_sampling.setInputCloud(input_);
257 random_sampling.setIndices(indices_);
258 random_sampling.setSample(nr_samples_);
259 random_sampling.setSeed(seed);
260 random_sampling.filter(*source_indices_);
261 }
262 else
263 source_indices_ = indices_;
264
265 // check usage of normals
266 if (source_normals_ && target_normals_ && source_normals_->size() == input_->size() &&
267 target_normals_->size() == target_->size())
268 use_normals_ = true;
269
270 // set up tree structures
271 if (target_cloud_updated_) {
272 if (!tree_ || !tree_->setInputCloud(target_, target_indices_))
273 tree_.reset(pcl::search::autoSelectMethod<PointTarget>(
274 target_, target_indices_, false, pcl::search::Purpose::one_knn_search));
275 target_cloud_updated_ = false;
276 }
277
278 // set predefined variables
279 constexpr int min_iterations = 4;
280 constexpr float diameter_fraction = 0.3f;
281
282 // get diameter of input cloud (distance between farthest points)
283 Eigen::Vector4f pt_min, pt_max;
284 pcl::getMinMax3D(*target_, *target_indices_, pt_min, pt_max);
285 diameter_ = (pt_max - pt_min).norm();
286
287 // derive the limits for the random base selection
288 float max_base_diameter = diameter_ * approx_overlap_ * 2.f;
289 max_base_diameter_sqr_ = max_base_diameter * max_base_diameter;
290
291#ifdef _OPENMP
292 if (nr_threads_ < 1) {
293 nr_threads_ = omp_get_num_procs();
294 PCL_DEBUG("[%s::initCompute] Setting number of threads to %i.\n",
295 reg_name_.c_str(),
296 nr_threads_);
297 }
298#endif // _OPENMP
299
300 // normalize the delta
301 if (normalize_delta_) {
302 float mean_dist = getMeanPointDensity<PointTarget>(
303 target_, *target_indices_, 0.05f * diameter_, nr_threads_);
304 delta_ *= mean_dist;
305 }
307 // heuristic determination of number of trials to have high probability of finding a
308 // good solution
309 if (max_iterations_ == 0) {
310 float first_est = std::log(small_error_) /
311 std::log(1.0 - std::pow(static_cast<double>(approx_overlap_),
312 static_cast<double>(min_iterations)));
313 max_iterations_ =
314 static_cast<int>(first_est / (diameter_fraction * approx_overlap_ * 2.f));
315 PCL_DEBUG("[%s::initCompute] Estimated max iterations as %i\n",
316 reg_name_.c_str(),
317 max_iterations_);
318 }
319
320 // set further parameter
321 if (score_threshold_ == std::numeric_limits<float>::max())
322 score_threshold_ = 1.f - approx_overlap_;
323
324 if (max_iterations_ < 4)
325 max_iterations_ = 4;
326
327 if (max_runtime_ < 1)
328 max_runtime_ = std::numeric_limits<int>::max();
329
330 // calculate internal parameters based on the the estimated point density
331 max_pair_diff_ = delta_ * 2.f;
332 max_edge_diff_ = delta_ * 4.f;
333 coincidation_limit_ = delta_ * 2.f; // EDITED: originally std::sqrt (delta_ * 2.f)
334 max_mse_ = powf(delta_ * 2.f, 2.f);
335 max_inlier_dist_sqr_ = powf(delta_ * 2.f, 2.f);
336 PCL_DEBUG("[%s::initCompute] delta_=%g, max_inlier_dist_sqr_=%g, "
337 "coincidation_limit_=%g, max_edge_diff_=%g, max_pair_diff_=%g\n",
338 reg_name_.c_str(),
339 delta_,
340 max_inlier_dist_sqr_,
341 coincidation_limit_,
342 max_edge_diff_,
343 max_pair_diff_);
344
345 // reset fitness_score
346 fitness_score_ = std::numeric_limits<float>::max();
347
348 return (true);
350
351///////////////////////////////////////////////////////////////////////////////////////////
352template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
353int
355 selectBase(pcl::Indices& base_indices, float (&ratio)[2])
356{
357 const float too_close_sqr = max_base_diameter_sqr_ * 0.01;
358
359 Eigen::VectorXf coefficients(4);
361 plane.setIndices(target_indices_);
362 Eigen::Vector4f centre_pt;
363 float nearest_to_plane = std::numeric_limits<float>::max();
364
365 // repeat base search until valid quadruple was found or ransac_iterations_ number of
366 // tries were unsuccessful
367 for (int i = 0; i < ransac_iterations_; i++) {
368 // random select an appropriate point triple
369 if (selectBaseTriangle(base_indices) < 0)
370 continue;
371
372 pcl::Indices base_triple(base_indices.begin(), base_indices.end() - 1);
373 plane.computeModelCoefficients(base_triple, coefficients);
374 pcl::compute3DCentroid(*target_, base_triple, centre_pt);
375
376 // loop over all points in source cloud to find most suitable fourth point
377 const PointTarget* pt1 = &((*target_)[base_indices[0]]);
378 const PointTarget* pt2 = &((*target_)[base_indices[1]]);
379 const PointTarget* pt3 = &((*target_)[base_indices[2]]);
380
381 for (const auto& target_index : *target_indices_) {
382 const PointTarget* pt4 = &((*target_)[target_index]);
383
384 float d1 = pcl::squaredEuclideanDistance(*pt4, *pt1);
385 float d2 = pcl::squaredEuclideanDistance(*pt4, *pt2);
386 float d3 = pcl::squaredEuclideanDistance(*pt4, *pt3);
387 float d4 = (pt4->getVector3fMap() - centre_pt.head<3>()).squaredNorm();
388
389 // check distance between points w.r.t minimum sampling distance; EDITED -> 4th
390 // point now also limited by max base line
391 if (d1 < too_close_sqr || d2 < too_close_sqr || d3 < too_close_sqr ||
392 d4 < too_close_sqr || d1 > max_base_diameter_sqr_ ||
393 d2 > max_base_diameter_sqr_ || d3 > max_base_diameter_sqr_)
394 continue;
395
396 // check distance to plane to get point closest to plane
397 float dist_to_plane = pcl::pointToPlaneDistance(*pt4, coefficients);
398 if (dist_to_plane < nearest_to_plane) {
399 base_indices[3] = target_index;
400 nearest_to_plane = dist_to_plane;
401 }
402 }
403
404 // check if at least one point fulfilled the conditions
405 if (nearest_to_plane != std::numeric_limits<float>::max()) {
406 // order points to build largest quadrangle and calculate intersection ratios of
407 // diagonals
408 setupBase(base_indices, ratio);
409 return (0);
410 }
411 }
412
413 // return unsuccessful if no quadruple was selected
414 return (-1);
415}
416
417///////////////////////////////////////////////////////////////////////////////////////////
418template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
419int
422{
423 const auto nr_points = target_indices_->size();
424 float best_t = 0.f;
425
426 // choose random first point
427 base_indices[0] = (*target_indices_)[rand() % nr_points];
428 auto* index1 = base_indices.data();
429
430 // random search for 2 other points (as far away as overlap allows)
431 for (int i = 0; i < ransac_iterations_; i++) {
432 auto* index2 = &(*target_indices_)[rand() % nr_points];
433 auto* index3 = &(*target_indices_)[rand() % nr_points];
434
435 Eigen::Vector3f u =
436 (*target_)[*index2].getVector3fMap() - (*target_)[*index1].getVector3fMap();
437 Eigen::Vector3f v =
438 (*target_)[*index3].getVector3fMap() - (*target_)[*index1].getVector3fMap();
439 float t =
440 u.cross(v).squaredNorm(); // triangle area (0.5 * sqrt(t)) should be maximal
441
442 // check for most suitable point triple
443 if (t > best_t && u.squaredNorm() < max_base_diameter_sqr_ &&
444 v.squaredNorm() < max_base_diameter_sqr_) {
445 best_t = t;
446 base_indices[1] = *index2;
447 base_indices[2] = *index3;
448 }
449 }
450
451 // return if a triplet could be selected
452 return (best_t == 0.f ? -1 : 0);
453}
454
455///////////////////////////////////////////////////////////////////////////////////////////
456template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
457void
459 setupBase(pcl::Indices& base_indices, float (&ratio)[2])
460{
461 float best_t = std::numeric_limits<float>::max();
462 const pcl::Indices copy(base_indices.begin(), base_indices.end());
463 pcl::Indices temp(base_indices.begin(), base_indices.end());
464
465 // loop over all combinations of base points
466 for (auto i = copy.begin(), i_e = copy.end(); i != i_e; ++i)
467 for (auto j = copy.begin(), j_e = copy.end(); j != j_e; ++j) {
468 if (i == j)
469 continue;
470
471 for (auto k = copy.begin(), k_e = copy.end(); k != k_e; ++k) {
472 if (k == j || k == i)
473 continue;
474
475 auto l = copy.begin();
476 while (l == i || l == j || l == k)
477 ++l;
478
479 temp[0] = *i;
480 temp[1] = *j;
481 temp[2] = *k;
482 temp[3] = *l;
483
484 // calculate diagonal intersection ratios and check for suitable segment to
485 // segment distances
486 float ratio_temp[2];
487 float t = segmentToSegmentDist(temp, ratio_temp);
488 if (t < best_t) {
489 best_t = t;
490 ratio[0] = ratio_temp[0];
491 ratio[1] = ratio_temp[1];
492 base_indices = temp;
493 }
494 }
495 }
496}
497
498///////////////////////////////////////////////////////////////////////////////////////////
499template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
500float
502 segmentToSegmentDist(const pcl::Indices& base_indices, float (&ratio)[2])
503{
504 // get point vectors
505 Eigen::Vector3f u = (*target_)[base_indices[1]].getVector3fMap() -
506 (*target_)[base_indices[0]].getVector3fMap();
507 Eigen::Vector3f v = (*target_)[base_indices[3]].getVector3fMap() -
508 (*target_)[base_indices[2]].getVector3fMap();
509 Eigen::Vector3f w = (*target_)[base_indices[0]].getVector3fMap() -
510 (*target_)[base_indices[2]].getVector3fMap();
511
512 // calculate segment distances
513 float a = u.dot(u);
514 float b = u.dot(v);
515 float c = v.dot(v);
516 float d = u.dot(w);
517 float e = v.dot(w);
518 float D = a * c - b * b;
519 float sN = 0.f, sD = D;
520 float tN = 0.f, tD = D;
521
522 // check segments
523 if (D < small_error_) {
524 sN = 0.f;
525 sD = 1.f;
526 tN = e;
527 tD = c;
528 }
529 else {
530 sN = (b * e - c * d);
531 tN = (a * e - b * d);
532
533 if (sN < 0.f) {
534 sN = 0.f;
535 tN = e;
536 tD = c;
537 }
538 else if (sN > sD) {
539 sN = sD;
540 tN = e + b;
541 tD = c;
542 }
543 }
544
545 if (tN < 0.f) {
546 tN = 0.f;
547
548 if (-d < 0.f)
549 sN = 0.f;
550
551 else if (-d > a)
552 sN = sD;
553
554 else {
555 sN = -d;
556 sD = a;
557 }
558 }
559
560 else if (tN > tD) {
561 tN = tD;
562
563 if ((-d + b) < 0.f)
564 sN = 0.f;
565
566 else if ((-d + b) > a)
567 sN = sD;
568
569 else {
570 sN = (-d + b);
571 sD = a;
572 }
573 }
574
575 // set intersection ratios
576 ratio[0] = (std::abs(sN) < small_error_) ? 0.f : sN / sD;
577 ratio[1] = (std::abs(tN) < small_error_) ? 0.f : tN / tD;
578
579 Eigen::Vector3f x = w + (ratio[0] * u) - (ratio[1] * v);
580 return (x.norm());
581}
582
583///////////////////////////////////////////////////////////////////////////////////////////
584template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
585int
587 bruteForceCorrespondences(int idx1, int idx2, pcl::Correspondences& pairs)
588{
589 const float max_norm_diff = 0.5f * max_norm_diff_ * M_PI / 180.f;
590
591 // calculate reference segment distance and normal angle
592 float ref_dist = pcl::euclideanDistance((*target_)[idx1], (*target_)[idx2]);
593 float ref_norm_angle =
594 (use_normals_ ? ((*target_normals_)[idx1].getNormalVector3fMap() -
595 (*target_normals_)[idx2].getNormalVector3fMap())
596 .norm()
597 : 0.f);
598
599 // loop over all pairs of points in source point cloud
600 auto it_out = source_indices_->begin(), it_out_e = source_indices_->end() - 1;
601 auto it_in_e = source_indices_->end();
602 for (; it_out != it_out_e; it_out++) {
603 auto it_in = it_out + 1;
604 const PointSource* pt1 = &(*input_)[*it_out];
605 for (; it_in != it_in_e; it_in++) {
606 const PointSource* pt2 = &(*input_)[*it_in];
607
608 // check point distance compared to reference dist (from base)
609 float dist = pcl::euclideanDistance(*pt1, *pt2);
610 if (std::abs(dist - ref_dist) < max_pair_diff_) {
611 // add here normal evaluation if normals are given
612 if (use_normals_) {
613 const NormalT* pt1_n = &((*source_normals_)[*it_out]);
614 const NormalT* pt2_n = &((*source_normals_)[*it_in]);
615
616 float norm_angle_1 =
617 (pt1_n->getNormalVector3fMap() - pt2_n->getNormalVector3fMap()).norm();
618 float norm_angle_2 =
619 (pt1_n->getNormalVector3fMap() + pt2_n->getNormalVector3fMap()).norm();
620
621 float norm_diff = std::min<float>(std::abs(norm_angle_1 - ref_norm_angle),
622 std::abs(norm_angle_2 - ref_norm_angle));
623 if (norm_diff > max_norm_diff)
624 continue;
625 }
626
627 pairs.emplace_back(*it_in, *it_out, dist);
628 pairs.emplace_back(*it_out, *it_in, dist);
629 }
630 }
631 }
632
633 // return success if at least one correspondence was found
634 return (pairs.empty() ? -1 : 0);
635}
636
637///////////////////////////////////////////////////////////////////////////////////////////
638template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
639int
641 determineBaseMatches(const pcl::Indices& base_indices,
642 std::vector<pcl::Indices>& matches,
643 const pcl::Correspondences& pairs_a,
644 const pcl::Correspondences& pairs_b,
645 const float (&ratio)[2])
646{
647 // calculate edge lengths of base
648 float dist_base[4];
649 dist_base[0] =
650 pcl::euclideanDistance((*target_)[base_indices[0]], (*target_)[base_indices[2]]);
651 dist_base[1] =
652 pcl::euclideanDistance((*target_)[base_indices[0]], (*target_)[base_indices[3]]);
653 dist_base[2] =
654 pcl::euclideanDistance((*target_)[base_indices[1]], (*target_)[base_indices[2]]);
655 dist_base[3] =
656 pcl::euclideanDistance((*target_)[base_indices[1]], (*target_)[base_indices[3]]);
657
658 // loop over first point pair correspondences and store intermediate points 'e' in new
659 // point cloud
661 cloud_e->resize(pairs_a.size() * 2);
662 auto it_pt = cloud_e->begin();
663 for (const auto& pair : pairs_a) {
664 const PointSource* pt1 = &((*input_)[pair.index_match]);
665 const PointSource* pt2 = &((*input_)[pair.index_query]);
666
667 // calculate intermediate points using both ratios from base (r1,r2)
668 for (int i = 0; i < 2; i++, it_pt++) {
669 it_pt->x = pt1->x + ratio[i] * (pt2->x - pt1->x);
670 it_pt->y = pt1->y + ratio[i] * (pt2->y - pt1->y);
671 it_pt->z = pt1->z + ratio[i] * (pt2->z - pt1->z);
672 }
673 }
674
675 // initialize new kd tree of intermediate points from first point pair correspondences
676 KdTreeReciprocalPtr tree_e(pcl::search::autoSelectMethod<PointSource>(
677 cloud_e,
678 true,
679 pcl::search::Purpose::radius_search)); // maybe check again if results do not have
680 // to be sorted
681
682 pcl::Indices ids;
683 std::vector<float> dists_sqr;
684
685 // loop over second point pair correspondences
686 for (const auto& pair : pairs_b) {
687 const PointTarget* pt1 = &((*input_)[pair.index_match]);
688 const PointTarget* pt2 = &((*input_)[pair.index_query]);
689
690 // calculate intermediate points using both ratios from base (r1,r2)
691 for (const float& r : ratio) {
692 PointTarget pt_e;
693 pt_e.x = pt1->x + r * (pt2->x - pt1->x);
694 pt_e.y = pt1->y + r * (pt2->y - pt1->y);
695 pt_e.z = pt1->z + r * (pt2->z - pt1->z);
696
697 // search for corresponding intermediate points
698 tree_e->radiusSearch(pt_e, coincidation_limit_, ids, dists_sqr);
699 for (const auto& id : ids) {
700 pcl::Indices match_indices(4);
701
702 match_indices[0] =
703 pairs_a[static_cast<int>(std::floor((id / 2.f)))].index_match;
704 match_indices[1] =
705 pairs_a[static_cast<int>(std::floor((id / 2.f)))].index_query;
706 match_indices[2] = pair.index_match;
707 match_indices[3] = pair.index_query;
708 if (match_indices[0] == match_indices[2] ||
709 match_indices[0] == match_indices[3] ||
710 match_indices[1] == match_indices[2] ||
711 match_indices[1] == match_indices[3])
712 continue;
713
714 // EDITED: added coarse check of match based on edge length (due to rigid-body )
715 if (checkBaseMatch(match_indices, dist_base) < 0)
716 continue;
717
718 matches.push_back(match_indices);
719 }
720 }
721 }
722
723 // return unsuccessful if no match was found
724 if (matches.empty()) {
725 PCL_DEBUG("[%s::determineBaseMatches] no matches found\n", reg_name_.c_str());
726 return -1;
727 }
728 else {
729 PCL_DEBUG("[%s::determineBaseMatches] found %zu matches\n",
730 reg_name_.c_str(),
731 matches.size());
732 return 0;
733 }
734}
735
736///////////////////////////////////////////////////////////////////////////////////////////
737template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
738int
740 checkBaseMatch(const pcl::Indices& match_indices, const float (&dist_ref)[4])
741{
742 float d0 =
743 pcl::euclideanDistance((*input_)[match_indices[0]], (*input_)[match_indices[2]]);
744 float d1 =
745 pcl::euclideanDistance((*input_)[match_indices[0]], (*input_)[match_indices[3]]);
746 float d2 =
747 pcl::euclideanDistance((*input_)[match_indices[1]], (*input_)[match_indices[2]]);
748 float d3 =
749 pcl::euclideanDistance((*input_)[match_indices[1]], (*input_)[match_indices[3]]);
750
751 // check edge distances of match w.r.t the base
752 return (std::abs(d0 - dist_ref[0]) < max_edge_diff_ &&
753 std::abs(d1 - dist_ref[1]) < max_edge_diff_ &&
754 std::abs(d2 - dist_ref[2]) < max_edge_diff_ &&
755 std::abs(d3 - dist_ref[3]) < max_edge_diff_)
756 ? 0
757 : -1;
758}
759
760///////////////////////////////////////////////////////////////////////////////////////////
761template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
762void
764 handleMatches(const pcl::Indices& base_indices,
765 std::vector<pcl::Indices>& matches,
766 MatchingCandidates& candidates)
767{
768 candidates.resize(1);
769 float fitness_score = std::numeric_limits<float>::max();
770
771 // loop over all Candidate matches
772 for (auto& match : matches) {
773 Eigen::Matrix4f transformation_temp;
774 pcl::Correspondences correspondences_temp;
775 correspondences_temp.emplace_back(match[0], base_indices[0], 0.0);
776 correspondences_temp.emplace_back(match[1], base_indices[1], 0.0);
777 correspondences_temp.emplace_back(match[2], base_indices[2], 0.0);
778 correspondences_temp.emplace_back(match[3], base_indices[3], 0.0);
779
780 // check match based on residuals of the corresponding points after
781 if (validateMatch(base_indices, match, correspondences_temp, transformation_temp) <
782 0)
783 continue;
784
785 // check resulting using a sub sample of the source point cloud and compare to
786 // previous matches
787 if (validateTransformation(transformation_temp, fitness_score) < 0)
788 continue;
789
790 // store best match as well as associated fitness_score and transformation
791 candidates[0].fitness_score = fitness_score;
792 candidates[0].transformation = transformation_temp;
793 correspondences_temp.erase(correspondences_temp.end() - 1);
794 candidates[0].correspondences = correspondences_temp;
795 }
796}
797
798///////////////////////////////////////////////////////////////////////////////////////////
799template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
800void
802 linkMatchWithBase(const pcl::Indices& base_indices,
803 pcl::Indices& match_indices,
804 pcl::Correspondences& correspondences)
805{
806 // calculate centroid of base and target
807 Eigen::Vector4f centre_base{0, 0, 0, 0}, centre_match{0, 0, 0, 0};
808 pcl::compute3DCentroid(*target_, base_indices, centre_base);
809 pcl::compute3DCentroid(*input_, match_indices, centre_match);
810
811 PointTarget centre_pt_base;
812 centre_pt_base.x = centre_base[0];
813 centre_pt_base.y = centre_base[1];
814 centre_pt_base.z = centre_base[2];
815
816 PointSource centre_pt_match;
817 centre_pt_match.x = centre_match[0];
818 centre_pt_match.y = centre_match[1];
819 centre_pt_match.z = centre_match[2];
820
821 // find corresponding points according to their distance to the centroid
822 pcl::Indices copy = match_indices;
823
824 auto it_match_orig = match_indices.begin();
825 for (auto it_base = base_indices.cbegin(), it_base_e = base_indices.cend();
826 it_base != it_base_e;
827 it_base++, it_match_orig++) {
828 float dist_sqr_1 =
829 pcl::squaredEuclideanDistance((*target_)[*it_base], centre_pt_base);
830 float best_diff_sqr = std::numeric_limits<float>::max();
831 int best_index = -1;
832
833 for (const auto& match_index : copy) {
834 // calculate difference of distances to centre point
835 float dist_sqr_2 =
836 pcl::squaredEuclideanDistance((*input_)[match_index], centre_pt_match);
837 float diff_sqr = std::abs(dist_sqr_1 - dist_sqr_2);
838
839 if (diff_sqr < best_diff_sqr) {
840 best_diff_sqr = diff_sqr;
841 best_index = match_index;
842 }
843 }
844
845 // assign new correspondence and update indices of matched targets
846 correspondences.emplace_back(best_index, *it_base, best_diff_sqr);
847 *it_match_orig = best_index;
848 }
849}
850
851///////////////////////////////////////////////////////////////////////////////////////////
852template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
853int
855 validateMatch(const pcl::Indices& base_indices,
856 const pcl::Indices& match_indices,
857 const pcl::Correspondences& correspondences,
858 Eigen::Matrix4f& transformation)
859{
860 // only use triplet of points to simplify process (possible due to planar case)
861 pcl::Correspondences correspondences_temp = correspondences;
862 correspondences_temp.erase(correspondences_temp.end() - 1);
863
864 // estimate transformation between correspondence set
865 transformation_estimation_->estimateRigidTransformation(
866 *input_, *target_, correspondences_temp, transformation);
867
868 // transform base points
869 PointCloudSource match_transformed;
870 pcl::transformPointCloud(*input_, match_indices, match_transformed, transformation);
871
872 // calculate residuals of transformation and check against maximum threshold
873 std::size_t nr_points = correspondences_temp.size();
874 float mse = 0.f;
875 for (std::size_t i = 0; i < nr_points; i++)
876 mse += pcl::squaredEuclideanDistance(match_transformed.points[i],
877 target_->points[base_indices[i]]);
878
879 mse /= nr_points;
880 return (mse < max_mse_ ? 0 : -1);
881}
882
883///////////////////////////////////////////////////////////////////////////////////////////
884template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
885int
887 validateTransformation(Eigen::Matrix4f& transformation, float& fitness_score)
888{
889 // transform source point cloud
890 PointCloudSource source_transformed;
892 *input_, *source_indices_, source_transformed, transformation);
893
894 std::size_t nr_points = source_transformed.size();
895 std::size_t terminate_value =
896 fitness_score > 1 ? 0
897 : static_cast<std::size_t>((1.f - fitness_score) * nr_points);
898
899 float inlier_score_temp = 0;
900 pcl::Indices ids(1);
901 std::vector<float> dists_sqr(1);
902 auto it = source_transformed.begin();
903
904 for (std::size_t i = 0; i < nr_points; it++, i++) {
905 // search for nearest point using kd tree search
906 tree_->nearestKSearch(*it, 1, ids, dists_sqr);
907 inlier_score_temp += (dists_sqr[0] < max_inlier_dist_sqr_ ? 1 : 0);
908
909 // early terminating
910 if (nr_points - i + inlier_score_temp < terminate_value)
911 break;
912 }
913
914 // check current costs and return unsuccessful if larger than previous ones
915 inlier_score_temp /= static_cast<float>(nr_points);
916 float fitness_score_temp = 1.f - inlier_score_temp;
917
918 if (fitness_score_temp > fitness_score)
919 return (-1);
920
921 fitness_score = fitness_score_temp;
922 return (0);
923}
924
925///////////////////////////////////////////////////////////////////////////////////////////
926template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
927void
929 finalCompute(const std::vector<MatchingCandidates>& candidates)
930{
931 // get best fitness_score over all tries
932 int nr_candidates = static_cast<int>(candidates.size());
933 int best_index = -1;
934 float best_score = std::numeric_limits<float>::max();
935 for (int i = 0; i < nr_candidates; i++) {
936 const float& fitness_score = candidates[i][0].fitness_score;
937 if (fitness_score < best_score) {
938 best_score = fitness_score;
939 best_index = i;
940 }
941 }
942 PCL_DEBUG(
943 "[%s::finalCompute] best score is %g (iteration %i), out of %zu iterations.\n",
944 reg_name_.c_str(),
945 best_score,
946 best_index,
947 candidates.size());
948
949 // check if a valid candidate was available
950 if (!(best_index < 0)) {
951 fitness_score_ = candidates[best_index][0].fitness_score;
952 final_transformation_ = candidates[best_index][0].transformation;
953 *correspondences_ = candidates[best_index][0].correspondences;
954
955 // here we define convergence if resulting fitness_score is below 1-threshold
956 converged_ = fitness_score_ < score_threshold_;
957 }
958}
959
960///////////////////////////////////////////////////////////////////////////////////////////
961
962#endif // PCL_REGISTRATION_IMPL_IA_4PCS_H_
void filter(Indices &indices)
Calls the filtering method and returns the filtered point cloud indices.
PCL base class.
Definition pcl_base.h:70
virtual void setInputCloud(const PointCloudConstPtr &cloud)
Provide a pointer to the input dataset.
Definition pcl_base.hpp:65
virtual void setIndices(const IndicesPtr &indices)
Provide a pointer to the vector of indices that represents the input data.
Definition pcl_base.hpp:72
std::size_t size() const
iterator begin() noexcept
std::vector< PointT, Eigen::aligned_allocator< PointT > > points
The point data.
shared_ptr< const PointCloud< PointT > > ConstPtr
RandomSample applies a random sampling with uniform probability.
void setSeed(unsigned int seed)
Set seed of random function.
void setSample(unsigned int sample)
Set number of indices to be sampled.
typename KdTreeReciprocal::Ptr KdTreeReciprocalPtr
std::string reg_name_
The registration method name.
typename PointCloudSource::Ptr PointCloudSourcePtr
int ransac_iterations_
The number of iterations RANSAC should run for.
TransformationEstimationPtr transformation_estimation_
A TransformationEstimation object, used to calculate the 4x4 rigid transformation.
int max_iterations_
The maximum number of iterations the internal optimization should run for.
void setIndices(const IndicesPtr &indices)
Provide a pointer to the vector of indices that represents the input data.
Definition sac_model.h:315
SampleConsensusModelPlane defines a model for 3D plane segmentation.
bool computeModelCoefficients(const Indices &samples, Eigen::VectorXf &model_coefficients) const override
Check whether the given index samples can form a valid plane model, compute the model coefficients fr...
Simple stopwatch.
Definition time.h:59
double getTimeSeconds() const
Retrieve the time in seconds spent since the last call to reset().
Definition time.h:74
virtual void finalCompute(const std::vector< MatchingCandidates > &candidates)
Final computation of best match out of vector of best matches.
Definition ia_fpcs.hpp:929
void setupBase(pcl::Indices &base_indices, float(&ratio)[2])
Setup the base (four coplanar points) by ordering the points and computing intersection ratios and se...
Definition ia_fpcs.hpp:459
int selectBaseTriangle(pcl::Indices &base_indices)
Select randomly a triplet of points with large point-to-point distances.
Definition ia_fpcs.hpp:421
virtual int bruteForceCorrespondences(int idx1, int idx2, pcl::Correspondences &pairs)
Search for corresponding point pairs given the distance between two base points.
Definition ia_fpcs.hpp:587
int selectBase(pcl::Indices &base_indices, float(&ratio)[2])
Select an approximately coplanar set of four points from the source cloud.
Definition ia_fpcs.hpp:355
virtual int validateTransformation(Eigen::Matrix4f &transformation, float &fitness_score)
Validate the transformation by calculating the number of inliers after transforming the source cloud.
Definition ia_fpcs.hpp:887
virtual int determineBaseMatches(const pcl::Indices &base_indices, std::vector< pcl::Indices > &matches, const pcl::Correspondences &pairs_a, const pcl::Correspondences &pairs_b, const float(&ratio)[2])
Determine base matches by combining the point pair candidate and search for coinciding intersection p...
Definition ia_fpcs.hpp:641
virtual void linkMatchWithBase(const pcl::Indices &base_indices, pcl::Indices &match_indices, pcl::Correspondences &correspondences)
Sets the correspondences between the base B and the match M by using the distance of each point to th...
Definition ia_fpcs.hpp:802
virtual void handleMatches(const pcl::Indices &base_indices, std::vector< pcl::Indices > &matches, MatchingCandidates &candidates)
Method to handle current candidate matches.
Definition ia_fpcs.hpp:764
int checkBaseMatch(const pcl::Indices &match_indices, const float(&ds)[4])
Check if outer rectangle distance of matched points fit with the base rectangle.
Definition ia_fpcs.hpp:740
float segmentToSegmentDist(const pcl::Indices &base_indices, float(&ratio)[2])
Calculate intersection ratios and segment to segment distances of base diagonals.
Definition ia_fpcs.hpp:502
virtual int validateMatch(const pcl::Indices &base_indices, const pcl::Indices &match_indices, const pcl::Correspondences &correspondences, Eigen::Matrix4f &transformation)
Validate the matching by computing the transformation between the source and target based on the four...
Definition ia_fpcs.hpp:855
void computeTransformation(PointCloudSource &output, const Eigen::Matrix4f &guess) override
Rigid transformation computation method.
Definition ia_fpcs.hpp:143
virtual bool initCompute()
Internal computation initialization.
Definition ia_fpcs.hpp:226
TransformationEstimation3Points represents the class for transformation estimation based on:
shared_ptr< pcl::search::Search< PointT > > Ptr
Definition search.h:84
virtual int nearestKSearch(const PointT &point, int k, Indices &k_indices, std::vector< float > &k_sqr_distances) const =0
Search for the k-nearest neighbors for the given query point.
Define standard C methods to do distance calculations.
Define methods for measuring time spent in code blocks.
void getMinMax3D(const pcl::PointCloud< PointT > &cloud, PointT &min_pt, PointT &max_pt)
Get the minimum and maximum values on each of the 3 (x-y-z) dimensions in a given pointcloud.
Definition common.hpp:295
void transformPointCloud(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, const Eigen::Matrix< Scalar, 4, 4 > &transform, bool copy_all_fields)
Apply a rigid transform defined by a 4x4 matrix.
unsigned int compute3DCentroid(ConstCloudIterator< PointT > &cloud_iterator, Eigen::Matrix< Scalar, 4, 1 > &centroid)
Compute the 3D (X-Y-Z) centroid of a set of points and return it as a 3D vector.
Definition centroid.hpp:57
double pointToPlaneDistance(const Point &p, double a, double b, double c, double d)
Get the distance from a point to a plane (unsigned) defined by ax+by+cz+d=0.
std::vector< MatchingCandidate, Eigen::aligned_allocator< MatchingCandidate > > MatchingCandidates
@ one_knn_search
The search method will mainly be used for nearestKSearch where k is 1.
@ radius_search
The search method will mainly be used for radiusSearch.
@ many_knn_search
The search method will mainly be used for nearestKSearch where k is larger than 1.
void ignore(const T &...)
Utility function to eliminate unused variable warnings.
Definition utils.h:62
float squaredEuclideanDistance(const PointType1 &p1, const PointType2 &p2)
Calculate the squared euclidean distance between the two given points.
Definition distances.h:182
float getMeanPointDensity(const typename pcl::PointCloud< PointT >::ConstPtr &cloud, float max_dist, int nr_threads=1)
Compute the mean point density of a given point cloud.
Definition ia_fpcs.hpp:55
std::vector< pcl::Correspondence, Eigen::aligned_allocator< pcl::Correspondence > > Correspondences
float euclideanDistance(const PointType1 &p1, const PointType2 &p2)
Calculate the euclidean distance between the two given points.
Definition distances.h:204
IndicesAllocator<> Indices
Type used for indices in PCL.
Definition types.h:133
shared_ptr< Indices > IndicesPtr
Definition pcl_base.h:58
#define M_PI
Definition pcl_macros.h:201
A point structure representing normal coordinates and the surface curvature estimate.