当前位置:   article > 正文

点云配准(1)ICP 点对点配准以及原理分析

点云配准

概述

三维点云配准 -- ICP 算法原理及推导 - 知乎

基于PCL的ICP及其变种算法实现_pcl icp-CSDN博客

      ICP算法对待拼接的2片点云,首先根据一定的准则确立对应点集P与Q,其中对应点对的个数为n。然后通过最小二乘法迭代计算最优的坐标变换,即旋转矩阵R和平移矢量t,使得误差函数最小。ICP算法计算简便直观且可使拼接具有较好的精度,但是算法的运行速度以及向全局最优的收敛性却在很大程度上依赖于给定的初始变换估计以及在迭代过程中对应关系的确立。各种粗拼接技术可为ICP算法提供较好的初始位置,所以迭代过程中确立正确对应点集以避免迭代陷入局部极值成为各种改进算法的关键,决定了算法的收敛速度与最终的拼接精度
https://blog.csdn.net/qq_29923461/article/details/120785717

一、矩阵

正交矩阵

矩阵迹(trace), 行列式(determinate)_矩阵的迹-CSDN博客

如果:AAt=E(E为单位矩阵,At表示“矩阵A的转置矩阵”。)或AtA=E,则n阶实矩阵A称为正交矩阵.

主要性质:

矩阵的迹

矩阵 的逆、 迹、 秩_逆矩阵的迹-CSDN博客

如何理解矩阵的「秩」? - 知乎

矩阵的迹是特征值的加和,也即矩阵A的主对角线元素的总和。

二、ICP点云原理推导 

迭代优化:

找到对应点后,我们就构建了两组对应的点集:
      

 

方法

有两种方法可以计算

1、利用线性代数的求解SVD

2、非线性优化方式Ax=B 

非线性优化的方法:

点云配准: 刚性ICP中 Point-to-Point 和 Point-to-Plane 公式推导 ==> 帮助你代码实现 - 知乎

点云刚性配准:point2point 和 point2plane 代码 - 知乎

  1. void ICP::point2point(float weight, Eigen::MatrixXf& A, Eigen::VectorXf& b)
  2. {
  3. A.resize(3*m_srcOfPair.size(), 6);
  4. b.resize(3 * m_srcOfPair.size());
  5. for (int j = 0; j < m_srcOfPair.size(); j++)
  6. {
  7. Eigen::Vector3f sourceCoordinates;
  8. sourceCoordinates = m_srcOfPair[j];
  9. Eigen::Vector3f targetCoordinates;
  10. targetCoordinates = m_targetOfPair[j];
  11. A(3 * j, 0) = 0;
  12. A(3 * j, 1) = sourceCoordinates[2];//源模型中当前点的z
  13. A(3 * j, 2) = -sourceCoordinates[1];//源模型中当前点的y
  14. A(3 * j, 3) = 1;
  15. A(3 * j, 4) = 0;
  16. A(3 * j, 5) = 0;
  17. A(3 * j + 1, 0) = -sourceCoordinates[2];
  18. A(3 * j + 1, 1) = 0;
  19. A(3 * j + 1, 2) = sourceCoordinates[0];
  20. A(3 * j + 1, 3) = 0;
  21. A(3 * j + 1, 4) = 1;
  22. A(3 * j + 1, 5) = 0;
  23. A(3 * j + 2, 0) = sourceCoordinates[1];
  24. A(3 * j + 2, 1) = -sourceCoordinates[0];
  25. A(3 * j + 2, 2) = 0;
  26. A(3 * j + 2, 3) = 0;
  27. A(3 * j + 2, 4) = 0;
  28. A(3 * j + 2, 5) = 1;
  29. b[3 * j] = targetCoordinates[0] - sourceCoordinates[0];
  30. b[3 * j + 1] = targetCoordinates[1] - sourceCoordinates[1];
  31. b[3 * j + 2] = targetCoordinates[2] - sourceCoordinates[2];
  32. }
  33. A *= weight;
  34. b *= weight;
  35. }

线性代数的求解-SVD 

下面这位博主已经写的很好了,但是在我看的阅读手推的时候,发现了一些细节性的东西,下面是我对整个流程的理解和细节补充。

点云配准经典方法——ICP 原理推导及PCL实现-CSDN博客

原理

前提:

在点云配准的时候如果发现原点云的个数大于目标点云的个数,那么我们的原点云要进行下采样(随机采样)等的相关操作,让原点云的个数小于等于目标点云的个数,不然下面这个公式是不成立的(个人理解)

求解最优变换:

三维点云配准 -- ICP 算法原理及推导 - 知乎

计算最优旋转 R 

计算两个点云的残差和最小

 我们可以先计算出各自的点云质心

p_{i}^{s}=p_{i}^{s}-\bar{ps}     原始点云的质心

p_{i}^{t}=p_{i}^{t}-\bar{pt}     目标点云的质心

我们不考虑平移:loss 函数可以写成;

F(R)= \sum_{i}^{N}||R*p_{i}^{s}-p_{i}^{t}||^{2}

去中心化,将点云的质心带入

下面这副图是表示去中心化 的东西,下面的没有带入,因为写起来麻烦

 用的还是旧的点云质心。

我们先化简

R*R^{t}=E   

我们带入到F(R)= \sum_{i}^{N}||R*p_{i}^{s}-p_{i}^{t}||^{2}  中

所以后面就只有     

R*=-2arg min(\sum_{i}^{N}(p_{s}^{i})^{T}Rp_{s}^{i}))

我们取掉常数-2,可以转换为

R*=arg max(\sum_{i}^{N}(p_{s}^{i})^{T}Rp_{s}^{i}))

那么我们可以通过迹来计算

