PCL入门系列 —— NormalSpaceSampling 基于法向空间的点云降采样

PCL入门系列 —— NormalSpaceSampling 基于法向空间的点云降采样


前言

随着工业自动化、智能化的不断推进,机器视觉(2D/3D)在工业领域的应用和重要程度也同步激增(识别、定位、抓取、测量,缺陷检测等),而针对不同作业场景进行解决方案设计时,通常会借助PCL、OpenCV、Eigen等简单方便的开源算法库进行方案的快速验证和迭代以满足作业场景下的目标需求。

为了让对工业机器视觉方向感兴趣的同学能够少走一些弯路,故推出了此一系列简易入门教程示例,让初次使用者能够最简单直观地感受到当前所用算法模块的执行效果。

后续会逐步扩增与工业机器视觉相关的一些其它内容,如:

项目案例剖析场景数据分析基础算法模块相机评测 等;

如有兴趣可加入群聊(若入群二维码被屏蔽,则可以通过Q群(1032861997)或评论、私信博主“群聊”,邀请入群),与同道同学及圈内同行一起交流讨论。

在这里插入图片描述


程序说明

展示NormalSpaceSampling(NSS)法向空间降采样效果;

输出结果

在这里插入图片描述

代码示例

/*
 * @File: normal_space_sample.cpp
 * @Brief: pcl course
 * @Description: 展示NSS法向空间采样效果
 * @Version: 0.0.1
 * @Author: MuYv
 */
#include <iostream>
#include <string>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/normal_space.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/visualization/pcl_visualizer.h>



int main(int argc, char** argv){
    if(argc != 2){
        std::cout<<"Usage: exec cloud_file_path"<<std::endl;
        return -1;
    }
    const std::string kCloudFilePath = argv[1]; // ../clouds/cabinet/cabinet_color_cloud.pcd

    // 定义变量
    pcl::PointCloud<pcl::PointXYZ>::Ptr \
            cloud_src(new pcl::PointCloud<pcl::PointXYZ>());
    pcl::PointCloud<pcl::Normal>::Ptr \
            cloud_normals(new pcl::PointCloud<pcl::Normal>());

    // 成功返回0,失败返回-1
    if(-1 == pcl::io::loadPCDFile(kCloudFilePath,*cloud_src)){
        std::cout<<"load pcd file failed. please check it."<<std::endl;
        return -2;
    }

    // 创建基于邻域的法向估计类对象
    // // 基于omp并行加速,需配置开启OpenMP
    // pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> ne; 
    // ne.setNumberOfThreads(10);
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
    // 创建一个空的kdtree对象,并把它传递给法线估计对象,
    // 用于创建基于输入点云数据的邻域搜索kdtree
    pcl::search::KdTree<pcl::PointXYZ>::Ptr \
                tree(new pcl::search::KdTree<pcl::PointXYZ>());
    // 传入待估计法线的点云数据,智能指针
    ne.setInputCloud(cloud_src);
    // 传入kdtree对象,智能指针
    ne.setSearchMethod(tree);
    // 设置邻域搜索半径
    ne.setRadiusSearch(0.1f);    // 设置半径时,要考虑到点云空间间距
    // // 也可以设置最近邻点个数
    // ne.setKSearch(25);
    // 设置视点源点,用于调整点云法向(指向视点),默认(0,0,0)
    ne.setViewPoint(0,0,0);
    // 计算法线数据
    ne.compute(*cloud_normals);

    // 通过concatenateFields函数将point和normal组合起来形成PointNormal点云数据
    pcl::PointCloud<pcl::PointNormal>::Ptr \
                cloud_with_normal(new pcl::PointCloud<pcl::PointNormal>());
    pcl::PointCloud<pcl::PointNormal>::Ptr \
        cloud_with_normal_sampled(new pcl::PointCloud<pcl::PointNormal>());
    pcl::concatenateFields(*cloud_src, *cloud_normals, *cloud_with_normal);

    // 创建法向空间采样(模板)类对象
    pcl::NormalSpaceSampling<pcl::PointNormal, pcl::Normal> nss;
    // 设置xyz三个法向空间的分类组数,此处设置为一致,根据具体场景可以调整
    const int kBinNum = 8;
    nss.setBins(kBinNum, kBinNum, kBinNum);
    // 如果传入的是有序点云,此处可以尝试设置为true
    nss.setKeepOrganized(false);
    // 设置随机种子,这样可以保证同样的输入可以得到同样的结果,便于debug分析
    nss.setSeed(200);   // random seed
    // 传入待采样的点云数据
    nss.setInputCloud(cloud_with_normal);
    // 传入用于采样分析的法线数据,需与传入点云数据一一对应
    nss.setNormals(cloud_normals);
    // 设置采样总数,即目标点云的总数据量
    const float kSampleRatio = 0.1f;
    nss.setSample(cloud_with_normal->size()*kSampleRatio);
    // 执行采样并带出采样结果
    nss.filter(*cloud_with_normal_sampled);

    // 创建可视化对象
    pcl::visualization::PCLVisualizer viewer("viewer");

    // 将当前窗口,拆分成横向的2个可视化窗口,以viewport区分(v1/v2)
    int v1; 
    int v2;
    //窗口参数分别对应 x_min, y_min, x_max, y_max, viewport
    viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);  
    viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);

    // 添加2d文字标签
    viewer.addText("v1", 10,10, 20, 1,0,0, "viewport_v1", v1);
    viewer.addText("v2", 10,10, 20, 0,1,0, "viewport_v2", v2);

    // 添加坐标系
    viewer.addCoordinateSystem(0.5);    // 单位:m

    // 设置可视化窗口背景色
    viewer.setBackgroundColor(0.2,0.2,0.2);     // r,g,b  0~1之间

    // 向v1窗口中添加点云
    viewer.addPointCloud(cloud_src,"cloud_src",v1);

    // 设置单一颜色
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointNormal> color(cloud_with_normal_sampled, 255, 0, 0);
    
    // 向v2窗口中添加点云
    viewer.addPointCloud<pcl::PointNormal>(cloud_with_normal_sampled,
                color,"cloud_sampled",v2);
    // 根据点云id,设置点云可视化属性,此处将可视化窗口中的点大小调整为2级
    // viewer.setPointCloudRenderingProperties \ 
    //      (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud_sampled");

    // // 向v2窗口中添加法线可视化
    // viewer.addPointCloudNormals<pcl::PointNormal, pcl::PointNormal> \
    //         (cloud_with_normal_sampled, cloud_with_normal_sampled, \
    //         1, 0.02, "cloud_normals", v2);

    // 关闭窗口则退出
    while(!viewer.wasStopped()){
        viewer.spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }

    return 0;
}

