当前位置: 首页 > 工具软件 > Non-rigid-ICP > 使用案例 >

PCL-ICP(IterativeClosestPoint)源码解析

笪涛
2023-12-01

1.PCL-ICP代码框架

话不多说,直接上代码

    #include <pcl/registration/icp.h>
    #include <pcl/point_types.h>
    pc_xyz::Ptr result = boost::make_shared<pc_xyz>();
    pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;

    icp.setMaxCorrespondenceDistance(max_correspondence_dist_);// 设置corr_dist_threshold_
    icp.setMaximumIterations(max_iterations_); //设置max_iterations_
    icp.setTransformationEpsilon(1e-6); //设置transformation_epsilon_
    icp.setEuclideanFitnessEpsilon(1e-2); // 设置euclidean_fitness_epsilon_
    icp.setInputSource(local_map_xyz);//设置input_ ,source_cloud_updated_ = true;
    icp.setInputTarget(global_map_xyz);//设置target_, target_cloud_updated_ = true;
    if (do_ransac_outlier_rejection_) {
        icp.setRANSACOutlierRejectionThreshold(ransac_outlier_rejection_threshold_);
        icp.setRANSACIterations(ransac_iterations_);
    }
    icp.align(*result);//主要匹配函数
    is_converged = icp.hasConverged();
    fitness = icp.getFitnessScore();
    correction = icp.getFinalTransformation();

2. pcl::IterativeClosestPoint类格

pcl::IterativeClosestPoint
pcl::Registration
pcl::PCLBase

3. pcl::Registration::align()详解

第1小节中的

  icp.align(*result);//主要匹配函数

对应的为pcl/registtation/impl/registation.hpp中的

template <typename PointSource, typename PointTarget, typename Scalar> inline void
pcl::Registration<PointSource, PointTarget, Scalar>::align (PointCloudSource &output)
{
align (output, Matrix4::Identity ());
}

其调用pcl::Registration<PointSource, PointTarget, Scalar>::align (PointCloudSource &output, const Matrix4& guess) 函数。
ICP的核心内容也就是该函数,接下来我们对其进行详细解释:

template <typename PointSource, typename PointTarget, typename Scalar> inline void
pcl::Registration<PointSource, PointTarget, Scalar>::align (PointCloudSource &output, const Matrix4& guess)
{
  if (!initCompute ()) //初始化kd树,及点云索引
    return;

  // Resize the output dataset
  if (output.points.size () != indices_->size ())
    output.points.resize (indices_->size ());
  // Copy the header
  output.header   = input_->header;
  // Check if the output will be computed for all points or only a subset
  if (indices_->size () != input_->points.size ())
  {
    output.width    = static_cast<uint32_t> (indices_->size ());
    output.height   = 1;
  }
  else
  {
    output.width    = static_cast<uint32_t> (input_->width);
    output.height   = input_->height;
  }
  output.is_dense = input_->is_dense;

  // Copy the point data to output
  for (size_t i = 0; i < indices_->size (); ++i)
    output.points[i] = input_->points[(*indices_)[i]];//将原始点云复制到output中

  // Set the internal point representation of choice unless otherwise noted
  if (point_representation_ && !force_no_recompute_) 
    tree_->setPointRepresentation (point_representation_);

  // Perform the actual transformation computation
  converged_ = false;
  final_transformation_ = transformation_ = previous_transformation_ = Matrix4::Identity ();

  // Right before we estimate the transformation, we set all the point.data[3] values to 1 to aid the rigid 
  // transformation
  for (size_t i = 0; i < indices_->size (); ++i)
    output.points[i].data[3] = 1.0;

  computeTransformation (output, guess);//求解旋转矩阵,动态绑定到pcl::IterativeClosestPoint::computeTransformation()

  deinitCompute ();// return (true); 直接返回true
}

3.1 pcl::Registration::initCompute()详解

initCompute用于初始化kd树,及点云索引