R*=arg max(\sum_{i}^{N}(P_{s}^{i})^{T}RP_{s}^{i}))

trace(AB)=trace(BA)  ,这里要求AB是一个矩阵,但是A、B不一定是一个方阵

那么  

trace(P_{t}^{T}RP_{s})=trace(RP_{t}^{T}P_{s})

下面我们定义一个矩阵H

H=P_{t}^{T}P_{s}

对H进行矩阵分解  H=U\sum V^{T}  svd 分解

trace(P_{t}^{T}RP_{s})=trace(RP_{t}^{T}P_{s}) =trace(RU\sum V^{T}) =trace(\sum V^{T}RU)

从上面我们可以看出 U、V、R都是正交矩阵,E是单位矩阵   ,那么 VRU 也是正交矩阵 

那么我们可以计算

M=V^{T}RU=\begin{bmatrix} m11& m12 & m13\\ m21& m22 & m23\\ m31& m32 & m33\\ \end{bmatrix}

将M矩阵带入到迹中

 trace(P_{t}^{T}RP_{s})=trace(RP_{t}^{T}P_{s}) =trace(RU\sum V^{T}) =trace(\sum V^{T}RU)=trace(\sum M)

trace(EM)=trace(\begin{bmatrix} \sigma 11& 0 & 0\\ 0& \sigma 22 & 0\\ 0&0 & \sigma 33\\ \end{bmatrix} *\begin{bmatrix} m11& m12 & m13\\ m21& m22 & m23\\ m31& m32 & m33\\ \end{bmatrix})

=\sigma 11m11+\sigma 22m22+\sigma 33m33

根据奇异值非负的性质和正交矩阵的性质(正交矩阵中的元素绝对值不大于 1),容易证得只有当 M 为单位阵时 trace(ΣM) 最大,即:

V^{T}RU=E

R=VU^{T} => R*=R=VU^{T}

三、 计算流程

核心逻辑

有两个点云,A(目标点云) B(原点云),然后我们能见B点云一步一步的靠近A点云的过程

0、点云预处理, 滤波等

1、对两个点云的点数比较,有必要就要做采样处理

2、计算两个点云的质心 \mu ^{_{p}} \mu ^{_{q}}, 我们可以设点R=E并且计算  t*=\mu ^{_{p}} - \mu ^{_{q}}

3、去中心化,并将上面的公式简化成P、Q的形式

4、H=PQ ,将H矩阵进行svd 矩阵分解  H=U\Sigma V^{T}

5、计算R T 

