1. 程式人生 > >3D功能如何在PCL中工作(How 3D Features work in PCL)

3D功能如何在PCL中工作(How 3D Features work in PCL)

本文件介紹了PCL中的三維特徵估計方法,並作為對pcl::Feature類內部感興趣的使用者或開發人員的指南。

#理論入門

在它們的原始表示中,3D對映系統的概念中所定義的僅使用其笛卡爾座標x,y,z相對於給定原點來表示。假設座標系的原點不隨時間變化,則在t1和t2獲得的兩個點p1和p2具有相同的座標。然而,比較這些點是一個不適當的問題,因為即使它們相對於一些距離度量(例如歐幾里德度量)是相等的,它們可以在完全不同的表面上被取樣,因此當與另一個表面一起被採用時表示完全不同的資訊周圍的點附近。那是因為沒有保證世界在t1和t2之間沒有變化。某些採集裝置可能會為取樣點提供額外的資訊,如強度或表面緩解值,甚至是顏色,但是這並不能完全解決問題,並且比較仍然不明確。

由於各種原因需要比較點的應用需要更好的特徵和度量來區分幾何表面。作為具有笛卡爾座標的單一實體的3D點的概念因此消失,並且本地描述符的新概念代替它。文獻中豐富的描述相同概念化的不同命名方案,如形狀描述符或幾何特徵,但對於本文件的其餘部分,將被稱為點特徵表示。

通過包含周圍鄰居,可以在特徵表示式中推斷和捕獲底層取樣表面幾何,這有助於解決歧義比較問題。 理想情況下,所得到的特徵對於駐留在相同或類似表面上的點是非常相似的(相對於一些度量),對於在不同表面上找到的點不同,如下圖所示。 一個好的點特徵表示區別於一個壞的特徵,能夠捕捉到相同的區域性表面特徵:
提出修改建議

剛性轉換 - 也就是說,資料中的3D旋轉和3D轉換不應該影響結果特徵向量F估計;
變化的取樣密度

- 原則上,或多或少密集地取樣的局部曲面片應該具有相同的特徵向量簽名;
噪聲 - 在資料中存在輕微噪聲的情況下,點要素表示必須在其特徵向量中保留相同或非常相似的值。

_images/good_features.jpg

一般來說,PCL特徵使用近似方法來使用快速kdtree查詢來計算查詢點的最近鄰居。 有兩種我們感興趣的查詢型別:

確定查詢點(也稱為k-search)的k個(使用者給定引數)鄰居;
確定半徑為r的球體內的查詢點的所有鄰居(也稱為半徑搜尋)。

注意

有關正確的k值或r值的討論,請參見Rusu論文

#術語
For the reminder of this article,我們會做一些縮寫,並且介紹一些符號,以簡化文中的解釋。 請參閱下面的表格,瞭解每個使用的術語。

(專案)term (解釋)explanation
Foo a class named Foo
FooPtr a boost shared pointer to a class Foo,
e.g., boost::shared_ptr<Foo>
FooConstPtr a const boost shared pointer to a class Foo,
e.g., const boost::shared_ptr<const Foo>

#如何通過輸入
由於PCL中幾乎所有繼承自pcl::PCLBase類的類,pcl::Feature類都以兩種不同的方式接受輸入資料:

1、一個完整的點雲資料集,通過setInputCloud(PointCloudConstPtr&)給出 - 是強制性的
任何特徵估計類,試圖估計給定輸入雲中每個點的特徵。

2、點雲資料集的一個子集,通過setInputCloud (PointCloudConstPtr &)setIndices (IndicesConstPtr &)給出 - 可選
任何特徵估計類將嘗試估計給定輸入雲中在給定索引列表中具有索引的每個點處的特徵。預設情況下,如果沒有指定一組索引,則將考慮雲中的所有點。*

另外,要使用的點鄰點的集合可以通過額外的呼叫setSearchSurface (PointCloudConstPtr &)來指定。 這個呼叫是可選的,當沒有給出搜尋表面時,預設使用輸入點雲資料集。

由於始終需要setInputCloud(),因此最多可以使用<setInputCloud(), setIndices(), setSearchSurface()>建立四個組合。 假設我們有兩個點雲,P={p_1, p_2, …p_n} and Q={q_1, q_2, …, q_n}。 下圖顯示了所有四種情況:

