PCL 基于法向量的点云边界提取(boundary)

PCL专栏目录及须知-优快云博客

注意:本方法对于规则的(如平面、立方体等)提取效果较好,工作中可以使用。

1.原理

根据法向量进行判断,计算某一个点云点与其它邻域点连线间的法向量夹角,若夹角大于设定的阈值,则认为该点为边界点。

(1)基于领域,计算点云法向量

(2)对每个点云点P,基于点云P法向量,做一个垂直于法向量的切平面

(3)取点云P周围领域范围内的点云,向其切平面投影(领域内的所有点云都拍到基于法向量做的切平面上)

(4)领域内的所有点与点云点P,两两连线成一系列的向量(下图中的绿色向量)。然后这些向量按照逆时针排序,连接成系列夹角(下图中的蓝色方向)

(5)找到最大的夹角(下图中的红色方向),作为点云点P的夹角α。

(6)判断α和用户设定的夹角阈值β之间的大小。如果α>β,那么判断为边界点;如果α<β,那么判断为不是边界点

如下图:

蓝色圈子:投影到切平面上的领域点。

绿色向量:两两连线成的一系列向量。

蓝色向量:两两向量之间的一系列夹角。

红色向量:找到的最大夹角。

2.使用场景

用于生成点云边界点

工作中较常用。

3.注意事项

4.关键函数

(1)法向量估计相关

1)取每个点云点P的K领域搜索的值,领域搜索取得的该部分点云,拟合平面,做平面的法向量,作为点云点的法向量。

setKSearch()

(1)估计点云边界相关

1)取每个点云点P的K领域搜索的值,领域搜索取得的该部分点云,将所有点云投影到法向量平面上,然后再做后续连线得向量,向量排序,得最大夹角等步骤

setKSearch()

2)边界信息保存为点云

最终结果boundary_point的个数和原始点云cloud的个数相同;其中boundary_point为一个char型的值,1为边界,0为非边界

    for (size_t i = 0; i < boundaries.points.size(); i++)
    {
        int flag = static_cast<int>(boundaries.points[i].boundary_point);                           // 最终结果boundary_point的个数和原始点云cloud的个数相同;其中boundary_point为一个char型的值,1为边界,0为非边界
        if (flag == 1)
            cloud_filtered->push_back(cloud->points[i]);
    }

5.代码

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/boundary.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>

int main()
{
    /****************法线估计边界法********************/
    // 原始点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile("D:/code/csdn/data/plane.pcd", *cloud);   // 加载原始点云数据
    // 结果点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

    // 生成点云法线
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normEst;                                      // 基于邻域的法线估计器
    pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);                    // 法向量保存
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());         // KD树
    normEst.setInputCloud(cloud);
    normEst.setSearchMethod(tree);
    normEst.setKSearch(50);                                                                         // 使用K领域搜索
    // normEst.setRadiusSearch(0.3);                                                                // 进行法向估计的领域半径(该半径范围内的点云拟合一个平面,生成点云法向量)
    normEst.compute(*normals);

    // 估计点云边界
    pcl::BoundaryEstimation<pcl::PointXYZ, pcl::Normal, pcl::Boundary> features;                    // 基于法线的点云边界估计器
    pcl::PointCloud<pcl::Boundary> boundaries;                                                      // 保存点云边界
    features.setInputCloud(cloud);
    features.setInputNormals(normals);
    features.setSearchMethod(tree);                                                                 // 树搜索
    features.setKSearch(50);                                                                        // K领域搜索
    //features.setRadiusSearch(0.2);                                                                // 也可使用半径搜索
    features.compute(boundaries);

    // 边界信息保存为点云
    for (size_t i = 0; i < boundaries.points.size(); i++)
    {
        int flag = static_cast<int>(boundaries.points[i].boundary_point);                           // 最终结果boundary_point的个数和原始点云cloud的个数相同;其中boundary_point为一个char型的值,1为边界,0为非边界
        if (flag == 1)
            cloud_filtered->push_back(cloud->points[i]);
    }

    /****************展示********************/
    boost::shared_ptr<pcl::visualization::PCLVisualizer> view_raw(new pcl::visualization::PCLVisualizer("raw"));
    view_raw->addPointCloud<pcl::PointXYZ>(cloud, "raw cloud");
    view_raw->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "raw cloud");

    boost::shared_ptr<pcl::visualization::PCLVisualizer> view_filtered(new pcl::visualization::PCLVisualizer("filter"));
    view_filtered->addPointCloud<pcl::PointXYZ>(cloud_filtered, "filtered cloud");
    view_filtered->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "filtered cloud");

    while (!view_raw->wasStopped())
    {
        view_raw->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }
    while (!view_filtered->wasStopped())
    {
        view_filtered->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }

    return 0;
}

6.结果展示

基于PCL(Point Cloud Library)的点云边界提取可以通过以下步骤实现: 首先,需要加载点云数据,可以从文件中加载或者实时采集。 其次,利用PCL中的NormalEstimation类估计点云数据的法向量法向量是计算边界的重要依据,能够帮助确定点云中的表面变化。 然后,使用PCL中的BoundaryEstimation类来估计点云边界。该类会根据法向量点云数据的几何信息来确定点云边界点,生成一个包含边界点索引的输出向量。 最后,可以根据边界点的索引,从原始点云数据中提取边界点的信息,包括坐标和其他属性。 以下是一个简单的C++代码示例,演示了如何使用PCL进行点云边界提取: ```cpp #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/features/normal_3d.h> #include <pcl/features/boundary.h> int main () { // 读取点云数据 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::io::loadPCDFile ("cloud.pcd", *cloud); // 估计法向量 pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne; ne.setInputCloud (cloud); pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ()); ne.setSearchMethod (tree); pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>); ne.setKSearch (20); ne.compute (*normals); // 边界提取 pcl::BoundaryEstimation<pcl::PointXYZ, pcl::Normal, pcl::Boundary> est; pcl::PointCloud<pcl::Boundary> boundaries; est.setInputCloud (cloud); est.setInputNormals (normals); est.setRadiusSearch (0.02); est.setAngleThreshold (M_PI/4); est.setSearchMethod (tree); est.compute (boundaries); // 提取边界点 for (size_t i = 0; i < boundaries.points.size (); ++i) { if (boundaries.points[i].boundary_point) std::cout << "边界点索引: " << i << " - " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl; } return (0); } ``` 这段代码首先加载了一个点云数据文件"cloud.pcd",然后进行法向量估计和边界提取,最后输出了边界点的坐标信息。通过这个代码示例,可以实现基于PCL点云边界提取功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值