Point Cloud Library (PCL)  1.14.1-dev
ia_kfpcs.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  *
7  * All rights reserved
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are met
11  *
12  * * Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  * * Redistributions in binary form must reproduce the above
15  * copyright notice, this list of conditions and the following
16  * disclaimer in the documentation and/or other materials provided
17  * with the distribution.
18  * * Neither the name of the copyright holder(s) nor the names of its
19  * contributors may be used to endorse or promote products derived
20  * from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  *
35  */
36 
37 #ifndef PCL_REGISTRATION_IMPL_IA_KFPCS_H_
38 #define PCL_REGISTRATION_IMPL_IA_KFPCS_H_
39 
40 #include <limits>
41 
42 namespace pcl {
43 
44 namespace registration {
45 
46 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
49 : indices_validation_(new pcl::Indices)
50 {
51  reg_name_ = "pcl::registration::KFPCSInitialAlignment";
52 }
53 
54 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
55 bool
57 {
58  // due to sparse keypoint cloud, do not normalize delta with estimated point density
59  if (normalize_delta_) {
60  PCL_WARN("[%s::initCompute] Delta should be set according to keypoint precision! "
61  "Normalization according to point cloud density is ignored.\n",
62  reg_name_.c_str());
63  normalize_delta_ = false;
64  }
65 
66  // initialize as in fpcs
68  initCompute();
69 
70  // set the threshold values with respect to keypoint characteristics
71  max_pair_diff_ = delta_ * 1.414f; // diff between 2 points of delta_ accuracy
72  coincidation_limit_ = delta_ * 2.828f; // diff between diff of 2 points
73  max_edge_diff_ =
74  delta_ *
75  3.f; // diff between 2 points + some inaccuracy due to quadruple orientation
76  max_mse_ =
77  powf(delta_ * 4.f, 2.f); // diff between 2 points + some registration inaccuracy
78  max_inlier_dist_sqr_ =
79  powf(delta_ * 8.f,
80  2.f); // set rel. high, because MSAC is used (residual based score function)
81 
82  // check use of translation costs and calculate upper boundary if not set by user
83  if (upper_trl_boundary_ < 0)
84  upper_trl_boundary_ = diameter_ * (1.f - approx_overlap_) * 0.5f;
85 
86  if (!(lower_trl_boundary_ < 0) && upper_trl_boundary_ > lower_trl_boundary_)
87  use_trl_score_ = true;
88  else
89  lambda_ = 0.f;
90 
91  // generate a subset of indices of size ransac_iterations_ on which to evaluate
92  // candidates on
93  if (indices_->size() <= static_cast<std::size_t>(ransac_iterations_) ||
94  ransac_iterations_ <= 0)
95  indices_validation_ = indices_;
96  else {
97  indices_validation_.reset(new pcl::Indices);
98  pcl::RandomSample<PointSource> random_sampling;
99  random_sampling.setInputCloud(input_);
100  random_sampling.setIndices(indices_);
101  random_sampling.setSample(ransac_iterations_);
102  random_sampling.filter(*indices_validation_);
103  }
104 
105  PCL_DEBUG("[%s::initCompute] delta_=%g, max_inlier_dist_sqr_=%g, "
106  "coincidation_limit_=%g, max_edge_diff_=%g, max_pair_diff_=%g\n",
107  reg_name_.c_str(),
108  delta_,
109  max_inlier_dist_sqr_,
110  coincidation_limit_,
111  max_edge_diff_,
112  max_pair_diff_);
113  return (true);
114 }
115 
116 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
117 void
119  const pcl::Indices& base_indices,
120  std::vector<pcl::Indices>& matches,
121  MatchingCandidates& candidates)
122 {
123  candidates.clear();
124 
125  // loop over all Candidate matches
126  for (auto& match : matches) {
127  Eigen::Matrix4f transformation_temp;
128  pcl::Correspondences correspondences_temp;
129  float fitness_score =
130  std::numeric_limits<float>::max(); // reset to std::numeric_limits<float>::max()
131  // to accept all candidates and not only best
132 
133  correspondences_temp.push_back(pcl::Correspondence(match[0], base_indices[0], 0.0));
134  correspondences_temp.push_back(pcl::Correspondence(match[1], base_indices[1], 0.0));
135  correspondences_temp.push_back(pcl::Correspondence(match[2], base_indices[2], 0.0));
136  correspondences_temp.push_back(pcl::Correspondence(match[3], base_indices[3], 0.0));
137 
138  // check match based on residuals of the corresponding points after transformation
139  if (validateMatch(base_indices, match, correspondences_temp, transformation_temp) <
140  0)
141  continue;
142 
143  // check resulting transformation using a sub sample of the source point cloud
144  // all candidates are stored and later sorted according to their fitness score
145  validateTransformation(transformation_temp, fitness_score);
146 
147  // store all valid match as well as associated score and transformation
148  candidates.push_back(
149  MatchingCandidate(fitness_score, correspondences_temp, transformation_temp));
150  }
151  // make sure that candidate with best fitness score is at the front, for early
152  // termination check
153  if (!candidates.empty()) {
154  auto best_candidate = candidates.begin();
155  for (auto iter = candidates.begin(); iter < candidates.end(); ++iter)
156  if (iter->fitness_score < best_candidate->fitness_score)
157  best_candidate = iter;
158  if (best_candidate != candidates.begin())
159  std::swap(*best_candidate, *candidates.begin());
160  }
161 }
162 
163 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
164 int
166  validateTransformation(Eigen::Matrix4f& transformation, float& fitness_score)
167 {
168  // transform sub sampled source cloud
169  PointCloudSource source_transformed;
171  *input_, *indices_validation_, source_transformed, transformation);
172 
173  const std::size_t nr_points = source_transformed.size();
174  float score_a = 0.f, score_b = 0.f;
175 
176  // residual costs based on mse
177  pcl::Indices ids(1);
178  std::vector<float> dists_sqr(1);
179  for (const auto& source : source_transformed) {
180  // search for nearest point using kd tree search
181  tree_->nearestKSearch(source, 1, ids, dists_sqr);
182  score_a += (dists_sqr[0] < max_inlier_dist_sqr_ ? dists_sqr[0]
183  : max_inlier_dist_sqr_); // MSAC
184  }
185 
186  score_a /= (max_inlier_dist_sqr_ * nr_points); // MSAC
187  // score_a = 1.f - (1.f - score_a) / (1.f - approx_overlap_); // make score relative
188  // to estimated overlap
189 
190  // translation score (solutions with small translation are down-voted)
191  float scale = 1.f;
192  if (use_trl_score_) {
193  float trl = transformation.rightCols<1>().head<3>().norm();
194  float trl_ratio =
195  (trl - lower_trl_boundary_) / (upper_trl_boundary_ - lower_trl_boundary_);
196 
197  score_b =
198  (trl_ratio < 0.f ? 1.f
199  : (trl_ratio > 1.f ? 0.f
200  : 0.5f * sin(M_PI * trl_ratio + M_PI_2) +
201  0.5f)); // sinusoidal costs
202  scale += lambda_;
203  }
204 
205  // calculate the fitness and return unsuccessful if smaller than previous ones
206  float fitness_score_temp = (score_a + lambda_ * score_b) / scale;
207  if (fitness_score_temp > fitness_score)
208  return (-1);
209 
210  fitness_score = fitness_score_temp;
211  return (0);
212 }
213 
214 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
215 void
217  const std::vector<MatchingCandidates>& candidates)
218 {
219  // reorganize candidates into single vector
220  std::size_t total_size = 0;
221  for (const auto& candidate : candidates)
222  total_size += candidate.size();
223 
224  candidates_.clear();
225  candidates_.reserve(total_size);
226 
227  for (const auto& candidate : candidates)
228  for (const auto& match : candidate)
229  candidates_.push_back(match);
230 
231  // sort according to score value
232  std::sort(candidates_.begin(), candidates_.end(), by_score());
233 
234  // return here if no score was valid, i.e. all scores are
235  // std::numeric_limits<float>::max()
236  if (candidates_[0].fitness_score == std::numeric_limits<float>::max()) {
237  converged_ = false;
238  return;
239  }
240 
241  // save best candidate as output result
242  // note, all other candidates are accessible via getNBestCandidates () and
243  // getTBestCandidates ()
244  fitness_score_ = candidates_[0].fitness_score;
245  final_transformation_ = candidates_[0].transformation;
246  *correspondences_ = candidates_[0].correspondences;
247  PCL_DEBUG("[%s::finalCompute] best score is %g, out of %zu candidate solutions.\n",
248  reg_name_.c_str(),
249  fitness_score_,
250  candidates_.size());
251 
252  // here we define convergence if resulting score is above threshold
253  converged_ = fitness_score_ < score_threshold_;
254 }
255 
256 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
257 void
259  int n, float min_angle3d, float min_translation3d, MatchingCandidates& candidates)
260 {
261  candidates.clear();
262 
263  // loop over all candidates starting from the best one
264  for (const auto& candidate : candidates_) {
265  // stop if current candidate has no valid score
266  if (candidate.fitness_score == std::numeric_limits<float>::max())
267  return;
268 
269  // check if current candidate is a unique one compared to previous using the
270  // min_diff threshold
271  bool unique = true;
272  for (const auto& c2 : candidates) {
273  Eigen::Matrix4f diff =
274  candidate.transformation.colPivHouseholderQr().solve(c2.transformation);
275  const float angle3d = Eigen::AngleAxisf(diff.topLeftCorner<3, 3>()).angle();
276  const float translation3d = diff.block<3, 1>(0, 3).norm();
277  unique = angle3d > min_angle3d && translation3d > min_translation3d;
278  if (!unique) {
279  break;
280  }
281  }
282 
283  // add candidate to best candidates
284  if (unique)
285  candidates.push_back(candidate);
286 
287  // stop if n candidates are reached
288  if (candidates.size() == n)
289  return;
290  }
291 }
292 
293 template <typename PointSource, typename PointTarget, typename NormalT, typename Scalar>
294 void
296  float t, float min_angle3d, float min_translation3d, MatchingCandidates& candidates)
297 {
298  candidates.clear();
299 
300  // loop over all candidates starting from the best one
301  for (const auto& candidate : candidates_) {
302  // stop if current candidate has score below threshold
303  if (candidate.fitness_score > t)
304  return;
305 
306  // check if current candidate is a unique one compared to previous using the
307  // min_diff threshold
308  bool unique = true;
309  for (const auto& c2 : candidates) {
310  Eigen::Matrix4f diff =
311  candidate.transformation.colPivHouseholderQr().solve(c2.transformation);
312  const float angle3d = Eigen::AngleAxisf(diff.topLeftCorner<3, 3>()).angle();
313  const float translation3d = diff.block<3, 1>(0, 3).norm();
314  unique = angle3d > min_angle3d && translation3d > min_translation3d;
315  if (!unique) {
316  break;
317  }
318  }
319 
320  // add candidate to best candidates
321  if (unique)
322  candidates.push_back(candidate);
323  }
324 }
325 
326 } // namespace registration
327 } // namespace pcl
328 
329 #endif // PCL_REGISTRATION_IMPL_IA_KFPCS_H_
void filter(Indices &indices)
Calls the filtering method and returns the filtered point cloud indices.
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
Definition: point_cloud.h:443
RandomSample applies a random sampling with uniform probability.
Definition: random_sample.h:56
void setSample(unsigned int sample)
Set number of indices to be sampled.
Definition: random_sample.h:89
std::string reg_name_
The registration method name.
Definition: registration.h:548
virtual bool initCompute()
Internal computation initialization.
Definition: ia_fpcs.hpp:225
void getTBestCandidates(float t, float min_angle3d, float min_translation3d, MatchingCandidates &candidates)
Get all unique candidate matches with fitness scores above a threshold t.
Definition: ia_kfpcs.hpp:295
void finalCompute(const std::vector< MatchingCandidates > &candidates) override
Final computation of best match out of vector of matches.
Definition: ia_kfpcs.hpp:216
void handleMatches(const pcl::Indices &base_indices, std::vector< pcl::Indices > &matches, MatchingCandidates &candidates) override
Method to handle current candidate matches.
Definition: ia_kfpcs.hpp:118
void getNBestCandidates(int n, float min_angle3d, float min_translation3d, MatchingCandidates &candidates)
Get the N best unique candidate matches according to their fitness score.
Definition: ia_kfpcs.hpp:258
int validateTransformation(Eigen::Matrix4f &transformation, float &fitness_score) override
Validate the transformation by calculating the score value after transforming the input source cloud.
Definition: ia_kfpcs.hpp:166
bool initCompute() override
Internal computation initialization.
Definition: ia_kfpcs.hpp:56
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.
Definition: transforms.hpp:221
std::vector< MatchingCandidate, Eigen::aligned_allocator< MatchingCandidate > > MatchingCandidates
std::vector< pcl::Correspondence, Eigen::aligned_allocator< pcl::Correspondence > > Correspondences
IndicesAllocator<> Indices
Type used for indices in PCL.
Definition: types.h:133
#define M_PI_2
Definition: pcl_macros.h:202
#define M_PI
Definition: pcl_macros.h:201
Correspondence represents a match between two entities (e.g., points, descriptors,...
Container for matching candidate consisting of.
Sorting of candidates based on fitness score value.