总结

基于其算法原理,会将法向相似甚至一致的点数据进行大批量降采样,同时尽可能保留法向特征较为“明显”位置的点云数据;


注:部分测试所用点云数据来源于网络,如有侵权,请联系博主删除,谢谢。

### PCL点云降采样 #### 1. 算原理 点云降采样是一种减少点云数据量的技术,通常用于优化计算效率和存储需求。PCL库提供了多种降采样,其中最常用的是基于体素网格(Voxel Grid)的方[^2]。 该方通过将三维空间划分为固定大小的小立方体(即体素),并对每个体素内的点进行简化操作来实现降采样。具体而言,在每个体素内保留一个代表性的点(通常是中心点或平均值),从而显著降低点云密度。 --- #### 2. 实现代码示例 以下是使用PCL库中的`pcl::VoxelGrid`类进行点云降采样的完整代码示例: ```cpp #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/voxel_grid.h> int main() { // 创建点云对象并加载PCD文件 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>); // 加载点云数据 if (pcl::io::loadPCDFile<pcl::PointXYZ>("input_cloud.pcd", *cloud) == -1) { //* load the file std::cerr << "无打开文件 input_cloud.pcd" << std::endl; return (-1); } // 创建体素栅格滤波器对象 pcl::VoxelGrid<pcl::PointXYZ> voxel_filter; // 设置输入点云 voxel_filter.setInputCloud(cloud); // 设置体素尺寸(单位:米) float leaf_size = 0.01f; // 定义体素边长为1厘米 voxel_filter.setLeafSize(leaf_size, leaf_size, leaf_size); // 执行滤波操作并将结果保存到新的点云对象中 voxel_filter.filter(*cloud_filtered); // 输出原始点云和过滤后点云的点数 std::cout << "原始点云有:" << cloud->points.size() << "个点." << std::endl; std::cout << "降采样后的点云有:" << cloud_filtered->points.size() << "个点." << std::endl; // 将降采样后的点云保存到新文件 pcl::io::savePCDFileASCII("filtered_cloud.pcd", *cloud_filtered); std::cout << "已成功保存降采样后的点云至 filtered_cloud.pcd 文件!" << std::endl; return (0); } ``` 此代码实现了以下功能: - 使用`pcl::io::loadPCDFile`函数加载点云数据。 - 利用`pcl::VoxelGrid`类定义了一个体素栅格滤波器,并设置了体素大小参数。 - 调用了`filter()`函数执行降采样操作。 - 最终将降采样后的点云保存到了一个新的`.pcd`文件中。 --- #### 3. 效果分析 通过对点云应用体素栅格降采样技术,可以有效减少点的数量,同时保持整体几何结构不变。这种方特别适用于大规模点云场景下的预处理阶段,能够提升后续算运行速度和内存利用率[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值