簡述
點云法向量估計這個問題,相信很多人在點云處理,曲面重建的過程中遇到過。表面法線是幾何體面的重要屬性。而點云數據集在真實物體的表面表現為一組定點樣本。對點云數據集的每個點的法線估計,可以看作是對表面法線的近似推斷。在開源庫提供我們調用便利的同時,了解其實現原理也有利于我們對問題的深刻認識!格物要致知:)
原理
確定表面一點法線的問題近似于估計表面的一個相切面法線的問題,因此轉換過來以后就變成一個最小二乘法平面擬合估計問題。
-
平面方程
為平面上點
處法向量的方向余弦,
為原點到平面的距離。
求平面方程即轉化為求四個參數。
求解過程
1. 待擬合平面點集
待擬合的平面方程:
任意點到平面的距離:
2. 要獲得最佳擬合平面,則需要滿足:
因此,轉化為求解極值的問題,
3. 分別對求偏導
將帶入任意點到平面的距離公式:
繼續求偏導
令
則:
同理:
將上述三式統一:
易得:
即轉化到了求解矩陣A的特征值與特征向量的問題,矩陣即為n個點的協方差矩陣。
即為該矩陣的一個特征向量。
4. 求最小特征向量
如上所示,求得的特征向量可能不止一個,那么如何來選取特征向量,使得求得法向量為最佳擬合平面的法向量呢?
由(內積形式),
,
由
因此,最小特征值對應的特征向量即為法向量
程序應用
- PCL中的NormalEstimation
#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->points.size () should have the same size as the input cloud->points.size ()*
}
-
OpenMP加速法線估計
PCL提供了表面法線估計的加速實現,基于OpenMP使用多核/多線程來加速計算。 該類的名稱是pcl :: NormalEstimationOMP,其API與單線程pcl :: NormalEstimation 100%兼容。 在具有8個內核的系統上,一般計算時間可以加快6-8倍。
include <pcl/point_types.h>
#include <pcl/features/normal_3d_omp.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::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> ne;
ne.setNumberOfThreads(12); // 手動設置線程數,否則提示錯誤
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->points.size () should have the same size as the input cloud->points.size ()*
}