6、计算两个点云的间距,计算loss 函数,并判断时候收敛,继续迭代,返回到2

  1. #include <pcl/io/ply_io.h>
  2. #include <pcl/io/pcd_io.h>
  3. #include <pcl/point_types.h>
  4. #include <pcl/point_cloud.h>
  5. #include <pcl/visualization/pcl_visualizer.h>
  6. #include <pcl/common/transforms.h>
  7. #include <pcl/registration/icp.h>
  8. #include <pcl/filters\voxel_grid.h>
  9. using namespace std;
  10. #if 1
  11. int main()
  12. {
  13. /*自己构建点云*/
  14. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_source(new pcl::PointCloud<pcl::PointXYZ>);//声明源点云
  15. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_target(new pcl::PointCloud<pcl::PointXYZ>);//声明目标点云
  16. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_icp(new pcl::PointCloud<pcl::PointXYZ>);//匹配后的点云
  17. pcl::PointCloud<pcl::PointXYZ>::Ptr my_cloud_icp(new pcl::PointCloud<pcl::PointXYZ>);//匹配后的点云
  18. /*自己构建的点云 */
  19. /*填充源点云*/
  20. cloud_source->width = 10000;
  21. cloud_source->height = 1;
  22. cloud_source->is_dense = false;
  23. cloud_source->points.resize(cloud_source->width * cloud_source->height);
  24. for (size_t i = 0; i < cloud_source->points.size(); ++i)
  25. {
  26. cloud_source->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
  27. cloud_source->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
  28. //先测试二维数据
  29. cloud_source->points[i].z =1;
  30. }
  31. /*构建变换矩阵*/
  32. float d_yaw = 30;//变化航向角度 单位度
  33. float d_x = 10;//变换平移x
  34. float d_y = 10;//变换平移y
  35. float s = sin(d_yaw * M_PI / 180);
  36. float c = cos(d_yaw * M_PI / 180);
  37. Eigen::Matrix3f T;//变换矩阵
  38. T << c, -s, d_x,
  39. s, c, d_y,
  40. 0, 0, 1;
  41. cout << "888888888888" << endl;
  42. cout << T << endl;
  43. cout << "888888888888" << endl;
  44. //将源点云赋值给目标点云
  45. *cloud_target = *cloud_source;
  46. /*将源点云经过变换矩阵,获得目标点云*/
  47. for (size_t i = 0; i < cloud_source->points.size(); ++i)
  48. {
  49. Eigen::Vector3f point_in, point_out;
  50. point_in << cloud_source->points[i].x, cloud_source->points[i].y, cloud_source->points[i].z;
  51. point_out = T * point_in;
  52. cloud_target->points[i].x = point_out[0];
  53. cloud_target->points[i].y = point_out[1];
  54. cloud_target->points[i].z = point_out[2];
  55. }
  56. /*求两个点云的几何中心*/
  57. int num_points = cloud_target->points.size();
  58. Eigen::Vector3f sum_point_in = Eigen::Vector3f::Zero();
  59. Eigen::Vector3f sum_point_out = Eigen::Vector3f::Zero();
  60. for (size_t i = 0; i < num_points; ++i)
  61. {
  62. sum_point_in[0] = sum_point_in[0] + cloud_source->points[i].x;
  63. sum_point_in[1] = sum_point_in[1] + cloud_source->points[i].y;
  64. sum_point_in[2] = sum_point_in[2] + cloud_source->points[i].z;
  65. sum_point_out[0] = sum_point_out[0] + cloud_target->points[i].x;
  66. sum_point_out[1] = sum_point_out[1] + cloud_target->points[i].y;
  67. sum_point_out[2] = sum_point_out[2] + cloud_target->points[i].z;
  68. }
  69. // 将点云保存起来
  70. pcl::io::savePCDFileBinaryCompressed("C:\\Users\\Albert\\Desktop\\halcon_to_pcl\\cloud_target.pcd",*cloud_target);
  71. pcl::io::savePCDFileBinaryCompressed("C:\\Users\\Albert\\Desktop\\halcon_to_pcl\\cloud_source.pcd", *cloud_source);
  72. Eigen::Vector3f target_centroid_my, source_centroid_my;
  73. //源点云几何中心
  74. target_centroid_my[0] = sum_point_in[0] / num_points;
  75. target_centroid_my[1] = sum_point_in[1] / num_points;
  76. target_centroid_my[2] = sum_point_in[2] / num_points;
  77. //目标点云几何中心
  78. source_centroid_my[0] = sum_point_out[0] / num_points;
  79. source_centroid_my[1] = sum_point_out[1] / num_points;
  80. source_centroid_my[2] = sum_point_out[2] / num_points;
  81. Eigen::Vector4f pcl_target_centroid,pcl_source_centroid;
  82. // 质心
  83. pcl::compute3DCentroid(*cloud_source, pcl_source_centroid);
  84. pcl::compute3DCentroid(*cloud_target, pcl_target_centroid);
  85. /*cout << "My " << endl;
  86. cout << target_centroid_my << endl;
  87. cout << endl;
  88. cout << "PCL " << endl;
  89. cout << pcl_target_centroid << endl;
  90. cout << endl;
  91. cout << "My " << endl;
  92. cout << pcl_source_centroid << endl;
  93. cout << endl;
  94. cout << "PCL " << endl;
  95. cout << source_centroid_my << endl;
  96. cout << endl;*/
  97. // 从上面我们可以得到两个点云,用来做匹配
  98. // 很重要的一步 去中心化
  99. for (size_t i = 0; i < num_points; ++i)
  100. {
  101. //源点云去中心化
  102. cloud_source->points[i].x = cloud_source->points[i].x - source_centroid_my[0];
  103. cloud_source->points[i].y = cloud_source->points[i].y - source_centroid_my[1];
  104. cloud_source->points[i].z = cloud_source->points[i].z - source_centroid_my[2];
  105. //目标点云去中心化
  106. cloud_target->points[i].x = cloud_target->points[i].x - target_centroid_my[0];
  107. cloud_target->points[i].y = cloud_target->points[i].y - target_centroid_my[1];
  108. cloud_target->points[i].z = cloud_target->points[i].z - target_centroid_my[2];
  109. }
  110. // 计算w 矩阵
  111. /*求W矩阵*/
  112. Eigen::Matrix3f W = Eigen::Matrix3f::Zero();//声明W矩阵
  113. for (size_t i = 0; i < num_points; ++i)
  114. {
  115. Eigen::Vector3f point_in, point_out;
  116. point_in << cloud_source->points[i].x, cloud_source->points[i].y, cloud_source->points[i].z;
  117. point_out << cloud_target->points[i].x, cloud_target->points[i].y, cloud_target->points[i].z;
  118. W = W + point_out * point_in.transpose();//累加求和
  119. }
  120. //
  121. //对W矩阵进行SVD分解
  122. Eigen::JacobiSVD<Eigen::MatrixXf> svd(W, Eigen::ComputeThinU | Eigen::ComputeThinV);
  123. //求V和U
  124. Eigen::Matrix3f V = svd.matrixV(), U = svd.matrixU();
  125. /*求R和t*/
  126. Eigen::Matrix3f R = U * V.transpose();//这里公式和课件里不一致
  127. Eigen::Vector3f t = target_centroid_my - R * source_centroid_my;
  128. std::cout << "R" << std::endl << R << std::endl;
  129. std::cout << "t" << std::endl << t << std::endl;
  130. // 将3*3 的矩阵变成4*4 的矩阵
  131. Eigen::Matrix4f RT = Eigen::Matrix4f::Identity();
  132. RT.block<3, 3>(0, 0) = R.matrix();
  133. RT.block<3,1>(0, 3) = t.matrix();
  134. cout << "------------------------------ " << endl;
  135. cout << RT << endl;
  136. cout << "------------------------------ " << endl;
  137. //
  138. // 通过PCL 的ICP进行计算
  139. pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
  140. icp.setInputSource(cloud_source);
  141. icp.setInputTarget(cloud_target);
  142. icp.setTransformationEpsilon(1e-10);
  143. icp.setMaxCorrespondenceDistance(100);
  144. icp.setEuclideanFitnessEpsilon(0.001);
  145. icp.setMaximumIterations(10000);
  146. icp.setUseReciprocalCorrespondences(false);
  147. icp.align(*cloud_icp);
  148. Eigen::Matrix4f G = icp.getFinalTransformation();
  149. cout << "pcl -icp " << endl;
  150. cout << G << endl;
  151. pcl::transformPointCloud(*cloud_source,*my_cloud_icp, RT);
  152. pcl::io::savePCDFileBinaryCompressed("C:\\Users\\Albert\\Desktop\\halcon_to_pcl\\pcl_icp.pcd", *cloud_icp);
  153. pcl::io::savePCDFileBinaryCompressed("C:\\Users\\Albert\\Desktop\\halcon_to_pcl\\myicp.pcd", *my_cloud_icp);
  154. system("pause");
  155. }
  156. #endif

