前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【3D篇】点云拼接

【3D篇】点云拼接

作者头像
threeQing
发布2021-09-29 15:37:54
1.6K0
发布2021-09-29 15:37:54
举报
文章被收录于专栏:机器视觉那些事儿

一 点云拼接流程

二 基于特征描述子的点云配准实验

2.1 第一组实验:源点云与目标点云完全相同,位姿不同;

输出信息:

VoxelGrid_Filter has finished in 0 s

VoxelGrid_Filter has finished in 0 s

resolution : 0.33944

computeResolution has finished in 0 s

computeSurfaceNormals() has finished in 0 s

computeSurfaceNormals() has finished in 0 s

computeFeatures_FPFH has finished in 3 s

computeFeatures_FPFH has finished in 3 s

--------------- SAC-IA ------------------

calculate time is: 5.83s

SAC has converged:1 score: 2.22288

0.918413 -0.379178 0.112884 25.9688

0.35343 0.914572 0.196583 32.3238

-0.17778 -0.140648 0.973967 15.8231

0 0 0 1

----------------- ICP -----------

ICP, FitnessScore: 0.0294127

0.808435 -0.588591 0.000618075 25.9931

0.588591 0.808434 -0.00204244 29.9981

0.000702663 0.0020145 1 15.0284

0 0 0 1

配准后效果图(绿色:源点云,红色:目标点云,蓝色:配准点云):

可以发现,红色点云与蓝色点云完全重合,配准效果很好。

2.2 第二组实验:源点云(45°)与目标点云(0°)存在较大部分重合;

输出信息:

D:\PCLData\bunny6\bun000.pcd

D:\PCLData\bunny6\bun045.pcd

resolution : 0.000583691

computeResolution has finished in 1 s

VoxelGrid_Filter, Input points: 40256; Output points: 3648

VoxelGrid_Filter has finished in 0 s

VoxelGrid_Filter, Input points: 40097; Output points: 3523

VoxelGrid_Filter has finished in 0 s

resolution : 0.00194114

computeResolution has finished in 0 s

computeSurfaceNormals() has finished in 0 s

computeSurfaceNormals() has finished in 0 s

computeFeatures_FPFH has finished in 3 s

computeFeatures_FPFH has finished in 3 s

--------------- SAC-IA ------------------

calculate time is: 7.877s

SAC has converged:1 score: 0.000152522

0.837255 0.38372 0.389567 -0.0998685

-0.34413 0.923413 -0.169951 0.0126603

-0.424945 0.0082306 0.905182 -0.00992364

0 0 0 1

----------------- ICP -----------

ICP, FitnessScore: 9.50956e-06

0.861307 -0.00769163 0.508033 -0.0513097

0.01093 0.999939 -0.00338782 1.89042e-05

-0.507974 0.00847142 0.861337 -0.0132623

0 0 0 1

配准后效果图(绿色:源点云,红色:目标点云,蓝色:配准点云):

可以发现,红色点云与蓝色点云几乎完全重合,配准效果很好。

2.3 第三组实验:源点云(90°)与目标点云(45°)存在少部分重合;

配准后效果图(绿色:源点云,红色:目标点云,蓝色:配准点云):

可以发现,红色点云与蓝色点云重合度很低,配准效果很差。

针对此问题,将源点云与目标点云中具有相同特征的点云部分分割出来,来计算变换矩阵;

分割出相同特征的点云(兔头)配准如下:

可以发现,红色点云与蓝色点云重合度较高,配准效果较好。

计算兔头的配准后转换矩阵,对源点云和目标点云进行配准。

可以发现,红色点云与蓝色点云配准效果改善很多,但也存在一定误差。

由以上实验可以得出:

1)重合点云的数量越多,配准越好。

2)配准比较耗时,特别是粗配准的SAC耗时较长;

四 源代码简介

4.1 头文件

代码语言:javascript
复制
br
代码语言:javascript
复制
#pragma once

#include "typeDef.h"