template <typename PointSource, typename PointTarget, typename Scalar>
bool
Registration<PointSource, PointTarget, Scalar>::initCompute()
{
  if (!target_) {
    PCL_ERROR("[pcl::registration::%s::compute] No input target dataset was given!\n",
              getClassName().c_str());
    return (false);
  }

  // Only update target kd-tree if a new target cloud was set
  if (target_cloud_updated_ && !force_no_recompute_) {//force_no_recompute_默认初始化为false,因此第一次运行时,进入该if判断
    tree_->setInputCloud(target_);//用目标点云初始化kd树
    target_cloud_updated_ = false;
  }

  // Update the correspondence estimation
  if (correspondence_estimation_) {
    correspondence_estimation_->setSearchMethodTarget(tree_, force_no_recompute_);//初始化correspondence_estimation_,包括    tree_ = tree;    force_no_recompute_ = force_no_recompute;    target_cloud_updated_ = true;
        correspondence_estimation_->setSearchMethodSource(tree_reciprocal_,
                                                      force_no_recompute_reciprocal_);
  }

  // Note: we /cannot/ update the search method on all correspondence rejectors, because
  // we know nothing about them. If they should be cached, they must be cached
  // individually.

  return (PCLBase<PointSource>::initCompute());//初始化与input_的size对应的indices_
}

3.2 pcl::IterativeClosestPoint::computeTransformation()详解

3.2.1 收敛状态简介

        enum ConvergenceState
        {
          CONVERGENCE_CRITERIA_NOT_CONVERGED,//默认值,converged_ = false;
          CONVERGENCE_CRITERIA_ITERATIONS,//迭代结果为达到迭代次数,此时若设置failure_after_max_iter_ = true,就会导致converged_ = false;默认状态是failure_after_max_iter_ = false,因此该情况下超过迭代次数会判断converged_ = true
          CONVERGENCE_CRITERIA_TRANSFORM,//平移距离的平方和满足transformation_epsilon_要求,则判断CONVERGENCE_CRITERIA_TRANSFORM,  converged_ = true
          CONVERGENCE_CRITERIA_ABS_MSE,//两次迭代的绝对距离差满足收敛要求,converged_ = true
          CONVERGENCE_CRITERIA_REL_MSE,//两次迭代的相对距离差满足收敛要求,converged_ = true
          CONVERGENCE_CRITERIA_NO_CORRESPONDENCES//迭代结果为对应点对太少,converged_ = false;
        };

3.2.2 computeTransformation代码解析

pcl::IterativeClosestPoint::computeTransformation() 位于pcl/registration/impl/icp.hpp文件中:

