PCL学习六:Filtering-滤波

参考引用

  • Point Cloud Library
  • 黑马机器人 | PCL-3D点云

1. 点云滤波概述

1.1 背景

  • 在获取点云数据时,由于设备精度、操作者经验、环境因素等带来的影响,以及电磁波衍射特性、被测物体表面性质变化和数据拼接配准操作过程的影响,点云数据中将不可避免地出现一些噪声点
  • 实际应用中除了这些测量随机误差产生的噪声点之外,由于受到外界干扰如:视线遮挡、障碍物等因素的影响,点云数据中往往存在着一些离主体点云较远的离群点,不同的获取设备点云噪声结构也有不同

1.2 解决方法

  • 通过滤波实现孔洞修复、最小信息损失的海量点云数据压缩处理等
    • 在点云处理流程中滤波处理作为预处理的第一步,往往对后续处理流程影响很大,只有在滤波预处理中将噪声点、离群点、孔洞、数据压缩等按照后续需求处理,才能够更好地进行配准、特征提取、曲面重建、可视化等后续流程
    • PCL 中点云滤波模块提供了很多灵活实用的滤波处理算法,例如:双边滤波、高斯滤波、条件滤波、直通滤波、基于随机采样一致性滤波 RANSAC 等

1.3 应用场景

  • 点云数据密度不规则需要平滑处理
  • 去除因为遮挡等问题造成的离群点
  • 数据量较大,需要进行下采样(Downsample)
  • 去除噪声数据

PCL 点云格式分为有序点云和无序点云

  • 一般深度相机采集到的点云的数据是有序点云,针对有序点云提供了双边滤波、高斯滤波、中值滤波
  • 激光雷达采集的点云的数据是无序点云,针对无序点云提供了体素栅格、随机采样

1.4 示例

  • 下图显示了一个去除噪声数据的示例
    • 由于测量误差,某些数据集会出现大量阴影点。这使局部点云 3D 特征的估算变得复杂。通过对每个点的邻域进行统计分析,并修剪掉不符合特定条件的异常值,进而过滤掉某些异常值
    • PCL 中实现稀疏离群值的消除,需要计算数据集中的点与邻居距离的分布。即对于每个点,都会计算从它到所有相邻点的平均距离。通过假设结果分布是具有均值和标准差的高斯分布,可以将那些平均距离在(由全局距离均值和标准差定义的区间)之外的所有点视为离群值,并将之从数据集中进行修剪
      在这里插入图片描述

2. 高斯滤波

  • 使用高斯卷积核对图片进行平滑(模糊)处理,是一种常见的线性图片过滤技术。每一个输出图片中的像素点都是其输入图片中周围邻居像素值的加权求和结果。其核心就是一个核函数的卷积操作,对图片进行低通滤波。高斯模糊(Gaussian blur / GB)图片滤波器定义如下
    G B [ I ] p = ∑ q ∈ S G σ ( ∥ p − q ∥ ) I q GB[I]_{\mathbf{p}}=\sum\limits_{\mathbf{q}\in\mathcal{S}}G_{\sigma}(\|\mathbf{p}-\mathbf{q}\|)I_{\mathbf{q}} GB[I]p=qSGσ(pq)Iq

    • 这里 G σ ( x ) G_σ(x) Gσ(x) 表示二维的高斯卷积核
      G σ ( x ) = 1 2 π σ 2 exp ⁡ ( − x 2 2 σ 2 ) G_\sigma(x)=\frac{1}{2\pi\sigma^2}\exp\left(-\frac{x^2}{2\sigma^2}\right) Gσ(x)=2πσ21exp(2σ2x2)
  • 高斯滤波是求相邻位置强度的加权平均值,其权值随到中心位置 p 的空间距离减小而减小。点 q 中心像素 p 的权重通过高斯分布 G σ ( ∥ p − q ∥ ) G_{\sigma}(\|\mathbf{p}-\mathbf{q}\|) Gσ(pq) 描述,这里的 σ 参数定义邻域的大小,也就是卷积核窗体大小, ∥ p − q ∥ \|\mathbf{p-q}\| pq 为两个向量差的范数,即其有向线段的长度。这种影响的强度只取决于像素之间的空间距离,而不是它们的绝对位置值。例如,一个亮像素对相邻的暗像素有很大的影响,尽管这两个像素值差异很大

  • 下图是在不同标准差 σ 时的高斯线性滤波,越大的 σ 边缘模糊的更厉害,因为其平均值是通过更大的范围计算出来的
    在这里插入图片描述

