#ifndef FAST_GICP_FAST_GICP_MP_IMPL_HPP #define FAST_GICP_FAST_GICP_MP_IMPL_HPP #include #include #include #include #include #include #include #include #include #include namespace fast_gicp { template FastGICPMultiPoints::FastGICPMultiPoints() { #ifdef _OPENMP num_threads_ = omp_get_max_threads(); #else num_threads_ = 1; #endif k_correspondences_ = 20; reg_name_ = "FastGICPMultiPoints"; max_iterations_ = 64; rotation_epsilon_ = 1e-5; transformation_epsilon_ = 1e-5; // corr_dist_threshold_ = 1.0; regularization_method_ = RegularizationMethod::PLANE; corr_dist_threshold_ = std::numeric_limits::max(); neighbor_search_radius_ = 0.5; } template FastGICPMultiPoints::~FastGICPMultiPoints() {} template void FastGICPMultiPoints::setRotationEpsilon(double eps) { rotation_epsilon_ = eps; } template void FastGICPMultiPoints::setNumThreads(int n) { num_threads_ = n; #ifdef _OPENMP if(n == 0) { num_threads_ = omp_get_max_threads(); } #endif } template void FastGICPMultiPoints::setCorrespondenceRandomness(int k) { k_correspondences_ = k; } template void FastGICPMultiPoints::setRegularizationMethod(RegularizationMethod method) { regularization_method_ = method; } template void FastGICPMultiPoints::setInputSource(const PointCloudSourceConstPtr& cloud) { pcl::Registration::setInputSource(cloud); calculate_covariances(*cloud, source_kdtree, source_covs); } template void FastGICPMultiPoints::setInputTarget(const PointCloudTargetConstPtr& cloud) { pcl::Registration::setInputTarget(cloud); calculate_covariances(cloud, target_kdtree, target_covs); } template void FastGICPMultiPoints::computeTransformation(PointCloudSource& output, const Matrix4& guess) { Eigen::Matrix x0; x0.head<3>() = Sophus::SO3f(guess.template block<3, 3>(0, 0)).log(); x0.tail<3>() = guess.template block<3, 1>(0, 3); // if(x0.head<3>().norm() < 1e-2) { // x0.head<3>() = (Eigen::Vector3f::Random()).normalized() * 1e-2; // } converged_ = false; GaussNewton solver; for(int i = 0; i < max_iterations_; i++) { nr_iterations_ = i; update_correspondences(x0); Eigen::MatrixXf J; Eigen::VectorXf loss = loss_ls(x0, &J); Eigen::Matrix delta = solver.delta(loss.cast(), J.cast()).cast(); x0.head<3>() = (Sophus::SO3f::exp(-delta.head<3>()) * Sophus::SO3f::exp(x0.head<3>())).log(); x0.tail<3>() -= delta.tail<3>(); if(is_converged(delta)) { converged_ = true; break; } } final_transformation_.setIdentity(); final_transformation_.template block<3, 3>(0, 0) = Sophus::SO3f::exp(x0.head<3>()).matrix(); final_transformation_.template block<3, 1>(0, 3) = x0.tail<3>(); pcl::transformPointCloud(*input_, output, final_transformation_); } template bool FastGICPMultiPoints::is_converged(const Eigen::Matrix& delta) const { double accum = 0.0; Eigen::Matrix3f R = Sophus::SO3f::exp(delta.head<3>()).matrix() - Eigen::Matrix3f::Identity(); Eigen::Vector3f t = delta.tail<3>(); Eigen::Matrix3f r_delta = 1.0 / rotation_epsilon_ * R.array().abs(); Eigen::Vector3f t_delta = 1.0 / transformation_epsilon_ * t.array().abs(); return std::max(r_delta.maxCoeff(), t_delta.maxCoeff()) < 1; } template void FastGICPMultiPoints::update_correspondences(const Eigen::Matrix& x) { Eigen::Matrix4f trans = Eigen::Matrix4f::Identity(); trans.block<3, 3>(0, 0) = Sophus::SO3f::exp(x.head<3>()).matrix(); trans.block<3, 1>(0, 3) = x.tail<3>(); correspondences.resize(input_->size()); sq_distances.resize(input_->size()); #pragma omp parallel for num_threads(num_threads_) for(int i = 0; i < input_->size(); i++) { PointTarget pt; pt.getVector4fMap() = trans * input_->at(i).getVector4fMap(); std::vector k_indices; std::vector k_sq_dists; target_kdtree.radiusSearch(pt, neighbor_search_radius_, k_indices, k_sq_dists); if(k_indices.empty()) { // target_kdtree.nearestKSearch(pt, 1, k_indices, k_sq_dists); } correspondences[i] = k_indices; sq_distances[i] = k_sq_dists; } } template Eigen::VectorXf FastGICPMultiPoints::loss_ls(const Eigen::Matrix& x, Eigen::MatrixXf* J) const { Eigen::Matrix4f trans = Eigen::Matrix4f::Identity(); trans.block<3, 3>(0, 0) = Sophus::SO3f::exp(x.head<3>()).matrix(); trans.block<3, 1>(0, 3) = x.tail<3>(); std::vector> losses(input_->size()); // use row-major arrangement for ease of repacking std::vector, Eigen::aligned_allocator>> Js(input_->size()); std::atomic_int count(0); #pragma omp parallel for num_threads(num_threads_) for(int i = 0; i < correspondences.size(); i++) { int source_index = i; const auto& mean_A = input_->at(source_index).getVector4fMap(); const auto& cov_A = source_covs[source_index]; const auto& k_indices = correspondences[i]; const auto& k_sq_dists = sq_distances[i]; if(k_indices.empty()) { continue; } double sum_w = 0.0; Eigen::Vector4d sum_mean_B = Eigen::Vector4d::Zero(); Eigen::Matrix4d sum_cov_B = Eigen::Matrix4d::Zero(); for(int j = 0; j < k_indices.size(); j++) { double w = 1 - std::sqrt(k_sq_dists[j]) / neighbor_search_radius_; w = std::max(1e-3, std::min(1.0, w)); sum_w += w; int target_index = k_indices[j]; sum_mean_B += w * target_->at(target_index).getVector4fMap().template cast(); sum_cov_B += w * target_covs[target_index].template cast(); } Eigen::Vector4f mean_B = (sum_mean_B / sum_w).template cast(); Eigen::Matrix4f cov_B = (sum_cov_B / sum_w).template cast(); Eigen::Vector4f transed_mean_A = trans * mean_A; Eigen::Vector4f d = mean_B - transed_mean_A; Eigen::Matrix4f RCR = cov_B + trans * cov_A * trans.transpose(); RCR(3, 3) = 1; Eigen::Matrix4f RCR_inv = RCR.inverse(); Eigen::Vector4f RCRd = RCR_inv * d; Eigen::Matrix dtdx0 = Eigen::Matrix::Zero(); dtdx0.block<3, 3>(0, 0) = skew(transed_mean_A.head<3>()); dtdx0.block<3, 3>(0, 3) = -Eigen::Matrix3f::Identity(); Eigen::Matrix jlossexp = RCR_inv * dtdx0; int n = count++; losses[n] = RCRd.head<3>(); Js[n] = jlossexp.block<3, 6>(0, 0); } int final_size = count; Eigen::VectorXf loss = Eigen::Map(losses.front().data(), final_size * 3); *J = Eigen::Map(Js.front().data(), 6, final_size * 3).transpose(); return loss; } template template bool FastGICPMultiPoints::calculate_covariances(const pcl::shared_ptr>& cloud, pcl::search::KdTree& kdtree, std::vector>& covariances) { kdtree.setInputCloud(cloud); covariances.resize(cloud->size()); #pragma omp parallel for num_threads(num_threads_) for(int i = 0; i < cloud->size(); i++) { std::vector k_indices; std::vector k_sq_distances; kdtree.nearestKSearch(cloud->at(i), k_correspondences_, k_indices, k_sq_distances); Eigen::Matrix data(4, k_correspondences_); for(int j = 0; j < k_indices.size(); j++) { data.col(j) = cloud->at(k_indices[j]).getVector4fMap(); } data.colwise() -= data.rowwise().mean().eval(); Eigen::Matrix4f cov = data * data.transpose(); if(regularization_method_ == RegularizationMethod::FROBENIUS) { double lambda = 1e-3; Eigen::Matrix3d C = cov.block<3, 3>(0, 0).cast() + lambda * Eigen::Matrix3d::Identity(); Eigen::Matrix3d C_inv = C.inverse(); covariances[i].setZero(); covariances[i].template block<3, 3>(0, 0) = (C_inv / C_inv.norm()).inverse().cast(); } else { Eigen::JacobiSVD svd(cov.block<3, 3>(0, 0), Eigen::ComputeFullU | Eigen::ComputeFullV); Eigen::Vector3f values; switch(regularization_method_) { default: std::cerr << "here must not be reached" << std::endl; abort(); case RegularizationMethod::PLANE: values = Eigen::Vector3f(1, 1, 1e-3); break; case RegularizationMethod::MIN_EIG: values = svd.singularValues().array().max(1e-3); break; case RegularizationMethod::NORMALIZED_MIN_EIG: values = svd.singularValues() / svd.singularValues().maxCoeff(); values = values.array().max(1e-3); break; } covariances[i].setZero(); covariances[i].template block<3, 3>(0, 0) = svd.matrixU() * values.asDiagonal() * svd.matrixV().transpose(); } } return true; } } // namespace fast_gicp #endif