extern "C"
{
  //计算分辨率
  float computeResolution(PointCloudPtr cloud);
  
  //体素滤波
  void VoxelGrid_Filter(PointCloudPtr input,
    PointCloudPtr output,
    float leafsize = 1.0);
  //基于统计学滤波
  void StatisticalOutlierRemoval_Filter(PointCloudPtr input,
    PointCloudPtr output,
    int K = 30,
    float stddevMulThresh = 1.0);
  

  //法向量计算
  void computeSurfaceNormals(FeatureCloud& cloud,
    int K = 50,
    float radius = 0,
    int numofthreads = 4);

  //FPFH计算
  void computeFeatures_FPFH(FeatureCloud &cloud,
    float R);
  
  /*****配准***/
  //SAC-IA
  void SAC_IA_Transform(FeatureCloud &source_cloud,
    FeatureCloud &target_cloud,
    float minsampleDistance,
    int numofSample,
    int correspondenceRandomness,
    Eigen::Matrix4f& final_transformation);
  //ICP
  float iterative_closest_points(std::string solver,
    bool flag_reciprocal, bool flag_ransac,
    FeatureCloud &source_cloud, FeatureCloud &target_cloud,
    float transEps, float corresDist, float EuclFitEps, float outlThresh, int maxIteration,
    Eigen::Matrix4f &final_transformation);

    //计算转换矩阵
  bool RotationTranslationCompute(FeatureCloud& cloudtarget,
    FeatureCloud& cloudsource,
    Eigen::Matrix4f &tranResult);

  //点云可视化
  void visualize_pcd(PointCloud::Ptr pcd_src,
    PointCloud::Ptr pcd_tgt,
    PointCloud::Ptr pcd_final);


  //基于欧氏距离的聚类
  void EuclideanClusterExtraction(PointCloudPtr pointInput,
    std::vector<PointCloudPtr>& cloudListOutput,
    float clusterTolerance = 0.02,
    int minClusterSize = 100,
    int maxClusterSize = 1000);
  //基于区域生长的聚类
  void RegionGrowingClusterExtraction(PointCloudPtr pointInput,
    std::vector<PointCloudPtr>& cloudListOutput,
    SurfaceNormals::Ptr normalInput,
    int minClusterSize,
    int maxClusterSize,
    int numofNeighbour = 30,
    float smoothThreshold = 3.0 / 180.0 * M_PI,
    float curvatureThreshold = 1.0);
  
  

}

4.2 源文件

代码语言:javascript
复制
#include "utilities.h"

//计算分辨率
float computeResolution(PointCloudPtr cloud)
{
  clock_t start, end;
  start = clock();

  float resolution = 0.0;
  int n_points = 0;
  int nres;
  std::vector<int> indices(2);
  std::vector<float> sqr_distances(2);
  pcl::search::KdTree<pcl::PointXYZ> tree;
  tree.setInputCloud(cloud);

  for (size_t i = 0; i < cloud->size(); ++i)
  {
    if (!pcl_isfinite((*cloud)[i].x))
    {
      continue;
    }
    //Considering the second neighbor since the first is the point itself.
    nres = tree.nearestKSearch(i, 2, indices, sqr_distances);
    if (nres == 2)
    {
      resolution += sqrt(sqr_distances[1]);
      ++n_points;
    }
  }
  if (n_points != 0)
  {
    resolution /= n_points;
  }

  std::cout << "resolution : " << resolution << endl;

  end = clock();
  std::cout << "computeResolution has finished in "
    << (end - start) / CLOCKS_PER_SEC << " s \n";

  return resolution;
}

//体素滤波
void VoxelGrid_Filter(PointCloudPtr input,
  PointCloudPtr output,
  float leafsize)
{
  clock_t start, end;
  start = clock();

  int num = input->size();
  pcl::VoxelGrid<PointT> voxelgrid_filter;//对体素网格中所有点求均值,以期望均值点代替原始点集,更精确
  voxelgrid_filter.setLeafSize(leafsize, leafsize, leafsize);
  voxelgrid_filter.setInputCloud(input);
  voxelgrid_filter.filter(*output);

  //pcl::ApproximateVoxelGrid<PointT> approximate_voxel_filter;//利用体素网格的中心(长方体的中心)代替原始点
  //approximate_voxel_filter.setLeafSize(leafsize, leafsize, leafsize);
  //approximate_voxel_filter.setInputCloud(input);
  //approximate_voxel_filter.filter(*output);

  std::cout << "VoxelGrid_Filter, Input points: " << num
    << "; Output points: " << output->size() << std::endl;
  end = clock();
  std::cout << "VoxelGrid_Filter has finished in "
    << (end - start) / CLOCKS_PER_SEC << " s \n";
}
//统计学滤波
void StatisticalOutlierRemoval_Filter(PointCloudPtr input,
  PointCloudPtr output,
  int K,
  float stddevMulThresh)
{
  clock_t start, end;
  start = clock();

  int num = input->size();
  pcl::StatisticalOutlierRemoval<PointT> statistical_filter;
  statistical_filter.setMeanK(K);
  statistical_filter.setStddevMulThresh(stddevMulThresh);
  statistical_filter.setInputCloud(input);
  statistical_filter.filter(*output);
  std::cout << "StatisticalOutlierRemoval_Filter, Input points: " << num
    << "; Output points: " << output->size() << std::endl;
  end = clock();
  std::cout << "StatisticalOutlierRemoval_Filter has finished in "
    << (end - start) / CLOCKS_PER_SEC << " s \n";
}

