本小节不仅解释如何增加你自己的PointT点类型,也介绍了PCL中的模板point类型,以及它们的用处和定义。PCL从开始就伴随着各种预定义的point类型,从用于XYZ数据到更复杂的n维直方图表示法,例如PFH(点特征直方图)。这些类型应该足够支持在PCL中应用的算法及方法,然而,也有情况下用户希望定义新的类型。
注意:由于PCL的快速更新,本节内容仅仅对PCL 0.x和1.x版本兼容,撰写本文档的时候预期在PCL 2.X中会有新的改变,但大的架构不变,只是添加些新定义的描述子等点类型之类的。
为什么用PointT类型
PCL的PointT可以追溯到它在ROS中作为开源库被开发的时候,大家一致认为,点云是复杂的n维结构,它需要能表示不同类型的信息,然而用户应该知道并理解需要传送什么样的信息,为了使代码更易于调试,考虑优化等等,下面给出一个例子,是对XYZ数据的简单操作,对带SSE功能的处理器,最高效的方法是存储3维坐标为浮点型,紧跟着一个浮点型数据作为填补位数以满足存储对齐要求:
struct PointXYZ
{
float x;
float y;
float z;
float padding;
};
然而,例如当用户在嵌入式平台上寻找编译PCL的时候,增加额外的填补就是浪费存储空间了,因此,可以用一个简单的不带最后浮点数的PointXYZ结构来替代。此外,如果你的应用程序需要一个包含XYZ三维数据、RGB信息(颜色)和每个点的估计法线的PointXYZRGBNormal类型,定义包含以上所有内容的结构是很简单的,由于PCL中所有的算法都是模板化的,除了更改的自定义结构之外,不需要做其他的更改,增加了代码的重用性和可读性。
为了涵盖能想到的所有可能的情况,PCL中定义了大量的point类型。下面是一小段,在point_types.hpp中有完整目录,这个列表很重要,因为在定义你自己的类型之前,需要了解已有的类型,如果你需要的类型,已经存在于PCL,那么就不需要重复定义了。
PointXYZ–成员变量: float x, y, z;
PointXYZ是使用最常见的一个点数据类型,因为它只包含三维xyz坐标信息,这三个浮点数附加一个浮点数来满足存储对齐,用户可利用points[i].data[0],或者points[i].x访问点的x坐标值。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
PointXYZI–成员变量: float x, y, z, intensity;
PointXYZI是一个简单的XYZ坐标加intensity的point类型,理想情况下,这四个变量将新建单独一个结构体,并且满足存储对齐,然而,由于point的大部分操作会把data[4]元素设置成0或1(用于变换),不能让intensity与xyz在同一个结构体中,如果这样的话其内容将会被覆盖。例如,两个点的点积会把他们的第四个元素设置成0,否则该点积没有意义,等等。因此,对于兼容存储对齐,用三个额外的浮点数来填补intensity,这样在存储方面效率较低,但是符合存储对齐要求,运行效率较高。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
struct
{
float intensity;
};
float data_c[4];
};
}
PointXYZRGBA–成员变量: float x, y, z; uint32_t rgba;
除了rgba信息被包含在一个整型变量中,其它的和PointXYZI类似。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
struct
{
uint32_t rgba;
};
float data_c[4];
};
PointXYZRGB - float x, y, z, rgb;
除了rgb信息被包含在一个浮点型变量中,其它和PointXYZRGB类似。rgb数据被压缩到一个浮点数里的原因在于早期PCL是作为ROS项目的一部分来开发的,那里RGB数据是用浮点数来传送的,PCL设计者希望所有遗留代码会重新更改(在PCL 2.x中很可能这样做),可能取消此数据类型。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
struct
{
float rgb;
};
float data_c[4];
};
PointXY-float x, y;
简单的二维x-y point结构
struct
{
float x;
float y;
};
InterestPoint-float x, y, z, strength;
除了strength表示关键点的强度的测量值,其它的和PointXYZI类似。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
struct
{
float strength;
};
float data_c[4];
};
Normal - float normal[3], curvature;
另一个最常用的数据类型,Normal结构体表示给定点所在样本曲面上的法线方向,以及对应曲率的测量值(通过曲面块特征值之间关系获得——查看NormalEstimation类API以便获得更多信息,后续章节有介绍),由于在PCL中对曲面法线的操作很普遍,还是用第四个元素来占位,这样就兼容SSE和高效计算,例如,用户访问法向量的第一个坐标,可以通过points[i].data_n[0]或者points[i].normal[0]或者points[i].normal_x,再一次强调,曲率不能被存储在同一个结构体中,因为它会被普通的数据操作覆盖掉。
union
{
float data_n[4];
float normal[3];
struct
{
float normal_x;
float normal_y;
float normal_z;
};
}
union
{
struct
{
float curvature;
};
float data_c[4];
};
PointNormal - float x, y, z; float normal[3], curvature;
PointNormal是存储XYZ数据的point结构体,并且包括采样点对应法线和曲率。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
float data_n[4];
float normal[3];
struct
{
float normal_x;
float normal_y;
float normal_z;
};
};
union
{
struct
{
float curvature;
};
float data_c[4];
};
PointXYZRGBNormal - float x, y, z, rgb, normal[3], curvature;
PointXYZRGBNormal存储XYZ数据和RGB颜色的point结构体,并且包括曲面法线和曲率。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
float data_n[4];
float normal[3];
struct
{
float normal_x;
float normal_y;
float normal_z;
};
}
union
{
struct
{
float rgb;
float curvature;
};
float data_c[4];
};
PointXYZINormal - float x, y, z, intensity, normal[3], curvature;
PointXYZINormal存储XYZ数据和强度值的point结构体,并且包括曲面法线和曲率。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
float data_n[4];
float normal[3];
struct
{
float normal_x;
float normal_y;
float normal_z;
};
}
union
{
struct
{
float intensity;
float curvature;
};
float data_c[4];
};
PointWithRange - float x, y, z (union with float point[4]), range;
PointWithRange除了range包含从所获得的视点到采样点的距离测量值之外,其它与PointXYZI类似。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
struct
{
float range;
};
float data_c[4];
};
PointWithViewpoint - float x, y, z, vp_x, vp_y, vp_z;
PointWithViewpoint除了vp_x、vp_y和vp_z以三维点表示所获得的视点之外,其它与PointXYZI一样。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
struct
{
float vp_x;
float vp_y;
float vp_z;
};
float data_c[4];
};
MomentInvariants - float j1, j2, j3;
MomentInvariants是一个包含采样曲面上面片的三个不变矩的point类型,描述面片上质量的分布情况。查看MomentInvariantsEstimation以获得更多信息。
struct
{
float j1,j2,j3;
};
PrincipalRadiiRSD - float r_min, r_max;
PrincipalRadiiRSD是一个包含曲面块上两个RSD半径的point类型,查看RSDEstimation以获得更多信息。
struct
{
float r_min,r_max;
};
Boundary - uint8_t boundary_point;
Boundary存储一个点是否位于曲面边界上的简单point类型,查看BoundaryEstimation以获得更多信息。
struct
{
uint8_t boundary_point;
};
PrincipalCurvatures - float principal_curvature[3], pc1, pc2;
PrincipalCurvatures包含给定点主曲率的简单point类型。查看PrincipalCurvaturesEstimation以获得更多信息。
struct
{
union
{
float principal_curvature[3];
struct
{
float principal_curvature_x;
float principal_curvature_y;
float principal_curvature_z;
};
};
float pc1;
float pc2;
};
PFHSignature125 - float pfh[125];
PFHSignature125包含给定点的PFH(点特征直方图)的简单point类型,查看PFHEstimation以获得更多信息
struct
{
float histogram[125];
};
FPFHSignature33 - float fpfh[33];
FPFHSignature33包含给定点的FPFH(快速点特征直方图)的简单point类型,查看FPFHEstimation以获得更多信息。
struct
{
float histogram[33];
};
VFHSignature308 - float vfh[308];
VFHSignature308包含给定点VFH(视点特征直方图)的简单point类型,查看VFHEstimation以获得更多信息。
struct
{
float histogram[308];
};
Narf36 - float x, y, z, roll, pitch, yaw; float descriptor[36];
Narf36包含给定点NARF(归一化对齐半径特征)的简单point类型,查看NARFEstimation以获得更多信息。
struct
{
float x,y,z,roll,pitch,yaw;
float descriptor[36];
};
BorderDescription - int x, y; BorderTraits traits;
BorderDescription包含给定点边界类型的简单point类型,看BorderEstimation以获得更多信息。
struct
{
int x,y;
BorderTraitstraits;
};
IntensityGradient - float gradient[3];
IntensityGradient包含给定点强度的梯度point类型,查看IntensityGradientEstimation以获得更多信息。
struct
{
union
{
float gradient[3];
struct
{
float gradient_x;
float gradient_y;
float gradient_z;
};
};
};
Histogram - float histogram[N];
Histogram用来存储一般用途的n维直方图。
template<int N>
struct Histogram
{
float histogram[N];
};
PointWithScale - float x, y, z, scale;
PointWithScale除了scale表示某点用于几何操作的尺度(例如,计算最近邻所用的球体半径,窗口尺寸等等),其它的和PointXYZI一样。
struct
{
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
float scale;
}
PointSurfel - float x, y, z, normal[3], rgba, radius, confidence, curvature;
PointSurfel存储XYZ坐标、曲面法线、RGB信息、半径、可信度和曲面曲率的复杂point类型。
union
{
float data[4];
struct
{
float x;
float y;
float z;
};
};
union
{
float data_n[4];
float normal[3];
struct
{
float normal_x;
float normal_y;
float normal_z;
};
};
union
{
struct
{
uint32_trgba;
float radius;
float confidence;
float curvature;
};
float data_c[4];
};
如何在模板类中使用这些point类型?
由于PCL模块较多,并且是一个模板库,在一个源文件里包含很多PCL算法会减慢编译过程,在撰写本文档的时候,大多数C++编译器仍然没有很好的优化来处理大量模板文件,尤其是涉及优化(-O2或者-O3)问题的时候。为了使包含和链接到PCL库的用户代码编译速度提高,我们使用显示的模板实例化,最终编译链接的库包括了所有可能的模板实例——在这些组合中使PCL中已经定义的point类型所有的模板类都能够直接调用,不需要重新编译,这意味着一旦PCL编译成库,任何用户代码都不需要编译模板化代码,这样就加速了用户编译过程。这个是通过在头文件中首先声明了我们的类和方法,再在模板类实现头文件中进行实现,配置在源文件中进行显示的实例化,最后在编译链接时分别实例化。举一个例子:
// foo.h
#ifndef PCL_FOO_
#define PCL_FOO_
template<typename PointT>
class Foo
{
public:
void compute(const pcl::PointCloud<PointT> &input,
pcl::PointCloud<PointT> &output);
}
#endif // PCL_FOO_
上面定义了模板类Foo,可以看到,它定义了方法和类,但是还没有实现任何功能。
// impl/foo.hpp
#ifndef PCL_IMPL_FOO_
#define PCL_IMPL_FOO_
#include "foo.h"
template<typename PointT>
void Foo::compute(const pcl::PointCloud<PointT> &input,
pcl::PointCloud<PointT> &output)
{
output=input;
}
#endif // PCL_IMPL_FOO_
上面定义了Foo::compute方法的模板实现, 这种定义通常不与用户代码混合。
// foo.cpp
#include "pcl/point_types.h"
#include "pcl/impl/instantiate.hpp"
#include "foo.h"
#include "impl/foo.hpp"
// Instantiations of specific point types
PCL_INSTANTIATE(Foo,PCL_XYZ_POINT_TYPES));
最后,上面展示了在PCL中是如何进行显示实例化的,宏PCL_INSTANTIATE仅仅检查给定的类型清单并为每一个类型创建对应一个类实例。pcl/include/pcl/impl/instantiate.hpp中有如下代码:
// PCL_INSTANTIATE: call to instantiate template TEMPLATE for all
// POINT_TYPES
#define PCL_INSTANTIATE_IMPL(r, TEMPLATE, POINT_TYPE) \
BOOST_PP_CAT(PCL_INSTANTIATE_, TEMPLATE)(POINT_TYPE)
#define PCL_INSTANTIATE(TEMPLATE, POINT_TYPES) \
BOOST_PP_SEQ_FOR_EACH(PCL_INSTANTIATE_IMPL, TEMPLATE, POINT_TYPES);
PCL_XYZ_POINT_TYPES在这里(在pcl/include/pcl/impl/point_types.hpp中):
// Define all point types that include XYZ data
#define PCL_XYZ_POINT_TYPES \
(pcl::PointXYZ) \
(pcl::PointXYZI) \
(pcl::PointXYZRGBA) \
(pcl::PointXYZRGB) \
(pcl::InterestPoint) \
(pcl::PointNormal) \
(pcl::PointXYZRGBNormal) \
(pcl::PointXYZINormal) \
(pcl::PointWithRange) \
(pcl::PointWithViewpoint) \
(pcl::PointWithScale)
实际上,如果你只是想给pcl::PointXYZ类型实例化Foo这一个实例类,你不需要使用宏,而只需要像下面这样简单地做:
注意:查看David Vandervoorde和Nicolai M. Josuttis所著的C++ Templates - The Complete Guide,可获得关于显示实例化的更多信息。
// foo.cpp
#include "pcl/point_types.h"
#include "pcl/impl/instantiate.hpp"
#include "foo.h"
#include "impl/foo.hpp"
template class Foo<pcl::PointXYZ>;
如何增加新的PointT类型
为了增加新的point类型,首先需要进行定义,例如:
struct MyPointType
{
float test;
};
然后,你得确保你的代码包含了PCL中特定的类/算法的模板头文件的实现,它将和你的新point类型MyPointType共同使用,例如,你想使用pcl::PassThrough。你只需要使用下面的代码即可:
#include <pcl/filters/passthrough.h>
#include <pcl/filters/impl/passthrough.hpp>
// the rest of the code goes here
如果你的代码是库的一部分,可以被他人使用,需要为你自己的MyPointType类型进行显示实例化。
实例
下面的代码段创建了包含XYZ数据的新point类型,连同一个的test的浮点型数据,这样满足存储对齐。
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
struct MyPointType //定义点类型结构
{
PCL_ADD_POINT4D; // 该点类型有4个元素
float test;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW// 确保new操作符对齐操作
}EIGEN_ALIGN16;// 强制SSE对齐
POINT_CLOUD_REGISTER_POINT_STRUCT(MyPointType,// 注册点类型宏
(float,x,x)
(float,y,y)
(float,z,z)
(float,test,test)
)
int
main(int argc,char** argv)
{
pcl::PointCloud<MyPointType> cloud;
cloud.points.resize(2);
cloud.width=2;
cloud.height=1;
cloud.points[0].test=1;
cloud.points[1].test=2;
cloud.points[0].x=cloud.points[0].y=cloud.points[0].z=0;
cloud.points[1].x=cloud.points[1].y=cloud.points[1].z=3;
pcl::io::savePCDFile("test.pcd",cloud);
}