template <typename PointSource, typename PointTarget, typename Scalar>
void
IterativeClosestPoint<PointSource, PointTarget, Scalar>::computeTransformation(
    PointCloudSource& output, const Matrix4& guess)
{
  // Point cloud containing the correspondences of each point in <input, indices>
  PointCloudSourcePtr input_transformed(new PointCloudSource);

  nr_iterations_ = 0;
  converged_ = false;

  // Initialise final transformation to the guessed one
  final_transformation_ = guess;

  // If the guessed transformation is non identity
  if (guess != Matrix4::Identity()) {
    input_transformed->resize(input_->size());
    // Apply guessed transformation prior to search for neighbours
    transformCloud(*input_, *input_transformed, guess);
  }
  else
    *input_transformed = *input_;

  transformation_ = Matrix4::Identity();

  // Make blobs if necessary
  determineRequiredBlobData();// 未指定correspondence_rejectors_ 以及点云都没有法向量的话,need_source_blob_以及need_target_blob_ 都是false,即不要求处理“二进制长对象(blob)”
  PCLPointCloud2::Ptr target_blob(new PCLPointCloud2);
  if (need_target_blob_)
    pcl::toPCLPointCloud2(*target_, *target_blob);

  // Pass in the default target for the Correspondence Estimation/Rejection code
  correspondence_estimation_->setInputTarget(target_); //设置CorrespondenceEstimationBase中的target_ ,target_cloud_updated_ = true;
  if (correspondence_estimation_->requiresTargetNormals())
    correspondence_estimation_->setTargetNormals(target_blob);
  // Correspondence Rejectors need a binary blob
  for (std::size_t i = 0; i < correspondence_rejectors_.size(); ++i) {
    registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i];
    if (rej->requiresTargetPoints())
      rej->setTargetPoints(target_blob);
    if (rej->requiresTargetNormals() && target_has_normals_)
      rej->setTargetNormals(target_blob);
  }

  convergence_criteria_->setMaximumIterations(max_iterations_);//设置DefaultConvergenceCriteria 中的max_iterations_
  convergence_criteria_->setRelativeMSE(euclidean_fitness_epsilon_);//设置DefaultConvergenceCriteria 中的mse_threshold_relative_
  convergence_criteria_->setTranslationThreshold(transformation_epsilon_);//设置DefaultConvergenceCriteria 中的translation_threshold_
  if (transformation_rotation_epsilon_ > 0)
    convergence_criteria_->setRotationThreshold(transformation_rotation_epsilon_);
  else
    convergence_criteria_->setRotationThreshold(1.0 - transformation_epsilon_);//设置DefaultConvergenceCriteria 中的rotation_threshold_

  // Repeat until convergence
  //以下进入icp迭代解算
  do {
    // Get blob data if needed
    PCLPointCloud2::Ptr input_transformed_blob;
    if (need_source_blob_) {
      input_transformed_blob.reset(new PCLPointCloud2);
      toPCLPointCloud2(*input_transformed, *input_transformed_blob);
    }
    // Save the previously estimated transformation
    previous_transformation_ = transformation_;

    // Set the source each iteration, to ensure the dirty flag is updated
    correspondence_estimation_->setInputSource(input_transformed);
    if (correspondence_estimation_->requiresSourceNormals())
      correspondence_estimation_->setSourceNormals(input_transformed_blob);
    // Estimate correspondences
    if (use_reciprocal_correspondence_)
      correspondence_estimation_->determineReciprocalCorrespondences(
          *correspondences_, corr_dist_threshold_);
    else
      correspondence_estimation_->determineCorrespondences(*correspondences_,
                                                           corr_dist_threshold_);//基于kd树最近邻查找最近点。

    // if (correspondence_rejectors_.empty ())
    CorrespondencesPtr temp_correspondences(new Correspondences(*correspondences_));
    for (std::size_t i = 0; i < correspondence_rejectors_.size(); ++i) {
      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(input_transformed_blob);
      if (rej->requiresSourceNormals() && source_has_normals_)
        rej->setSourceNormals(input_transformed_blob);
      rej->setInputCorrespondences(temp_correspondences);
      rej->getCorrespondences(*correspondences_);
      // Modify input for the next iteration
      if (i < correspondence_rejectors_.size() - 1)
        *temp_correspondences = *correspondences_;
    }

    std::size_t cnt = correspondences_->size();
    // Check whether we have enough correspondences
    if (static_cast<int>(cnt) < 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;//若对应点对数太少,则直接终止icp,收敛状态为false
    }

    // Estimate the transform
    transformation_estimation_->estimateRigidTransformation(
        *input_transformed, *target_, *correspondences_, transformation_);//进行svd位姿解算

    // Transform the data
    transformCloud(*input_transformed, *input_transformed, transformation_);

    // Obtain the final transformation
    final_transformation_ = transformation_ * final_transformation_;//叠加位姿估算结果

    ++nr_iterations_;

    // Update the vizualization of icp convergence
    // if (update_visualizer_ != 0)
    //  update_visualizer_(output, source_indices_good, *target_, target_indices_good );

    converged_ = static_cast<bool>((*convergence_criteria_));//这里显式类型转换调用了pcl::registration::ConvergenceCriteria中对bool的重载,该重载中调用了pcl::registration::DefaultConvergenceCriteria<Scalar>::hasConverged()函数
  } while (convergence_criteria_->getConvergenceState() ==
           pcl::registration::DefaultConvergenceCriteria<
               Scalar>::CONVERGENCE_CRITERIA_NOT_CONVERGED);

  // Transform the input cloud using the final transformation
  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));//打印最终的变换矩阵

  // Copy all the values
  output = *input_;
  // Transform the XYZ + normals
  transformCloud(*input_, output, final_transformation_);//进行坐标转换获取最终结果output
}

