【PCL】—— 点云滤波

在获取点云数据时,由于设备精度,操作者经验环境因素带来的影响,以及电磁波的衍射特性,被测物体表面性质变化和数据拼接配准操作过程的影响,点云数据中将不可避免的出现一些噪声。

几种需要进行点云滤波处理情况如下:

  • 点云数据密度不规则需要平滑;
  • 因为遮挡等问题造成离群点需要去除;
  • 大量数据需要下采样;
  • 噪声数据需要去除。

PCL常规滤波手段均进行了很好的封装,对点云的滤波通过调用各个滤波器对象来完成。主要的滤波器有直通滤波器体素格滤波器统计滤波器半径滤波器等。

常用方法如下:

  • 使用直通滤波器对点云进行滤波处理
  • 使用体素滤波对点云进行下采样
  • 使用统计滤波器移除离群点
  • 使用条件滤波或半径滤波移除离群点

直通滤波

直通滤波器就是根据点云的属性(属性比如x,y,z,颜色值等),在点的属性上设置范围,对点进行滤波,保留范围内的或保留范围外的。

官网教程——https://pcl.readthedocs.io/projects/tutorials/en/latest/passthrough.html#passthrough
测试案例1(保留z轴范围内0-1m内的点云)

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/visualization/cloud_viewer.h>

typedef pcl::PointXYZ PointT;

int
main(int argc, char **argv) {
    
    
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

    //写入点云
    cloud->width = 5;
    cloud->height = 1;
    cloud->points.resize(cloud->width * cloud->height);

    for (size_t i = 0; i < cloud->points.size(); ++i) {
    
    
        cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
        cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
        cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
    }

    std::cerr << "Cloud before filtering: " << std::endl;
    for (size_t i = 0; i < cloud->points.size(); ++i)
        std::cerr << "    " << cloud->points[i].x << " "
                  << cloud->points[i].y << " "
                  << cloud->points[i].z << std::endl;

    // Create the filtering object
    pcl::PassThrough<pcl::PointXYZ> pass;
    pass.setInputCloud(cloud);          // 1. 设置输入源
    pass.setFilterFieldName("z");       // 2. 设置过滤域名
    pass.setFilterLimits(0.0, 1.0);     // 3. 设置过滤范围
//    pass.setFilterLimitsNegative(true); // 设置获取Limits之外的内容
    pass.filter(*cloud_filtered);       // 4. 执行过滤,并将结果输出到cloud_filtered

    std::cerr << "Cloud after filtering: " << std::endl;
    for (size_t i = 0; i < cloud_filtered->points.size(); ++i)
        std::cerr << "    " << cloud_filtered->points[i].x << " "
                  << cloud_filtered->points[i].y << " "
                  << cloud_filtered->points[i].z << std::endl;

    pcl::visualization::CloudViewer viewer("Cloud Viewer");

    //这里会一直阻塞直到点云被渲染
    viewer.showCloud(cloud);
    while (!viewer.wasStopped()) {
    
    
    }
    return (0);
}

执行程序之后,会有如下结果

Cloud before filtering:
    0.352222 -0.151883 -0.106395
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304
    -0.734766 0.854581 -0.0361733
    -0.4607 -0.277468 -0.916762
Cloud after filtering:
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304

过滤过程的图形显示如下所示,其中坐标系中红色为(x)、绿色为(y)、蓝色为(z)。图中的五个点中绿色的点为被过滤后的点,红色的点为被过滤掉的点。
在这里插入图片描述

测试案例2

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/passthrough.h>
#include <pcl/visualization/cloud_viewer.h>

