分享

点云拟合

 mscdj 2019-12-04

最近项目需要 这方面的工作,于是开始研究这个了;

圆柱几何特征:圆柱面上的点到其轴线的距离恒等于半径

圆柱的方程:

首先是 PCL库自带的圆柱模型拟合,由于在查找最佳圆柱面的过程中会过滤很多点,因此考虑利用最小二乘的模型来拟合最接近实际点云的一个圆柱面,code如下,只是简单的调库,原理没仔细看:

  1. #include "pch.h"
  2. #include <iostream>
  3. #include<pcl/io/pcd_io.h>
  4. #include<pcl/point_types.h>
  5. #include<pcl/point_cloud.h>
  6. #include<pcl/segmentation/sac_segmentation.h>
  7. #include<pcl/search/search.h>
  8. #include<pcl/search/kdtree.h>
  9. #include<pcl/features/normal_3d.h>
  10. #include<pcl/common/common.h>
  11. // use ransanc to fit cylinder
  12. int fitCylinder(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in, pcl::PointCloud<pcl::PointXYZ>::Ptr &cloud_out) {
  13. // Create segmentation object for cylinder segmentation and set all the parameters
  14. pcl::ModelCoefficients::Ptr coeffients_cylinder(new pcl::ModelCoefficients);
  15. pcl::PointIndices::Ptr inliers_cylinder(new pcl::PointIndices);
  16. // Normals
  17. pcl::search::Search<pcl::PointXYZ>::Ptr tree = boost::shared_ptr<pcl::search::Search<pcl::PointXYZ>>(new pcl::search::KdTree<pcl::PointXYZ>);
  18. pcl::PointCloud<pcl::Normal>::Ptr normalsFilter(new pcl::PointCloud<pcl::Normal>);
  19. pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normalEstimator;
  20. normalEstimator.setSearchMethod(tree);
  21. normalEstimator.setInputCloud(cloud_in);
  22. normalEstimator.setKSearch(30);
  23. normalEstimator.compute(*normalsFilter);
  24. pcl::SACSegmentationFromNormals<pcl::PointXYZ, pcl::Normal> seg;
  25. seg.setOptimizeCoefficients(true);
  26. seg.setModelType(pcl::SACMODEL_CYLINDER);
  27. seg.setMethodType(pcl::SAC_RANSAC);
  28. //seg.setNormalDistanceWeight(0.1);
  29. seg.setNormalDistanceWeight(0.2); // for coarse data
  30. seg.setMaxIterations(1000);
  31. seg.setDistanceThreshold(0.3);
  32. seg.setRadiusLimits(0, 6); // radius is within 8 centimeters
  33. seg.setInputCloud(cloud_in);
  34. seg.setInputNormals(normalsFilter);
  35. // Perform segment
  36. seg.segment(*inliers_cylinder, *coeffients_cylinder);
  37. std::cerr << "Cylinder coefficient: " << *coeffients_cylinder << std::endl;
  38. // Ouput extracted cylinder
  39. pcl::ExtractIndices<pcl::PointXYZ> extract;
  40. extract.setInputCloud(cloud_in);
  41. extract.setIndices(inliers_cylinder);
  42. extract.filter(*cloud_out);
  43. pcl::PCDWriter wr;
  44. wr.write("Extract_Cylinder.pcd", *cloud_out);
  45. float sum_D = 0.0;
  46. float sum_Ave = 0.0;
  47. float x0 = coeffients_cylinder->values[0];
  48. float y0 = coeffients_cylinder->values[1];
  49. float z0 = coeffients_cylinder->values[2];
  50. float l = coeffients_cylinder->values[3];
  51. float m= coeffients_cylinder->values[4];
  52. float n = coeffients_cylinder->values[5];
  53. float r0 = coeffients_cylinder->values[6];
  54. for (int i = 0; i < cloud_out->points.size(); i++) {
  55. float x = cloud_out->points[i].x;
  56. float y = cloud_out->points[i].y;
  57. float z = cloud_out->points[i].z;
  58. // D=part1+part2
  59. float part1 = pow(x - x0, 2) + pow(y - y0, 2) + pow(z - z0, 2) - pow(r0, 2);
  60. float part2 = -pow(l*(x - x0) + m * (y - y0) + n * (z - z0), 2) / (l*l + m * m + n * n);
  61. sum_D +=pow( part1 + part2,2);
  62. sum_Ave += fabs(part1 + part2);
  63. }
  64. std::cerr << "The average difference is " << sum_Ave / cloud_out->points.size() << std::endl;;
  65. std::cerr << "The Difference of average difference is " << sum_D / cloud_out->points.size() << std::endl;;
  66. // evaluate the cylinder quation
  67. }

尝试一下发现,圆柱方程无法简单地采用线性最小二乘的模型,考虑采用非线性最小二乘的解法,待续...

 

2019年4月 继续更

最近用最小二乘把 点云平面拟合,球拟合,圆柱拟合一锅端了,因为太懒一直没更233,慢慢全部补上公式推导和code。其中圆柱面拟合用到的非线性迭代的方法。

首先,简化圆柱面方程:

令:

算法流程如下:

 