3.2.3 pcl::registration::DefaultConvergenceCriteria::hasConverged ()代码解析

template <typename Scalar> bool
pcl::registration::DefaultConvergenceCriteria<Scalar>::hasConverged ()
{
  convergence_state_ = CONVERGENCE_CRITERIA_NOT_CONVERGED;

  PCL_DEBUG ("[pcl::DefaultConvergenceCriteria::hasConverged] Iteration %d out of %d.\n", iterations_, max_iterations_);
  // 1. Number of iterations has reached the maximum user imposed number of iterations
  if (iterations_ >= max_iterations_)
  {
    if (failure_after_max_iter_)
      return (false);
    else
    {
      convergence_state_ = CONVERGENCE_CRITERIA_ITERATIONS;
      return (true);
    }
    return (failure_after_max_iter_ ? false : true);
  }

  // 2. The epsilon (difference) between the previous transformation and the current estimated transformation
  double cos_angle = 0.5 * (transformation_.coeff (0, 0) + transformation_.coeff (1, 1) + transformation_.coeff (2, 2) - 1);//罗德里格斯公式
  double translation_sqr = transformation_.coeff (0, 3) * transformation_.coeff (0, 3) +
                           transformation_.coeff (1, 3) * transformation_.coeff (1, 3) +
                           transformation_.coeff (2, 3) * transformation_.coeff (2, 3);//平移二范数
  PCL_DEBUG ("[pcl::DefaultConvergenceCriteria::hasConverged] Current transformation gave %f rotation (cosine) and %f translation.\n", cos_angle, translation_sqr);

  if (cos_angle >= rotation_threshold_ && translation_sqr <= translation_threshold_)//平移距离的二范数满足要求则判断CONVERGENCE_CRITERIA_TRANSFORM
  {
    if (iterations_similar_transforms_ < max_iterations_similar_transforms_)
    {
      // Increment the number of transforms that the thresholds are allowed to be similar
      ++iterations_similar_transforms_;
      return (false);
    }
    else
    {
      iterations_similar_transforms_ = 0;
      convergence_state_ = CONVERGENCE_CRITERIA_TRANSFORM;
      return (true);
    }
  }

  correspondences_cur_mse_ = calculateMSE (correspondences_);//计算平均距离,单位为m
  PCL_DEBUG ("[pcl::DefaultConvergenceCriteria::hasConverged] Previous / Current MSE for correspondences distances is: %f / %f.\n", correspondences_prev_mse_, correspondences_cur_mse_);

  // 3. The relative sum of Euclidean squared errors is smaller than a user defined threshold
  // Absolute
  if (fabs (correspondences_cur_mse_ - correspondences_prev_mse_) < mse_threshold_absolute_)//两次迭代的绝对距离差满足收敛要求
  {
    if (iterations_similar_transforms_ < max_iterations_similar_transforms_)
    {
      // Increment the number of transforms that the thresholds are allowed to be similar
      ++iterations_similar_transforms_;
      return (false);
    }
    else
    {
      iterations_similar_transforms_ = 0;
      convergence_state_ = CONVERGENCE_CRITERIA_ABS_MSE;
      return (true);
    }
  }
  
  // Relative
  if (fabs (correspondences_cur_mse_ - correspondences_prev_mse_) / correspondences_prev_mse_ < mse_threshold_relative_)//两次迭代的相对距离差满足收敛要求
  {
    if (iterations_similar_transforms_ < max_iterations_similar_transforms_)
    {
      // Increment the number of transforms that the thresholds are allowed to be similar
      ++iterations_similar_transforms_;
      return (false);
    }
    else
    {
      iterations_similar_transforms_ = 0;
      convergence_state_ = CONVERGENCE_CRITERIA_REL_MSE;
      return (true);
    }
  }

  correspondences_prev_mse_ = correspondences_cur_mse_;

  return (false);
}
 类似资料: