PCL学习六:Filtering-滤波

news2024/11/22 11:15:01

参考引用

  • 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
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java的spi机制使用场景讲解和具体使用

八股文背多了&#xff0c;相信大家都听说过一个词&#xff0c;SPI扩展。 有的面试官就很喜欢问这个问题&#xff0c;SpringBoot的自动装配是如何实现的&#xff1f; 基本上&#xff0c;你一说是基于spring的SPI扩展机制&#xff0c;再把spring.factories文件和EnableAutoConf…

回收站文件恢复,分享4个巧妙解决方法!

案例&#xff1a;回收站文件怎么恢复 【清理电脑时一不小心清空了我的回收站&#xff0c;有朋友知道该怎么恢复吗&#xff1f;急急急&#xff01;】 回收站对于电脑用户来说&#xff0c;可以带来很多的方便&#xff0c;能让用户能够在删除文件后将其恢复。但是&#xff0c;有…

C++之正则表达式

目录 #include • .&#xff1a;换行符以外的任何字符 • […]&#xff1a;…字符中的任何一个 • [^…]&#xff1a;…字符之外的任何一个 • [[:charclass:]]&#xff1a;指定之字符类charclass中的一个 • \n, \t, \f, \r, \v&#xff1a;换行符&#xff0c;tab符号&#xf…

协程实现原理

大家好&#xff0c;我是易安&#xff01;今天我们来探讨一个问题&#xff0c;Go 协程的实现原理。此“协程”非彼”携程“。 线程实现模型 讲协程之前&#xff0c;我们先看下线程的模型。 实现线程主要有三种方式&#xff1a;轻量级进程和内核线程一对一相互映射实现的1:1线程…

自动驾驶经验分享

人生经验总结 第一个要聊的就是在自动驾驶行业工作的这几年&#xff0c;有什么人生经验可以总结一下。 我觉得从这几个方面&#xff0c;首先第一个是能力上&#xff0c;能力上你需要去锻炼&#xff0c;做成功一件事情的一个能力&#xff1b;技术上&#xff0c;对前沿的技术要…

并发编程java

1、CountDownLatch&#xff1a; 如果我们知道了我们的需要执行的任务数&#xff0c;那么我们可以用java并发包下的CountDownLatch&#xff0c;直接上代码&#xff1a; public class CountDownLaunch {private static final Executor executor Executors.newFixedThreadPool(…

SpringBoot参数校验

简单数据类型 SpringBoot自带了validation工具可以从后端对前端传来的参数进行校验&#xff0c;用法如下&#xff1a; 引入validation起步依赖 <!-- 参数校验 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>sprin…

springboot、SpringCloud 常见版本版本介绍

官方版本号&#xff08;2023年5月6日&#xff09; Spring Boot 版本说明 Spring Boot的版本号分析&#xff1a; Spring Boot的版本以数字表示。例如&#xff1a;Spring Boot 2.4.1.RELEASE --> 主版本.次版本.增量版本&#xff08;Bug修复&#xff09; 主版本&#xff0c…

学系统集成项目管理工程师(中项)系列18a_进度管理(上)

1. 规划项目进度管理 1.1. 为实施项目进度管理制定政策、程序&#xff0c;并形成文档化的项目进度管理计划的过程 1.2. 输入 1.2.1. 项目管理计划 1.2.1.1. 范围基准 1.2.1.2. 其他信息 1.2.2. 项目章程 1.2.2.1. 【19下选43】 1.2.2.2. 项目章程中规定的项目审批要求和总…

python ---->>利用 urllib 库获取网络资源

我的个人博客主页&#xff1a;如果’真能转义1️⃣说1️⃣的博客主页 &#xff08;1&#xff09;关于Python基本语法学习---->可以参考我的这篇博客《我在VScode学Python》 &#xff08;2&#xff09;pip是必须的在我们学习python这门语言的过程中Python ----&#xff1e;&a…

SAP: SMARTFORMS

事务码&#xff1a;SMARTFORMS 1、输入表格名&#xff0c;点击创建/更改/显示 2、设置页格式 查看页格式事务码&#xff1a;SPAD 创建的详细流程&#xff1a;详见博客ABAP开发Smartform实例_abap smartform_小强pp的博客-CSDN博客 SMARTFORMS TEMPLATE使用方法_Seele_1018的…

MT6771安卓手机核心板MT6771核心板方案智能模块

MT6771核心板是一款基于MTK平台、工业级高性能、可运行android10.0操作系统的4GAI安卓智能模块&#xff0c;核心处理器架构采用ARM4xCortex-A73upto2.0GHzARM4xCortex-A53upto2.0GHz&#xff0c;为智能设备提供了很好的运算支持。很高兴看到这个模块集成了4G LTE连接和高能效。…

界面开发框架Qt新手入门 - 自定义排序/筛选模型示例(二)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 自定义排序/筛选模型…

ALOHA 开源机械臂(Viper 300 Widow X 250 6DOF机械臂组成)第一部分

软件简介&#xff1a; ALOHA 即 A Low-cost Open-source Hardware System for Bimanual Teleoperation&#xff0c;是一个低成本的开源双手遥控操作硬件系统&#xff0c;即开源机械臂。其算法 Action Chunking with Transformers (ACT) 采用了神经网络模型 Transformers&#…

#杂谈 个人嵌入式开发的学习

本人目前从事的是嵌入式软件开发的相关工作。这是一个关于个人做项目时用过的开发工具的杂谈&#xff0c;仅是为了记录学习经历&#xff0c;同时也为和我有同样瞎搞东西的爱好者提供一个学习思路。 前言 我的技术栈&#xff1a; 下面介绍一下我用过在或者还在用的开发工具&…

JavaWeb综合案例-Servlet优化

将WebServlet的访问路径不要写死&#xff0c;写成通配符的形式 1. 反射笔记&#xff08;后续代码会用到该机制&#xff09; 1.1 基础概念 JAVA反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&am…

【Nodejs】Express实现接口

介绍 Express 是一个第三方模块&#xff0c;用于快速搭建服务器 类似于jquery与DOMExpress 是一个基于 Node.js 平台&#xff0c;快速、开放、极简的 web 开发框架。express保留了http模块的基本API&#xff0c;使用express的时候&#xff0c;也能使用http的APIexpress还额外封…

【花雕学AI】我们如何才能避免被ChatGPT替代?——一个跨学科的视角

ChatGPT是一个由OpenAI开发的AI文本工具&#xff0c;它可以理解和生成自然语言&#xff0c;从而与用户进行对话。ChatGPT是基于GPT-3或者GPT-4模型的&#xff0c;这是目前最大和最先进的语言模型之一。ChatGPT通过在大量的互联网文本数据上进行预训练和强化学习&#xff0c;学习…

linux修改程序的配置文件

修改指定文件中的数&#xff0c;例如创建一个文件如图 把6修改成7 修改完成 代码如下&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #incl…

【英语】大学英语CET考试,阅读部分1(阅读概述,SectionC仔细阅读140)

文章目录 1、阅读概述1.1 考试概况&#xff1a;大纲解读备考策略1.2 做题原则&#xff1a;定位1.3 标点符号和句子逻辑1.4 一级词汇 2、细节题&#xff08;10题占9题&#xff09;2.1 逻辑关系&#xff08;并列和递进&#xff0c;同一方向&#xff09;2.2 逻辑关系&#xff08;转…