深圳幻海软件技术有限公司 欢迎您!

基于PCL的点云平面以及边界的提取

2023-04-12

1、PCL点云边界提取该边界提取采用PCL库里边的方法,基于法线估计来实现的边界检测与提取:首先从原始点云上计算出法线,再由法线结合数据估计出边界。#include<iostream>#include<pcl/io/pcd_io.h>#include<pcl/point

1、PCL 点云边界提取
该边界提取采用PCL库里边的方法,基于法线估计来实现的边界检测与提取:首先从原始点云上计算出法线,再由法线结合数据估计出边界。

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


int
main(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("test.pcd", *cloud);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normEst;
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
normEst.setInputCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr(cloud));
normEst.setRadiusSearch(0.1);
normEst.compute(*normals);
pcl::PointCloud<pcl::Boundary> boundaries;
pcl::BoundaryEstimation<pcl::PointXYZ, pcl::Normal, pcl::Boundary> boundEst;
boundEst.setInputCloud(cloud);
boundEst.setInputNormals(normals);
boundEst.setRadiusSearch(0.1);
boundEst.setAngleThreshold(M_PI / 4);
boundEst.setSearchMethod(pcl::search::KdTree<pcl::PointXYZ>::Ptr(new pcl::search::KdTree<pcl::PointXYZ>));
boundEst.compute(boundaries);

pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_boundary(new pcl::PointCloud<pcl::PointXYZ>);
for (int i = 0; i < cloud->points.size(); i++)
{

if (boundaries[i].boundary_point > 0)
{
cloud_boundary->push_back(cloud->points[i]);
}
}

boost::shared_ptr<pcl::visualization::PCLVisualizer> view(new pcl::visualization::PCLVisualizer("3D"));
int v1(0);
view->createViewPort(0.0, 0.0, 0.5, 1.0, v1);
view->setBackgroundColor(0.3, 0.3, 0.3, v1);
view->addText("Raw point clouds", 10, 10, "v1_text", v1);
int v2(0);
view->createViewPort(0.5, 0.0, 1, 1.0, v2);
view->setBackgroundColor(0.5, 0.5, 0.5, v2);
view->addText("Boudary point clouds", 10, 10, "v2_text", v2);
view->addPointCloud<pcl::PointXYZ>(cloud, "sample cloud", v1);
view->addPointCloud<pcl::PointXYZ>(cloud_boundary, "cloud_boundary", v2);
view->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, "sample cloud", v1);
view->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "cloud_boundary", v2);
view->addCoordinateSystem(1.0);
view->initCameraParameters();

view->spin();

return 0;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60


2、点云焊道提取

思路一:先提取平面,再从边界中提取轮廓。

思路二:计算点云法向量方向,根据法向量判断边界。

先尝试了思路一,具体实现步骤如下。

(1)首先计算点云法向量

// Create the normal estimation class, and pass the input dataset to it
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>);
// Use all neighbors in a sphere of radius 1cm
ne.setRadiusSearch(1);
//ne.setKSearch(20);
ne.compute(*normals);
pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointNormal>);
pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(2)采用RANSAC提取平面