四、PCL源码 

 align 对齐函数

pcl::Registration<PointSource, PointTarget, Scalar>::align (PointCloudSource &output)

主要是将点云的个数对应一致

  1. //
  2. template <typename PointSource, typename PointTarget, typename Scalar> inline void
  3. pcl::Registration<PointSource, PointTarget, Scalar>::align (PointCloudSource &output)
  4. {
  5. align (output, Matrix4::Identity ());
  6. }
  7. //
  8. template <typename PointSource, typename PointTarget, typename Scalar> inline void
  9. pcl::Registration<PointSource, PointTarget, Scalar>::align (PointCloudSource &output, const Matrix4& guess)
  10. {
  11. if (!initCompute ())
  12. return;
  13. // Resize the output dataset
  14. if (output.points.size () != indices_->size ())
  15. output.points.resize (indices_->size ());
  16. // Copy the header
  17. output.header = input_->header;
  18. // Check if the output will be computed for all points or only a subset
  19. if (indices_->size () != input_->points.size ())
  20. {
  21. output.width = static_cast<uint32_t> (indices_->size ());
  22. output.height = 1;
  23. }
  24. else
  25. {
  26. output.width = static_cast<uint32_t> (input_->width);
  27. output.height = input_->height;
  28. }
  29. output.is_dense = input_->is_dense;
  30. // Copy the point data to output
  31. for (size_t i = 0; i < indices_->size (); ++i)
  32. output.points[i] = input_->points[(*indices_)[i]];
  33. // Set the internal point representation of choice unless otherwise noted
  34. if (point_representation_ && !force_no_recompute_)
  35. tree_->setPointRepresentation (point_representation_);
  36. // Perform the actual transformation computation
  37. // 收敛
  38. converged_ = false;
  39. final_transformation_ = transformation_ = previous_transformation_ = Matrix4::Identity ();
  40. // Right before we estimate the transformation, we set all the point.data[3] values to 1 to aid the rigid
  41. // transformation
  42. for (size_t i = 0; i < indices_->size (); ++i)
  43. output.points[i].data[3] = 1.0;
  44. // 开始计算旋转方程
  45. computeTransformation (output, guess);
  46. deinitCompute ();
  47. }

 主要的函数是  computeTransformation 函数