int
main(int argc, char **argv) {
    
    
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZI>);

    // 加载pcd文件到cloud
    if (pcl::io::loadPCDFile("../data/result.pcd", *cloud) == -1)
    {
    
    
        std::cout << "Could not load pcd file!" << std::endl;
        return -1;
    }

    // Create the filtering object
    pcl::PassThrough<pcl::PointXYZI> pass;
    pass.setInputCloud(cloud);          // 1. 设置输入源
    pass.setFilterFieldName("z");       // 2. 设置过滤域名
    pass.setFilterLimits(-1.0, 1.0);     // 3. 设置过滤范围
    // pass.setFilterLimitsNegative(true); // 设置获取Limits之外的内容
    pass.filter(*cloud_filtered);       // 4. 执行过滤,并将结果输出到cloud_filtered

    pcl::visualization::PCLVisualizer viewer_filter("Cloud-Filtered Viewer");
    //背景颜色设置
    viewer_filter.setBackgroundColor(0, 0, 0);
    //按照z字段进行渲染
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor_filter(cloud_filtered, "intensity");
    //显示点云,其中fildColor为颜色显示
    viewer_filter.addPointCloud<pcl::PointXYZI>(cloud_filtered, fildColor_filter, "sample");
    //设置点云大小
    viewer_filter.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample");
    // // 循环判断是否退出
    // while (!viewer_filter.wasStopped()) {
    
    
    //     viewer_filter.spinOnce();
    // }

    pcl::visualization::PCLVisualizer viewer("Cloud Viewer");
    //背景颜色设置
    viewer.setBackgroundColor(0, 0, 0);
    //按照z字段进行渲染
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor(cloud, "intensity");
    //显示点云,其中fildColor为颜色显示
    viewer.addPointCloud<pcl::PointXYZI>(cloud, fildColor, "sample");
    //设置点云大小
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample");
    
    // 循环判断是否退出
    while (!viewer.wasStopped() && !viewer_filter.wasStopped()) {
    
    
        viewer_filter.spinOnce();
        viewer.spinOnce();
    }
    return 0;
}

测试效果

在这里插入图片描述

降采样

体素法滤波,即减少点的数量,减少点云数据,并同时保持点云的形状特征,在提高配准、曲面重建、形状识别等算法速度中非常实用。

PCL实现的VoxelGrid类通过输入的点云数据创建一个三维体素栅格(可把体素栅格想象为微小的空间三维立方体的集合),然后在每个体素(即三维立方体)内,用体素中所有点的重心来近似显示体素中其他点,这样该体素内所有点就用一个重心点最终表示,对于所有体素处理后得到过滤后的点云。

在这里插入图片描述

官方教程—— https://pcl-tutorials.readthedocs.io/en/latest/voxel_grid.html#voxelgrid

#include <iostream>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/visualization/cloud_viewer.h>

int main (int argc, char** argv)
{
    
    
  pcl::PCLPointCloud2::Ptr cloud (new pcl::PCLPointCloud2 ());
  pcl::PCLPointCloud2::Ptr cloud_filtered (new pcl::PCLPointCloud2 ());

  // Fill in the cloud data
  pcl::PCDReader reader; // 也可以用创建reader的方式来读PCD文件
  // Replace the path below with the path where you saved your file
  reader.read ("../data/result.pcd", *cloud); // Remember to download the file first!

  std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height 
       << " data points (" << pcl::getFieldsList (*cloud) << ")." << std::endl;

  // Create the filtering object
  pcl::VoxelGrid<pcl::PCLPointCloud2> sor;
  sor.setInputCloud (cloud);
  sor.setLeafSize (0.5f, 0.5f, 0.5f);
  sor.filter (*cloud_filtered);

  std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height 
       << " data points (" << pcl::getFieldsList (*cloud_filtered) << ")." << std::endl;

  pcl::PCDWriter writer;
  writer.write ("../data/result_downsampled.pcd", *cloud_filtered, 
         Eigen::Vector4f::Zero (), Eigen::Quaternionf::Identity (), false);

  return (0);
}
PointCloud before filtering: 98322 data points (x y z intensity).
PointCloud after filtering: 11733 data points (x y z intensity).

pcl_viewer查看效果,点云数目明显减少。

在这里插入图片描述

使用统计滤波(statisticalOutlierRemoval)移除离群点

激光扫描通常会产生密度不均匀的点云数据集,另外测量中的误差也会产生稀疏的离群点,局部点云特征运算(如采样点处法向量或曲率变化率)复杂,容易导致点云配准等后期处理失败。

常用解决思路:每个点的邻域进行一个统计分析,并修剪掉一些不符合一定标准的点。假设得到的结果是一个高斯分布,其形状是由均值和标准差决定,则平均距离在标准范围之外的点,可以被定义为离群点并可从数据中去除。

