PCL专栏目录及须知-CSDN博客
注意:本方法对于规则的(如平面、立方体等)提取效果较好,工作中可以使用。
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;
}