点云降采样
   
    
    
    1 概述
   
三维点云往往包含大量冗余数据,直接处理计算量大,消耗时间长,因此对其进行降采样是十分必要的。降采样同时也是点云预处理过程中的关键环节。
    
    
    2 常用方法
   
    
    
    2.1 体素网格下采样
   
    
    
    2.1.1 原理
   
    
     体素(Voxel)
    
    :将三维空间划分成一个个立体的方格,每个方格就叫一个体素。
   
   
在每个体素中可能存在几个点,也可能没有点。降采样的思路为:检查每个体素中是否有点存在,若有,则用一个点代替体素内的点集,通常,这个采样点可以是体素中所有点坐标的平均值(质心),也可以是中心点或者离中心点最近的点。
    
    
    2.1.2 流程
   
- 计算点云的包围盒,将包围盒离散成小体素。体素的长宽高尺寸可以通过用户设定,也可以指定三个方向上的体素个数;
- 获取落在每个体素中的点集,在每个体素中取一个采样点代替原来的点集。
    
    
    2.1.3 特点
   
- 效率高
- 采样点分布比较均匀
- 可以通过控制体素大小间接控制采样点的距离(采样后点云的稀疏程度)
- 采集到的点云数量不可控
    
    
    2.2 均匀下采样
   
    
    
    2.2.1 原理
   
均匀下采样有多种不同的采样方式。
类似体素网格采样,同样需要将点云空间划分,不同的是,在均匀采样中是以半径为r的球体。选取距离球体中心最近的点作为采样点替代落在球体中的点集,此时,采样点的坐标为源点云中一点的坐标,不同于体素下采样中使用质心进行替代而产生新的点坐标。可以修改球体半径r,实现对采样点云稀疏程度的控制。
均匀采样是指每隔固定的点数采样一次。样本按点的顺序执行,始终选择从第 1 个点开始,而不是随机选择。显然点存储的顺序不同,得到的结果也会不一样。从这个角度来看,这种方法比较适合有序点云的降采样。这种方法适合均匀采集到的点云,如果点云本身不均匀,那么以固定点数采样很有可能造成某一部分的点云没被采样到。相比于体素的采样方法,点云均匀采样后的点数是固定可控的,而体素采样后的点云数量是不可控的。
最远点采样是较为简单的一种,首先需要选取一个种子点,并设置一个内点集合,每次从点云中不属于内点的集合找出一点距离内点最远的点,如下图,这里的距离计算方式为该点至内点所有点的最小距离。这种方式的下采样点云分布均匀,但是算法复杂度较高效率低。
     
   
    
    
    2.2.2 特点
   
- 分布很均匀
- 不会移动源点云,准确度较高
- 时间复杂度偏高。可以采用分治的方法提高效率
    
    
    2.3 几何采样(曲率下采样)
   
    
    
    2.3.1 原理
   
以点云的几何特征作为采样依据,以曲率为例。在点云中任意一点都存在某曲面,曲率示意图如下所示(密切圆半径r的倒数为曲率),曲率越大,弧的弯曲程度越大,表示该地方的特征点越多,故在点云曲率越大的地方,采样点数越多。
     
   
    
    
    2.3.2 流程
   
1)首先计算每个点的K领域,然后计算点到领域点的法线夹角值,以此来近似达到曲率的效果并提高计算效率,因为曲率越大的地方,夹角值越大。
2)设置一个角度阈值,当点的领域夹角值大于阈值时被认为是特征明显的区域,其余区域为不明显区域。
    3)对明显和不明显区域进行均匀采样,采样数分别为
    
     
      
       U 
(
1
−
V
)
        U(1-V)
      
      
       
        
        
        
         U
        
        
         (
        
        
         1
        
        
        
        
         −
        
        
        
       
       
        
        
        
         V
        
        
         )
        
       
      
     
    
    和$UV $, $ U$是目标采样数,V是均匀采样性。
   
    
    
    2.3.3特点
   
- 几何特征越明显的区域,采样个数分布越多(曲率越大,采样点数越多)
- 效率高
- 采样点局部均匀分布
- 稳定性高:通过几何特征区域的划分,使得采样结果抗噪性更强
    
    
    2.4 随机下采样
   
原理简单,顾名思义,指定采样点个数,进行随机点去除进行采样操作。
    
    
    2.4.1 特点
   