computeTransformation函数

  

  1. ///
  2. template <typename PointSource, typename PointTarget, typename Scalar> void
  3. pcl::IterativeClosestPoint<PointSource, PointTarget, Scalar>::computeTransformation (
  4. PointCloudSource &output, const Matrix4 &guess)
  5. {
  6. // Point cloud containing the correspondences of each point in <input, indices>
  7. PointCloudSourcePtr input_transformed (new PointCloudSource);
  8. nr_iterations_ = 0;
  9. converged_ = false;
  10. // Initialise final transformation to the guessed one
  11. final_transformation_ = guess;
  12. // If the guessed transformation is non identity
  13. if (guess != Matrix4::Identity ())
  14. {
  15. input_transformed->resize (input_->size ());
  16. // Apply guessed transformation prior to search for neighbours
  17. transformCloud (*input_, *input_transformed, guess);
  18. }
  19. else
  20. *input_transformed = *input_;
  21. transformation_ = Matrix4::Identity ();
  22. // Make blobs if necessary
  23. determineRequiredBlobData ();
  24. PCLPointCloud2::Ptr target_blob (new PCLPointCloud2);
  25. if (need_target_blob_)
  26. pcl::toPCLPointCloud2 (*target_, *target_blob);
  27. // Pass in the default target for the Correspondence Estimation/Rejection code
  28. correspondence_estimation_->setInputTarget (target_); // 目标点云
  29. if (correspondence_estimation_->requiresTargetNormals ())
  30. correspondence_estimation_->setTargetNormals (target_blob);
  31. // Correspondence Rejectors need a binary blob
  32. for (size_t i = 0; i < correspondence_rejectors_.size (); ++i)
  33. {
  34. registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i];
  35. if (rej->requiresTargetPoints ())
  36. rej->setTargetPoints (target_blob);
  37. if (rej->requiresTargetNormals () && target_has_normals_)
  38. rej->setTargetNormals (target_blob);
  39. }
  40. convergence_criteria_->setMaximumIterations (max_iterations_); // 最大迭代次数
  41. convergence_criteria_->setRelativeMSE (euclidean_fitness_epsilon_); // icp.setEuclideanFitnessEpsilon(0.001);
  42. convergence_criteria_->setTranslationThreshold (transformation_epsilon_); // icp.setTransformationEpsilon(1e-5);
  43. convergence_criteria_->setRotationThreshold (1.0 - transformation_epsilon_); //
  44. // Repeat until convergence
  45. // 开始迭代 直到 收敛
  46. do
  47. {
  48. // Get blob data if needed
  49. PCLPointCloud2::Ptr input_transformed_blob;
  50. if (need_source_blob_)
  51. {
  52. input_transformed_blob.reset (new PCLPointCloud2);
  53. toPCLPointCloud2 (*input_transformed, *input_transformed_blob);
  54. }
  55. // Save the previously estimated transformation
  56. previous_transformation_ = transformation_;
  57. // Set the source each iteration, to ensure the dirty flag is updated
  58. correspondence_estimation_->setInputSource (input_transformed);
  59. if (correspondence_estimation_->requiresSourceNormals ())
  60. correspondence_estimation_->setSourceNormals (input_transformed_blob);
  61. // Estimate correspondences 估计对应关系
  62. if (use_reciprocal_correspondence_) // 使用对等对应
  63. correspondence_estimation_->determineReciprocalCorrespondences (*correspondences_, corr_dist_threshold_);
  64. else //determineCorrespondences
  65. correspondence_estimation_->determineCorrespondences (*correspondences_, corr_dist_threshold_);
  66. // determineCorrespondences
  67. /* 该函数用于求取两幅点云的对应关系,主要流程如下:
  68. 将 target 点云加入 kdtree
  69. 遍历 source 点云中的每一个点,寻找对应 target 点云中的最近点
  70. 将以上一组点云作为一对匹配关系保存下来*/
  71. //template <typename PointSource, typename PointTarget, typename Scalar> void
  72. // pcl::registration::CorrespondenceEstimation<PointSource, PointTarget, Scalar>::determineCorrespondences
  73. // (pcl::Correspondences & correspondences, double max_distance)
  74. //{
  75. // double max_dist_sqr = max_distance * max_distance;
  76. // correspondences.resize(indices_->size());
  77. // std::vector<int> index(1);
  78. // std::vector<float> distance(1);
  79. // pcl::Correspondence corr;
  80. // unsigned int nr_valid_correspondences = 0;
  81. // // Iterate over the input set of source indices
  82. // for (std::vector<int>::const_iterator idx = indices_->begin(); idx != indices_->end(); ++idx)
  83. // {
  84. // tree_->nearestKSearch(input_->points[*idx], 1, index, distance);
  85. // if (distance[0] > max_dist_sqr)
  86. // continue;
  87. // corr.index_query = *idx;
  88. // corr.index_match = index[0];
  89. // corr.distance = distance[0];
  90. // correspondences[nr_valid_correspondences++] = corr;
  91. // }
  92. // correspondences.resize(nr_valid_correspondences);
  93. //}
  94. //if (correspondence_rejectors_.empty ())
  95. // 对应关系
  96. CorrespondencesPtr temp_correspondences (new Correspondences (*correspondences_));
  97. for (size_t i = 0; i < correspondence_rejectors_.size (); ++i)
  98. {
  99. registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i];
  100. PCL_DEBUG ("Applying a correspondence rejector method: %s.\n", rej->getClassName ().c_str ());
  101. if (rej->requiresSourcePoints ())
  102. rej->setSourcePoints (input_transformed_blob);
  103. if (rej->requiresSourceNormals () && source_has_normals_)
  104. rej->setSourceNormals (input_transformed_blob);
  105. rej->setInputCorrespondences (temp_correspondences);
  106. rej->getCorrespondences (*correspondences_);
  107. // Modify input for the next iteration
  108. if (i < correspondence_rejectors_.size () - 1)
  109. *temp_correspondences = *correspondences_;
  110. }
  111. size_t cnt = correspondences_->size ();
  112. // Check whether we have enough correspondences
  113. if (static_cast<int> (cnt) < min_number_correspondences_)
  114. {
  115. PCL_ERROR ("[pcl::%s::computeTransformation] Not enough correspondences found. Relax your threshold parameters.\n", getClassName ().c_str ());
  116. convergence_criteria_->setConvergenceState(pcl::registration::DefaultConvergenceCriteria<Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
  117. converged_ = false;
  118. break;
  119. }
  120. // Estimate the transform
  121. transformation_estimation_->estimateRigidTransformation (*input_transformed, *target_, *correspondences_, transformation_);
  122. //estimateRigidTransformation
  123. //void pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation(
  124. // const pcl::PointCloud<PointSource> &cloud_src,
  125. // const pcl::PointCloud<PointTarget> &cloud_tgt,
  126. // const pcl::Correspondences & correspondences,
  127. // Matrix4 & transformation_matrix) const
  128. //{
  129. // ConstCloudIterator<PointSource> source_it(cloud_src, correspondences, true);
  130. // ConstCloudIterator<PointTarget> target_it(cloud_tgt, correspondences, false);
  131. // estimateRigidTransformation(source_it, target_it, transformation_matrix);
  132. //}
  133. //template <typename PointSource, typename PointTarget, typename Scalar> inline void
  134. // pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation(
  135. // ConstCloudIterator<PointSource>& source_it,
  136. // ConstCloudIterator<PointTarget>& target_it,
  137. // Matrix4& transformation_matrix) const
  138. //{
  139. // transformation_matrix.setIdentity();
  140. // Eigen::Matrix<Scalar, 4, 1> centroid_src, centroid_tgt;
  141. // // 计算质心
  142. // compute3DCentroid(source_it, centroid_src);
  143. // compute3DCentroid(target_it, centroid_tgt);
  144. // source_it.reset(); target_it.reset();
  145. // // 减去质心
  146. // Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> cloud_src_demean, cloud_tgt_demean;
  147. // demeanPointCloud(source_it, centroid_src, cloud_src_demean);
  148. // demeanPointCloud(target_it, centroid_tgt, cloud_tgt_demean);
  149. // // SVD 分解
  150. // getTransformationFromCorrelation(cloud_src_demean, centroid_src, cloud_tgt_demean, centroid_tgt, transformation_matrix);
  151. //}
  152. //template <typename PointSource, typename PointTarget, typename Scalar> void
  153. // pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::getTransformationFromCorrelation(
  154. // const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& cloud_src_demean,
  155. // const Eigen::Matrix<Scalar, 4, 1>& centroid_src,
  156. // const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& cloud_tgt_demean,
  157. // const Eigen::Matrix<Scalar, 4, 1>& centroid_tgt,
  158. // Matrix4& transformation_matrix) const
  159. //{
  160. // transformation_matrix.setIdentity();
  161. // // Assemble the correlation matrix H = source * target'
  162. // Eigen::Matrix<Scalar, 3, 3> H = (cloud_src_demean * cloud_tgt_demean.transpose()).topLeftCorner(3, 3);
  163. // // Compute the Singular Value Decomposition SVD分解H矩阵
  164. // Eigen::JacobiSVD<Eigen::Matrix<Scalar, 3, 3> > svd(H, Eigen::ComputeFullU | Eigen::ComputeFullV);
  165. // Eigen::Matrix<Scalar, 3, 3> u = svd.matrixU();
  166. // Eigen::Matrix<Scalar, 3, 3> v = svd.matrixV();
  167. // 计算 R 矩阵
  168. // // Compute R = V * U'
  169. // if (u.determinant() * v.determinant() < 0)
  170. // {
  171. // for (int x = 0; x < 3; ++x)
  172. // v(x, 2) *= -1;
  173. // }
  174. // Compute R = V * U'
  175. // Eigen::Matrix<Scalar, 3, 3> R = v * u.transpose();
  176. // // Return the correct transformation
  177. // transformation_matrix.topLeftCorner(3, 3) = R;
  178. // const Eigen::Matrix<Scalar, 3, 1> Rc(R * centroid_src.head(3));
  179. // t*=pt-R*ps
  180. // transformation_matrix.block(0, 3, 3, 1) = centroid_tgt.head(3) - Rc;
  181. //}
  182. // Tranform the data
  183. transformCloud (*input_transformed, *input_transformed, transformation_);
  184. // Obtain the final transformation
  185. final_transformation_ = transformation_ * final_transformation_;
  186. ++nr_iterations_;
  187. // Update the vizualization of icp convergence
  188. //if (update_visualizer_ != 0)
  189. // update_visualizer_(output, source_indices_good, *target_, target_indices_good );
  190. converged_ = static_cast<bool> ((*convergence_criteria_));
  191. }
  192. while (!converged_);
  193. // Transform the input cloud using the final transformation
  194. 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",
  195. final_transformation_ (0, 0), final_transformation_ (0, 1), final_transformation_ (0, 2), final_transformation_ (0, 3),
  196. final_transformation_ (1, 0), final_transformation_ (1, 1), final_transformation_ (1, 2), final_transformation_ (1, 3),
  197. final_transformation_ (2, 0), final_transformation_ (2, 1), final_transformation_ (2, 2), final_transformation_ (2, 3),
  198. final_transformation_ (3, 0), final_transformation_ (3, 1), final_transformation_ (3, 2), final_transformation_ (3, 3));
  199. // Copy all the values
  200. output = *input_;
  201. // Transform the XYZ + normals
  202. transformCloud (*input_, output, final_transformation_);
  203. }

