PCL——从点云到网格(三)点云到Mesh

参考:
https://mp.weixin.qq.com/s/GFDWOudJ08In6jFyrZ7hhg
https://mp.weixin.qq.com/s/FfHkVY-lmlOSf4jKoZqjEA

通过之前的两篇文章,从得到点云,到对点云的下采样,去离群点。接着就是对点云的平滑,计算法线,最后生成Mesh。

点云平滑

平滑也是滤波的一种,让点云看起来稍微光滑一些。存在一些不规则数据,很难用前面提到过的统计分析等滤波方法消除,就需要平滑来处理和修复漏洞。
平滑操作在配准之后。
我使用的平滑的方法是重采样,实质上是移动最小二乘(MLS)。
主要的类是 pcl::MovingLeastSquares ,用它进行重采样操作。
参数啥的我其实还是不会调,还是用的别人的东西。

//重采样平滑点云
void SmoothPointcloud(pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud_in, pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud_out)
{
	// 对点云重采样 
	std::cout<<"begin smooth: size " << cloud_in->size() << std::endl;
	pcl::search::KdTree<pcl::PointXYZRGB>::Ptr treeSampling(new pcl::search::KdTree<pcl::PointXYZRGB>); // 创建用于最近邻搜索的KD-Tree
	pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGB> mls;  // 定义最小二乘实现的对象mls
	mls.setSearchMethod(treeSampling);    // 设置KD-Tree作为搜索方法
	mls.setComputeNormals(false);  //设置在最小二乘计算中是否需要存储计算的法线
	mls.setInputCloud(cloud_in);        //设置待处理点云
	mls.setPolynomialOrder(2);             // 拟合2阶多项式拟合
	mls.setPolynomialFit(false);  // 设置为false可以 加速 smooth
	mls.setSearchRadius(0.05); // 单位m.设置用于拟合的K近邻半径
	mls.process(*cloud_out);        //输出
	std::cout << "success smooth, size: " << cloud_out->size() << std::endl;

}

KdTree我理解为方便管理点云中的点的一个结构,通过这个二叉树可以方便的找到我们所要的某个点。
KDTree参考: https://blog.csdn.net/fandq1223/article/details/53176098
setSearchRadius 搜索某个点周围5cm处的所有点,用二阶多项式拟合这些点。

平滑点云之后,开始下一步操作:

点云的法向量计算

由于计算点云的法向量而不是Mesh的法向量,因此比较麻烦。目前是两种重建算法,使用曲面重建的方法 和 使用近似值直接从点云中推断法线。
由于刚刚接触,参考资料中使用的是后一种方法,从该点最近邻计算的协方差矩阵的特征向量和特征值的分析,直接调用PCL封装好的函数即可。
pcl::NormalEstimation 估计法线的类

//计算法线
pcl::PointCloud<pcl::Normal>::Ptr CalculateNormal(pcl::PointCloud<pcl::PointXYZRGB>::Ptr &cloud_in)
{
	// 法线估计
	pcl::StopWatch time;
	std::cout << "begin compute normal.... " << std::endl;
	pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> normalEstimation;                    //创建法线估计的对象
	normalEstimation.setInputCloud(cloud_in);                                    //输入点云
	pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZRGB>);          // 创建用于最近邻搜索的KD-Tree
	normalEstimation.setSearchMethod(tree);
	pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);     // 定义输出的点云法线
																					 // K近邻确定方法,使用k个最近点,或者确定一个以r为半径的圆内的点集来确定都可以,两者选1即可
	normalEstimation.setKSearch(10);                    // 使用当前点周围最近的10个点
														//normalEstimation.setRadiusSearch(0.03);            //对于每一个点都用半径为3cm的近邻搜索方式
	normalEstimation.compute(*normals);                 //计算法线

	std::cout << "success compute normal,time(s):  "<< time.getTimeSeconds() << std::endl;
	return normals;
}

这个函数是我写的函数,接受输入点云Ptr,返回法向量Ptr。normalEstimation.setKSearch(10) 这个参数对于法向量的估计影响还是比较大的,不能太大,也不能太小。

之后在计算好了法向量,就可以得到Mesh了。

贪心三角化算法得到Mesh