3. 双边滤波

双边滤波算法,是通过取邻近采样点的加权平均来修正当前采样点的位置,从而达到滤波效果。同时也会有选择地剔除部分与当前采样点 “差异” 太大的相邻采样点,从而达到保持原特征的目的

  • 双边滤波可以保留边缘信息,其实质也是计算邻居像素的加权平均和,非常类似于高斯卷积。不同之处在于双边滤波器在平滑的同时考虑到与邻边像素颜色值的差异,进而保留边缘信息。双边滤波器的关键思想是一个像素对另一个像素影响程度,不应该只和位置距离有关,还应该具有相似的像素颜色值,因此双边滤波器是一种非线性滤波器

  • 相等距离情况下,颜色值接近的像素点权重应当高一些,颜色值差异大的像素点权重应当小一些。于是,双边滤波 bilateral filter(BF)的定义如下
    B F [ I ] p = 1 W p ∑ q ∈ S G σ s ( ∥ p − q ∥ ) G σ r ( ∣ I p − I q ∣ ) I q BF[I]_{\mathbf{p}}=\frac{1}{W_{\mathbf{p}}}\sum\limits_{\mathbf{q}\in\mathcal{S}}G_{\sigma_s}(\|\mathbf{p}-\mathbf{q}\|)G_{\sigma_r}\left(|I_{\mathbf{p}}-I_{\mathbf{q}}|\right)I_{\mathbf{q}} BF[I]p=Wp1qSGσs(pq)Gσr(IpIq)Iq

    • 这里通过归一化因子 W p W_p Wp 保证像素的权重和为 1.0
      W p = ∑ q ∈ S G σ s ( ∥ p − q ∥ ) G σ r ( ∣ I p − I q ∣ ) W_{\mathbf{p}}=\sum_{\mathbf{q}\in\mathcal{S}}G_{\sigma_\mathbf{s}}(\|\mathbf{p}-\mathbf{q}\|)G_{\sigma_\mathbf{r}}(|I_\mathbf{p}-I_\mathbf{q}|) Wp=qSGσs(pq)Gσr(IpIq)
  • 双边滤波里的两个权重域的概念

    • 空间域(spatial domain S)和像素范围域(range domain R)
      • 双边滤波的核函数是空间域核与像素范围域核的综合结果
  • 综合结论

    • 在图像的平坦区域,像素值变化很小,对应的像素范围域权重接近于 1,此时空间域权重起主要作用,相当于进行高斯模糊
    • 在图像的边缘区域,像素值变化很大,像素范围域权重变大,从而保持了边缘的信息
  • 两个权重对图像的影响
    在这里插入图片描述

由于点云本身是稀疏且不连贯的,所以通过双边滤波对点云的 RGB 图做上采样后,将点云对应到 RGB 图,可以得到边缘更完整清晰的 3D 点云。BF 为双边滤波,MED 为中值滤波,AVE 为均值滤波

在这里插入图片描述