//法向量 
void computeSurfaceNormals(FeatureCloud& cloud,
  int K,
  float radius,
  int numofthreads)
{
  clock_t start, end;
  start = clock();

  NormalsPtr normals_(new SurfaceNormals);
  pcl::NormalEstimationOMP<PointT, NormalT> norm_est;
  pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>());

  norm_est.setNumberOfThreads(numofthreads);
  norm_est.setSearchMethod(tree);
  if (radius != 0) {

    norm_est.setRadiusSearch(radius);
  }
  else {
    norm_est.setKSearch(K);
  }
  norm_est.setInputCloud(cloud.getCloud());
  norm_est.compute(*normals_);
  cloud.setInputNormal(normals_);
  end = clock();
  std::cout << "computeSurfaceNormals() has finished in "
    << (end - start) / CLOCKS_PER_SEC << " s \n";
};


//FPFH计算
void computeFeatures_FPFH(FeatureCloud &cloud,
  float R)
{
  clock_t start, end;
  start = clock();

  FPFH_features::Ptr fpfh_features_(new FPFH_features);
  FPFH_features::Ptr nanremoved_(new FPFH_features);

  pcl::FPFHEstimationOMP<PointT, NormalT, FPFH33_FeatureT> fpfh;
  fpfh.setNumberOfThreads(4);
  fpfh.setSearchSurface(cloud.getCloud());
  fpfh.setInputCloud(cloud.getCloud());
  fpfh.setInputNormals(cloud.getNormal());
  pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
  fpfh.setSearchMethod(tree);
  fpfh.setRadiusSearch(R);

  // Use all neighbors in a sphere of radius 5cm
  // IMPORTANT: the radius used here has to be larger than the radius used to estimate the surface normals!!!
  // Compute the features
  fpfh.compute(*fpfh_features_);

  cloud.setFeatures_FPFH(fpfh_features_);
  end = clock();
  std::cout << "computeFeatures_FPFH has finished in "
    << (end - start) / CLOCKS_PER_SEC << " s \n";
}

//ICP
float iterative_closest_points(FeatureCloud &source_cloud, FeatureCloud &target_cloud,
  float transEps, float corresDist, float EuclFitEps, float outlThresh, int maxIteration,
  Eigen::Matrix4f &final_transformation)
{
  std::cout << "----------------- ICP -----------" << std::endl;
  PointCloud::Ptr Final(new PointCloud);
  pcl::IterativeClosestPoint<PointT, PointT> icp;

  icp.setInputSource(source_cloud.getCloud());
  icp.setInputTarget(target_cloud.getCloud());

  icp.setMaximumIterations(maxIteration);
  icp.setTransformationEpsilon(transEps);
  //icp.setMaxCorrespondenceDistance(corresDist);
  //icp.setEuclideanFitnessEpsilon(EuclFitEps);
  icp.setRANSACOutlierRejectionThreshold(0.01);

  icp.align(*Final, final_transformation);

  final_transformation = icp.getFinalTransformation();
  std::cout << "ICP, FitnessScore: " << icp.getFitnessScore() << std::endl;
  std::cout << std::endl << final_transformation << std::endl;

  return icp.getFitnessScore();

}
//SAC-IA
void SAC_IA_Transform(FeatureCloud &source_cloud,
  FeatureCloud &target_cloud,
  float minsampleDistance,
  int numofSample,
  int correspondenceRandomness,
  Eigen::Matrix4f& final_transformation)
{
  std::cout << "--------------- SAC-IA ------------------" << std::endl;
  clock_t start;
  clock_t end;
  start = clock();
  //SAC配准
  pcl::SampleConsensusInitialAlignment<PointT, PointT, FPFH33_FeatureT> scia;
  scia.setInputSource(source_cloud.getCloud());
  scia.setInputTarget(target_cloud.getCloud());
  scia.setSourceFeatures(source_cloud.getFeatures_FPFH());
  scia.setTargetFeatures(target_cloud.getFeatures_FPFH());

  scia.setMaximumIterations(30);                             //协方差越精确,但是计算效率越低.(可省)
  scia.setMinSampleDistance(minsampleDistance);
  //scia.setNumberOfSamples(numofSample);//设置每次迭代计算中使用的样本数量(可省),可节省时间
  scia.setCorrespondenceRandomness(correspondenceRandomness);//设置计算协方差时选择多少近邻点,该值越大,

  PointCloudPtr result(new PointCloud);
  scia.align(*result);
  end = clock();
  std::cout << "calculate time is: " << float(end - start) / CLOCKS_PER_SEC << "s" << endl;
  std::cout << "SAC has converged:" << scia.hasConverged() << "  score: " << scia.getFitnessScore() << endl;
  std::cout << std::endl << scia.getFinalTransformation() << std::endl;
  final_transformation = scia.getFinalTransformation();
}