代码:

  1. #include <Eigen/Dense>
  2. #include <pcl/point_types.h>
  3. #include <pcl/point_cloud.h>
  4. typedef struct _cylinder_coefficient_t { //圆柱参数
  5. float x;
  6. float y;
  7. float z;
  8. float l;
  9. float m;
  10. float n;
  11. float r;
  12. }cylinder_coefficient_t;
  13. int Leastsquare::cylinder_fit(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in, cylinder_coefficient_t *initial_params, cylinder_coefficient_t *final_params)
  14. {
  15. using namespace Eigen;
  16. // initial params x y z, l m n, r
  17. MatrixXd params(7, 1);
  18. params << 0, 0, 0, 0, 0, 0, 0;
  19. MatrixXd X(7, 1);
  20. X << initial_params->x, initial_params->y, initial_params->z
  21. + 1, initial_params->l, initial_params->m, initial_params->n, initial_params->r;
  22. //X << 89, -43, 19.6,0.01, 0.01, -0.99, 4;
  23. std::cout << "\n initial cylinder params=" << params + X << std::endl;
  24. int times = 0;
  25. while ((fabs(X(3, 0)) + fabs(X(4, 0)) + fabs(X(5, 0))) > 0.001 || fabs(X(6, 0)) > 0.01|| times==0) {
  26. // 改正
  27. params += X;
  28. std::cout << "\nparams=" << params << std::endl;
  29. float x0 = params(0, 0); float y0 = params(1, 0); float z0 = params(2, 0);
  30. float a0 = params(3, 0); float b0 = params(4, 0); float c0 = params(5, 0);
  31. float r0 = params(6, 0);
  32. float f0;
  33. int size = cloud_in->points.size(); //point size, M size
  34. MatrixXd m(size, 7), mT(7, size), B(size, 1);
  35. for (int i = 0; i < size; i++) {
  36. float x = cloud_in->points[i].x;
  37. float y = cloud_in->points[i].y;
  38. float z = cloud_in->points[i].z;
  39. f0 = pow(x - x0, 2) + pow(y - y0, 2) + pow(z - z0, 2) - pow((a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0)), 2) - r0 * r0;
  40. B(i, 0) = -f0;
  41. m(i, 0) = (a0 * (a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0)) - (x - x0)) * 2;
  42. m(i, 1) = (b0 * (a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0)) - (y - y0)) * 2;
  43. m(i, 2) = (c0 * (a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0)) - (z - z0)) * 2;
  44. m(i, 3) = -(a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0))*(x - x0) * 2;
  45. m(i, 4) = -(a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0))*(y - y0) * 2;
  46. m(i, 5) = -(a0*(x - x0) + b0 * (y - y0) + c0 * (z - z0))*(z - z0) * 2;
  47. m(i, 6) = -2*r0;
  48. }
  49. X = m.colPivHouseholderQr().solve(B); //改正数
  50. std::cout << "\nX=" << X << std::endl;
  51. //// V=Ax-B, x=(ATA)-1AT*B
  52. //mT = m.transpose();
  53. //MatrixXd mTm(7, 7), mTm_inv(7, 7);
  54. //mTm = mT * m;
  55. //mTm_inv = mTm.inverse();
  56. //X = mTm_inv * mT*B;
  57. //std::cout << "\nX=" << X << std::endl;
  58. times++;
  59. }
  60. std::cout << "After " << times << " iterations," << "\n final cylinder params=" << params << std::endl;
  61. float sum_D = 0.0;
  62. float sum_Ave = 0.0;
  63. float x0 = params(0, 0);
  64. float y0 = params(1, 0);
  65. float z0 = params(2, 0);
  66. float l = params(3, 0);
  67. float m = params(4, 0);
  68. float n = params(5, 0);
  69. float r0 = fabs(params(6, 0)); //r must be positive, int fitting process, it can be negtive
  70. for (int i = 0; i < cloud_in->points.size(); i++) {
  71. float x = cloud_in->points[i].x;
  72. float y = cloud_in->points[i].y;
  73. float z = cloud_in->points[i].z;
  74. // D=part1+part2
  75. float part1 = pow(x - x0, 2) + pow(y - y0, 2) + pow(z - z0, 2) - pow(r0, 2);
  76. float part2 = -pow(l*(x - x0) + m * (y - y0) + n * (z - z0), 2) / (l*l + m * m + n * n);
  77. sum_D += pow(part1 + part2, 2);
  78. sum_Ave += fabs(part1 + part2);
  79. }
  80. std::cerr << "The average difference is " << sum_Ave / cloud_in->points.size() << std::endl;;
  81. std::cerr << "The Difference of average difference is " << sum_D / cloud_in->points.size() << std::endl;;
  82. // evaluate the cylinder quation
  83. final_params->x = params(0, 0);
  84. final_params->y = params(1, 0);
  85. final_params->z = params(2, 0);
  86. final_params->l = params(3, 0);
  87. final_params->m = params(4, 0);
  88. final_params->n = params(5, 0);
  89. final_params->r = params(6, 0);
  90. return 0;
  91. }

结果:

有几点需要注意的:圆柱拟合出来的r有时候为正,有时候为负,但数值是对的,所以我直接在迭代结束的时候取了绝对值。可能对于 方程式来说,这两个值没有区别,感觉是在求这个7维方程中,有两个极值点,r的值是互为正反的。代码中 未考虑可能陷入局部最优的情况。

2019年5月,继续更,评论中有朋友提到未能利用到 abc的平方和为1这一限制条件,感谢提醒,经过思考大概有一些思路,再引入一个未知数m, 把m(a*a+b*b+c*c-1)加入f中(或者简单点直接令m=1等等),暂时还没有用代码测试,感兴趣的朋友可以试试。

同类工作:

点云拟合—平面拟合————https://blog.csdn.net/zhangxz259/article/details/90174923

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章