4. PassThrough Filter(直通滤波器)

  • 直通滤波算作最为简单、粗暴的一种滤波方式,就是直接对点云的 X、Y、Z 轴的点云坐标约束来进行滤波,可以约束只在 Z 轴,或者 XYZ 三个坐标轴共同约束来达到点云滤波效果

  • passthrough.cpp

    #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;
        }
    
        // 从外部导入 pcd 点云文件
        //pcl::PCDReader reader;
        //reader.read("xxx.pcd", *cloud);
    
        // 创建滤波对象:将点云中 Z 坐标在(0,1)范围外的点过滤掉
        pcl::PassThrough<pcl::PointXYZ> pass;
        pass.setInputCloud(cloud);          // 1. 设置输入点云
        pass.setFilterFieldName("z");       // 2. 设置过滤时所需要点云类型的Z字段
        pass.setFilterLimits(0.0, 1.0);     // 3. 设置在过滤字段的范围
        // pass.setFilterLimitsNegative(true); // 设置保留范围内还是过滤掉范围内,是否保存滤波的限制范围内的点云,默认为false,保存限制范围内点云
        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::PCDWriter writer;
        //writer.write("xxx_filtered.pcd", *cloud_filtered, false);
        
        // 点云可视化
        pcl::visualization::CloudViewer viewer("Cloud Viewer");
    
        // 这里会一直阻塞直到点云被渲染
        viewer.showCloud(cloud);
        while (!viewer.wasStopped()) {
        }
        return (0);
    }
    
    // 不仅限于对单一坐标轴的过滤,其实主要就是再一次进行目标坐标轴过滤即可
    // 通过重复使用直通滤波就可以进行三维区间的滤波
    // filter range X-axis
    pcl::PassThrough<pcl::PointXYZ> pass;
    pass.setInputCloud(cloud);
    pass.setFilterFieldName("x");
    pass.setFilterLimits(-5.0, 5.0);
    // pass.setFilterLimitsNegative(true);
    pass.filter(*cloud_filtered2);
    
    // filter range Y-axis
    pass.setInputCloud(cloud_filtered2);
    pass.setFilterFieldName("y");
    pass.setFilterLimits(-5.0, 5.0);
    pass.filter(*cloud_filtered3);
    
    // filter range Z-axis
    pass.setInputCloud(cloud_filtered3);
    pass.setFilterFieldName("z");
    pass.setFilterLimits(-0.5, 3.0);
    pass.filter(*cloud_filtered);
    
  • 配置文件 CMakeLists.txt

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    project(passthrough)
    
    find_package(PCL 1.2 REQUIRED)
    
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    add_definitions(${PCL_DEFINITIONS})
    
    add_executable(passthrough passthrough.cpp)
    target_link_libraries(passthrough ${PCL_LIBRARIES})
    
  • 编译并执行

    $ mkdir build
    $ cd build
    $ cmake ..
    $ make
    
    $ ./passthrough
    
    // 输出结果:下图中绿色表示为滤波后剩余的点,红色表示为已被滤波器去除的点
    // 如果使用 pass.setFilterLimitsNegative (true);,则以下结果取反
    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
    

在这里插入图片描述

5. VoxelGrid filter(体素网格滤波器)

  • 通过体素网格滤波器实现点云降采样,减少点数量的同时保证点云的形状特征,可以提高配准、曲面重建、形状识别等算法的速度,并保证准确性

  • PCL 是实现的 VoxelGrid 类通过输入的点云数据创建一个三维体素栅格,容纳后每个体素内用体素中所有点的重心来近似显示体素中其他点,这样该体素内所有点都用一个重心点最终表示,对于所有体素处理后得到的过滤后的点云,这种方法比用体素中心逼近的方法更慢,但是对于采样点对应曲面的表示更为准确

  • voxel_grid.cpp

    #include <iostream>
    #include <pcl/io/pcd_io.h>
    #include <pcl/point_types.h>
    #include <pcl/filters/voxel_grid.h>
    
    int main (int argc, char** argv) {
        pcl::PCLPointCloud2::Ptr cloud(new pcl::PCLPointCloud2());
        pcl::PCLPointCloud2::Ptr cloud_filtered(new pcl::PCLPointCloud2());
    
        // 从文件读取点云图
        pcl::PCDReader reader;
        reader.read ("../data/table_scene_lms400.pcd", *cloud); // 改为自己的 pcd 文件路径
    
        std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height 
                  << " data points (" << pcl::getFieldsList(*cloud) << ")." << std::endl;
    
        // 创建一个长宽高分别是 1cm 的体素过滤器,cloud作为输入数据,cloud_filtered作为输出数据
        float leftSize = 0.01f;
        pcl::VoxelGrid<pcl::PCLPointCloud2> sor;
        sor.setInputCloud(cloud);
        sor.setLeafSize(leftSize, leftSize, leftSize);
        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/table_scene_lms400_downsampled.pcd", *cloud_filtered);
    
        return (0);
    }
    
  • 配置文件 CMakeLists.txt

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    project(voxel_grid)
    
    find_package(PCL 1.2 REQUIRED)
    
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    add_definitions(${PCL_DEFINITIONS})
    
    add_executable(voxel_grid voxel_grid.cpp)
    target_link_libraries(voxel_grid ${PCL_LIBRARIES})
    
  • 编译并执行

    $ mkdir build
    $ cd build
    $ cmake ..
    $ make
    
    $ ./voxel_grid ../data/table_scene_lms400.pcd
    
    // 输出结果
    PointCloud before filtering: 460400 data points (x y z intensity distance sid).
    PointCloud after filtering: 41049 data points (x y z intensity distance sid).
    
    # 对比查看滤波前后的 pcd 文件(见下图)
    $ pcl_viewer -multiview 1 ../data/table_scene_lms400.pcd ../data/table_scene_lms400_downsampled.pcd
    

在这里插入图片描述

6. 离群点移除

  • 激光扫描通常会生成不同点密度的点云数据集。此外,测量误差会导致稀疏的异常值,从而进一步破坏结果。这会使局部点云特征(例如表面法线或曲率变化)的估计复杂化,从而导致错误的值,进而可能导致点云配准失败。通过对每个点的邻域进行统计分析,并对不符合特定条件的部分进行修整,可以解决其中一些不规则现象
  • 稀疏离群值的消除基于输入数据集中点到邻居距离的分布的计算。对于每个点,计算从它到所有相邻点的平均距离。通过假设结果分布是具有均值和标准差的高斯分布,可以将其平均距离在由全局距离均值和标准差定义的区间之外的所有点视为离群值并从数据集中进行修剪

6.1 StatisticalOutlierRemoval(统计学离群点移除过滤器)

  • 实现步骤

    • 查找每一个点的所有邻域点
    • 计算每个点到其邻居的距离 d i j d_{ij} dij
      • 其中 i = [ 1 , . . . , m ] i = [1,...,m] i=[1,...,m] 表示共 m 个点, j = [ 1 , . . . , k ] j=[1,...,k] j=[1,...,k] 表示每个点有k个邻居
    • 根据高斯分布 d ∼ N ( μ , σ ) d\sim N(\mu,\sigma) dN(μ,σ) 模型化距离参数,计算所有点与邻居的 μ \mu μ(距离的均值)与 σ \sigma σ(距离的标准差)
      μ = 1 n k ∑ i = 1 m ∑ j = 1 k d i j , σ = 1 n k ∑ i = 1 m ∑ j = 1 k ( d i j − μ ) 2 \mu=\frac{1}{nk}\sum_{i=1}^m\sum_{j=1}^k d_{ij},\\ \sigma=\sqrt{\frac{1}{nk}\sum_{i=1}^m\sum_{j=1}^k\left(d_{ij}-\mu\right)^2} μ=nk1i=1mj=1kdij,σ=nk1i=1mj=1k(dijμ)2
    • 为每一个点,计算其与邻居的距离均值 ∑ j = 1 k d i j \sum_{j=1}^{k}d_{i j} j=1kdij
    • 遍历所有点,如果其距离的均值大于高斯分布的指定置信度,则移除
  • statistical_removal.cpp

#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::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);

    // 从文件读取点云
    pcl::PCDReader reader;
    reader.read<pcl::PointXYZ> ("../data/table_scene_lms400.pcd", *cloud);

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

    // 创建过滤器,每个点分析计算时考虑的最近邻居个数为 50 个
    // 设置标准差阈值为 1,意味着所有距离查询点的平均距离的标准偏差 均大于 1 个标准偏差的所有点 都将被标记为离群值并删除
    // 计算输出并将其存储在 cloud_filtered 中
    pcl::StatisticalOutlierRemoval<pcl::PointXYZ> 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::PointXYZ> ("../data/table_scene_lms400_inliers.pcd", *cloud_filtered, false);

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

    return (0);
}
  • 配置文件 CMakeLists.txt

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    project(statistical_removal)
    
    find_package(PCL 1.2 REQUIRED)
    
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    add_definitions(${PCL_DEFINITIONS})
    
    add_executable (statistical_removal statistical_removal.cpp)
    target_link_libraries (statistical_removal ${PCL_LIBRARIES})
    
  • 编译并执行

    $ mkdir build
    $ cd build
    $ cmake ..
    $ make
    
    $ ./statistical_removal ../data/table_scene_lms400.pcd
    
    // 输出结果
    Cloud before filtering: 
    header: seq: 0 stamp: 0 frame_id: 
    
    points[]: 460400
    width: 460400
    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[]: 451410
    width: 451410
    height: 1
    is_dense: 1
    sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]
    
    # 下图一:黄绿色部分为移除的离群点,粉红色部分为保留的点
    $ pcl_viewer ../data/table_scene_lms400_inliers.pcd ../data/table_scene_lms400_outliers.pcd
    
    # 下图二:左图为已处理离群点后的点云,右图为被移除的离群点云
    $ pcl_viewer -multiview 1 ../data/table_scene_lms400_inliers.pcd ../data/table_scene_lms400_outliers.pcd
    

在这里插入图片描述

在这里插入图片描述

6.2 多滤波器方案

  • ConditionalRemoval 滤波器

    • 条件滤波,设置不同维度滤波规则进行滤波,删除给定输入点云中不满足一个或多个给定条件的所有索引
    • 该滤波器删除点云中不符合用户指定的一个或者多个条件的数据点
  • RadiusOutlinerRemoval 滤波器

    • 半径离群值滤波,删除其输入点云中在特定范围内至少没有一定数量的邻居的所有索引,在点云数据中,设定每个点一定范围内周围至少有足够多的近邻,不满足就会被删除
    • 在点云数据中,用户指定每个点的一定范围内周围至少要有足够多的近邻。例如下图,如果指定至少要有 1 个邻居,只有黄色的点会被删除,如果指定至少要有 2 个邻居,黄色和绿色的点都将被删除
      在这里插入图片描述
  • remove_outliers.cpp

#include <iostream>
#include <pcl/point_cloud.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/visualization/pcl_visualizer.h>

typedef pcl::PointXYZ PointType;