_images/features_input_explained.png
setIndices() = false, setSearchSurface() = false - 這是毫無疑問的PCL中最常用的情況,使用者只需要提供一個PointCloud資料集,並期望在雲中的所有點上估計某個特徵。

由於我們不希望根據是否給定一組索引和/或搜尋表面來維護不同的實現拷貝,所以每當indices = false時,PCL建立一組內部索引(作為std::vector<int>),基本上指向整個資料集(索引= 1…N,其中N是雲中的點數)。

在上圖中,這對應於最左邊的情況。首先,我們估計p_1的最近鄰居,然後估計p_2的最近鄰居,依此類推,直到我們耗盡了P中的所有點。

setIndices() = true, setSearchSurface() = false - 如前所述,特徵估計方法將只計算給定索引向量中具有索引的那些點的特徵;

在上圖中,這對應於第二種情況。在這裡,我們假設p_2的索引不是給定的索引向量的一部分,所以在p2處不會估計鄰居或特徵。

setIndices() = false, setSearchSurface() = true - 就像在第一種情況下一樣,將對所有給定的點作為輸入來估計特徵,但是在setSearchSurface()中給出的底層相鄰曲面將被用於獲得輸入的最近鄰居點,而不是輸入雲本身;

在上圖中,這對應於第三種情況。如果Q={q_1, q_2} 是另一個作為輸入的雲,與P不同,P是Q的搜尋表面,則q_1和q_2的鄰居將從P中計算出來。

setIndices() = true, setSearchSurface() = true - 這可能是最罕見的情況,其中既有索引又有搜尋表面。在這種情況下,使用setSearchSurface()中給出的搜尋表面資訊,將僅對<input, indices>對中的一個子集估計要素。

最後,如上圖所示,這對應於最後(最右邊)的情況。在這裡,我們假設q_2的指數不是Q給出的指數向量的一部分,所以在q2處不會估計鄰居或特徵。

當使用setSearchSurface()時,最有用的例子是當我們有一個非常密集的輸入資料集時,但是我們不想估計它在所有點上的特徵,而是在使用pcl_keypoints中的方法發現的一些關鍵點 在雲的下采樣版本(例如,使用pcl::VoxelGrid<T>濾波器獲得)。 在這種情況下,我們通過setInputCloud()傳遞下采樣/關鍵點輸入,並將原始資料傳遞給setSearchSurface()

#正常估計的一個例子

一旦確定,查詢點的相鄰點就可以被用來估計捕獲查詢點周圍的基本取樣表面的幾何結構的區域性特徵表示。 描述表面幾何的一個重要問題是首先在座標系中推斷其方向,即估計其正常。 表面法線是表面的重要屬性,並在許多領域大量使用,如計算機圖形學應用,以應用正確的光源,產生陰影和其他視覺效果。
以下程式碼片段將估計輸入資料集中所有點的一組曲面法線。

#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 ()
}

以下程式碼片段將為輸入資料集中的點的子集估算一組曲面法線。

#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 a set of indices to be used. For simplicity, we're going to be using the first 10% of the points in cloud
  std::vector<int> indices (floor (cloud->points.size () / 10));
  for (size_t i = 0; indices.size (); ++i) indices[i] = i;

  // Create the normal estimation class, and pass the input dataset to it
  pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
  ne.setInputCloud (cloud);

  // Pass the indices
  boost::shared_ptr<std::vector<int> > indicesptr (new std::vector<int> (indices));
  ne.setIndices (indicesptr);

  // 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 indicesptr->size ()
}

最後,下面的程式碼片段將估計輸入資料集中所有點的一組曲面法線,但將使用另一個數據集估計它們的最近鄰居。如前所述,這是一個很好的用例,當輸入是表面下采樣版本。

#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>

{
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downsampled (new pcl::PointCloud<pcl::PointXYZ>);

  ... read, pass in or create a point cloud ...

  ... create a downsampled version of it ...

  // Create the normal estimation class, and pass the input dataset to it
  pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
  ne.setInputCloud (cloud_downsampled);

  // Pass the original data (before downsampling) as the search surface
  ne.setSearchSurface (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 surface dataset.
  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_downsampled->points.size ()
}

Note

       @PhDThesis{RusuDoctoralDissertation, author = {Radu Bogdan Rusu}, title = {Semantic 3D Object Maps for Everyday Manipulation in Human Living Environments}, school = {Computer Science department, Technische Universitaet Muenchen, Germany}, year = {2009}, month = {October} }

How 3D Features work in PCL