- 能控制采样点个数
- 随机性太强
    
    
    3 PCL 下采样实践
   
    
    
    3.1 体素网格下采样
   
    在PCL库中,我们可以使用
    
     pcl::VoxelGrid<pcl::PointXYZ>
    
    将空间点云体素化,得到对应的体素网格,再进行体素下采样。
   
关键代码:
pcl::VoxelGrid<pcl::PointXYZ> sor;		// 体素化网格
sor.setInputCloud(cloud);							
sor.setLeafSize(0.01f, 0.01f, 0.01f);   // 设置体素栅格的大小为 1x1x1cm
sor.filter(*cloud_filtered);
测试代码如下:
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/visualization/pcl_visualizer.h>
int main(int argc, char** argv)
{
    //pcl::PCLPointCloud2::Ptr cloud(new pcl::PCLPointCloud2());
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    //pcl::PCLPointCloud2::Ptr cloud_filtered(new pcl::PCLPointCloud2());
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
    // Fill in the cloud data
    pcl::PCDReader reader;
    // Replace the path below with the path where you saved your file
    reader.read("table_scene_lms400.pcd", *cloud); // Remember to download the file first!
    std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height
        << " data points (" << pcl::getFieldsList(*cloud) << ").";
    // Create the filtering object
    pcl::VoxelGrid<pcl::PointXYZ> sor;
    sor.setInputCloud(cloud);
    sor.setLeafSize(0.01f, 0.01f, 0.01f);
    sor.filter(*cloud_filtered);
    std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height
        << " data points (" << pcl::getFieldsList(*cloud_filtered) << ").";
    //pcl::PCDWriter writer;
    //writer.write("table_scene_lms400_downsampled.pcd", *cloud_filtered,
        //Eigen::Vector4f::Zero(), Eigen::Quaternionf::Identity(), false);
    pcl::visualization::PCLVisualizer viewer("demo");
    int v1(0);
    int v2(1);
    viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1); 
    viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
    // The color we will be using
    float bckgr_gray_level = 0.0;  // Black
    float txt_gray_lvl = 1.0 - bckgr_gray_level;
    // Original point cloud is white
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud_in_color_h(cloud, (int)255 * txt_gray_lvl, (int)255 * txt_gray_lvl, (int)255 * txt_gray_lvl);
    viewer.addPointCloud(cloud, cloud_in_color_h, "cloud_in_v1", v1);       //viewer.addPointCloud(cloud_in, cloud_in_color_h, "cloud_in_v2", v2);
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud_out_green(cloud_filtered, 20, 180, 20);
    viewer.addPointCloud(cloud_filtered, cloud_out_green, "cloud_out", v2); 
    viewer.setBackgroundColor(bckgr_gray_level, bckgr_gray_level, bckgr_gray_level, v2);     
    viewer.setSize(1280, 1024);  // Visualiser window size
    //viewer.showCloud(cloud_out);
    while (!viewer.wasStopped())
    {
        viewer.spinOnce();
    }
    return (0);
}
将官方源码中的
pcl::PCLPointCloud2::Ptr
替换成
pcl::PointCloud<pcl::PointXYZ>::Ptr
,更加方便点云的图形显示。
测试结果如下:
setLeafSize(0.01f, 0.01f, 0.01f)
    
     [460400-->41049]
    
   
setLeafSize(0.1f, 0.1f, 0.1f)
    
     [460400-->629]
    
   
    
    
    3.2 均匀下采样
   
对点云数据创建一个三维体素栅格,然后取每个体素中最接近体素中心的点,代替体素中所有点。
关键代码:
// Uniform sampling object
pcl::UniformSampling<pcl::PointXYZ> filter;		// 创建均匀采样对象
filter.setInputCloud(cloud);					// 设置待采样点云
filter.setRadiusSearch(0.01f);					// 设置采样半径
filter.filter(*cloud_filtered);					// 执行均匀采样,结果保存在cloud_filtered中
测试结果:
   
    
    
    参考
   
    [1] https://blog.csdn.net/suiyingy/article/details/124368059
    
    [2] https://zhuanlan.zhihu.com/p/353761080
    
    [3] https://blog.csdn.net/yanfeng1022/article/details/109322887
    
    [4] https://blog.csdn.net/u013351270/article/details/68945056
   
 