// 指针所指向的点云数据是一个常量,使用该指针访问和操作所指向的点云数据时,不能对其进行修改
// 这种指针类型通常用于传递指向点云数据的指针参数,以保证传递过程中数据不发生修改
void showPointClouds(const pcl::PointCloud<PointType>::Ptr &cloud, const pcl::PointCloud<PointType>::Ptr &cloud2) {
    pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));

    viewer->setBackgroundColor(0.05, 0.05, 0.05, 0);
    // 添加一个普通点云
    pcl::visualization::PointCloudColorHandlerCustom<PointType> single_color(cloud, 0, 255, 0);
    viewer->addPointCloud<PointType>(cloud, single_color, "sample cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");
    // 添加第二个点云
    pcl::visualization::PointCloudColorHandlerCustom<PointType> single_color2(cloud, 255, 0, 0);
    viewer->addPointCloud<PointType>(cloud2, single_color2, "sample cloud 2");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 4, "sample cloud 2");
    viewer->addCoordinateSystem(1.0);

    while (!viewer->wasStopped()) {
        viewer->spinOnce();
    }
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
        exit(0);
    }
    pcl::PointCloud<PointType>::Ptr cloud(new pcl::PointCloud<PointType>);
    pcl::PointCloud<PointType>::Ptr cloud_filtered(new pcl::PointCloud<PointType>);

    cloud->width = 100;
    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);
    }
    
    // pcl::PCDReader reader;
    // reader.read<PointType> ("../data/table_scene_lms400.pcd", *cloud);
    
    /*
    字符串比较函数 strcmp() 接受两个字符串作为输入,并根据字典(母)序将它们进行比较
        1. 如果第一个字符串小于第二个字符串,则函数返回一个负整数
        2. 如果两个字符串相等,则返回 0
        3. 如果第一个字符串大于第二个字符串,则返回一个正整数
    */
    if (strcmp(argv[1], "-r") == 0) {
        pcl::RadiusOutlierRemoval<PointType> outrem;
        outrem.setInputCloud(cloud);    // 设置输入点云
        outrem.setRadiusSearch(0.4);    // 设置搜索半径为 0.4 个单位
        outrem.setMinNeighborsInRadius(2);    // 设置在上述半径内至少需要 2 个邻居点才能保留点云中的某个点
        outrem.filter(*cloud_filtered);      // 对输入点云进行滤波,并将输出结果保存 cloud_filtered
    } else if (strcmp(argv[1], "-c") == 0) {
        // 用于保存多个滤波条件的逻辑与关系(and),仅保留 z 高度在 [0.0, 0.8] 之间的点云子集
        pcl::ConditionAnd<PointType>::Ptr range_cond(new pcl::ConditionAnd<PointType>());
        // 第一个比较条件:用于比较点云中每个点的 z 字段是否大于 0.0
            // 第一个参数表示比较对象为点云中点的 z 字段
            // 第二个参数 pcl::ComparisonOps::GT 表示比较操作是大于(greater than)操作
            // 第三个参数表示要比较的值为 0.0
        range_cond->addComparison(pcl::FieldComparison<PointType>::ConstPtr(
                                new pcl::FieldComparison<PointType>("z", pcl::ComparisonOps::GT, 0.0)));
        // 第二个比较条件:用于比较点云中每个点的 z 字段是否小于 0.8
        // ConstPtr 表示该指针所指向的对象是一个常量,即其指向的点云数据不能被修改
        range_cond->addComparison(pcl::FieldComparison<PointType>::ConstPtr(
                                new pcl::FieldComparison<PointType>("z", pcl::ComparisonOps::LT, 0.8)));
        pcl::ConditionalRemoval<PointType> condrem;
        condrem.setCondition(range_cond);
        condrem.setInputCloud(cloud);
        condrem.setKeepOrganized(true);    // 在过滤操作中保留点云的有效性和结构
        condrem.filter(*cloud_filtered);
    } else {
        std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
        exit(0);
    }
    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;
    }
    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;
    }

    showPointClouds(cloud, cloud_filtered);
    
    // pcl::PCDWriter writer;
    // writer.write<PointType> ("../data/table_scene_lms400_outliers.pcd", *cloud_filtered, false);

    return 0;
}
  • 配置文件 CMakeLists.txt

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    project(remove_outliers)
    
    find_package(PCL 1.2 REQUIRED)
    
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    add_definitions(${PCL_DEFINITIONS})
    
    add_executable (remove_outliers remove_outliers.cpp)
    target_link_libraries (remove_outliers ${PCL_LIBRARIES})
    
  • 编译并执行

    $ mkdir build
    $ cd build
    $ cmake ..
    $ make
    
    $ ./remove_outliers -c    #(条件滤波:下图一)
    $ ./remove_outliers -r    #(半径离群值滤波:下图二)
    

在这里插入图片描述

在这里插入图片描述