但是里面有几个特别重要的函数

 determineCorrespondences函数

determineRequiredBlobData()函数

estimateRigidTransformation函数----非常重要的函数

determineCorrespondences函数

  1. /* 该函数用于求取两幅点云的对应关系,主要流程如下:
  2. 将 target 点云加入 kdtree
  3. 遍历 source 点云中的每一个点,寻找对应 target 点云中的最近点
  4. 将以上一组点云作为一对匹配关系保存下来*/
  5. template <typename PointSource, typename PointTarget, typename Scalar> void
  6. pcl::registration::CorrespondenceEstimation<PointSource, PointTarget, Scalar>::determineCorrespondences
  7. (pcl::Correspondences & correspondences, double max_distance)
  8. {
  9. double max_dist_sqr = max_distance * max_distance;
  10. correspondences.resize(indices_->size());
  11. std::vector<int> index(1);
  12. std::vector<float> distance(1);
  13. pcl::Correspondence corr;
  14. unsigned int nr_valid_correspondences = 0;
  15. // Iterate over the input set of source indices
  16. for (std::vector<int>::const_iterator idx = indices_->begin(); idx != indices_->end(); ++idx)
  17. {
  18. tree_->nearestKSearch(input_->points[*idx], 1, index, distance);
  19. if (distance[0] > max_dist_sqr)
  20. continue;
  21. corr.index_query = *idx;
  22. corr.index_match = index[0];
  23. corr.distance = distance[0];
  24. correspondences[nr_valid_correspondences++] = corr;
  25. }
  26. correspondences.resize(nr_valid_correspondences);
  27. }

determineRequiredBlobData函数

  1. template <typename PointSource, typename PointTarget, typename Scalar> void
  2. pcl::IterativeClosestPoint<PointSource, PointTarget, Scalar>::determineRequiredBlobData ()
  3. {
  4. need_source_blob_ = false;
  5. need_target_blob_ = false;
  6. // Check estimator
  7. need_source_blob_ |= correspondence_estimation_->requiresSourceNormals ();
  8. need_target_blob_ |= correspondence_estimation_->requiresTargetNormals ();
  9. // Add warnings if necessary
  10. if (correspondence_estimation_->requiresSourceNormals () && !source_has_normals_)
  11. {
  12. PCL_WARN("[pcl::%s::determineRequiredBlobData] Estimator expects source normals, but we can't provide them.\n", getClassName ().c_str ());
  13. }
  14. if (correspondence_estimation_->requiresTargetNormals () && !target_has_normals_)
  15. {
  16. PCL_WARN("[pcl::%s::determineRequiredBlobData] Estimator expects target normals, but we can't provide them.\n", getClassName ().c_str ());
  17. }
  18. // Check rejectors
  19. for (size_t i = 0; i < correspondence_rejectors_.size (); i++)
  20. {
  21. registration::CorrespondenceRejector::Ptr& rej = correspondence_rejectors_[i];
  22. need_source_blob_ |= rej->requiresSourcePoints ();
  23. need_source_blob_ |= rej->requiresSourceNormals ();
  24. need_target_blob_ |= rej->requiresTargetPoints ();
  25. need_target_blob_ |= rej->requiresTargetNormals ();
  26. if (rej->requiresSourceNormals () && !source_has_normals_)
  27. {
  28. PCL_WARN("[pcl::%s::determineRequiredBlobData] Rejector %s expects source normals, but we can't provide them.\n", getClassName ().c_str (), rej->getClassName ().c_str ());
  29. }
  30. if (rej->requiresTargetNormals () && !target_has_normals_)
  31. {
  32. PCL_WARN("[pcl::%s::determineRequiredBlobData] Rejector %s expects target normals, but we can't provide them.\n", getClassName ().c_str (), rej->getClassName ().c_str ());
  33. }
  34. }
  35. }

