pcl里面的法线估计
法線估計是一個很重要的特征,常常在被使用在很多計算機視覺的應用里面,比如可以用來推出光源的位置,通過陰影與其他視覺影響。
給一個幾何表面,去推斷給定點的法線方向,即垂直向量的方向往往是不容易的。然而,在我們獲取物體表面的點云數據后,有兩大選擇:
1.從已獲取的點云數據集中得到潛在表面,并用表面網格化技術,來計算網格的表面法線。
2.使用近似值來推斷點云數據集的表面法線。
盡管有很多法線估計的方法存在,但是我們這次將會講的是最簡單的方法。表面法線的問題可以近似化解為切面的問題,這個切面的問題又會變成最小二乘法擬合平面的問題。
解決表面法線估計的問題可以最終化簡為對一個協方差矩陣的特征向量和特征值的分析(或者也叫PCA-Principal Component Analysis 主成分分析),這個協方差矩陣是由查詢點的最近鄰產生的。對于每個點Pi,我們假設協方差矩陣C如下:
這里K指的是離點的最近的K個點,是最近鄰的中心,是第j個特征值,是第j個特征向量。
下面是一段用來估計協方差矩陣的代碼
// Placeholder for the 3x3 covariance matrix at each surface patchEigen::Matrix3f covariance_matrix;// 16-bytes aligned placeholder for the XYZ centroid of a surface patchEigen::Vector4f xyz_centroid;// Estimate the XYZ centroidcompute3DCentroid (cloud, xyz_centroid);// Compute the 3x3 covariance matrixcomputeCovarianceMatrix (cloud, xyz_centroid, covariance_matrix);
總的來說,因為數學上沒有方法解決法線的符號,比如一個球面,法線可以指向球心,也可以背向球心。下面的兩幅圖像是描述廚房的點云圖,右邊的圖像是通過高斯擴展圖(EGI?Extended Gaussian Image),也常常叫做法線球。法線球是一個描述點云里面所有法線方向的一種方式。因為數據集是2.5D的,何為2.5D,你可以把上下,左右,前后看成一個D,然后現實生活里面往往不可能每個方向都兼顧,比如攝像機只能拍到前面的,所有是1(上下)+1(左右)+0.5(前)叫2.5D,當然這是建立在攝像機為單一視角的情況下,即攝像機不會動,一直是固定的。所以理論上,EGI圖,即高斯球也應該是2.5D的,因為你攝像機是向前拍攝的,所以物體的法線也是向前的,然而因為這個算法的原因。主成分分析這個算法,不能解決法線的符號,所以導致了法線指向可能往前,也可能往后,導致整個球里面各個方向都可能存在著法線。
解決上面的法線方向不定的問題,我們得知道視角的向量,這就很簡單了,只要法線和? 視角與點的連線,這兩條線的夾角是銳角,即這兩個向量的點積大于0即可。
我們經過上述的處理后,圖片就變成了這樣
可以看到左邊的那副圖,法線的指向全都變成一個方向了,同時高斯擴展圖只有前半個球面是有法線的,即我們的方法是有效的。
我們可以使用下面的方法去改變法線的方向
flipNormalTowardsViewpoint (const PointT &point, float vp_x, float vp_y, float vp_z, Eigen::Vector4f &normal);上面的這個方法就像我們剛才說的,只適用于單一視角的情況下。
選擇正確的比例
就像前面說的,預測一個表面法線需要最近鄰的方法,那么如何設置最近鄰所需要的半徑與點的個數呢?
這個問題是非常重要的,是對點的特征自動化評估的重要因素。為了更好的闡述這個問題,下面的圖將顯示選擇一個小比例和大比例的影響。左邊的圖是比例(r和k)比較小的情況,我們發現它的法線是另人滿意的,而右邊就不盡人意了,你看那個桌角的位置,有一個法線出軌了,是一個小三,不屬于上一個表面也不屬于側面,這就是比例選擇太大的弊端,所以小比例往往更注重細節,更適合描述比較復雜的物體。
我們得根據不同的細節來選取比例,簡單的說,如果邊緣的曲率在杯子的把柄和圓柱體之間的時候,比例需要比較小來獲取足夠的細節。
下面是官方的一段代碼段:
#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 itpcl::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 datasetspcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);// Use all neighbors in a sphere of radius 3cmne.setRadiusSearch (0.03);// Compute the featuresne.compute (*cloud_normals);// cloud_normals->points.size () should have the same size as the input cloud->points.size ()* }NormalEstimation這個類
做了以下3件事
1.得到p的最近鄰
2.計算p的表面法線n
3.檢查法線的朝向,然后撥亂反正。
默認的視角是(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);前面兩個參數很好理解,plane_parameters包含了4個參數,前面三個是法線的(nx,ny,nz)坐標,加上一個?nc . p_plane (centroid here) + p的坐標,然后最后一個參數是曲率。
接下去是我寫的一個代碼,先通過從磁盤里面加載一個PCD文件,然后進行降低采樣和濾波等操作,最后通過PCLVisulizer顯示出來.
#include <iostream> #include <pcl/visualization/cloud_viewer.h> #include<pcl/io/pcd_io.h> #include<pcl/point_types.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/features/normal_3d.h> #include <boost/thread/thread.hpp> #include <pcl/common/common_headers.h> #include <pcl/filters/voxel_grid.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/console/parse.h> #include <pcl/filters/statistical_outlier_removal.h>int main () {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_old (new pcl::PointCloud<pcl::PointXYZ>);pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downsampled (new pcl::PointCloud<pcl::PointXYZ>);pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);pcl::io::loadPCDFile ("test_pcd.pcd", *cloud_old);//Use a voxelSampler to downsamplepcl::VoxelGrid<pcl::PointXYZ> voxelSampler;voxelSampler.setInputCloud(cloud_old);voxelSampler.setLeafSize(0.01f, 0.01f, 0.01f);voxelSampler.filter(*cloud_downsampled);//Use a filter to reduce noisepcl::StatisticalOutlierRemoval<pcl::PointXYZ> statFilter;statFilter.setInputCloud(cloud_downsampled);statFilter.setMeanK(10);statFilter.setStddevMulThresh(0.2);statFilter.filter(*cloud);// Create the normal estimation class, and pass the input dataset to itpcl::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 datasetspcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);// Use all neighbors in a sphere of radius 1cmne.setRadiusSearch(0.01);// Compute the featuresne.compute (*normals);boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));viewer->setBackgroundColor (0, 0, 0);viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud");viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");viewer->addPointCloudNormals<pcl::PointXYZ, pcl::Normal> (cloud, normals, 10, 0.2, "normals");viewer->addCoordinateSystem (1.0);viewer->initCameraParameters ();while (!viewer->wasStopped()){viewer->spinOnce (100);boost::this_thread::sleep (boost::posix_time::microseconds (100000));}return 0; }因為我的PCD的點云文件里面的點是PointXYZ類型,所以顯示得時候,都是白色的,下面上一張效果圖。
這是一張桌子,可以看到上面充滿了密密麻麻的法線。如果你要下載這個PCD文件,點擊下面這個鏈接。
點擊打開鏈接
總結
以上是生活随笔為你收集整理的pcl里面的法线估计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 点集的视点特征直方图的评估
- 下一篇: 【OpenCV 例程200篇】87. 频