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

ICP 算法流程

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

  1. 给定两幅点云:

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

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 onefinal_transformation_ = guess;// 循环开始do{// 计算两幅点云的对应关系      关键函数 1correspondence_estimation_->determineCorrespondences (*correspondences_, corr_dist_threshold_);// 根据对应关系求取变换矩阵     关键函数 2transformation_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_);// 最终得到配准后的点云outputtransformCloud (*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 indicesfor (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 DecompositionEigen::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 transformationtransformation_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 中也是这样实现的,不过源码中具体的函数分工很细,不断的跳着查看挺麻烦,但是如果对流程理解了,具体看源码也没那么难了。

PCL 实现 ICP 算法原理源码解析相关推荐

  1. PCL 实现 SAC_IA 算法原理源码解析

    PCL 实现 SAC_IA 算法原理源码解析 采样一致性算法(SAC_IA)用于点云粗配准中,配准效果还不错,PCL 中也实现了该算法,本文深入 PCL 源码,学习一下 SAC_IA 算法在 PCL ...

  2. 六、Dubbo协议模块原理源码解析

    课程概要: RPC协议基本组成 RPC协议报文编码与实现详解 Dubbo中所支持RPC协议与使用 RPC协议基本组成 RPC 协议名词解释 在一个典型RPC的使用场景中,包含了服务发现.负载.容错.网 ...

  3. vue双向绑定原理源码解析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/maxlove123 ...

  4. Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  5. 单例设计模式-Enum枚举单例、原理源码解析以及反编译实战

    package com.learn.design.pattern.creational.singleton;/*** 这个类是Enum类型* 这个枚举非常简单* 枚举类是Object* 他在多线程的时 ...

  6. java怎么让main方法不退出_JAVA线程池原理源码解析—为什么启动一个线程池,提交一个任务后,Main方法不会退出?...

    public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ...

  7. python 聚类分析实战案例:K-means算法(原理源码)

    K-means算法: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xvV44zrK-1573127992123)(https://img-blog.csdn.net/ ...

  8. python聚类分析案例_python 聚类分析实战案例:K-means算法(原理源码)

    K-means算法: 关于步骤:参考之前的博客 关于代码与数据:暂时整理代码如下:后期会附上github地址,上传原始数据与代码完整版, 各种聚类算法的对比:参考连接 Kmeans算法的缺陷 1.聚类 ...

  9. Spring之事务底层原理源码解析

    文章目录 一.`@EnableTransactionManagement`工作原理 二.Spring事务基本执行原理 三.Spring事务详细执行流程 四.Spring事务传播机制 五.Spring事 ...

  10. 深入解析SpringBoot核心运行原理和运作原理源码

    SpringBoot核心运行原理 Spring Boot 最核心的功能就是自动配置,第 1 章中我们已经提到,功能的实现都是基于"约定优于配置"的原则.那么 Spring Boot ...

最新文章

  1. 第六十一天 how can I 坚持
  2. js 数组,字符串,JSON,bind, Name
  3. Spark SQL JOIN操作代码示例
  4. Little Sub and Traveling
  5. 保持边缘平滑的图像(曲率)
  6. 《Orange’s 一个操作系统的实现》3.保护模式7-特权级转移(通过调用门转移目标段-无特权级转换)...
  7. gradle项目搭建
  8. 3D呈现transform-style(CSS3)
  9. 【突变检验合集】含Pettitt突变检验等
  10. 入手评测 暗影骑士龙和暗影骑士擎哪个更值得入手
  11. virtualbox出现failed to attach usb,VERR_PDM_NO_USB_PORTS问题解决
  12. c++程序设计报告总结
  13. mysql中关于表的删除和表中数据的删除
  14. 多元函数的泰勒展开(Taylor series expansion)
  15. 解析计算机科学与技术论文引言,计算机科学与技术专业毕业论文写作指导
  16. SPM12入门案例1
  17. 达沃斯论坛创始人邀阿里张勇对话 谈全球数字经济未来
  18. 【C语言】结构体、共用体、位域
  19. {技术资料参数}低功耗LED数码管显示驱动;LCD低功耗/抗干扰液晶显示驱动;高灵敏度抗干扰,低功耗触摸芯片(IC)
  20. PostgreSQL 12 `GRANT` 命令

热门文章

  1. js从服务器获取word文档,JavaScript-js如何获取word文档页数
  2. 三极管的基础知识(下)①
  3. java中数字转换汉语中人民币的大写
  4. 主成分分析二级指标权重_确定权重方法之一:主成分分析
  5. W10笔记本电脑弄成WIFI
  6. 使用Typora+PicGo+Gitee+坚果云搭建免费高效的个人云笔记
  7. Unity射线检测整理
  8. Android 微信登陆的坑
  9. 用户密码MD5和SHA加密
  10. html5获取经纬度页面,html5获取经纬度