1.理论基础
计算曲面上一点的法向量类似于估计与曲面相切的平面的法向量,这就转化成了最小二乘平面拟合估计问题。
在查询点的近邻处创建的协方差矩阵,因此估计曲面法向量的求解便简化为,对协方差矩阵特征向量和特征值的分析。具体来讲,对任意一个点pi,我们从下式中计算协方差矩阵:
其中k是pi点的近邻点个数, p_hat表示近邻点的三维质心λj表示协方差矩阵的第j个特征值,Vj表示第j个特征向量。
从一组点中估计协方差矩阵可以使用:
// Placeholder for the 3x3 covariance matrix at each surface patch
Eigen::Matrix3f covariance_matrix;
// 16-bytes aligned placeholder for the XYZ centroid of a surface patch
Eigen::Vector4f xyz_centroid;
// Estimate the XYZ centroid
compute3DCentroid (cloud, xyz_centroid);
// Compute the 3x3 covariance matrix
computeCovarianceMatrix (cloud, xyz_centroid, covariance_matrix);
一般来说,由于没有数学方法来求解法线的符号,因此通过主成分分析(PCA)计算的法线方向不明确,并且在整个点云数据集上的方向不一致。下图显示了代表厨房环境一部分的较大数据集的两个部分的这些影响。左下角的图显示了扩展高斯图像(EGI),也称为法线球体,它描述了点云中所有法线的方向。由于数据集是2.5维的,因此是从单个视点获取的,法线应仅出现在EGI中球体的一半上。但是,由于方向不一致,它们分布在整个球体上。
如果视点Vp实际上是已知的,那么解决这个问题很容易。要使所有的法向量ni全部朝向视点,它们需要满足方程:
下图显示了上图中数据集中的所有法线始终朝向视点后的结果。
要在PCL中手动重新定位给定点法线,可以使用:
flipNormalTowardsViewpoint (const PointT &point, float vp_x, float vp_y, float vp_z, Eigen::Vector4f &normal);
2.选择合适的尺度
估计曲面上一个点的法向量需要找到这个点的最近邻,最近邻估计问题的细节提出了正确比例因子的问题:给出一个点云数据集,那么如何选择合适的近邻k值?
这个问题是非常重要的,并且在点特征表示的自动估计(即,没有用户给定的阈值)中构成限制因素。为了更好的说明这个问题,下图展示了选择小尺度对比大尺度的效果。
图的左侧部分描述了一个合理的精心选择的比例因子,估计的曲面法线与两个平面近似垂直,小边缘在整个桌子上都可见。但是,如果比例因子太大(右部分),因此近邻点集比相邻曲面的覆盖点更大,则估计的点特征表示会失真,在两个平面曲面的边缘上旋转曲面法线,并涂抹边缘和抑制精细细节。在不涉及太多细节的情况下,假设现在,必须根据应用程序所需的细节级别来选择用于确定点的邻域的比例就足够了。简单地说,如果杯子把手和圆柱形部分之间边缘的曲率很重要,则比例因子需要足够小才能捕捉到这些细节,否则就需要足够大。
3.估计法向量
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
... read, pass in or create a point cloud ...
// Create the normal estimation class, and pass the input dataset to it
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud (cloud);
// Create an empty kdtree representation, and pass it to the normal estimation object.
// Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
ne.setSearchMethod (tree);
// Output datasets
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
// Use all neighbors in a sphere of radius 3cm
ne.setRadiusSearch (0.03);
// Compute the features
ne.compute (*cloud_normals);
// cloud_normals->size () should have the same size as the input cloud->size ()*
}
从NormalEstimation类的compute的调用仅做了下面的事:
对于点云P中的每个点p
1.获取p的最近邻
2.计算p的曲面法向量n
3.检查n是否始终朝向视点,否则翻转
视点默认在(0,0,0)处,可以通过函数改变:
setViewPoint (float vpx, float vpy, float vpz);
要计算单个点的法向量,使用:
computePointNormal (const pcl::PointCloud<PointInT> &cloud, const std::vector<int> &indices, Eigen::Vector4f &plane_parameters, float &curvature);
其中cloud是输入点云,indices表示cloud的k近邻集合,plane_parameters和curvature表示法向量的估计输出,plane_parameters的前三个参数表示法向量(nx,ny,nz),第四个参数D=nc.p_plane(这里表示质心)+p。输出曲面曲率估计为协方差矩阵的特征值之间的关系(如上所述):