PCL 实现 ICP 算法原理源码解析

PCL 实现 ICP 算法原理源码解析

ICP 算法流程

icp 算法可以使用 SVD 分解进行求解,在 PCL 中也是这么实现的,首先看一下 icp 算法使用 SVD 分解的流程:

  1. 给定两幅点云:
    • P P P(source)
    • Q Q Q(target)
  2. 获取两幅点云之间的匹配关系
  3. 计算旋转矩阵 R R R,平移向量 t t t
    1. 首先计算两幅点云的质心: p ^ \hat{p} p^ q ^ \hat{q} q^
    2. 计算两幅点云在减去质心之后对应新的点云 P ′ P' P Q Q Q
    3. 进行 SVD 分解: P ′ Q ′ T = U ∑ V T P'Q'^T = U\sum V^T PQT=UVT
    4. R = V U T R = VU^T R=VUT, t = q ^ − R p ^ t=\hat{q}-R\hat{p} t=q^Rp^
  4. 重复 2、3 步骤,若迭代次数达到设定值或 R R R t t t 变化小于阈值,则停止迭代

PCL 实现 ICP 算法

在 PCL 中,icp 算法也是通过 SVD 实现的,下面的函数是从源码中摘取的部分函数,函数内部的一些代码大多数被删掉了,只保留了一些核心的内容,当然看起来有一些变量存在未定义行为,但不妨碍理解整个实现流程,如果觉得不适应,请移步源码。

align

align 用于点云配准的计算函数,是点云配准的入口,里面包含核心函数 computeTransformation。

void pcl::Registration<PointSource, PointTarget, Scalar>::align(PointCloudSource &output, const Matrix4& guess)
{
    computeTransformation (output, guess);
}

computeTransformation

这是计算旋转矩阵的核心函数,函数体内有一个循环,就是通过该循环进行不断的迭代,求取最终的变换矩阵。

void pcl::IterativeClosestPoint<PointSource, PointTarget, Scalar>::computeTransformation(PointCloudSource &output, const Matrix4 &guess)
{
  PointCloudSourcePtr input_transformed (new PointCloudSource);

  nr_iterations_ = 0;
  converged_ = false;

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

  // 循环开始
  do
  {
    // 计算两幅点云的对应关系		关键函数 1
    correspondence_estimation_->determineCorrespondences (*correspondences_, corr_dist_threshold_);

    // 根据对应关系求取变换矩阵		关键函数 2
    transformation_estimation_->estimateRigidTransformation (*input_transformed, *target_, *correspondences_, transformation_);

    // 将变换矩阵作用于source点云
    transformCloud (*input_transformed, *input_transformed, transformation_);

    // 更新最终的变换矩阵    
    final_transformation_ = transformation_ * final_transformation_;

    ++nr_iterations_;

    converged_ = static_cast<bool> ((*convergence_criteria_));
  }
  while (!converged_);
    
  // 最终得到配准后的点云output
  transformCloud (*input_, output, final_transformation_);
}

在该函数内部,有几个重要的函数,它们实现了必要的功能,下面展开说一下:

1.determineCorrespondences

该函数用于求取两幅点云的对应关系,主要流程如下:

  1. 将 target 点云加入 kdtree
  2. 遍历 source 点云中的每一个点,寻找对应 target 点云中的最近点
  3. 将以上一组点云作为一对匹配关系保存下来
template <typename PointSource, typename PointTarget, typename Scalar> void
pcl::registration::CorrespondenceEstimation<PointSource, PointTarget, Scalar>::determineCorrespondences (pcl::Correspondences &correspondences, double max_distance)
{
    double max_dist_sqr = max_distance * max_distance;

    correspondences.resize (indices_->size ());

    std::vector<int> index (1);
    std::vector<float> distance (1);
    pcl::Correspondence corr;
    unsigned int nr_valid_correspondences = 0;

    // Iterate over the input set of source indices
    for (std::vector<int>::const_iterator idx = indices_->begin (); idx != indices_->end (); ++idx)
    {
        tree_->nearestKSearch (input_->points[*idx], 1, index, distance);
        if (distance[0] > max_dist_sqr)
            continue;

        corr.index_query = *idx;
        corr.index_match = index[0];
        corr.distance = distance[0];
        correspondences[nr_valid_correspondences++] = corr;
    }

    correspondences.resize (nr_valid_correspondences);
}
2.estimateRigidTransformation

该函数用于进行刚体变换,内部有两个核心函数。

void pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation (
    const pcl::PointCloud<PointSource> &cloud_src,
    const pcl::PointCloud<PointTarget> &cloud_tgt,
    const pcl::Correspondences &correspondences,
    Matrix4 &transformation_matrix) const
{
  ConstCloudIterator<PointSource> source_it (cloud_src, correspondences, true);
  ConstCloudIterator<PointTarget> target_it (cloud_tgt, correspondences, false);
  estimateRigidTransformation (source_it, target_it, transformation_matrix);
}

ConstCloudIterator

该函数被调用了两次,目的是将 source 与 target 内的点根据对应关系对应起来,即通过索引直接对应,source[i] 对应 target[i]。

