点云滤波与匹配进阶

0. 简介

之前作者专门为点云匹配写了几篇博客,但是我们发现最近几年有更多的新方法已经在不断地被使用。同时之前有些内容也没有很好的概括,所以这里我们将作为一篇进阶文章来介绍这些方法的使用。

1. 地面点去除

处了使用一些复杂的方法(FEC)或是一些简单的方法(根据高度来滤除)以外,还可以使用Ransac的方法完成平面拟合

复制代码
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/passthrough.h>
#include <pcl/segmentation/sac_segmentation.h>

void RemovePointsUnderGround(const pcl::PointCloud<pcl::PointXYZI>& cloud_in,
                             pcl::PointCloud<pcl::PointXYZI>& cloud_out)
{
    // 对输入点云进行降采样
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_downsampled(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::VoxelGrid<pcl::PointXYZI> voxel_grid;
    voxel_grid.setInputCloud(cloud_in.makeShared());
    voxel_grid.setLeafSize(0.1f, 0.1f, 0.1f); // 设置体素格大小
    voxel_grid.filter(*cloud_downsampled);

    // 创建一个滤波器对象,用于提取地面平面
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PassThrough<pcl::PointXYZI> pass_through;
    pass_through.setInputCloud(cloud_downsampled);
    pass_through.setFilterFieldName("z"); // 对z轴进行滤波
    pass_through.setFilterLimits(-1.5, 0.5); // 设置滤波范围,过滤掉z轴在-1.5到0.5之间的点
    pass_through.filter(*cloud_filtered);

    // 创建一个分割对象,用于提取地面平面
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    pcl::SACSegmentation<pcl::PointXYZI> segmentation;
    segmentation.setInputCloud(cloud_filtered);
    segmentation.setModelType(pcl::SACMODEL_PLANE);
    segmentation.setMethodType(pcl::SAC_RANSAC);
    segmentation.setDistanceThreshold(0.1); // 设置距离阈值,点到平面的距离小于该阈值的点将被认为是地面点
    segmentation.segment(*inliers, *coefficients);

    // 创建一个提取对象,用于提取地面点
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_ground(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::ExtractIndices<pcl::PointXYZI> extract;
    extract.setInputCloud(cloud_filtered);
    extract.setIndices(inliers);
    extract.setNegative(false); // 提取地面点,即保留inliers对应的点
    extract.filter(*cloud_ground);

    // 创建一个提取对象,用于提取非地面点
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_non_ground(new pcl::PointCloud<pcl::PointXYZI>);
    extract.setNegative(true); // 提取非地面点,即去除inliers对应的点
    extract.filter(*cloud_non_ground);

    // 将结果保存到输出点云中
    cloud_out = *cloud_non_ground;
}

2. PCA主成分判别

除了去除点云以外,还可以通过主成分判别来判断我们分割的是否是地面。其中eigenvectors()函数得到的矩阵中的三个列向量分别对应于数据的主成分轴。这些主成分轴是按照数据方差的降序排列的,即第一个列向量对应的是数据的第一主成分轴,第二个列向量对应的是数据的第二主成分轴,第三个列向量对应的是数据的第三主成分轴。对于PCA的特征值和特征向量可以从PCA | 特征值和特征向量-CSDN博客这里理解。

复制代码
#include <iostream>
#include <vector>
#include <pcl/point_types.h>
#include <pcl/common/pca.h>

bool EstimatePlane(const pcl::PointCloud<pcl::PointXYZI>& cloud)
{

    // 将输入点云数据转换为PCL点云格式
    for (const auto& point : cloud)
    {
        pcl::PointXYZ pclPoint;
        pclPoint.x = point.x();
        pclPoint.y = point.y();
        pclPoint.z = point.z();
        cloud->push_back(pclPoint);
    }

    // 创建PCA对象
    pcl::PCA<pcl::PointXYZ> pca;
    pca.setInputCloud(cloud);

    // 计算主成分
    Eigen::Vector3f eigenValues = pca.getEigenValues();
    Eigen::Matrix3f eigenVectors = pca.getEigenVectors();

    // 获取地面法向量,因为最小的就是第三列,所以最后一列是地面(0,0,1),如果是墙面那就(x,1-x,0)
    Eigen::Vector3f groundNormal = eigenVectors.col(2);#eigen_vector.block<3, 1>(0, 2)//最小成分的主成分向量,对应的是地面的法线,因为地面XY都存在比较大的主成分
    // 如果是其他的比如灯杆这种,一般的就会是fabs(eigen_vector.block<3, 1>(0, 0).dot(Eigen::Vector3f::UnitZ()))的形式,也就是最大主成分,沿着最大主成分方向

    bool is_ground = (fabs(groundNormal.dot(
                              Eigen::Vector3f::UnitZ())) > 0.98) &&
                         (eigenValues(2) < 0.05 * 0.05);//最小得列和地面法线重合|a||b|cos,并且eigenValues重要程度满足要求,因为地面基本等于0,所以特征值也很小 https://blog.csdn.net/xinxiangwangzhi_/article/details/118228160
    // 如果是其他的比如灯杆这种,一般的就会是eigen_values(0) > 10 * eigen_values(1)

    return is_ground;
}

3. GICP配准

GICP配准这块在之前的博客经典论文阅读之-GICP(ICP大一统)中已经详细讲过了,下面就是一个示例代码

复制代码
Eigen::Matrix4d gicp_trans(
    pcl::PointCloud<PointType>::Ptr source_cloud,
    pcl::PointCloud<PointType>::Ptr target_cloud) {
  CHECK(source_cloud);
  CHECK(target_cloud);

  pcl::GeneralizedIterativeClosestPoint<PointType, PointType> gicp;
  gicp.setInputSource(source_cloud);
  gicp.setInputTarget(target_cloud);


  gicp.setMaxCorrespondenceDistance(10.0);
  gicp.setMaximumIterations(100);
  gicp.setMaximumOptimizerIterations(100);
  gicp.setRANSACIterations(100);
  gicp.setRANSACOutlierRejectionThreshold(1.0);
  gicp.setTransformationEpsilon(0.01);
  gicp.setUseReciprocalCorespondences(false);

  LOG(INFO) << "MaxCorrespondenceDistance: " << gicp.getMaxCorrespondenceDistance();
  LOG(INFO) << "MaximumIterations: " << gicp.getMaximumIterations();
  LOG(INFO) << "MaximumOptimizerIterations: " << gicp.getMaximumOptimizerIterations();
  LOG(INFO) << "RANSACIterations: " << gicp.getRANSACIterations();
  LOG(INFO) << "RANSACOutlierRejectionThreshold: " << gicp.getRANSACOutlierRejectionThreshold();
  LOG(INFO) << "TransformationEpsilon: " << gicp.getTransformationEpsilon();
  LOG(INFO) << "MaxCorrespondenceDistance: " << gicp.getMaxCorrespondenceDistance();
  LOG(INFO) << "RANSACOutlierRejectionThreshold: " << gicp.getRANSACOutlierRejectionThreshold();
  LOG(INFO) << "UseReciprocalCorrespondences: " << gicp.getUseReciprocalCorrespondences();

  pcl::PointCloud<PointType>::Ptr aligned_source =
      boost::make_shared<pcl::PointCloud<PointType>>();
  gicp.align(*aligned_source);
  CHECK(aligned_source);
  LOG(INFO) << "Final transformation: " << std::endl << gicp.getFinalTransformation();
  if (gicp.hasConverged()) {
    LOG(INFO) << "GICP converged." << std::endl
              << "The score is " << gicp.getFitnessScore();
  } else {
    LOG(INFO) << "GICP did not converge.";
  }

  LOG(INFO) << "Saving aligned source cloud to: " << params_.aligned_cloud_filename;
  pcl::io::savePCDFile(params_.aligned_cloud_filename, *aligned_source);

  return  gicp.getFinalTransformation();
}

点击 点云滤波与匹配进阶 - 古月居可查看全文

相关推荐
JiNan.YouQuan.Soft5 小时前
Windows下编译CGMA
其他·1024程序员节
AMASENSYS艾马斯5 小时前
变频器过载能力参数解读与工程应用
驱动开发·嵌入式硬件·其他·硬件工程·精益工程
Yurko1313 小时前
原码、反码、补码的快速学习
笔记·学习·其他
打螺丝的小李1 天前
企业已经上了ERP和MES,还需不需要再单独上一套QMS?
其他
老吴的商业笔记2 天前
GEO源码搭建公司怎么选?服务商自建系统的成本与交付逻辑拆解
其他
notion20252 天前
(自测可用!)Xftp 8.0安装包下载安装教程
其他
金立基实业有限公司2 天前
替塑油在纸塑包装领域的核心定义是什么
其他
立时得胶水2 天前
制袋速度改动之后,封口强度明显波动?
其他
北京海得康3 天前
奎扎替尼quizartinib心脏风险与QT间期管理:黑框警告、监测方案与禁忌人群梳理
其他
工业传感笔记3 天前
工业传感器国产替代的工程评估框架:从规格覆盖到工况验证的四个维度
其他·自动化