Point Cloud Library (PCL)  1.15.1-dev
sample_consensus_prerejective.hpp
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  * Copyright (c) 2010-2012, Willow Garage, Inc.
6  * Copyright (c) 2012-, Open Perception, Inc.
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
12  * are met:
13  *
14  * * Redistributions of source code must retain the above copyright
15  * notice, this list of conditions and the following disclaimer.
16  * * Redistributions in binary form must reproduce the above
17  * copyright notice, this list of conditions and the following
18  * disclaimer in the documentation and/or other materials provided
19  * with the distribution.
20  * * Neither the name of the copyright holder(s) nor the names of its
21  * contributors may be used to endorse or promote products derived
22  * from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  *
37  * $Id$
38  *
39  */
40 
41 #ifndef PCL_REGISTRATION_SAMPLE_CONSENSUS_PREREJECTIVE_HPP_
42 #define PCL_REGISTRATION_SAMPLE_CONSENSUS_PREREJECTIVE_HPP_
43 
44 #include <pcl/search/auto.h> // for autoSelectMethod
45 
46 namespace pcl {
47 
48 template <typename PointSource, typename PointTarget, typename FeatureT>
49 void
51  const FeatureCloudConstPtr& features)
52 {
53  if (features == nullptr || features->empty()) {
54  PCL_ERROR(
55  "[pcl::%s::setSourceFeatures] Invalid or empty point cloud dataset given!\n",
56  getClassName().c_str());
57  return;
58  }
59  input_features_ = features;
60 }
61 
62 template <typename PointSource, typename PointTarget, typename FeatureT>
63 void
65  const FeatureCloudConstPtr& features)
66 {
67  if (features == nullptr || features->empty()) {
68  PCL_ERROR(
69  "[pcl::%s::setTargetFeatures] Invalid or empty point cloud dataset given!\n",
70  getClassName().c_str());
71  return;
72  }
73  target_features_ = features;
74  feature_tree_.reset(pcl::search::autoSelectMethod<FeatureT>(
75  target_features_, false, pcl::search::Purpose::many_knn_search));
76 }
77 
78 template <typename PointSource, typename PointTarget, typename FeatureT>
79 void
81  const PointCloudSource& cloud, int nr_samples, pcl::Indices& sample_indices)
82 {
83  if (nr_samples > static_cast<int>(cloud.size())) {
84  PCL_ERROR("[pcl::%s::selectSamples] ", getClassName().c_str());
85  PCL_ERROR("The number of samples (%d) must not be greater than the number of "
86  "points (%zu)!\n",
87  nr_samples,
88  static_cast<std::size_t>(cloud.size()));
89  return;
90  }
91 
92  sample_indices.resize(nr_samples);
93  int temp_sample;
94 
95  // Draw random samples until n samples is reached
96  for (int i = 0; i < nr_samples; i++) {
97  // Select a random number
98  sample_indices[i] = getRandomIndex(static_cast<int>(cloud.size()) - i);
99 
100  // Run through list of numbers, starting at the lowest, to avoid duplicates
101  for (int j = 0; j < i; j++) {
102  // Move value up if it is higher than previous selections to ensure true
103  // randomness
104  if (sample_indices[i] >= sample_indices[j]) {
105  sample_indices[i]++;
106  }
107  else {
108  // The new number is lower, place it at the correct point and break for a sorted
109  // list
110  temp_sample = sample_indices[i];
111  for (int k = i; k > j; k--)
112  sample_indices[k] = sample_indices[k - 1];
113 
114  sample_indices[j] = temp_sample;
115  break;
116  }
117  }
118  }
119 }
120 
121 template <typename PointSource, typename PointTarget, typename FeatureT>
122 void
124  const pcl::Indices& sample_indices,
125  std::vector<pcl::Indices>& similar_features,
126  pcl::Indices& corresponding_indices)
127 {
128  // Allocate results
129  corresponding_indices.resize(sample_indices.size());
130  std::vector<float> nn_distances(k_correspondences_);
131 
132  // Loop over the sampled features
133  for (std::size_t i = 0; i < sample_indices.size(); ++i) {
134  // Current feature index
135  const auto& idx = sample_indices[i];
136 
137  // Find the k nearest feature neighbors to the sampled input feature if they are not
138  // in the cache already
139  if (similar_features[idx].empty())
140  feature_tree_->nearestKSearch(*input_features_,
141  idx,
142  k_correspondences_,
143  similar_features[idx],
144  nn_distances);
145 
146  // Select one at random and add it to corresponding_indices
147  if (k_correspondences_ == 1)
148  corresponding_indices[i] = similar_features[idx][0];
149  else
150  corresponding_indices[i] =
151  similar_features[idx][getRandomIndex(k_correspondences_)];
152  }
153 }
154 
155 template <typename PointSource, typename PointTarget, typename FeatureT>
156 void
158  PointCloudSource& output, const Eigen::Matrix4f& guess)
159 {
160  // Some sanity checks first
161  if (!input_features_) {
162  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
163  PCL_ERROR(
164  "No source features were given! Call setSourceFeatures before aligning.\n");
165  return;
166  }
167  if (!target_features_) {
168  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
169  PCL_ERROR(
170  "No target features were given! Call setTargetFeatures before aligning.\n");
171  return;
172  }
173 
174  if (input_->size() != input_features_->size()) {
175  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
176  PCL_ERROR("The source points and source feature points need to be in a one-to-one "
177  "relationship! Current input cloud sizes: %ld vs %ld.\n",
178  input_->size(),
179  input_features_->size());
180  return;
181  }
182 
183  if (target_->size() != target_features_->size()) {
184  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
185  PCL_ERROR("The target points and target feature points need to be in a one-to-one "
186  "relationship! Current input cloud sizes: %ld vs %ld.\n",
187  target_->size(),
188  target_features_->size());
189  return;
190  }
191 
192  if (inlier_fraction_ < 0.0f || inlier_fraction_ > 1.0f) {
193  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
194  PCL_ERROR("Illegal inlier fraction %f, must be in [0,1]!\n", inlier_fraction_);
195  return;
196  }
197 
198  const float similarity_threshold =
199  correspondence_rejector_poly_->getSimilarityThreshold();
200  if (similarity_threshold < 0.0f || similarity_threshold >= 1.0f) {
201  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
202  PCL_ERROR("Illegal prerejection similarity threshold %f, must be in [0,1[!\n",
203  similarity_threshold);
204  return;
205  }
206 
207  if (k_correspondences_ <= 0) {
208  PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
209  PCL_ERROR("Illegal correspondence randomness %d, must be > 0!\n",
210  k_correspondences_);
211  return;
212  }
213 
214  // Initialize prerejector (similarity threshold already set to default value in
215  // constructor)
216  correspondence_rejector_poly_->setInputSource(input_);
217  correspondence_rejector_poly_->setInputTarget(target_);
218  correspondence_rejector_poly_->setCardinality(nr_samples_);
219  int num_rejections = 0; // For debugging
220 
221  // Initialize results
222  final_transformation_ = guess;
223  inliers_.clear();
224  float lowest_error = std::numeric_limits<float>::max();
225  converged_ = false;
226 
227  // Temporaries
228  pcl::Indices inliers;
229  float inlier_fraction;
230  float error;
231 
232  // If guess is not the Identity matrix we check it
233  if (!guess.isApprox(Eigen::Matrix4f::Identity(), 0.01f)) {
234  getFitness(inliers, error);
235  inlier_fraction =
236  static_cast<float>(inliers.size()) / static_cast<float>(input_->size());
237 
238  if (inlier_fraction >= inlier_fraction_ && error < lowest_error) {
239  inliers_ = inliers;
240  lowest_error = error;
241  converged_ = true;
242  }
243  }
244 
245  // Feature correspondence cache
246  std::vector<pcl::Indices> similar_features(input_->size());
247 
248  // Start
249  for (int i = 0; i < max_iterations_; ++i) {
250  // Temporary containers
251  pcl::Indices sample_indices;
252  pcl::Indices corresponding_indices;
253 
254  // Draw nr_samples_ random samples
255  selectSamples(*input_, nr_samples_, sample_indices);
256 
257  // Find corresponding features in the target cloud
258  findSimilarFeatures(sample_indices, similar_features, corresponding_indices);
259 
260  // Apply prerejection
261  if (!correspondence_rejector_poly_->thresholdPolygon(sample_indices,
262  corresponding_indices)) {
263  ++num_rejections;
264  continue;
265  }
266 
267  // Estimate the transform from the correspondences, write to transformation_
268  transformation_estimation_->estimateRigidTransformation(
269  *input_, sample_indices, *target_, corresponding_indices, transformation_);
270 
271  // Take a backup of previous result
272  const Matrix4 final_transformation_prev = final_transformation_;
273 
274  // Set final result to current transformation
275  final_transformation_ = transformation_;
276 
277  // Transform the input and compute the error (uses input_ and final_transformation_)
278  getFitness(inliers, error);
279 
280  // Restore previous result
281  final_transformation_ = final_transformation_prev;
282 
283  // If the new fit is better, update results
284  inlier_fraction =
285  static_cast<float>(inliers.size()) / static_cast<float>(input_->size());
286 
287  // Update result if pose hypothesis is better
288  if (inlier_fraction >= inlier_fraction_ && error < lowest_error) {
289  inliers_ = inliers;
290  lowest_error = error;
291  converged_ = true;
292  final_transformation_ = transformation_;
293  }
294  }
295 
296  // Apply the final transformation
297  if (converged_)
298  transformPointCloud(*input_, output, final_transformation_);
299 
300  // Debug output
301  PCL_DEBUG("[pcl::%s::computeTransformation] Rejected %i out of %i generated pose "
302  "hypotheses.\n",
303  getClassName().c_str(),
304  num_rejections,
305  max_iterations_);
306 }
307 
308 template <typename PointSource, typename PointTarget, typename FeatureT>
309 void
311  pcl::Indices& inliers, float& fitness_score)
312 {
313  // Initialize variables
314  inliers.clear();
315  inliers.reserve(input_->size());
316  fitness_score = 0.0f;
317 
318  // Use squared distance for comparison with NN search results
319  const float max_range = corr_dist_threshold_ * corr_dist_threshold_;
320 
321  // Transform the input dataset using the final transformation
322  PointCloudSource input_transformed;
323  input_transformed.resize(input_->size());
324  transformPointCloud(*input_, input_transformed, final_transformation_);
325 
326  // For each point in the source dataset
327  for (std::size_t i = 0; i < input_transformed.size(); ++i) {
328  // Find its nearest neighbor in the target
329  pcl::Indices nn_indices(1);
330  std::vector<float> nn_dists(1);
331  tree_->nearestKSearch(input_transformed[i], 1, nn_indices, nn_dists);
332 
333  // Check if point is an inlier
334  if (nn_dists[0] < max_range) {
335  // Update inliers
336  inliers.push_back(i);
337 
338  // Update fitness score
339  fitness_score += nn_dists[0];
340  }
341  }
342 
343  // Calculate MSE
344  if (!inliers.empty())
345  fitness_score /= static_cast<float>(inliers.size());
346  else
347  fitness_score = std::numeric_limits<float>::max();
348 }
349 
350 } // namespace pcl
351 
352 #endif
void resize(std::size_t count)
Resizes the container to contain count elements.
Definition: point_cloud.h:463
std::size_t size() const
Definition: point_cloud.h:444
Eigen::Matrix< Scalar, 4, 4 > Matrix4
Definition: registration.h:59
void setTargetFeatures(const FeatureCloudConstPtr &features)
Provide a boost shared pointer to the target point cloud's feature descriptors.
void computeTransformation(PointCloudSource &output, const Eigen::Matrix4f &guess) override
Rigid transformation computation method.
typename FeatureCloud::ConstPtr FeatureCloudConstPtr
void findSimilarFeatures(const pcl::Indices &sample_indices, std::vector< pcl::Indices > &similar_features, pcl::Indices &corresponding_indices)
For each of the sample points, find a list of points in the target cloud whose features are similar t...
void setSourceFeatures(const FeatureCloudConstPtr &features)
Provide a boost shared pointer to the source point cloud's feature descriptors.
void selectSamples(const PointCloudSource &cloud, int nr_samples, pcl::Indices &sample_indices)
Select nr_samples sample points from cloud while making sure that their pairwise distances are greate...
void getFitness(pcl::Indices &inliers, float &fitness_score)
Obtain the fitness of a transformation The following metrics are calculated, based on final_transform...
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
@ many_knn_search
The search method will mainly be used for nearestKSearch where k is larger than 1.
IndicesAllocator<> Indices
Type used for indices in PCL.
Definition: types.h:133