在这里插入图片描述
统计滤波移除离群点的步骤如下:

  1. 查找每一个点的所有邻域点
  2. 计算每个点到其邻域点的距离 d i j d_{ij} dij,其中 i = [ 1 , 2 , . . . , m ] i=[1,2,...,m] i=[1,2,...,m]表示共有 m m m个点, j = [ 1 , 2 , . . . , k ] j=[1,2,...,k] j=[1,2,...,k]表示共有 k k k个邻域点
  3. 根据高斯分布 d   N ( μ , σ ) d~N(\mu,\sigma) d N(μ,σ)模型化距离参数,计算所有点与邻域点的 μ \mu μ(距离的均值), σ \sigma σ(距离的标准差),如下: μ = 1 n k ∑ i = 1 m ∑ j = 1 k d i j \mu {\rm{ = }}\frac{ {\rm{1}}}{ {nk}}\sum\limits_{i = 1}^m {\sum\limits_{j = 1}^k { {d_{ij}}} } μ=nk1i=1mj=1kdij σ = 1 n k ∑ i = 1 m ∑ j = 1 k ( d i j − μ ) 2 \sigma {\rm{ = }}\sqrt {\frac{ {\rm{1}}}{ {nk}}\sum\limits_{i = 1}^m {\sum\limits_{j = 1}^k { { {({d_{ij}} - \mu )}^2}} } } σ=nk1i=1mj=1k(dijμ)2 在此步骤时,同时也把每个点与其邻域点的距离均值求出 ∑ j = 1 k d i j {\sum\limits_{j = 1}^k { { { {d_{ij}} }}} } j=1kdij
    遍历所有点,若其距离的均值大于高斯分布的指定置信度,则移除,例如: ∑ j = 1 k d i j > μ + 3 σ o r ∑ j = 1 k d i j < μ − 3 σ \sum\limits_{j = 1}^k { {d_{ij}}} > \mu + 3\sigma or\sum\limits_{j = 1}^k { {d_{ij}}} < \mu - 3\sigma j=1kdij>μ+3σorj=1kdij<μ3σ
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>

int main (int argc, char** argv)
{
    
    
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZI>);

    // 从文件读取点云
    // Fill in the cloud data
    pcl::PCDReader reader;
    // Replace the path below with the path where you saved your file
    reader.read<pcl::PointXYZI> ("../data/result.pcd", *cloud);

    std::cerr << "Cloud before filtering: " << std::endl;
    std::cerr << *cloud << std::endl;

    // 创建过滤器,每个点分析计算时考虑的最近邻居个数为50个;
    // 设置标准差阈值为1,这意味着所有距离查询点的平均距离的标准偏差均大于1个标准偏差的所有点都将被标记为离群值并删除。
    // 计算输出并将其存储在cloud_filtered中

    // Create the filtering object
    pcl::StatisticalOutlierRemoval<pcl::PointXYZI> sor;
    sor.setInputCloud (cloud);
    // 设置平均距离估计的最近邻居的数量K
    sor.setMeanK (50);
    // 设置标准差阈值系数
    sor.setStddevMulThresh (1.0);
    // 执行过滤
    sor.filter (*cloud_filtered);

    std::cerr << "Cloud after filtering: " << std::endl;
    std::cerr << *cloud_filtered << std::endl;
    // 将留下来的点保存到后缀为_inliers.pcd的文件
    pcl::PCDWriter writer;
    writer.write<pcl::PointXYZI> ("../data/result_inliers.pcd", *cloud_filtered, false);

    // 使用个相同的过滤器,但是对输出结果取反,则得到那些被过滤掉的点,保存到_outliers.pcd文件
    sor.setNegative (true);
    sor.filter (*cloud_filtered);
    writer.write<pcl::PointXYZI> ("../data/result_outliers.pcd", *cloud_filtered, false);

    return 0;
}

执行程序,输出以下结果

Cloud before filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 98322
width: 98322
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Cloud after filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 90637
width: 90637
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

用pcl_viwer查看
在这里插入图片描述

使用条件滤波(ConditionalRemoval)或 半径滤波(RadiusOutlinerRemoval)移除离群点

条件滤波器:通过设定滤波条件进行滤波,有点分段函数的味道,当点云在一定范围则留下,不在则舍弃。