//计算转换矩阵
bool RotationTranslationCompute(FeatureCloud& cloudtarget,
  FeatureCloud& cloudsource,
  Eigen::Matrix4f &tranResult)
{
  std::vector<int> mapping;
  pcl::removeNaNFromPointCloud(*cloudtarget.getCloud(), *cloudtarget.getCloud(), mapping);
  pcl::removeNaNFromPointCloud(*cloudsource.getCloud(), *cloudsource.getCloud(), mapping);
  //Resolution Calculation
  float resolution = 0.0;
  resolution = computeResolution(cloudtarget.getCloud());

  if (false)
  {
    //Statistical filter
    StatisticalOutlierRemoval_Filter(cloudtarget.getCloud(), cloudtarget.getCloud(), 30, 1.0);
    StatisticalOutlierRemoval_Filter(cloudsource.getCloud(), cloudsource.getCloud(), 30, 1.0);
  }

  //降采样获取关键点
  if (true)
  {
    float leafSize = resolution * 5;
    VoxelGrid_Filter(cloudtarget.getCloud(), cloudtarget.getCloud(), leafSize);
    VoxelGrid_Filter(cloudsource.getCloud(), cloudsource.getCloud(), leafSize);
  }

  resolution = computeResolution(cloudtarget.getCloud());//更新分辨率

  //Normal Calculation
  int normal_K = 50;
  float normal_R = resolution * 5;
  computeSurfaceNormals(cloudtarget, normal_K, normal_R);
  computeSurfaceNormals(cloudsource, normal_K, normal_R);

  //Feature describe
  float FPFH_radius = resolution * 5;
  computeFeatures_FPFH(cloudtarget, FPFH_radius);
  computeFeatures_FPFH(cloudsource, FPFH_radius);

  Eigen::Matrix4f sac_trans;
  //SAC-IA
  if (true)
  {
    float minsampleDistance = resolution * 2;
    int numofSample = 4;
    int correspondenceRandomness = 280;
    SAC_IA_Transform(cloudsource, cloudtarget, minsampleDistance,
      numofSample, correspondenceRandomness, sac_trans);
  }

  //ICP
  if (true)
  {
    float transEps = 1e-10;//设置两次变化矩阵之间的差值(一般设置为1e-10即可)
    float maxCorresDist = resolution * 0.4;//设置对应点对之间的最大距离(此值对配准结果影响较大)
    float EuclFitEps = 0.003;//设置收敛条件是均方误差和小于阈值,停止迭代;
    float outlThresh = resolution * 1.5;
    int maxIteration = 80;
    iterative_closest_points(cloudsource, cloudtarget,
      transEps, maxCorresDist, EuclFitEps,
      outlThresh, maxIteration, sac_trans);
  }

  tranResult = sac_trans;
  return true;
}