estimateRigidTransformation函数

  1. void pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation(
  2. const pcl::PointCloud<PointSource> &cloud_src,
  3. const pcl::PointCloud<PointTarget> &cloud_tgt,
  4. const pcl::Correspondences & correspondences,
  5. Matrix4 & transformation_matrix) const
  6. {
  7. ConstCloudIterator<PointSource> source_it(cloud_src, correspondences, true);
  8. ConstCloudIterator<PointTarget> target_it(cloud_tgt, correspondences, false);
  9. estimateRigidTransformation(source_it, target_it, transformation_matrix);
  10. }
  11. template <typename PointSource, typename PointTarget, typename Scalar> inline void
  12. pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation(
  13. ConstCloudIterator<PointSource>& source_it,
  14. ConstCloudIterator<PointTarget>& target_it,
  15. Matrix4& transformation_matrix) const
  16. {
  17. transformation_matrix.setIdentity();
  18. Eigen::Matrix<Scalar, 4, 1> centroid_src, centroid_tgt;
  19. // 计算质心
  20. compute3DCentroid(source_it, centroid_src);
  21. compute3DCentroid(target_it, centroid_tgt);
  22. source_it.reset(); target_it.reset();
  23. // 减去质心
  24. Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> cloud_src_demean, cloud_tgt_demean;
  25. demeanPointCloud(source_it, centroid_src, cloud_src_demean);
  26. demeanPointCloud(target_it, centroid_tgt, cloud_tgt_demean);
  27. // SVD 分解
  28. getTransformationFromCorrelation(cloud_src_demean, centroid_src, cloud_tgt_demean, centroid_tgt, transformation_matrix);
  29. }
  30. //template <typename PointSource, typename PointTarget, typename Scalar> void
  31. pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::getTransformationFromCorrelation(
  32. const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& cloud_src_demean,
  33. const Eigen::Matrix<Scalar, 4, 1>& centroid_src,
  34. const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& cloud_tgt_demean,
  35. const Eigen::Matrix<Scalar, 4, 1>& centroid_tgt,
  36. Matrix4& transformation_matrix) const
  37. {
  38. transformation_matrix.setIdentity();
  39. // Assemble the correlation matrix H = source * target'
  40. Eigen::Matrix<Scalar, 3, 3> H = (cloud_src_demean * cloud_tgt_demean.transpose()).topLeftCorner(3, 3);
  41. // Compute the Singular Value Decomposition SVD分解H矩阵
  42. Eigen::JacobiSVD<Eigen::Matrix<Scalar, 3, 3> > svd(H, Eigen::ComputeFullU | Eigen::ComputeFullV);
  43. Eigen::Matrix<Scalar, 3, 3> u = svd.matrixU();
  44. Eigen::Matrix<Scalar, 3, 3> v = svd.matrixV();
  45. 计算 R 矩阵
  46. // Compute R = V * U'
  47. if (u.determinant() * v.determinant() < 0)
  48. {
  49. for (int x = 0; x < 3; ++x)
  50. v(x, 2) *= -1;
  51. }
  52. // Compute R = V * U'
  53. Eigen::Matrix<Scalar, 3, 3> R = v * u.transpose();
  54. // Return the correct transformation
  55. transformation_matrix.topLeftCorner(3, 3) = R;
  56. const Eigen::Matrix<Scalar, 3, 1> Rc(R * centroid_src.head(3));
  57. // t*=pt-R*ps
  58. transformation_matrix.block(0, 3, 3, 1) = centroid_tgt.head(3) - Rc;
  59. }

ConstCloudIterator 函数

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

  1. template <class PointT>
  2. pcl::ConstCloudIterator<PointT>::ConstCloudIterator (
  3. const PointCloud<PointT>& cloud, const Correspondences& corrs, bool source)
  4. {
  5. std::vector<int> indices;
  6. indices.reserve (corrs.size ());
  7. if (source)
  8. {
  9. for (typename Correspondences::const_iterator indexIt = corrs.begin (); indexIt != corrs.end (); ++indexIt)
  10. indices.push_back (indexIt->index_query);
  11. }
  12. else
  13. {
  14. for (typename Correspondences::const_iterator indexIt = corrs.begin (); indexIt != corrs.end (); ++indexIt)
  15. indices.push_back (indexIt->index_match);
  16. }
  17. iterator_ = new typename pcl::ConstCloudIterator<PointT>::ConstIteratorIdx (cloud, indices);
  18. }

五、测试

CC点云匹配

CloudCompare 技巧四 点云匹配_cloudcompare点云配准-CSDN博客