半径滤波器:以某点为中心画一个圆计算落在该圆中点的数量,当数量大于给定值时,则保留该点,数量小于给定值则剔除该点。此算法运行速度快,依序迭代留下的点一定是最密集的,但是圆的半径和圆内点的数目都需要人工指定。

下图有助于可视化RadiusOutlierRemoval过滤器对象的作用。用户指定邻居的个数,要每个点必须在指定半径内具有指定个邻居才能保留在PointCloud中。例如,如果指定了1个邻居,则只会从PointCloud中删除黄点。如果指定了2个邻居,则黄色和绿色的点都将从PointCloud中删除。

在这里插入图片描述

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/visualization/pcl_visualizer.h>

void showPointClouds(const pcl::PointCloud<pcl::PointXYZI>::Ptr &cloud){
    
    
    pcl::visualization::PCLVisualizer viewer("Cloud Viewer");
    //背景颜色设置
    viewer.setBackgroundColor(0, 0, 0);
    //按照z字段进行渲染
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor(cloud, "intensity");
    //显示点云,其中fildColor为颜色显示
    viewer.addPointCloud<pcl::PointXYZI>(cloud, fildColor, "sample");
    //设置点云大小
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample");

    while (!viewer.wasStopped())
    {
    
    
        viewer.spinOnce();
    }
    
}

int main(int argc, char **argv) {
    
    
    if (argc != 2)
    {
    
    
        //选择-r为半径滤波;选择-c为条件滤波
        std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
        exit(0);
    }
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZI>);

    // 从文件读取点云
    // Fill in the cloud data
    pcl::PCDReader reader;
    // Replace the path below with the path where you saved your file
    reader.read<pcl::PointXYZI> ("../data/result.pcd", *cloud);

    if (strcmp(argv[1], "-r") == 0) 
    {
    
    
        //半径滤波
        pcl::RadiusOutlierRemoval<pcl::PointXYZI> outrem;
        // build the filter
        outrem.setInputCloud(cloud);
        outrem.setRadiusSearch(0.4); //设置半径滤波时圆的半径
        outrem.setMinNeighborsInRadius(2); //设置圆内的点数
        // apply filter
        outrem.filter(*cloud_filtered);

        //保存相应pcd文件,以方便后续查看
        pcl::PCDWriter writer;
        writer.write<pcl::PointXYZI>("../data/result_remove_outliers_r.pcd", *cloud_filtered, false);
    } 
    else if (strcmp(argv[1], "-c") == 0) 
    {
    
    
        //条件滤波
        // build the condition
        // 过滤条件:z轴方向上,大于0.0,小于8.0
        pcl::ConditionAnd<pcl::PointXYZI>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZI>());
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZI>::ConstPtr(
                new pcl::FieldComparison<pcl::PointXYZI>("z", pcl::ComparisonOps::GT, 0.0)));
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZI>::ConstPtr(
                new pcl::FieldComparison<pcl::PointXYZI>("z", pcl::ComparisonOps::LT, 0.8)));

        // build the filter
        pcl::ConditionalRemoval<pcl::PointXYZI> condrem;
        condrem.setCondition(range_cond);
        condrem.setInputCloud(cloud);
        condrem.setKeepOrganized(false);
        // apply filter
        condrem.filter(*cloud_filtered);

        //保存相应pcd文件,以方便后续查看
        pcl::PCDWriter writer;
        writer.write<pcl::PointXYZI>("../data/result_remove_outliers_c.pcd", *cloud_filtered, false);
    } 
    else 
    {
    
    
        std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
        exit(0);
    }


    std::cerr << "Cloud before filtering: " << std::endl;
    std::cerr << *cloud << std::endl;

    // display pointcloud after filtering
    std::cerr << "Cloud after filtering: " << std::endl;
    std::cerr << *cloud_filtered << std::endl;

    showPointClouds(cloud_filtered);
    return (0);
}

半径滤波

Cloud before filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 98322
width: 98322
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Cloud after filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 95967
width: 95967
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

在这里插入图片描述

条件滤波

Cloud before filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 98322
width: 98322
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Cloud after filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 9904
width: 9904
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/sinat_52032317/article/details/130435585