template <class PointT>
pcl::ConstCloudIterator<PointT>::ConstCloudIterator (
    const PointCloud<PointT>& cloud, const Correspondences& corrs, bool source)
{
    std::vector<int> indices;
    indices.reserve (corrs.size ());
    if (source)
    {
        for (typename Correspondences::const_iterator indexIt = corrs.begin (); indexIt != corrs.end (); ++indexIt)
            indices.push_back (indexIt->index_query);
    }
    else
    {
        for (typename Correspondences::const_iterator indexIt = corrs.begin (); indexIt != corrs.end (); ++indexIt)
            indices.push_back (indexIt->index_match);
    }
    iterator_ = new typename pcl::ConstCloudIterator<PointT>::ConstIteratorIdx (cloud, indices);
}

estimateRigidTransformation

该函数是为最终的 SVD 分解做准备,首先要求两幅点云各自的质心,然后计算两幅点云去质心之后的新点云,然后传入最后的函数。

template <typename PointSource, typename PointTarget, typename Scalar> inline void
pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation (
    ConstCloudIterator<PointSource>& source_it,
    ConstCloudIterator<PointTarget>& target_it,
    Matrix4 &transformation_matrix) const
{
    transformation_matrix.setIdentity ();

    Eigen::Matrix<Scalar, 4, 1> centroid_src, centroid_tgt;
    // 计算质心
    compute3DCentroid (source_it, centroid_src);
    compute3DCentroid (target_it, centroid_tgt);
    source_it.reset (); target_it.reset ();

    // 减去质心
    Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> cloud_src_demean, cloud_tgt_demean;
    demeanPointCloud (source_it, centroid_src, cloud_src_demean);
    demeanPointCloud (target_it, centroid_tgt, cloud_tgt_demean);

    // SVD 分解
    getTransformationFromCorrelation (cloud_src_demean, centroid_src, cloud_tgt_demean, centroid_tgt, transformation_matrix);
}

getTransformationFromCorrelation

该函数就是最终的函数了,通过 SVD 分解求出了 R 和 t,SVD 分解则是调用了 Eigen 内的函数。

template <typename PointSource, typename PointTarget, typename Scalar> void
pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::getTransformationFromCorrelation (
    const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> &cloud_src_demean,
    const Eigen::Matrix<Scalar, 4, 1> &centroid_src,
    const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> &cloud_tgt_demean,
    const Eigen::Matrix<Scalar, 4, 1> &centroid_tgt,
    Matrix4 &transformation_matrix) const
{
  transformation_matrix.setIdentity ();

  // Assemble the correlation matrix H = source * target'
  Eigen::Matrix<Scalar, 3, 3> H = (cloud_src_demean * cloud_tgt_demean.transpose ()).topLeftCorner (3, 3);

  // Compute the Singular Value Decomposition
  Eigen::JacobiSVD<Eigen::Matrix<Scalar, 3, 3> > svd (H, Eigen::ComputeFullU | Eigen::ComputeFullV);
  Eigen::Matrix<Scalar, 3, 3> u = svd.matrixU ();
  Eigen::Matrix<Scalar, 3, 3> v = svd.matrixV ();

  // Compute R = V * U'
  if (u.determinant () * v.determinant () < 0)
  {
    for (int x = 0; x < 3; ++x)
      v (x, 2) *= -1;
  }

  Eigen::Matrix<Scalar, 3, 3> R = v * u.transpose ();

  // Return the correct transformation
  transformation_matrix.topLeftCorner (3, 3) = R;
  const Eigen::Matrix<Scalar, 3, 1> Rc (R * centroid_src.head (3));
  transformation_matrix.block (0, 3, 3, 1) = centroid_tgt.head (3) - Rc;
}

总结

icp 算法的流程如上所述,在 PCL 中也是这样实现的,不过源码中具体的函数分工很细,不断的跳着查看挺麻烦,但是如果对流程理解了,具体看源码也没那么难了。

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
PCL(点云库)中的ICP(迭代最近点)算法是一种常见的点云配准算法,用于将多个点云的坐标系对齐。ICP算法的时间复杂度与多个因素相关,包括点云的大小、迭代次数、采样密度以及计算设备的性能等。 首先,点云的大小对ICP算法的时间影响较大。假设点云1包含m个点,点云2包含n个点,那么ICP算法的时间复杂度接近O(m*n),即需要遍历所有点对进行计算。因此,随着点云规模的增大,ICP算法的时间也会线性增长。 其次,迭代次数也会影响ICP算法的时间。ICP算法的收敛过程需要多次迭代,直到达到最佳对齐结果。迭代次数越多,算法耗时越长。因此,当需要更高精度的配准结果时,迭代次数会相应增加,导致算法的耗时增加。 此外,采样密度也会对ICP算法的时间产生影响。当点云的采样密度较高时,算法需要计算更多的点对之间的距离,从而增加计算量和耗时。因此,为了减少计算时间,可以在配准之前对点云进行降采样操作。 最后,计算设备的性能也会影响ICP算法的时间。ICP算法需要进行矩阵运算、距离计算等复杂的数值计算,对计算资源要求较高。良好的计算设备性能可以加速算法的执行,提高配准的效率。 综上所述,PCL中的ICP算法的时间复杂度与点云的大小、迭代次数、采样密度和计算设备的性能等因素相关。在实际应用中,可以通过对点云进行降采样、增加计算资源等策略来优化算法的执行时间。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值