表面法线是几何表面的重要属性,在许多领域(例如计算机图形应用程序)中被大量使用,以应用正确的光源来生成阴影和其他视觉效果。
给定一个几何曲面,通常很容易将曲面上某个点的法线方向推断为垂直于该点曲面的向量。但是,由于我们获取的点云数据集代表了真实表面上的一组点样本,因此有两种可能性:
使用表面网格划分技术从获取的点云数据集中获取下伏表面,然后从网格中计算表面法线;
使用近似值直接从点云数据集中推断表面法线。
本教程将解决后者,即给定一个点云数据集,直接计算云中每个点的表面法线。
理论入门
尽管存在许多不同的正态估计方法,但我们将在本教程中重点介绍的方法是最简单的一种,其公式如下。确定表面上一点的法线的问题近似为估计与表面相切的平面的法线的问题,这反过来又变成了最小二乘平面拟合估计问题。
1 // Placeholder for the 3x3 covariance matrix at each surface patch
2 Eigen::Matrix3f covariance_matrix;
3 // 16-bytes aligned placeholder for the XYZ centroid of a surface patch
4 Eigen::Vector4f xyz_centroid;
5
6 // Estimate the XYZ centroid
7 compute3DCentroid (cloud, xyz_centroid);
8
9 // Compute the 3x3 covariance matrix
10 computeCovarianceMatrix (cloud, xyz_centroid, covariance_matrix);
选择正确的比例
如前所述,需要根据该点的周围点邻域支持(也称为 k-neighborhood)来估计点处的表面法线。
最近邻估计问题的细节提出了正确比例因子的问题:给定一个采样点云数据集,正确的k(通过pcl::Feature::setKSearch给出)或r(通过 pcl::Feature ::setRadiusSearch ) 值用于确定一个点的最近邻集
下图显示了选择较小尺度(即小r或k)与选择较大尺度(即大r或k)。图的左边部分描绘了一个合理选择的比例因子,估计的表面法线对于两个平面表面大致垂直,并且整个桌子上的小边缘都可见。然而,如果比例因子太大(右侧部分),因此邻居集是来自相邻表面的较大覆盖点,则估计的点特征表示会失真,在两个平面表面的边缘处具有旋转的表面法线,并且被涂抹边缘和抑制精细细节。
如果杯子把手和圆柱形部分之间边缘的曲率很重要,则比例因子需要足够小才能捕捉到这些细节,否则就需要大。
估计法线
以下代码片段将为输入数据集中的所有点估计一组表面法线。
1#include <pcl/point_types.h>
2#include <pcl/features/normal_3d.h>
3
4{
5 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
6
7 ... read, pass in or create a point cloud ...
8
9 // Create the normal estimation class, and pass the input dataset to it
10 pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
11 ne.setInputCloud (cloud);
12
13 // Create an empty kdtree representation, and pass it to the normal estimation object.
14 // Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).
15 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
16 ne.setSearchMethod (tree);
17
18 // Output datasets
19 pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
20
21 // Use all neighbors in a sphere of radius 3cm
22 ne.setRadiusSearch (0.03);
23
24 // Compute the features
25 ne.compute (*cloud_normals);
26
27 // cloud_normals->size () should have the same size as the input cloud->size ()*
28}