/* * Software License Agreement (BSD License) * * Point Cloud Library (PCL) - www.pointclouds.org * Copyright (c) 2010-2012, Willow Garage, Inc. * Copyright (c) 2012-, Open Perception, Inc. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the copyright holder(s) nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $Id$ * */ #ifndef PCL_REGISTRATION_SAMPLE_CONSENSUS_PREREJECTIVE_HPP_ #define PCL_REGISTRATION_SAMPLE_CONSENSUS_PREREJECTIVE_HPP_ namespace pcl { template void SampleConsensusPrerejective::setSourceFeatures( const FeatureCloudConstPtr& features) { if (features == nullptr || features->empty()) { PCL_ERROR( "[pcl::%s::setSourceFeatures] Invalid or empty point cloud dataset given!\n", getClassName().c_str()); return; } input_features_ = features; } template void SampleConsensusPrerejective::setTargetFeatures( const FeatureCloudConstPtr& features) { if (features == nullptr || features->empty()) { PCL_ERROR( "[pcl::%s::setTargetFeatures] Invalid or empty point cloud dataset given!\n", getClassName().c_str()); return; } target_features_ = features; feature_tree_->setInputCloud(target_features_); } template void SampleConsensusPrerejective::selectSamples( const PointCloudSource& cloud, int nr_samples, pcl::Indices& sample_indices) { if (nr_samples > static_cast(cloud.size())) { PCL_ERROR("[pcl::%s::selectSamples] ", getClassName().c_str()); PCL_ERROR("The number of samples (%d) must not be greater than the number of " "points (%zu)!\n", nr_samples, static_cast(cloud.size())); return; } sample_indices.resize(nr_samples); int temp_sample; // Draw random samples until n samples is reached for (int i = 0; i < nr_samples; i++) { // Select a random number sample_indices[i] = getRandomIndex(static_cast(cloud.size()) - i); // Run trough list of numbers, starting at the lowest, to avoid duplicates for (int j = 0; j < i; j++) { // Move value up if it is higher than previous selections to ensure true // randomness if (sample_indices[i] >= sample_indices[j]) { sample_indices[i]++; } else { // The new number is lower, place it at the correct point and break for a sorted // list temp_sample = sample_indices[i]; for (int k = i; k > j; k--) sample_indices[k] = sample_indices[k - 1]; sample_indices[j] = temp_sample; break; } } } } template void SampleConsensusPrerejective::findSimilarFeatures( const pcl::Indices& sample_indices, std::vector& similar_features, pcl::Indices& corresponding_indices) { // Allocate results corresponding_indices.resize(sample_indices.size()); std::vector nn_distances(k_correspondences_); // Loop over the sampled features for (std::size_t i = 0; i < sample_indices.size(); ++i) { // Current feature index const auto& idx = sample_indices[i]; // Find the k nearest feature neighbors to the sampled input feature if they are not // in the cache already if (similar_features[idx].empty()) feature_tree_->nearestKSearch(*input_features_, idx, k_correspondences_, similar_features[idx], nn_distances); // Select one at random and add it to corresponding_indices if (k_correspondences_ == 1) corresponding_indices[i] = similar_features[idx][0]; else corresponding_indices[i] = similar_features[idx][getRandomIndex(k_correspondences_)]; } } template void SampleConsensusPrerejective::computeTransformation( PointCloudSource& output, const Eigen::Matrix4f& guess) { // Some sanity checks first if (!input_features_) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR( "No source features were given! Call setSourceFeatures before aligning.\n"); return; } if (!target_features_) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR( "No target features were given! Call setTargetFeatures before aligning.\n"); return; } if (input_->size() != input_features_->size()) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR("The source points and source feature points need to be in a one-to-one " "relationship! Current input cloud sizes: %ld vs %ld.\n", input_->size(), input_features_->size()); return; } if (target_->size() != target_features_->size()) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR("The target points and target feature points need to be in a one-to-one " "relationship! Current input cloud sizes: %ld vs %ld.\n", target_->size(), target_features_->size()); return; } if (inlier_fraction_ < 0.0f || inlier_fraction_ > 1.0f) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR("Illegal inlier fraction %f, must be in [0,1]!\n", inlier_fraction_); return; } const float similarity_threshold = correspondence_rejector_poly_->getSimilarityThreshold(); if (similarity_threshold < 0.0f || similarity_threshold >= 1.0f) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR("Illegal prerejection similarity threshold %f, must be in [0,1[!\n", similarity_threshold); return; } if (k_correspondences_ <= 0) { PCL_ERROR("[pcl::%s::computeTransformation] ", getClassName().c_str()); PCL_ERROR("Illegal correspondence randomness %d, must be > 0!\n", k_correspondences_); return; } // Initialize prerejector (similarity threshold already set to default value in // constructor) correspondence_rejector_poly_->setInputSource(input_); correspondence_rejector_poly_->setInputTarget(target_); correspondence_rejector_poly_->setCardinality(nr_samples_); int num_rejections = 0; // For debugging // Initialize results final_transformation_ = guess; inliers_.clear(); float lowest_error = std::numeric_limits::max(); converged_ = false; // Temporaries pcl::Indices inliers; float inlier_fraction; float error; // If guess is not the Identity matrix we check it if (!guess.isApprox(Eigen::Matrix4f::Identity(), 0.01f)) { getFitness(inliers, error); inlier_fraction = static_cast(inliers.size()) / static_cast(input_->size()); if (inlier_fraction >= inlier_fraction_ && error < lowest_error) { inliers_ = inliers; lowest_error = error; converged_ = true; } } // Feature correspondence cache std::vector similar_features(input_->size()); // Start for (int i = 0; i < max_iterations_; ++i) { // Temporary containers pcl::Indices sample_indices; pcl::Indices corresponding_indices; // Draw nr_samples_ random samples selectSamples(*input_, nr_samples_, sample_indices); // Find corresponding features in the target cloud findSimilarFeatures(sample_indices, similar_features, corresponding_indices); // Apply prerejection if (!correspondence_rejector_poly_->thresholdPolygon(sample_indices, corresponding_indices)) { ++num_rejections; continue; } // Estimate the transform from the correspondences, write to transformation_ transformation_estimation_->estimateRigidTransformation( *input_, sample_indices, *target_, corresponding_indices, transformation_); // Take a backup of previous result const Matrix4 final_transformation_prev = final_transformation_; // Set final result to current transformation final_transformation_ = transformation_; // Transform the input and compute the error (uses input_ and final_transformation_) getFitness(inliers, error); // Restore previous result final_transformation_ = final_transformation_prev; // If the new fit is better, update results inlier_fraction = static_cast(inliers.size()) / static_cast(input_->size()); // Update result if pose hypothesis is better if (inlier_fraction >= inlier_fraction_ && error < lowest_error) { inliers_ = inliers; lowest_error = error; converged_ = true; final_transformation_ = transformation_; } } // Apply the final transformation if (converged_) transformPointCloud(*input_, output, final_transformation_); // Debug output PCL_DEBUG("[pcl::%s::computeTransformation] Rejected %i out of %i generated pose " "hypotheses.\n", getClassName().c_str(), num_rejections, max_iterations_); } template void SampleConsensusPrerejective::getFitness( pcl::Indices& inliers, float& fitness_score) { // Initialize variables inliers.clear(); inliers.reserve(input_->size()); fitness_score = 0.0f; // Use squared distance for comparison with NN search results const float max_range = corr_dist_threshold_ * corr_dist_threshold_; // Transform the input dataset using the final transformation PointCloudSource input_transformed; input_transformed.resize(input_->size()); transformPointCloud(*input_, input_transformed, final_transformation_); // For each point in the source dataset for (std::size_t i = 0; i < input_transformed.size(); ++i) { // Find its nearest neighbor in the target pcl::Indices nn_indices(1); std::vector nn_dists(1); tree_->nearestKSearch(input_transformed[i], 1, nn_indices, nn_dists); // Check if point is an inlier if (nn_dists[0] < max_range) { // Update inliers inliers.push_back(i); // Update fitness score fitness_score += nn_dists[0]; } } // Calculate MSE if (!inliers.empty()) fitness_score /= static_cast(inliers.size()); else fitness_score = std::numeric_limits::max(); } } // namespace pcl #endif