code 测试

  1. #include <pcl/io/ply_io.h>
  2. #include <pcl/io/pcd_io.h>
  3. #include <pcl/point_types.h>
  4. #include <pcl/point_cloud.h>
  5. #include <pcl/visualization/pcl_visualizer.h>
  6. #include <pcl/common/transforms.h>
  7. #include <pcl/registration/icp.h>
  8. #include <pcl/filters\voxel_grid.h>
  9. using namespace std;
  10. typedef pcl::PointXYZ PointT;
  11. typedef pcl::PointCloud<PointT> PointCloud;
  12. PointCloud::Ptr cloud_target(new PointCloud);
  13. PointCloud::Ptr cloud_source(new PointCloud);
  14. PointCloud::Ptr cloud_icp(new PointCloud);
  15. pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("global_visualizer"));
  16. const double translation_step = 0.005; // 设定一个平移的步长
  17. const double rotation_step = M_PI / 72; // 设定一个旋转的步长
  18. void keyboardEventOccurred(const pcl::visualization::KeyboardEvent& event, void* viewer_void);
  19. Eigen::Affine3f G = Eigen::Affine3f::Identity();
  20. int main() {
  21. //加载点云
  22. string target_path = "C:\\Users\\Albert\\Desktop\\halcon_to_pcl\\1.pcd";
  23. string source_path = "C:\\Users\\Albert\\Desktop\\halcon_to_pcl\\2.pcd";
  24. pcl::io::loadPCDFile(target_path, *cloud_target);
  25. pcl::io::loadPCDFile(source_path, *cloud_source);
  26. //点云降采样
  27. pcl::VoxelGrid<pcl::PointXYZ> VG;
  28. VG.setInputCloud(cloud_target);
  29. VG.setLeafSize(0.001f, 0.001f, 0.001f);
  30. VG.filter(*cloud_target);
  31. VG.setInputCloud(cloud_source);
  32. VG.setLeafSize(0.001f, 0.001f, 0.001f);
  33. VG.filter(*cloud_source);
  34. //可视化相关
  35. int v1 = 1;
  36. viewer->createViewPort(0.0, 0.0, 1.0, 1.0, v1);
  37. viewer->setBackgroundColor(255, 255, 255, v1);
  38. viewer->addPointCloud(cloud_target, pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>(cloud_target, 0.0, 0.0, 0.0), "cloud_target", v1);
  39. viewer->addPointCloud(cloud_source, pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>(cloud_source, 255.0, 0.0, 0.0), "cloud_source", v1);
  40. viewer->registerKeyboardCallback(keyboardEventOccurred, (void*)viewer.get());
  41. while (!viewer->wasStopped()) {
  42. // 应用变换
  43. pcl::transformPointCloud(*cloud_source, *cloud_source, G);
  44. G = Eigen::Affine3f::Identity();
  45. // 更新视图
  46. viewer->removePointCloud("cloud_source");
  47. viewer->addPointCloud<pcl::PointXYZ>(cloud_source, pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>(cloud_source, 255.0, 0.0, 0.0), "cloud_source", v1);
  48. viewer->removePointCloud("cloud_target");
  49. viewer->addPointCloud<pcl::PointXYZ>(cloud_target, pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>(cloud_target, 0.0, 0.0, 0.0), "cloud_target", v1);
  50. viewer->spinOnce(10); // 每次更新等待10ms
  51. }
  52. }
  53. //键盘回调函数
  54. void keyboardEventOccurred(const pcl::visualization::KeyboardEvent& event, void* viewer_void)
  55. {
  56. if (event.keyDown())
  57. {
  58. if (event.getKeySym() == "space") {
  59. pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
  60. icp.setInputSource(cloud_source);
  61. icp.setInputTarget(cloud_target);
  62. icp.setTransformationEpsilon(1e-10);
  63. icp.setMaxCorrespondenceDistance(100);
  64. icp.setEuclideanFitnessEpsilon(0.001);
  65. icp.setMaximumIterations(10000);
  66. icp.setUseReciprocalCorrespondences(false);
  67. icp.align(*cloud_icp);
  68. G = icp.getFinalTransformation();
  69. }
  70. else
  71. {
  72. switch (event.getKeySym()[0])
  73. {
  74. case 'w':
  75. G.translation() += Eigen::Vector3f(translation_step, 0, 0);
  76. break;
  77. case 's':
  78. G.translation() -= Eigen::Vector3f(translation_step, 0, 0);
  79. break;
  80. case 'a':
  81. G.translation() -= Eigen::Vector3f(0, translation_step, 0);
  82. break;
  83. case 'd':
  84. G.translation() += Eigen::Vector3f(0, translation_step, 0);
  85. break;
  86. case 'z':
  87. G.translation() += Eigen::Vector3f(0, 0, translation_step);
  88. break;
  89. case 'c':
  90. G.translation() -= Eigen::Vector3f(0, 0, translation_step);
  91. break;
  92. case 'r':
  93. G.rotate(Eigen::AngleAxisf(rotation_step, Eigen::Vector3f::UnitX()));
  94. break; // Roll
  95. case 'p':
  96. G.rotate(Eigen::AngleAxisf(rotation_step, Eigen::Vector3f::UnitY()));
  97. break; // Pitch
  98. case 'y':
  99. G.rotate(Eigen::AngleAxisf(rotation_step, Eigen::Vector3f::UnitZ()));
  100. break; // Yaw
  101. default: break;
  102. }
  103. }
  104. }
  105. cout << "变换矩阵 " << endl;
  106. cout << G.matrix() << endl;
  107. cout << endl;
  108. cout << endl;
  109. cout << endl;
  110. }

六、ICP点对点的优缺点

优点

简单,不需要对点云进行分割和特征提取

缺点

计算速度慢,开销大

只考虑点对点之间的距离,缺少对点云特征的利用

对R t的设定比较重要会影响速度

很容易拿到局部最优的R t 

点云的噪点影响很大,所以一般在进行ICP迭代的时候要先进行一次滤波

推广

point-to-plane

点对面的计算考虑到点云的特征和结构,不会陷入局部最优

plane-to-plane

考虑点云结构和特征,计算面到面的距离

Generalized ICP (GICP)

考虑Point-to-Point   plane-to-plane  plane-to-plane,这样精度就会提高

Normal Iterative Closest Point(NICP)

考虑法向量和局部曲率,更进一步的进行匹配

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号