来源:pcl官网
PFH
- 简介
- 局部点的法线和曲率不能对应唯一的点
- 理论准备
- 使用目标点k临近区域内的邻域点,通过归一化曲率和法线,并对其进行编码,构建多维直方图
- 多维直方图包含不变的6维姿态、邻域点不同的采样频率、不同水平的噪声
- 术语
p q : p_q: pq:待查询点,红色的点
r : r: r:半径
k : k: k:邻域点的个数并且全在半径r内,相互之间互相连接
通过计算邻域点内k个点对之间的直方图,得到PFH描述子,最终计算复杂度为 O ( k 2 ) O(k^2) O(k2)
- 计算两点之间
p
i
,
p
j
p_i,p_j
pi,pj之间的法线
n
i
,
n
j
n_i,n_j
ni,nj,在其中一个点上定义一个固定坐标系(
u
v
w
uvw
uvw坐标系),存在如下关系:
u = n s v = u × p t − p s ∣ ∣ p t − p s ∣ ∣ 2 w = u × v u=n_s\\ v=u×\frac{p_t-p_s}{||p_t-p_s||_2}\\ w=u×v u=nsv=u×∣∣pt−ps∣∣2pt−psw=u×v
法线 n s n_s ns与 n t n_t nt之间的法线之差可以表示为一组角度:
{
α
=
v
∗
n
t
ϕ
=
u
∗
p
t
−
p
s
d
θ
=
arctan
(
w
∗
n
t
,
u
∗
n
t
)
d
=
∣
∣
p
t
−
p
s
∣
∣
\begin{cases}\alpha=v*n_t\\ \phi=u*\frac{p_t-p_s}{d}\\ \theta=\arctan(w*n_t,u*n_t)\\ d=||p_t-p_s||\end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧α=v∗ntϕ=u∗dpt−psθ=arctan(w∗nt,u∗nt)d=∣∣pt−ps∣∣
每个点对可以得到一组4个值
<
α
,
ϕ
,
θ
,
d
>
<\alpha,\phi,\theta,d>
<α,ϕ,θ,d>,将所有4元组合并到直方图,每一维划分为b个子区域,统计临近点落在子区域的次数,得到
b
4
b^4
b4个直方图,在2.5数据集中最好忽略
d
d
d维时
- pcl中每维数据使用5个区间,默认不使用
d
d
d维
- pcl中代码
#include <pcl/io/pcd_io.h>
#include <pcl/features/integral_image_normal.h>
#include <pcl/features/normal_3d.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/features/pfh.h>
#include <pcl/filters/filter_indices.h>
using namespace pcl;
using namespace std;
int main(int argc, const char **argv)
{
PointCloud<PointXYZ>::Ptr cloud(new PointCloud<PointXYZ>());
PointCloud<Normal>::Ptr normals(new PointCloud<Normal>());
PointCloud<PointXYZ>::Ptr cloudNan(new PointCloud<PointXYZ>());
PointCloud<Normal>::Ptr normalsNan(new PointCloud<Normal>());
PointCloud<PFHSignature125>::Ptr cloudPFH(new PointCloud<PFHSignature125>());
pcl::io::loadPCDFile("/home/n1/notes/pcl/PFH/test.pcd", *cloud);
pcl::NormalEstimation<PointXYZ, Normal> ne; //创建法线滤波器
ne.setInputCloud(cloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
ne.setSearchMethod(tree); //设置搜索方法
ne.setRadiusSearch(0.01); //设置搜索半径
ne.compute(*normals);
std::vector<int> indices; //处理无效值
//pcl::removeNaNFromPointCloud(normals,normals,indices);//剔除无效数据
//剔除无效点
for (int i = 0; i < normals->size(); i++)
{
if (pcl::isFinite<pcl::Normal>((*normals)[i]))
{
// PCL_WARN("normals[%d] is finite\n", i);
continue;
}
cloudNan->push_back(cloud->points[i]);
normalsNan->push_back(normals->points[i]);
}
cloudNan->points.resize (cloudNan->width * cloudNan->height);
normalsNan->points.resize (normalsNan->width * normalsNan->height);
cout<<"size:"<<cloudNan->size()<<endl;
pcl::PFHEstimation<PointXYZ,Normal,PFHSignature125> pfh;
pfh.setInputCloud(cloudNan);
pfh.setInputNormals(normalsNan);
pcl::search::KdTree<pcl::PointXYZ>::Ptr pfhtree (new pcl::search::KdTree<pcl::PointXYZ> ());
pfh.setSearchMethod(pfhtree);
pfh.setRadiusSearch(0.03);
pfh.compute(*cloudPFH);
cout<<"cloudPFH size:"<<cloudPFH->size()<<endl;
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
viewer->setBackgroundColor(0.0, 0.0, 0.0);
viewer->addPointCloudNormals<pcl::PointXYZ, pcl::Normal>(cloud, normals);
while (!viewer->wasStopped())
{
viewer->spinOnce();
}
return 0;
}
FPFH(fast Point Feature Histograms)
简化了直方图,使其计算复杂度从 O ( n k 2 ) O(nk^2) O(nk2)降低到 O ( n k ) O(nk) O(nk)
-
4.1 步骤一:计算Pq和它邻域点之间(查询点与临近点) α , β , θ \alpha,\beta,\theta α,β,θ,记为 S P F H SPFH SPFH值
{ α = v ∗ n t ϕ = u ∗ p t − p s d θ = arctan ( w ∗ n t , u ∗ n t ) d = ∣ ∣ p t − p s ∣ ∣ \begin{cases}\alpha=v*n_t\\ \phi=u*\frac{p_t-p_s}{d}\\ \theta=\arctan(w*n_t,u*n_t)\\ d=||p_t-p_s||\end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧α=v∗ntϕ=u∗dpt−psθ=arctan(w∗nt,u∗nt)d=∣∣pt−ps∣∣ -
4.2 步骤二:重新计算每个点的k邻域,使用临近点的SPFH值来代替直方图:
w k : w_k: wk:权重为查询点与邻域点之间的距离
F P F H ( P q ) = S P F H ( P q ) + 1 k ∑ i = 1 k 1 w k ∗ S P F H ( P k ) FPFH(P_q)=SPFH(P_q)+\frac{1}{k}\sum_{i=1}^k\frac{1}{w_k}*SPFH(P_k) FPFH(Pq)=SPFH(Pq)+k1i=1∑kwk1∗SPFH(Pk) -
4.3 可以实时提取,论文中的步骤如下:
-
4.4 pcl中代码:
核心代码,替他同PFH
pcl::FPFHEstimation<PointXYZ,Normal,FPFHSignature33> fpfh;
fpfh.setInputCloud(cloudNan);
fpfh.setInputNormals(normalsNan);
pcl::search::KdTree<pcl::PointXYZ>::Ptr fpfhtree (new pcl::search::KdTree<pcl::PointXYZ> ());
fpfh.setSearchMethod(fpfhtree);
fpfh.setRadiusSearch(0.03);//必须大于法线半径
fpfh.compute(*cloudFPFH);
VFH(viewpoint feature Histogram)
官网
用作聚类或者6维位姿估计
- 理论
VFH源于FPFH,因为FPFH描述子便于识别,但是起步不具有伸缩不变性,所以在FPFH上添加视角点,以保持其尺度不变性。
视角点方向的计算与FPFH中计算相对法线时同时进行 - 如何计算
2.1 收集视角点与每个法线的夹角作为直方图来计算视点分量。(将法线平移到的几何中心点与视角点构成的夹角)
c:几何中心点
2.2 计算FPFH的参数 α , ϕ , θ \alpha,\phi,\theta α,ϕ,θ
3.源代码
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/features/vfh.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/integral_image_normal.h>
#include <pcl/visualization/pcl_plotter.h>
using namespace pcl;
using namespace std;
using namespace pcl::visualization;
int main(int argc, const char** argv) {
PCLPlotter *plotter = new PCLPlotter("My Plotter");
PointCloud<PointXYZ>::Ptr cloud(new PointCloud<PointXYZ>());
PointCloud<Normal>::Ptr normals(new PointCloud<Normal>());
PointCloud<PointXYZ>::Ptr cloudNan(new PointCloud<PointXYZ>());
PointCloud<Normal>::Ptr normalsNan(new PointCloud<Normal>());
PointCloud<VFHSignature308>::Ptr cloudVFH(new PointCloud<VFHSignature308>());
pcl::io::loadPCDFile("/home/n1/notes/pcl/VFH/test.pcd", *cloud);
pcl::NormalEstimation<PointXYZ, Normal> ne; //创建法线滤波器
ne.setInputCloud(cloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
ne.setSearchMethod(tree); //设置搜索方法
ne.setRadiusSearch(0.01); //设置搜索半径
ne.compute(*normals);
//剔除无效点
for (int i = 0; i < normals->size(); i++)
{
if (pcl::isFinite<pcl::Normal>((*normals)[i]))
{
// PCL_WARN("normals[%d] is finite\n", i);
continue;
}
cloudNan->push_back(cloud->points[i]);
normalsNan->push_back(normals->points[i]);
}
cloudNan->points.resize (cloudNan->width * cloudNan->height);
normalsNan->points.resize (normalsNan->width * normalsNan->height);
cout<<"size:"<<cloudNan->size()<<endl;
pcl::VFHEstimation<PointXYZ,Normal,VFHSignature308> VFH;
VFH.setInputCloud(cloud);
VFH.setInputNormals(normals);
VFH.setRadiusSearch(0.03);
pcl::search::KdTree<pcl::PointXYZ>::Ptr VFHtree(new pcl::search::KdTree<pcl::PointXYZ>());
VFH.setSearchMethod(tree);
VFH.compute(*cloudVFH);
cout<<"size:"<<cloudVFH->size()<<endl;
plotter->setXRange(0, 350); //设置y轴范围
double x[308];
double y[308];
for(int i=0;i<308;i++)
{
x[i]=i;
y[i]=(cloudVFH->points[0].histogram[i]);
}
while (!plotter->wasStopped())
{
plotter->addPlotData (x, y, 308, "VFH");//x,y,点的个数,曲线名
plotter->spinOnce(2000);
plotter->clearPlots();
}
return 0;
}