/* * Software License Agreement (BSD License) * * Point Cloud Library (PCL) - www.pointclouds.org * Copyright (c) 2009-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. * */ #ifndef PCL_REGISTRATION_IMPL_JOINT_ICP_HPP_ #define PCL_REGISTRATION_IMPL_JOINT_ICP_HPP_ #include #include namespace pcl { template void JointIterativeClosestPoint::computeTransformation( PointCloudSource& output, const Matrix4& guess) { // Point clouds containing the correspondences of each point in if (sources_.size() != targets_.size() || sources_.empty() || targets_.empty()) { PCL_ERROR("[pcl::%s::computeTransformation] Must set InputSources and InputTargets " "to the same, nonzero size!\n", getClassName().c_str()); return; } bool manual_correspondence_estimations_set = true; if (correspondence_estimations_.empty()) { manual_correspondence_estimations_set = false; correspondence_estimations_.resize(sources_.size()); for (std::size_t i = 0; i < sources_.size(); i++) { correspondence_estimations_[i] = correspondence_estimation_->clone(); KdTreeReciprocalPtr src_tree(new KdTreeReciprocal); KdTreePtr tgt_tree(new KdTree); correspondence_estimations_[i]->setSearchMethodTarget(tgt_tree); correspondence_estimations_[i]->setSearchMethodSource(src_tree); } } if (correspondence_estimations_.size() != sources_.size()) { PCL_ERROR("[pcl::%s::computeTransform] Must set CorrespondenceEstimations to be " "the same size as the joint\n", getClassName().c_str()); return; } std::vector inputs_transformed(sources_.size()); for (std::size_t i = 0; i < sources_.size(); i++) { inputs_transformed[i].reset(new PointCloudSource); } nr_iterations_ = 0; converged_ = false; // Initialise final transformation to the guessed one final_transformation_ = guess; // Make a combined transformed input and output std::vector input_offsets(sources_.size()); std::vector target_offsets(targets_.size()); PointCloudSourcePtr sources_combined(new PointCloudSource); PointCloudSourcePtr inputs_transformed_combined(new PointCloudSource); PointCloudTargetPtr targets_combined(new PointCloudTarget); std::size_t input_offset = 0; std::size_t target_offset = 0; for (std::size_t i = 0; i < sources_.size(); i++) { // If the guessed transformation is non identity if (guess != Matrix4::Identity()) { // Apply guessed transformation prior to search for neighbours this->transformCloud(*sources_[i], *inputs_transformed[i], guess); } else { *inputs_transformed[i] = *sources_[i]; } *sources_combined += *sources_[i]; *inputs_transformed_combined += *inputs_transformed[i]; *targets_combined += *targets_[i]; input_offsets[i] = input_offset; target_offsets[i] = target_offset; input_offset += inputs_transformed[i]->size(); target_offset += targets_[i]->size(); } transformation_ = Matrix4::Identity(); // Make blobs if necessary determineRequiredBlobData(); // Pass in the default target for the Correspondence Estimation/Rejection code for (std::size_t i = 0; i < sources_.size(); i++) { correspondence_estimations_[i]->setInputTarget(targets_[i]); if (correspondence_estimations_[i]->requiresTargetNormals()) { PCLPointCloud2::Ptr target_blob(new PCLPointCloud2); pcl::toPCLPointCloud2(*targets_[i], *target_blob); correspondence_estimations_[i]->setTargetNormals(target_blob); } } PCLPointCloud2::Ptr targets_combined_blob(new PCLPointCloud2); if (!correspondence_rejectors_.empty() && need_target_blob_) pcl::toPCLPointCloud2(*targets_combined, *targets_combined_blob); for (std::size_t i = 0; i < correspondence_rejectors_.size(); ++i) { registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i]; if (rej->requiresTargetPoints()) rej->setTargetPoints(targets_combined_blob); if (rej->requiresTargetNormals() && target_has_normals_) rej->setTargetNormals(targets_combined_blob); } convergence_criteria_->setMaximumIterations(max_iterations_); convergence_criteria_->setRelativeMSE(euclidean_fitness_epsilon_); convergence_criteria_->setTranslationThreshold(transformation_epsilon_); convergence_criteria_->setRotationThreshold(1.0 - transformation_epsilon_); // Repeat until convergence std::vector partial_correspondences_(sources_.size()); for (std::size_t i = 0; i < sources_.size(); i++) { partial_correspondences_[i].reset(new pcl::Correspondences); } do { // Save the previously estimated transformation previous_transformation_ = transformation_; // Set the source each iteration, to ensure the dirty flag is updated correspondences_->clear(); for (std::size_t i = 0; i < correspondence_estimations_.size(); i++) { correspondence_estimations_[i]->setInputSource(inputs_transformed[i]); // Get blob data if needed if (correspondence_estimations_[i]->requiresSourceNormals()) { PCLPointCloud2::Ptr input_transformed_blob(new PCLPointCloud2); toPCLPointCloud2(*inputs_transformed[i], *input_transformed_blob); correspondence_estimations_[i]->setSourceNormals(input_transformed_blob); } // Estimate correspondences on each cloud pair separately if (use_reciprocal_correspondence_) { correspondence_estimations_[i]->determineReciprocalCorrespondences( *partial_correspondences_[i], corr_dist_threshold_); } else { correspondence_estimations_[i]->determineCorrespondences( *partial_correspondences_[i], corr_dist_threshold_); } PCL_DEBUG("[pcl::%s::computeTransformation] Found %d partial correspondences for " "cloud [%d]\n", getClassName().c_str(), partial_correspondences_[i]->size(), i); for (std::size_t j = 0; j < partial_correspondences_[i]->size(); j++) { pcl::Correspondence corr = partial_correspondences_[i]->at(j); // Update the offsets to be for the combined clouds corr.index_query += input_offsets[i]; corr.index_match += target_offsets[i]; correspondences_->push_back(corr); } } PCL_DEBUG("[pcl::%s::computeTransformation] Total correspondences: %d\n", getClassName().c_str(), correspondences_->size()); PCLPointCloud2::Ptr inputs_transformed_combined_blob; if (need_source_blob_) { inputs_transformed_combined_blob.reset(new PCLPointCloud2); toPCLPointCloud2(*inputs_transformed_combined, *inputs_transformed_combined_blob); } CorrespondencesPtr temp_correspondences(new Correspondences(*correspondences_)); for (std::size_t i = 0; i < correspondence_rejectors_.size(); ++i) { PCL_DEBUG("Applying a correspondence rejector method: %s.\n", correspondence_rejectors_[i]->getClassName().c_str()); registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i]; PCL_DEBUG("Applying a correspondence rejector method: %s.\n", rej->getClassName().c_str()); if (rej->requiresSourcePoints()) rej->setSourcePoints(inputs_transformed_combined_blob); if (rej->requiresSourceNormals() && source_has_normals_) rej->setSourceNormals(inputs_transformed_combined_blob); rej->setInputCorrespondences(temp_correspondences); rej->getCorrespondences(*correspondences_); // Modify input for the next iteration if (i < correspondence_rejectors_.size() - 1) *temp_correspondences = *correspondences_; } // Check whether we have enough correspondences if (correspondences_->size() < min_number_correspondences_) { PCL_ERROR("[pcl::%s::computeTransformation] Not enough correspondences found. " "Relax your threshold parameters.\n", getClassName().c_str()); convergence_criteria_->setConvergenceState( pcl::registration::DefaultConvergenceCriteria< Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES); converged_ = false; break; } // Estimate the transform jointly, on a combined correspondence set transformation_estimation_->estimateRigidTransformation( *inputs_transformed_combined, *targets_combined, *correspondences_, transformation_); // Transform the combined data this->transformCloud( *inputs_transformed_combined, *inputs_transformed_combined, transformation_); // And all its components for (std::size_t i = 0; i < sources_.size(); i++) { this->transformCloud( *inputs_transformed[i], *inputs_transformed[i], transformation_); } // Obtain the final transformation final_transformation_ = transformation_ * final_transformation_; ++nr_iterations_; // Update the visualization of icp convergence // if (update_visualizer_ != 0) // update_visualizer_(output, source_indices_good, *target_, target_indices_good ); converged_ = static_cast((*convergence_criteria_)); } while (!converged_); PCL_DEBUG("Transformation " "is:\n\t%5f\t%5f\t%5f\t%5f\n\t%5f\t%5f\t%5f\t%5f\n\t%5f\t%5f\t%5f\t%5f\n\t%" "5f\t%5f\t%5f\t%5f\n", final_transformation_(0, 0), final_transformation_(0, 1), final_transformation_(0, 2), final_transformation_(0, 3), final_transformation_(1, 0), final_transformation_(1, 1), final_transformation_(1, 2), final_transformation_(1, 3), final_transformation_(2, 0), final_transformation_(2, 1), final_transformation_(2, 2), final_transformation_(2, 3), final_transformation_(3, 0), final_transformation_(3, 1), final_transformation_(3, 2), final_transformation_(3, 3)); // For fitness checks, etc, we'll use an aggregated cloud for now (should be // evaluating independently for correctness, but this requires propagating a few // virtual methods from Registration) IterativeClosestPoint::setInputSource( sources_combined); IterativeClosestPoint::setInputTarget( targets_combined); // If we automatically set the correspondence estimators, we should clear them now if (!manual_correspondence_estimations_set) { correspondence_estimations_.clear(); } // By definition, this method will return an empty cloud (for compliance with the ICP // API). We can figure out a better solution, if necessary. output = PointCloudSource(); } template void JointIterativeClosestPoint:: determineRequiredBlobData() { need_source_blob_ = false; need_target_blob_ = false; // Check estimators for (std::size_t i = 0; i < correspondence_estimations_.size(); i++) { CorrespondenceEstimationPtr& ce = correspondence_estimations_[i]; need_source_blob_ |= ce->requiresSourceNormals(); need_target_blob_ |= ce->requiresTargetNormals(); // Add warnings if necessary if (ce->requiresSourceNormals() && !source_has_normals_) { PCL_WARN("[pcl::%s::determineRequiredBlobData] Estimator expects source normals, " "but we can't provide them.\n", getClassName().c_str()); } if (ce->requiresTargetNormals() && !target_has_normals_) { PCL_WARN("[pcl::%s::determineRequiredBlobData] Estimator expects target normals, " "but we can't provide them.\n", getClassName().c_str()); } } // Check rejectors for (std::size_t i = 0; i < correspondence_rejectors_.size(); i++) { registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i]; need_source_blob_ |= rej->requiresSourcePoints(); need_source_blob_ |= rej->requiresSourceNormals(); need_target_blob_ |= rej->requiresTargetPoints(); need_target_blob_ |= rej->requiresTargetNormals(); if (rej->requiresSourceNormals() && !source_has_normals_) { PCL_WARN("[pcl::%s::determineRequiredBlobData] Rejector %s expects source " "normals, but we can't provide them.\n", getClassName().c_str(), rej->getClassName().c_str()); } if (rej->requiresTargetNormals() && !target_has_normals_) { PCL_WARN("[pcl::%s::determineRequiredBlobData] Rejector %s expects target " "normals, but we can't provide them.\n", getClassName().c_str(), rej->getClassName().c_str()); } } } } // namespace pcl #endif /* PCL_REGISTRATION_IMPL_JOINT_ICP_HPP_ */