Point Cloud Library (PCL) 1.15.1-dev
Loading...
Searching...
No Matches
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#include <algorithm> // for std::remove
47
48namespace pcl {
49
50template <typename PointSource, typename PointTarget, typename FeatureT>
51void
53 const FeatureCloudConstPtr& features)
54{
55 if (features == nullptr || features->empty()) {
56 PCL_ERROR(
57 "[pcl::%s::setSourceFeatures] Invalid or empty point cloud dataset given!\n",
58 getClassName().c_str());
59 return;
60 }
61 input_features_ = features;
62}
63
64template <typename PointSource, typename PointTarget, typename FeatureT>
65void
67 const FeatureCloudConstPtr& features)
68{
69 if (features == nullptr || features->empty()) {
70 PCL_ERROR(
71 "[pcl::%s::setTargetFeatures] Invalid or empty point cloud dataset given!\n",
72 getClassName().c_str());
73 return;
74 }
75 target_features_ = features;
76 feature_tree_.reset(pcl::search::autoSelectMethod<FeatureT>(
77 target_features_, false, pcl::search::Purpose::many_knn_search));
78}
79
80template <typename PointSource, typename PointTarget, typename FeatureT>
81void
83 const PointCloudSource& cloud, int nr_samples, pcl::Indices& sample_indices)
84{
85 if (nr_samples > static_cast<int>(cloud.size())) {
86 PCL_ERROR("[pcl::%s::selectSamples] ", getClassName().c_str());
87 PCL_ERROR("The number of samples (%d) must not be greater than the number of "
88 "points (%zu)!\n",
89 nr_samples,
90 static_cast<std::size_t>(cloud.size()));
91 return;
92 }
93
94 sample_indices.resize(nr_samples);
95 int temp_sample;
96
97 // Draw random samples until n samples is reached
98 for (int i = 0; i < nr_samples; i++) {
99 // Select a random number
100 sample_indices[i] = getRandomIndex(static_cast<int>(cloud.size()) - i);
101
102 // Run through list of numbers, starting at the lowest, to avoid duplicates
103 for (int j = 0; j < i; j++) {
104 // Move value up if it is higher than previous selections to ensure true
105 // randomness
106 if (sample_indices[i] >= sample_indices[j]) {
107 sample_indices[i]++;
108 }
109 else {
110 // The new number is lower, place it at the correct point and break for a sorted
111 // list
112 temp_sample = sample_indices[i];
113 for (int k = i; k > j; k--)
114 sample_indices[k] = sample_indices[k - 1];
115
116 sample_indices[j] = temp_sample;
117 break;
118 }
119 }
120 }
121}
122
123template <typename PointSource, typename PointTarget, typename FeatureT>
124void
126 const pcl::Indices& sample_indices,
127 std::vector<pcl::Indices>& similar_features,
128 pcl::Indices& corresponding_indices)
129{
130 // Allocate results
131 corresponding_indices.resize(sample_indices.size());
132 std::vector<float> nn_distances(k_correspondences_);
133
134 // Loop over the sampled features
135 for (std::size_t i = 0; i < sample_indices.size(); ++i) {
136 // Current feature index
137 const auto& idx = sample_indices[i];
138
139 // Find the k nearest feature neighbors to the sampled input feature if they are not
140 // in the cache already
141 if (similar_features[idx].empty())
142 feature_tree_->nearestKSearch(*input_features_,
143 idx,
144 k_correspondences_,
145 similar_features[idx],
146 nn_distances);
147
148 // Select one at random and add it to corresponding_indices
149 if (k_correspondences_ == 1)
150 corresponding_indices[i] = similar_features[idx][0];
151 else
152 corresponding_indices[i] =
153 similar_features[idx][getRandomIndex(k_correspondences_)];
154 }
155}
156
157template <typename PointSource, typename PointTarget, typename FeatureT>
158void
160 PointCloudSource& output, const Eigen::Matrix4f& guess)
161{
162 // Some sanity checks first
163 if (!input_features_) {
164 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
165 PCL_ERROR(
166 "No source features were given! Call setSourceFeatures before aligning.\n");
167 return;
168 }
169 if (!target_features_) {
170 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
171 PCL_ERROR(
172 "No target features were given! Call setTargetFeatures before aligning.\n");
173 return;
174 }
175
176 if (input_->size() != input_features_->size()) {
177 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
178 PCL_ERROR("The source points and source feature points need to be in a one-to-one "
179 "relationship! Current input cloud sizes: %ld vs %ld.\n",
180 input_->size(),
181 input_features_->size());
182 return;
183 }
184
185 if (target_->size() != target_features_->size()) {
186 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
187 PCL_ERROR("The target points and target feature points need to be in a one-to-one "
188 "relationship! Current input cloud sizes: %ld vs %ld.\n",
189 target_->size(),
190 target_features_->size());
191 return;
192 }
193
194 if (inlier_fraction_ < 0.0f || inlier_fraction_ > 1.0f) {
195 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
196 PCL_ERROR("Illegal inlier fraction %f, must be in [0,1]!\n", inlier_fraction_);
197 return;
198 }
199
200 const float similarity_threshold =
201 correspondence_rejector_poly_->getSimilarityThreshold();
202 if (similarity_threshold < 0.0f || similarity_threshold >= 1.0f) {
203 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
204 PCL_ERROR("Illegal prerejection similarity threshold %f, must be in [0,1[!\n",
205 similarity_threshold);
206 return;
207 }
208
209 if (k_correspondences_ <= 0) {
210 PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str());
211 PCL_ERROR("Illegal correspondence randomness %d, must be > 0!\n",
212 k_correspondences_);
213 return;
214 }
215
216 // Initialize prerejector (similarity threshold already set to default value in
217 // constructor)
218 correspondence_rejector_poly_->setInputSource(input_);
219 correspondence_rejector_poly_->setInputTarget(target_);
220 correspondence_rejector_poly_->setCardinality(nr_samples_);
221 int num_rejections = 0; // For debugging
222
223 // Initialize results
224 final_transformation_ = guess;
225 inliers_.clear();
226 float lowest_error = std::numeric_limits<float>::max();
227 converged_ = false;
228
229 // Temporaries
230 pcl::Indices inliers;
231 float inlier_fraction;
232 float error;
233
234 // If guess is not the Identity matrix we check it
235 if (!guess.isApprox(Eigen::Matrix4f::Identity(), 0.01f)) {
236 getFitness(inliers, error);
237 inlier_fraction =
238 static_cast<float>(inliers.size()) / static_cast<float>(input_->size());
239
240 if (inlier_fraction >= inlier_fraction_ && error < lowest_error) {
241 inliers_ = inliers;
242 lowest_error = error;
243 converged_ = true;
244 }
245 }
246
247 // Feature correspondence cache
248 std::vector<pcl::Indices> similar_features(input_->size());
249 // Either we pre-compute all similar features here (parallel but some results may not
250 // be used), or we compute them on-demand in findSimilarFeatures(). We try to estimate
251 // which is faster, based on the probability of each search result being used during
252 // the registration:
253 if ((1.0 - std::pow(1.0 - nr_samples_ / static_cast<double>(input_->size()),
254 max_iterations_)) *
255 num_threads_ >=
256 1.0) {
257 std::vector<float> nn_distances(k_correspondences_);
258#pragma omp parallel for default(none) shared(similar_features) \
259 firstprivate(nn_distances) num_threads(num_threads_) schedule(dynamic, 64)
260 for (std::ptrdiff_t i = 0; i < static_cast<std::ptrdiff_t>(input_->size()); ++i) {
261 nn_distances.clear();
262 feature_tree_->nearestKSearch(
263 (*input_features_)[i], k_correspondences_, similar_features[i], nn_distances);
264 }
265 }
266
267 // Temporary containers
268 pcl::Indices sample_indices;
269 pcl::Indices corresponding_indices;
270
271 // Start
272 for (int i = 0; i < max_iterations_; ++i) {
273
274 // Draw nr_samples_ random samples
275 selectSamples(*input_, nr_samples_, sample_indices);
276
277 // Find corresponding features in the target cloud
278 findSimilarFeatures(sample_indices, similar_features, corresponding_indices);
279
280 // Apply prerejection
281 if (!correspondence_rejector_poly_->thresholdPolygon(sample_indices,
282 corresponding_indices)) {
283 ++num_rejections;
284 continue;
285 }
286
287 // Estimate the transform from the correspondences, write to transformation_
288 transformation_estimation_->estimateRigidTransformation(
289 *input_, sample_indices, *target_, corresponding_indices, transformation_);
290
291 // Take a backup of previous result
292 const Matrix4 final_transformation_prev = final_transformation_;
293
294 // Set final result to current transformation
295 final_transformation_ = transformation_;
296
297 // Transform the input and compute the error (uses input_ and final_transformation_)
298 getFitness(inliers, error);
299
300 // Restore previous result
301 final_transformation_ = final_transformation_prev;
302
303 // If the new fit is better, update results
304 inlier_fraction =
305 static_cast<float>(inliers.size()) / static_cast<float>(input_->size());
306
307 // Update result if pose hypothesis is better
308 if (inlier_fraction >= inlier_fraction_ && error < lowest_error) {
309 inliers_ = inliers;
310 lowest_error = error;
311 converged_ = true;
312 final_transformation_ = transformation_;
313 }
314 }
315
316 // Apply the final transformation
317 if (converged_)
318 transformPointCloud(*input_, output, final_transformation_);
319
320 // Debug output
321 PCL_DEBUG("[pcl::%s::computeTransformation] Rejected %i out of %i generated pose "
322 "hypotheses.\n",
323 getClassName().c_str(),
324 num_rejections,
325 max_iterations_);
326}
327
328template <typename PointSource, typename PointTarget, typename FeatureT>
329void
331 pcl::Indices& inliers, float& fitness_score)
332{
333 // Initialize variables
334 inliers.clear();
335 inliers.resize(input_->size());
336 float fitness_score_tmp = 0.0f;
337
338 // Use squared distance for comparison with NN search results
339 const float max_range = corr_dist_threshold_ * corr_dist_threshold_;
340
341 // Transform the input dataset using the final transformation
342 PointCloudSource input_transformed;
343 input_transformed.resize(input_->size());
344 transformPointCloud(*input_, input_transformed, final_transformation_);
345
346 // For each point in the source dataset
347#pragma omp parallel for default(none) shared(input_transformed, max_range, inliers) \
348 reduction(+ : fitness_score_tmp) num_threads(num_threads_) schedule(dynamic, 64)
349 for (std::ptrdiff_t i = 0; i < static_cast<std::ptrdiff_t>(input_transformed.size());
350 ++i) {
351 // Find its nearest neighbor in the target
352 pcl::Indices nn_indices(1);
353 std::vector<float> nn_dists(1);
354 tree_->nearestKSearch(input_transformed[i], 1, nn_indices, nn_dists);
355
356 // Check if point is an inlier
357 if (nn_dists[0] < max_range) {
358 inliers[i] = i;
359
360 // Update fitness score
361 fitness_score_tmp += nn_dists[0];
362 }
363 else {
364 inliers[i] = UNAVAILABLE;
365 }
366 }
367 // finally we remove all that are not inliers:
368 inliers.erase(std::remove(inliers.begin(), inliers.end(), UNAVAILABLE),
369 inliers.end());
370
371 // Calculate MSE
372 if (!inliers.empty())
373 fitness_score = fitness_score_tmp / static_cast<float>(inliers.size());
374 else
375 fitness_score = std::numeric_limits<float>::max();
376}
377
378} // namespace pcl
379
380#endif
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 Registration< PointSource, PointTarget >::PointCloudSource PointCloudSource
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...
typename Registration< PointSource, PointTarget >::Matrix4 Matrix4
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.
@ many_knn_search
The search method will mainly be used for nearestKSearch where k is larger than 1.
static constexpr index_t UNAVAILABLE
Definition pcl_base.h:62
IndicesAllocator<> Indices
Type used for indices in PCL.
Definition types.h:133