//点云可视化
void visualize_pcd(PointCloud::Ptr pcd_src,
  PointCloud::Ptr pcd_tgt,
  PointCloud::Ptr pcd_final)
{
  //int vp_1, vp_2;
  // Create a PCLVisualizer object
  pcl::visualization::PCLVisualizer viewer("registration Viewer");
  //viewer.createViewPort (0.0, 0, 0.5, 0.2, vp_1);
   // viewer.createViewPort (0.5, 0, 0.2, 0.2, vp_2);
  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> src_h(pcd_src, 0, 255, 0);
  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> tgt_h(pcd_tgt, 255, 0, 0);
  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> final_h(pcd_final, 0, 0, 255);
  viewer.addPointCloud(pcd_src, src_h, "source cloud");
  viewer.addPointCloud(pcd_tgt, tgt_h, "tgt cloud");
  viewer.addPointCloud(pcd_final, final_h, "final cloud");
  //viewer.addCoordinateSystem(1.0);
  while (!viewer.wasStopped())
  {
    viewer.spinOnce(100);
    boost::this_thread::sleep(boost::posix_time::microseconds(100000));
  }
}


//基于区域生长的聚类
void RegionGrowingClusterExtraction(PointCloudPtr pointInput,
  std::vector<PointCloudPtr>& cloudListOutput,
  SurfaceNormals::Ptr normalInput,
  int minClusterSize,
  int maxClusterSize,
  int numofNeighbour,
  float smoothThreshold,
  float curvatureThreshold)
{
  clock_t start, end;
  start = clock();
  pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
  pcl::RegionGrowing<PointT, NormalT> reg;
  reg.setMinClusterSize(minClusterSize);
  reg.setMaxClusterSize(maxClusterSize);
  reg.setSearchMethod(tree);
  reg.setNumberOfNeighbours(numofNeighbour);
  reg.setInputCloud(pointInput);
  reg.setInputNormals(normalInput);
  reg.setSmoothnessThreshold(smoothThreshold);

  std::vector<pcl::PointIndices> cluster_indices;
  reg.extract(cluster_indices);
  std::cout << "Number of clusters is equal to " << cluster_indices.size() << std::endl;

  int j = 1;
  for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin(); it != cluster_indices.end(); ++it)
  {
    PointCloudPtr cloud_cluster(new PointCloud);
    for (std::vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); ++pit)
    {
      cloud_cluster->points.push_back(pointInput->points[*pit]);
    }
    cloud_cluster->width = cloud_cluster->points.size();
    cloud_cluster->height = 1;
    cloud_cluster->is_dense = true;

    std::cout << "PointCloud representing the Cluster: " << j << " , " << cloud_cluster->points.size() << " data points." << std::endl;
    j++;
    cloudListOutput.push_back(cloud_cluster);

  }
  end = clock();
  std::cout << "RegionGrowingClusterExtraction has finished in "
    << float(end - start) / CLOCKS_PER_SEC << " s \n";
}

4.3 Demo

代码语言:javascript
复制
#include "utilities.h"
#include <iostream>
#include"sstream"
#include <fstream>

int main()
{
  std::string cloud_path1 = "D:\\PCLData\\bunny6\\bun045.pcd";
  std::string cloud_path2 = "D:\\PCLData\\bunny6\\bun090.pcd";
  PointCloudPtr tempcloud1(new PointCloud);//0
  PointCloudPtr tempcloud2(new PointCloud);//45
  pcl::io::loadPCDFile(cloud_path1, *tempcloud1);
  std::cout << cloud_path1 << std::endl;
  pcl::io::loadPCDFile(cloud_path2, *tempcloud2);
  std::cout << cloud_path2 << std::endl;
  PointCloudPtr cloud0(new PointCloud);//0
  PointCloudPtr cloud45(new PointCloud);//45


  FeatureCloud temp1;
  FeatureCloud temp2;
  pcl::copyPointCloud(*tempcloud1, *cloud0);
  pcl::copyPointCloud(*tempcloud2, *cloud45);
  temp1.setInputCloud(cloud0);
  temp2.setInputCloud(cloud45);

  PointCloudPtr tempCloud(new PointCloud);
  Eigen::Matrix4f tranTemp = Eigen::Matrix4f::Identity();
  if (RotationTranslationCompute(temp1, temp2, tranTemp))
  {
    pcl::copyPointCloud(*tempcloud2, *tempCloud);

    pcl::transformPointCloud(*tempCloud, *tempCloud, tranTemp);

    std::cout << "comprehensive tran: " << std::endl << tranTemp << std::endl;

  }
  else {
    std::cout << "Error: RotationTranslationCompute was failed." << std::endl;
  }
  return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器视觉那些事儿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档