pcl::SACSegmentationFromNormals<pcl::PointXYZ, pcl::Normal> seg;
pcl::ModelCoefficients::Ptr coefficients_plane(new pcl::ModelCoefficients), coefficients_cylinder(new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers_plane(new pcl::PointIndices), inliers_cylinder(new pcl::PointIndices);
pcl::PCDWriter writer;
pcl::ExtractIndices<pcl::PointXYZ> extract;
// Create the segmentation object for the planar model and set all the parameters
seg.setOptimizeCoefficients(true);//设置对估计的模型系数需要进行优化
seg.setModelType(pcl::SACMODEL_NORMAL_PLANE); //设置分割模型
seg.setNormalDistanceWeight(0.1);//设置表面法线权重系数
seg.setMethodType(pcl::SAC_RANSAC);//设置采用RANSAC作为算法的参数估计方法
seg.setMaxIterations(500); //设置迭代的最大次数
seg.setDistanceThreshold(0.5); //设置内点到模型的距离允许最大值
seg.setInputCloud(cloud);
seg.setInputNormals(normals);
// Obtain the plane inliers and coefficients
seg.segment(*inliers_plane, *coefficients_plane);
std::cerr << "Plane coefficients: " << *coefficients_plane << std::endl;
// Extract the planar inliers from the input cloud
extract.setInputCloud(cloud);
extract.setIndices(inliers_plane);
extract.setNegative(false);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

提取的效果如下:

值得注意的是参数seg.setMaxIterations()和seg.setDistanceThreshold();的设置,需要根据点云数据的间隔来取值,不然无法得到想要的提取效果。

(3)计算边界

要先计算分割出平面的法向量,然后提取轮廓边界。

//calculate boundary;
pcl::PointCloud<pcl::Boundary> boundary;
pcl::BoundaryEstimation<pcl::PointXYZ, pcl::Normal, pcl::Boundary> est;
est.setInputCloud(cloud_plane);
est.setInputNormals(normals_plane);
est.setSearchMethod(tree_plane);
est.setKSearch(50); //一般这里的数值越高,最终边界识别的精度越好
pcl::search::KdTree<pcl::PointXYZ>));
est.compute(boundary);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

est.setKSearch()的数值越高,最终边界识别的精度越好,但是识别出的边缘具有斜锯齿效果,不知道是否取决于算法原理,打算看看源码研究一番。

以上三个步骤大体实现了轮廓提取的目的,虽然效果还不够理想。更需要的是上图中的内轮廓,而不包括矩形边界,后期需要在改进算法除去。

3、焊点提取

体素栅格:

pcl::VoxelGrid<pcl::PointXYZ> sor;
sor.setInputCloud(cloudA);
sor.setLeafSize(1.2f, 1.2f, 1.2f);//设置滤波时创建的体素大小为1.2cm立方体
sor.filter(*cloud_filtered);
  • 1
  • 2
  • 3
  • 4

高斯:

pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>); //kdtree搜索
pcl::PointCloud<pcl::PointNormal> mls_points;
pcl::MovingLeastSquares<pcl::PointXYZ, pcl::PointNormal> mls;
mls.setComputeNormals(true);// 最小二乘计算中
mls.setInputCloud(cloud);
mls.setPolynomialFit(true);  //多项式拟合提高精度,
mls.setPolynomialOrder(2);//2次多项式
mls.setSearchMethod(tree);
mls.setSearchRadius(0.05);//半径
mls.setSqrGaussParam(10);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

导入点云:

//导入pcd
pcl::PointCloud<pcl::PointXYZ>::Ptr cloudA(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("xx.pcd", *cloudA) == -1)
{
PCL_ERROR("未导入点云\n");
return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.法线计算:

//先计算法线
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
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.setRadiusSearch(1);//搜索半径1cm
ne.compute(*normals);
pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointNormal>);
pcl::concatenateFields(*cloudA, *normals, *cloud_with_normals);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.提取平面:采用RANSAC提取平面

     setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_NORMAL_PLANE); 
seg.setNormalDistanceWeight(0.1);//法线权重系数0.1
seg.setMethodType(pcl::SAC_RANSAC);
seg.setMaxIterations(1000); //迭代的次数1000
seg.setDistanceThreshold(0.5); //内点到模型的距离最大0.5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4.边界提取

normEst.setKSearch(9);  //法向估计的点数
normEst.compute(*normals1);
est.setInputCloud(cloud_filtered);
est.setInputNormals(normals1);
est.setSearchMethod(tree1);
est.setKSearch(20);  
est.compute(boundaries);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7


参考文献:
https://blog.csdn.net/uranus1992/article/details/107047504
https://zhuanlan.zhihu.com/p/32111069

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览43883 人正在系统学习中