相关工具使用

  • 对一个点云进行降采样
    # 三个轴向上的体素大小,即 X 轴、Y 轴和 Z 轴的体素大小均为 0.03
    $ pcl_voxel_grid input.pcd output.pcd -leaf 0.03,0.03,0.03
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/18192.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大型数据库期末总复习【SQL server 2008 基础教程】

一、概述 1.Microsoft SQL Server系统的体系结构 Microsoft SQL Server 2008系统由4个主要部分组成。这4个部分被称为4个服务&#xff0c;这些服务分别是数据库引擎、分析服务、报表服务和集成服务。这些服务之间相互存在和相互应用&#xff0c;它们的关系示意图如图所示&…

“世界中医药之都” 亳州市医保局领导一行莅临万民健康交流指导

为进一步推进智慧医疗、智慧服务、智慧管理“三位一体”为主旨的“智慧中医、健康社区”项目建设。2023 年 5 月 3 日&#xff0c;“世界中医药之都” 亳州市医保局 局长 吴旭春 、 医保中心主任秦克靖 、 办公室主任徐伟 等一行 5 人莅临 万民健康交流 指导工作 &#xff0c…

JQuery实现自定义滚动条

在页面中虽然可以通过CSS修改滚动条的样式,但是部分属性是无法自己修改和设置的&#xff0c;而且不同浏览器存在兼容问题&#xff0c;因此通过JS来实现滚动条在自定义滚动条的环境下也是有必要的。 接下来&#xff0c;我们来实现上图两种情况下滚动条的实现。 一、页面搭建 1.…

白宫召见科技巨头 讨论AI潜在风险 以确保人们从创新中受益

ChatGPT的问世&#xff0c;被认为是通用人工智能发展的“奇点”和强人工智能即将到来的“拐点”&#xff0c;甚至有业内人士推测所有数字化系统和各个行业都可能被其重新“洗牌”。 乐观主义者表示&#xff0c;人工智能的核心是对人类大脑的模拟&#xff0c;其目的是延伸和增强…

mysql数据库之事务

1.事务的概念 事务是一种机制、一个操作序列&#xff0c;包含了一组数据库操作命令&#xff0c;并且把所有的命令作为一个 整体一起向系统提交或撤销操作请求&#xff0c;即这一组数据库命令要么都执行&#xff0c;要么都不执行。 事务是一个不可分割的工作逻辑单元&#xf…

ES6-Class类

ES6 提供了更接近传统语言的写法&#xff0c;引入了 Class &#xff08;类&#xff09;这个概念&#xff0c;作为对 象的模板。通过 class 关键字&#xff0c;可以定义类。基本上&#xff0c; ES6 的 class 可以看作只是 一个语法糖&#xff0c;它的绝大部分功能&…

代码随想录算法训练营第三十二天 | 利润题、覆盖范围题

122.买卖股票的最佳时机II 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;贪心算法也能解决股票问题&#xff01;LeetCode&#xff1a;122.买卖股票最佳时机II_哔哩哔哩_bilibili 状态&#xff1a;根本做不出来&#xff0c;思路太巧了。 思路 想获…

DT7遥控DBUS协议解析

文章目录 运行环境&#xff1a;1.1 DBUS协议解析1)DT7遥控2)配置串口引脚3)配置串口接收DMA 2.1例程代码移植1)例程移动到 Inc 和 Src2)makefile添加.c文件 3.1核心代码解释4.1代码修改1)bsp_rc.c 和 remote_control.c2)调用代码 5.1调试1)硬件接线2)串口工具监视拨杆数据 运行…

【C++】哈希

一、unordered系列关联式容器 在C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可达到 l o g 2 N log_2 N log2​N&#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点非常多时&#xff0c;查询效率也不理想。 …

Linux学习之Shell(一)

Shell概述 1&#xff09;Linux提供的Shell解析器有 [xiaominghadoop101 ~]$ cat /etc/shells /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bin/bash /usr/sbin/nologin /bin/tcsh /bin/csh2&#xff09;bash和sh的关系 [xiaominghadoop101 bin]$ ll | grep bash -rwxr…

