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 人正在系统学习中