点云构成三角网格的流程。Mesh存储的信息为:顶点索引,边索引,面片索引。
目前网格生成分为两大类算法,插值法(通过原始数据点得到)和逼近法(用分片线性曲面逼近原始点集)。
pcl::GreedyProjectionTriangulation 贪心三角化算法计算Mesh,使用贪心投影三角化对有向点云进行三角化,它更适用于采样点云来自表面连续光滑的曲面,并且点云的密度变化比较均匀的情况。
流程(参考资料上很详细,PCL都封装好了):

  1. 先将点云根据法向量投影到某个二维平面上(为某一个样本三角面片);
  2. 对该平面内的投影后的点云做三角化(基于Delaunay三角剖分 )得到投影后的点的拓扑连接关系
  3. 得到拓扑连接之后各三维点的拓扑链接就出来了,得到了Mesh。

在平滑之后要处理掉 InvalidPoints 否则无法进行Mesh。


#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/features/normal_3d.h>
#include <pcl/surface/mls.h> //最小二乘 重采样平滑
#include <pcl/surface/poisson.h>  //泊松重建
#include <pcl/geometry/polygon_mesh.h> //MESH
#include <pcl/surface/gp3.h>  //贪心三角形

//贪心三角化算法得到Mesh
pcl::PolygonMesh greedy_traingle_GenerateMesh(pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_in, pcl::PointCloud<pcl::Normal>::Ptr normals)
{	
	pcl::StopWatch time;

	std::cout << "begin  mesh..." << std::endl;

	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_out(new pcl::PointCloud<pcl::PointXYZRGB>());
	
	SmoothPointcloud(cloud_in, cloud_out);
	EraseInvalidPoints(cloud_out);

	// 将点云位姿、颜色、法线信息连接到一起
	pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
	pcl::concatenateFields(*cloud_in, *normals, *cloud_with_normals);

	//定义搜索树对象
	pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree2(new pcl::search::KdTree<pcl::PointXYZRGBNormal>);
	tree2->setInputCloud(cloud_with_normals);

	// 三角化
	pcl::GreedyProjectionTriangulation<pcl::PointXYZRGBNormal> gp3;   // 定义三角化对象
	pcl::PolygonMesh triangles; //存储最终三角化的网络模型

	// 设置三角化参数
	gp3.setSearchRadius(0.1);  //设置搜索时的半径,也就是KNN的球半径
	gp3.setMu(2.5);  //设置样本点搜索其近邻点的最远距离为2.5倍(典型值2.5-3),这样使得算法自适应点云密度的变化
	gp3.setMaximumNearestNeighbors(100);    //设置样本点最多可搜索的邻域个数,典型值是50-100

	gp3.setMinimumAngle(M_PI / 18); // 设置三角化后得到的三角形内角的最小的角度为10°
	gp3.setMaximumAngle(2 * M_PI / 3); // 设置三角化后得到的三角形内角的最大角度为120°

	gp3.setMaximumSurfaceAngle(M_PI / 4); // 设置某点法线方向偏离样本点法线的最大角度45°,如果超过,连接时不考虑该点
	gp3.setNormalConsistency(false);  //设置该参数为true保证法线朝向一致,设置为false的话不会进行法线一致性检查

	gp3.setInputCloud(cloud_with_normals);     //设置输入点云为有向点云
	gp3.setSearchMethod(tree2);   //设置搜索方式
	gp3.reconstruct(triangles);  //重建提取三角化
	cloud_with_normals->width = cloud_with_normals->height = 0;
	std::cout << "success traingles, time(s) "<< time.getTimeSeconds() << std::endl;
	return triangles;
}

pcl::concatenateFields 的两个输入点云的大小一定要是一样的(点云个数),否则在处理的时候会报错。

点云到Mesh的流程大概就是:
下采样,滤波,平滑,法向量,Mesh

Mesh的效果和点云质量相关。我这里用的是RGB点云,Mesh里面就自带颜色的,因此没有Texture也可以看到颜色(否则还得自己做Texture,UV啥的都不会),如果要导入Unity,就得改一下输出格式和Shader。

这些流程不是最好的,可以做优化,等我学的多一些了。。。应该会做一下吧

  • 25
    点赞
  • 197
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值