HTML <area> 标签

实例 带有可点击区域的图像映射: <img src="planets.jpg" border="0" usemap="#planetmap" alt="Planets" /><map name="planetmap" id="planetmap"><area shape="circle" coords=&q…

不用花一分钱!!!获得一个自己的网页版chatGPT

不用花一分钱&#xff01;&#xff01;&#xff01;获得一个自己的网页版chatGPT 当然还是需要一个chatGPT账号的&#xff0c;不会注册的同学可以看一下这篇文章 chatGPT到底要怎么注册 那就先让我们看一下效果吧 chatgpt-web介绍 github项目地址 https://github.com/Chanzha…

Formik使用详解

Formik使用详解 1 引言 在现代Web应用程序中&#xff0c;表单是一种不可避免的输入机制&#xff0c;但是处理表单的过程可能会变得非常复杂。Formik是一个React表单库&#xff0c;它的目标是简化表单处理的过程。本文将介绍Formik的主要功能和用途&#xff0c;以及如何使用它来…

OSI七层网络模型+TCP/IP四层模型

OSI七层模型&#xff1a; 物理层&#xff1a;主要定义物理设备标准&#xff0c;如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流&#xff08;就是由1、0转化为电流强弱来进行传输&#xff0c;到达目的地后再转化为1、0&#xff0c;也就…

2023年淮阴工学院五年一贯制专转本财务管理基础考试大纲

2023年淮阴工学院五年一贯制专转本财务管理基础考试大纲 一、考核对象 本课程的考核对象为五年一贯制高职专转本财务管理专业入学考试普通在校生考生。 二、考核方式 本课程考核采用闭卷笔试的方式。 三、命题依据及原则 1、命题依据 本课程考核命题教材为靳磊编著&…

1_5 pytorch操作

一、torch 算子 1、torch.nn.functional.affine_grid(theta, size) 给定一组仿射矩阵(theta)&#xff0c;生成一个2d的采样位置(流场)&#xff0c;通常与 grid_sample() 结合使用,用于空间仿射变换网络&#xff0c;用于对2D或3D数据进行仿射变换。 输入&#xff1a;theta(Te…

AIGPT中文版(无需魔法,直接使用)不愧是生活工作的好帮手。

AIGPT AIGPT是一款非常强大的人工智能技术的语言处理工具软件&#xff0c;它具有 AI绘画 功能、AI写作、写论文、写代码、哲学探讨、创作等功能&#xff0c;可以说是生活和工作中的好帮手。 我们都知道使用ChatGPT是需要账号以及使用魔法的&#xff0c;其中的每一项对我们初学…

使用JPA自动生成代码(轻松上手看了就会版)

目录 背景&#xff1a;方案概念&#xff1a;JPA 的主要作用 jpa简单使用&#xff08;Springboot项目&#xff09;jpa进阶使用总结 背景&#xff1a; 项目需要自动生成sql代码&#xff0c;不需要写sql语句&#xff0c;能够自动进行查询&#xff0c;我想到了JPA。 方案 概念&a…

Linux驱动编程(分层分离编程思想)

1、面向对象 ⚫ 字符设备驱动程序抽象出一个 file_operations 结构体&#xff1b; ⚫ 我们写的程序针对硬件部分抽象出 led_operations 结构体。 2、分层 上层实现硬件无关的操作&#xff0c;比如注册字符设备驱动&#xff1a;leddrv.c 下层实现硬件相关的操作&#xff0c;比如…

Makefile零基础教学(一)初识makefile

从这篇文章开始就开始进入 Makefile 的零基础教程&#xff0c;相信只要看了本教程的都可以对 Makefile 有一个清晰的理解和正确的运用。那么现在就开始我们的 Makefile 学习之路。 文章目录 一、什么是 Makefile&#xff0c;优点&#xff1f;二、什么是 make, 为什么使用make?…