402 lines
25 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Point Feature Histograms (PFH) descriptors &mdash; Point Cloud Library 1.12.0 documentation</title>
<script type="text/javascript" src="_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/language_data.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Point Cloud Library
</a>
<div class="version">
1.12.0
</div>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<!-- Local TOC -->
<div class="local-toc"><ul>
<li><a class="reference internal" href="#">Point Feature Histograms (PFH) descriptors</a></li>
<li><a class="reference internal" href="#theoretical-primer">Theoretical primer</a></li>
<li><a class="reference internal" href="#estimating-pfh-features">Estimating PFH features</a></li>
</ul>
</div>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Point Cloud Library</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> &raquo;</li>
<li>Point Feature Histograms (PFH) descriptors</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<div class="section" id="point-feature-histograms-pfh-descriptors">
<span id="pfh-estimation"></span><h1>Point Feature Histograms (PFH) descriptors</h1>
<p>As point feature representations go, surface normals and curvature estimates
are somewhat basic in their representations of the geometry around a specific
point. Though extremely fast and easy to compute, they cannot capture too much
detail, as they approximate the geometry of a points k-neighborhood with only
a few values. As a direct consequence, most scenes will contain many points
with the same or very similar feature values, thus reducing their informative
characteristics.</p>
<p>This tutorial introduces a family of 3D feature descriptors coined PFH (Point
Feature Histograms) for simplicity, presents their theoretical advantages and
discusses implementation details from PCLs perspective. As a prerequisite,
please go ahead and read the <a class="reference internal" href="normal_estimation.html#normal-estimation"><span class="std std-ref">Estimating Surface Normals in a PointCloud</span></a> tutorial first, as PFH
signatures rely on both xyz 3D data as well as surface normals.</p>
</div>
<div class="section" id="theoretical-primer">
<h1>Theoretical primer</h1>
<p>The goal of the PFH formulation is to encode a points k-neighborhood
geometrical properties by generalizing the mean curvature around the point
using a multi-dimensional histogram of values. This highly dimensional
hyperspace provides an informative signature for the feature representation, is
invariant to the 6D pose of the underlying surface, and copes very well with
different sampling densities or noise levels present in the neighborhood.</p>
<p>A Point Feature Histogram representation is based on the relationships between
the points in the k-neighborhood and their estimated surface normals. Simply
put, it attempts to capture as best as possible the sampled surface variations
by taking into account all the interactions between the directions of the
estimated normals. <strong>The resultant hyperspace is thus dependent on the quality of
the surface normal estimations at each point.</strong></p>
<p>The figure below presents an influence region diagram of the PFH computation
for a query point (<img class="math" src="_images/math/1557bf30f68d8d9460f124be9bad1e7739a98601.png" alt="p_q"/>), marked with red and placed in the middle of a
circle (sphere in 3D) with radius <strong>r</strong>, and all its <strong>k</strong> neighbors (points
with distances smaller than the radius <strong>r</strong>) are fully interconnected in a
mesh. The final PFH descriptor is computed as a histogram of relationships
between all pairs of points in the neighborhood, and thus has a computational
complexity of <img class="math" src="_images/math/8862ee4098dec790a4e4b45e61e06aec3c31a84f.png" alt="O(k^2)"/>.</p>
<img alt="_images/pfh_diagram.png" class="align-center" src="_images/pfh_diagram.png" />
<p>To compute the relative difference between two points <img class="math" src="_images/math/24b68632b58b3294cc8f170cef67b2dd9510e981.png" alt="p_i"/> and
<img class="math" src="_images/math/1190488800b0c07da55d5e0ad0670b6057326799.png" alt="p_j"/> and their associated normals <img class="math" src="_images/math/4fbb0705523f8fdd2c3c5f55e4f8c4a9a46f18a6.png" alt="n_i"/> and <img class="math" src="_images/math/335c8f497d0ef19e9563a0c5afaa7075cc8b934f.png" alt="n_j"/>, we
define a fixed coordinate frame at one of the points (see the figure below).</p>
<div class="math">
<p><img src="_images/math/3e4246b085f695d74072d1be058cf8283ed94655.png" alt="{\mathsf u} =&amp; \boldsymbol{n}_s \\
{\mathsf v} =&amp; {\mathsf u} \times \frac{(\boldsymbol{p}_t-\boldsymbol{p}_s)}{{\|\boldsymbol{p}_t-\boldsymbol{p}_s\|}_{2}} \\
{\mathsf w} =&amp; {\mathsf u} \times {\mathsf v}"/></p>
</div><img alt="_images/pfh_frame.png" class="align-center" src="_images/pfh_frame.png" />
<p>Using the above <strong>uvw</strong> frame, the difference between the two normals
<img class="math" src="_images/math/d199132dcb19f0f36399dc29e10254152ab2ebbb.png" alt="n_s"/> and <img class="math" src="_images/math/5537db0936ff9cbc45558f392e1f3a35977ccfac.png" alt="n_t"/> can be expressed as a set of angular features as
follows:</p>
<div class="math">
<p><img src="_images/math/477bd704365b5b567c59680f376bb72176a593fe.png" alt="\alpha &amp;= {\mathsf v} \cdot \boldsymbol{n}_t \\
\phi &amp;= {\mathsf u} \cdot \frac{(\boldsymbol{p}_t - \boldsymbol{p}_s)}{d}\\
\theta &amp;= \arctan ({\mathsf w} \cdot \boldsymbol{n}_t, {\mathsf u} \cdot \boldsymbol{n}_t) \\"/></p>
</div><p>where <strong>d</strong> is the Euclidean distance between the two points
<img class="math" src="_images/math/2fd6abe0ca103b0d25edfadd05e2c891f71c422e.png" alt="\boldsymbol{p}_s"/> and <img class="math" src="_images/math/7d124ff00e84ac8a1004937d907356a2dbe5cecc.png" alt="\boldsymbol{p}_t"/>,
<img class="math" src="_images/math/6ad49a3f75eefdaec677226c90f97163c0b0e5a9.png" alt="d={\|\boldsymbol{p}_t-\boldsymbol{p}_s\|}_2"/>. The quadruplet
<img class="math" src="_images/math/f43f1a8d80bece6c18b82afb73528cbda3f308ae.png" alt="\langle\alpha, \phi, \theta, d\rangle"/> is computed for each pair of two
points in k-neighborhood, therefore reducing the 12 values (xyz and normal
information) of the two points and their normals to 4.</p>
<p>To estimate a PFH quadruplet for a pair of points, use:</p>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="n">computePairFeatures</span> <span class="p">(</span><span class="k">const</span> <span class="n">Eigen</span><span class="o">::</span><span class="n">Vector4f</span> <span class="o">&amp;</span><span class="n">p1</span><span class="p">,</span> <span class="k">const</span> <span class="n">Eigen</span><span class="o">::</span><span class="n">Vector4f</span> <span class="o">&amp;</span><span class="n">n1</span><span class="p">,</span>
<span class="k">const</span> <span class="n">Eigen</span><span class="o">::</span><span class="n">Vector4f</span> <span class="o">&amp;</span><span class="n">p2</span><span class="p">,</span> <span class="k">const</span> <span class="n">Eigen</span><span class="o">::</span><span class="n">Vector4f</span> <span class="o">&amp;</span><span class="n">n2</span><span class="p">,</span>
<span class="kt">float</span> <span class="o">&amp;</span><span class="n">f1</span><span class="p">,</span> <span class="kt">float</span> <span class="o">&amp;</span><span class="n">f2</span><span class="p">,</span> <span class="kt">float</span> <span class="o">&amp;</span><span class="n">f3</span><span class="p">,</span> <span class="kt">float</span> <span class="o">&amp;</span><span class="n">f4</span><span class="p">);</span>
</pre></div>
</div>
<p>See the API documentation for additional details.</p>
<p>To create the final PFH representation for the query point, the set of all
quadruplets is binned into a histogram. The binning process divides each
features value range into <strong>b</strong> subdivisions, and counts the number of
occurrences in each subinterval. Since three out of the four features presented
above are measures of the angles between normals, their values can easily be
normalized to the same interval on the trigonometric circle. A binning example
is to divide each feature interval into the same number of equal parts, and
therefore create a histogram with <img class="math" src="_images/math/f905139f98abe36c5de73786c4c9e8911864f6e9.png" alt="b^4"/> bins in a fully correlated space.
In this space, a histogram bin increment corresponds to a point having certain
values for all its 4 features. The figure below presents examples of Point
Feature Histograms representations for different points in a cloud.</p>
<p>In some cases, the fourth feature, <strong>d</strong>, does not present an extreme
significance for 2.5D datasets, usually acquired in robotics, as the distance
between neighboring points increases from the viewpoint. Therefore, omitting
<strong>d</strong> for scans where the local point density influences this feature dimension
has proved to be beneficial.</p>
<img alt="_images/example_pfhs.jpg" class="align-center" src="_images/example_pfhs.jpg" />
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">For more information and mathematical derivations, including an analysis of PFH signatures for different surface geometries please see <a class="reference internal" href="how_features_work.html#rusudissertation" id="id1">[RusuDissertation]</a>.</p>
</div>
</div>
<div class="section" id="estimating-pfh-features">
<h1>Estimating PFH features</h1>
<p>Point Feature Histograms are implemented in PCL as part of the <a class="reference external" href="http://docs.pointclouds.org/trunk/a02944.html">pcl_features</a> library.</p>
<p>The default PFH implementation uses 5 binning subdivisions (e.g., each of the
four feature values will use this many bins from its value interval), and does
not include the distances (as explained above although the
<strong>computePairFeatures</strong> method can be called by the user to obtain the
distances too, if desired) which results in a 125-byte array (<img class="math" src="_images/math/4222ce6070b04c6b2afef24c87e6ac442266790f.png" alt="5^3"/>) of
float values. These are stored in a <strong>pcl::PFHSignature125</strong> point type.</p>
<p>The following code snippet will estimate a set of PFH features for all the
points in the input dataset.</p>
<div class="highlight-cpp notranslate"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf">&lt;pcl/point_types.h&gt;</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">&lt;pcl/features/pfh.h&gt;</span><span class="cp"></span>
<span class="p">{</span>
<span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PointXYZ</span><span class="o">&gt;::</span><span class="n">Ptr</span> <span class="n">cloud</span> <span class="p">(</span><span class="k">new</span> <span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PointXYZ</span><span class="o">&gt;</span><span class="p">);</span>
<span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">Normal</span><span class="o">&gt;::</span><span class="n">Ptr</span> <span class="n">normals</span> <span class="p">(</span><span class="k">new</span> <span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">Normal</span><span class="o">&gt;</span> <span class="p">());</span>
<span class="p">...</span> <span class="n">read</span><span class="p">,</span> <span class="n">pass</span> <span class="n">in</span> <span class="n">or</span> <span class="n">create</span> <span class="n">a</span> <span class="n">point</span> <span class="n">cloud</span> <span class="n">with</span> <span class="n">normals</span> <span class="p">...</span>
<span class="p">...</span> <span class="p">(</span><span class="nl">note</span><span class="p">:</span> <span class="n">you</span> <span class="n">can</span> <span class="n">create</span> <span class="n">a</span> <span class="n">single</span> <span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">PointNormal</span><span class="o">&gt;</span> <span class="k">if</span> <span class="n">you</span> <span class="n">want</span><span class="p">)</span> <span class="p">...</span>
<span class="c1">// Create the PFH estimation class, and pass the input dataset+normals to it</span>
<span class="n">pcl</span><span class="o">::</span><span class="n">PFHEstimation</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PointXYZ</span><span class="p">,</span> <span class="n">pcl</span><span class="o">::</span><span class="n">Normal</span><span class="p">,</span> <span class="n">pcl</span><span class="o">::</span><span class="n">PFHSignature125</span><span class="o">&gt;</span> <span class="n">pfh</span><span class="p">;</span>
<span class="n">pfh</span><span class="p">.</span><span class="n">setInputCloud</span> <span class="p">(</span><span class="n">cloud</span><span class="p">);</span>
<span class="n">pfh</span><span class="p">.</span><span class="n">setInputNormals</span> <span class="p">(</span><span class="n">normals</span><span class="p">);</span>
<span class="c1">// alternatively, if cloud is of tpe PointNormal, do pfh.setInputNormals (cloud);</span>
<span class="c1">// Create an empty kdtree representation, and pass it to the PFH estimation object.</span>
<span class="c1">// Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).</span>
<span class="n">pcl</span><span class="o">::</span><span class="n">search</span><span class="o">::</span><span class="n">KdTree</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PointXYZ</span><span class="o">&gt;::</span><span class="n">Ptr</span> <span class="n">tree</span> <span class="p">(</span><span class="k">new</span> <span class="n">pcl</span><span class="o">::</span><span class="n">search</span><span class="o">::</span><span class="n">KdTree</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PointXYZ</span><span class="o">&gt;</span> <span class="p">());</span>
<span class="c1">//pcl::KdTreeFLANN&lt;pcl::PointXYZ&gt;::Ptr tree (new pcl::KdTreeFLANN&lt;pcl::PointXYZ&gt; ()); -- older call for PCL 1.5-</span>
<span class="n">pfh</span><span class="p">.</span><span class="n">setSearchMethod</span> <span class="p">(</span><span class="n">tree</span><span class="p">);</span>
<span class="c1">// Output datasets</span>
<span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PFHSignature125</span><span class="o">&gt;::</span><span class="n">Ptr</span> <span class="n">pfhs</span> <span class="p">(</span><span class="k">new</span> <span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">PFHSignature125</span><span class="o">&gt;</span> <span class="p">());</span>
<span class="c1">// Use all neighbors in a sphere of radius 5cm</span>
<span class="c1">// IMPORTANT: the radius used here has to be larger than the radius used to estimate the surface normals!!!</span>
<span class="n">pfh</span><span class="p">.</span><span class="n">setRadiusSearch</span> <span class="p">(</span><span class="mf">0.05</span><span class="p">);</span>
<span class="c1">// Compute the features</span>
<span class="n">pfh</span><span class="p">.</span><span class="n">compute</span> <span class="p">(</span><span class="o">*</span><span class="n">pfhs</span><span class="p">);</span>
<span class="c1">// pfhs-&gt;size () should have the same size as the input cloud-&gt;size ()*</span>
<span class="p">}</span>
</pre></div>
</td></tr></table></div>
<p>The actual <strong>compute</strong> call from the <strong>PFHEstimation</strong> class does nothing internally but:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="n">each</span> <span class="n">point</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">cloud</span> <span class="n">P</span>
<span class="mf">1.</span> <span class="n">get</span> <span class="n">the</span> <span class="n">nearest</span> <span class="n">neighbors</span> <span class="n">of</span> <span class="n">p</span>
<span class="mf">2.</span> <span class="k">for</span> <span class="n">each</span> <span class="n">pair</span> <span class="n">of</span> <span class="n">neighbors</span><span class="p">,</span> <span class="n">compute</span> <span class="n">the</span> <span class="n">three</span> <span class="n">angular</span> <span class="n">values</span>
<span class="mf">3.</span> <span class="nb">bin</span> <span class="nb">all</span> <span class="n">the</span> <span class="n">results</span> <span class="ow">in</span> <span class="n">an</span> <span class="n">output</span> <span class="n">histogram</span>
</pre></div>
</div>
<p>To compute a single PFH representation from a k-neighborhood, use:</p>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="n">computePointPFHSignature</span> <span class="p">(</span><span class="k">const</span> <span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">PointInT</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">cloud</span><span class="p">,</span>
<span class="k">const</span> <span class="n">pcl</span><span class="o">::</span><span class="n">PointCloud</span><span class="o">&lt;</span><span class="n">PointNT</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">normals</span><span class="p">,</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">indices</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">nr_split</span><span class="p">,</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXf</span> <span class="o">&amp;</span><span class="n">pfh_histogram</span><span class="p">);</span>
</pre></div>
</div>
<p>Where <em>cloud</em> is the input point cloud that contains the points, <em>normals</em> is
the input point cloud that contains the normals (could be equal to cloud if
<em>PointInT=PointNT=PointNormal</em>), <em>indices</em> represents the set of k-nearest
neighbors from <em>cloud</em>, <em>nr_split</em> is the number of subdivisions to use for the
binning process for each feature interval, and <em>pfh_histogram</em> is the output
resultant histogram as an array of float values.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p>For efficiency reasons, the <strong>compute</strong> method in <strong>PFHEstimation</strong> does not check if the normals contain NaN or infinite values.
Passing such values to <strong>compute()</strong> will result in undefined output.
It is advisable to check the normals, at least during the design of the processing chain or when setting the parameters.
This can be done by inserting the following code before the call to <strong>compute()</strong>:</p>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">normals</span><span class="o">-&gt;</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pcl</span><span class="o">::</span><span class="n">isFinite</span><span class="o">&lt;</span><span class="n">pcl</span><span class="o">::</span><span class="n">Normal</span><span class="o">&gt;</span><span class="p">((</span><span class="o">*</span><span class="n">normals</span><span class="p">)[</span><span class="n">i</span><span class="p">]))</span>
<span class="p">{</span>
<span class="n">PCL_WARN</span><span class="p">(</span><span class="s">&quot;normals[%d] is not finite</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
</div>
<p class="last">In production code, preprocessing steps and parameters should be set so that normals are finite or raise an error.</p>
</div>
</div>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>