点云库PCL学习教程PCL基础_第1页
点云库PCL学习教程PCL基础_第2页
点云库PCL学习教程PCL基础_第3页
点云库PCL学习教程PCL基础_第4页
点云库PCL学习教程PCL基础_第5页
已阅读5页,还剩99页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第3章PCL基础本章首先简述了PCLC++编程规范,为以后章节的阅读和读者编写出PCL风格的代码做一定铺垫,为了让用户基于PCL开发出自己的扩展,紧接着通过实例详细介绍了在PCL框架下如何建立用户自定义类,最后介绍了PCL中点的已有类型以及如何自定义点类型以满足扩展需求。掌握第3章的内容之后,读者可轻松阅读PCL源码和后续章节中的例子程序,也为读者后续自行开发奠定了基础。本章各小节目录3.1PCLC++编程规范3.2如何编写新的PCL类3.3PCL已有点类型介绍和增加自定义的点类型3.4PCL中异常处理机制3.1PCLC++编程规范架构师为了确保在PCL中所有代码风格的一致性,使得其他开发者及用户容易理解源码,PCL开发者制定并遵循着一套严格的编写规范,PCL的开发者都默认此规范,除非有充足的理由才可以不遵循这些规范。当然这些规范也并不是一成不变的,但提出和更改规则的人需要考虑兼容性,那就是让新加的规则与现有的所有代码相适应。3.1.1PCL推荐的命名规范1.文件命名(1)所有的文件名单词之间应该用下划线隔开,例如unordered_map.hpp。(2)头文件的扩展名为.h。(3)模板类实现文件的扩展名是.hpp。(4)源文件的扩展名是.cpp。2.目录命名所有的目录及其子目录命名应该符合,如果由多个单词组成,其之间用下划线隔开,PCL中各个目录遵循以下规则:(1)头文件都应放在源码目录树中的include/下。(2)模板类实现文件都应放在目录树中的include/impl/下。(3)源文件都应放在目录树中的src/下。3.Include语句当文件在同一目录下时Include指示语句用双引号,在其他情况下则用尖括号,例如:#include<pcl/module_name/file_name.h>#include<pcl/module_name/impl/file_name.hpp>#include“file_name.cpp”//在同一目录下4.宏定义命名宏定义中字母都采用大写格式,为头文件所定义的宏最后面还需要加上下划线,并且名称从include下目录开始,例如pcl/filters/bilateral.h对应PCL_FILTERS_BILATERL_H_。#ifndef和#define定义放在BSD协议后面代码前面。#endif定义一直在文件结尾,并且加上一句注释掉的宏对应头文件的宏定义,例如://theBSDlicense#ifndefPCL_MODULE_NAME_IMPL_FILE_NAME_HPP_//为避免重复包含头文件而定义的宏#definePCL_MODULE_NAME_IMPL_FILE_NAME_HPP_//thecode#endif//PCL_MODULE_NAME_IMPL_FILE_NAME_HPP_5.命名空间命名命名空间多于一个单词的,单词之间应该用下划线连接,例如:namespacepcl_io{…}6.类/结构命名类名(和其他自定义类型的名称)应该是CamelCased(驼峰命名)命名规范,也就是连写单词组成命名,每个单词首字母大写。但是有例外:如果类名包含一个缩写,这个缩写应该全部大写,类名和结构名最好是名词组成的名字,例如PFHEstimation代替了EstimatePFH,下面是正确的命名代码例子:classExampleClass;classPFHEstimation;7.函数/成员函数命名函数和类的成员函数的命名应该采用camelCased,也就是连写单词组成命名,除了首个单词首字母小写其他单词首字母大写,它们的参数命名单词之间用下划线隔开,函数和类的成员函数命名最好采用动词,应该确保这些名字能清楚的表达函数和类成员函数的功能,例如,checkForErrors()而不是errorCheck(),dumpDataToFile()而不是dataFiledump(),正确的用法:intapplyExample(intexample_arg);8.变量命名变量的命名应该单词之间用下划线隔开例如:intmy_variable;(1)迭代子变量命名。迭代子变量应该反应出它们迭代的对象,例如:std::list<int>pid_list;std::list<int>::iteratorpid_it;//指示迭代的对象为点的索引(2)常量命名。常量的名字应该是全大写,例如:conststaticintMY_CONSTANT=1000;(3)成员变量命名。类的成员变量命名单词之间用下划线隔开并且以下划线结尾,例如:intexample_int_;//对阅读PCL源码很有帮助,可明显区分成员变量与局部变量9.Return语句return语句需要在圆括号中设返回值,即规定return语句必须有返回值,大家知道,return如果没有返回值也会编译,例如:intmain(){return(0);}3.1.2PCL推荐的缩进与格式在PCL中每个代码块的标准缩进是两个空格,在任何情况下可以用制表符或者其他空格间隔的方式进行格式化代码,PCL利用多样化的GNU类型的格式。命名空间缩进格式在头文件里,命名空间的内容应该缩进两个空格,例如:namespacepcl{classFoo{…};}在一个实现文件里,对每一个类成员函数或函数的命名必须添加命名空间限定,例如:voidpcl::Foo::bar(){…}2.类格式一个模板类的模板参数必须与类定义在不同行,例如:template<typenameT>classFoo{…}3.函数/类成员函数格式每一个函数的返回类型声明必须与函数声明放在不同的行,例如:voidbar();在函数实现的时候也一样,返回类型声明必须与函数声明放在不同的行,例如:voidbar(){…}或者:voidFoo::bar(){…}或者:template<typenameT>voidFoo<T>::bar(){…}4.花括号花括号成对出现,与上一句代码另起一行定义,必须闭合才组成合理的程序块,例如:if(a<b){…}else{…}下面的情况花括号可以省略,例如:if(a<b)x=2*a;5.空格格式让我们再来强调一次,在PCL中的每一个代码块的标准缩进是两个空格,这里用单个空格来隔开函数/类成员函数名字与其参数列表,例如:intexampleMethod(intexample_arg);如果在头文件内嵌套应用了命名空间名,需要将其缩进两个空格,例如:namespacefoo{namespacebar{voidmethod(intmy_var);}}类和结构成员采用两个空格进行缩进,访问权限限定(public,privateandprotected)与类成员一级,而在其限定下的成员则需要缩进两个空格。例如:namepsacefoo{classBar{inti;public:voidbaz();}}6.自动格式化代码PCL提供下面一套规则文件通过多种不同的集成开发环境、编辑器等可以自动格式化编码。(1)Emacs,可以利用PCLC/C++配置文件(),下载并存储此文件,再按如下操作进行:3.1.3设计结构1.类和应用程序接口对于PCL的大多数类而言,调用接口(所有public成员)是不含公开成员变量而只有采用两种成员方法(不排除有部分类公开成员):(1)固定的类型,它允许通过get/set修改或添加参数以及输入数据。(2)实际实现功能的函数,例如运算、滤波、分割、配准等处理功能。2.参数传递get/set类型的方式遵循下面的规则:(1)如果大量的数据需要传送(常见的例子是在PCL中输入数据)优先采用boost共享指针,而不是传送实际的数据。(2)成对的get与set类型成员函数总是需要采用一致的数据类型。(3)对于get类型成员函数而言,如果只有一个参数需要被传递则会通过返回值,如果是两个或两个以上的参数需要传递,则通过引用方式进行传递。对于运算、滤波、分割等类型的参数遵循以下规则:(1)无论传递数据的大小,返回参数最好是非指针型参数。(2)总是通过引用方式来传递输出参数。3.2如何编写新的PCL类把代码转换成符合PCL思路和句法的代码,对于第一次接触该基础架构的人会显得比较困难,会提出若干疑问。本小节介绍如何编写新的PCL类以及经常碰到的问题,也解释了在PCL目录树下与全球PCL用户共享你的代码有哪些优势,这里提倡共享,读者也可以把这种观念应用于其他类似的项目中,无论读者是自己直接写还是改写已有代码,本节的内容都很有帮助,最重要的是可以帮助读者快速阅读了解PCL中的源码。3.2.1优势:为什么加入PCL开源开发模式开发者不可能事先考虑他们编写的代码片段可能所有的用途,但也奠定了一定基础……由于有限的资源和时间,发现解决方案以及应用程序中所有可能出现的漏洞是困难的,由于资源的限制,可能开始的时候进行处理也是不合适的。除了上面介绍到的,用户的贡献可能会有以下众多的作用:(1)别人以用户的代码为基础建立新的项目。(2)学习其他人新的用法。(3)无忧无虑的维护者身份。(4)在社区名声会提高。为了举例说明代码转换过程,我们选择下面的例子实现对给定点云中的强度数据应用双边滤波器,把结果保存到磁盘。首先,在本书提供光盘的第3章例1文件夹中,打开名为mainBilateralFilter.cpp的代码文件,这里打开的源代码段包括以下步骤:(1)输入/输出代码块:从磁盘读数据,向磁盘写数据;(2)初始化代码块:用kd树建立一种搜索最近邻的方法;(3)实际算法代码块:对每个点进行双边滤波处理。我们的目的是把给出的源码程序转换成PCL的类,以便能够在其他地方重复使用。3.2.2建立文件结构有两种不同的方法来建立文件结构:①分别编写代码,作为独立的PCL类在PCL代码树之外;②直接把文件建立的PCL代码目录树中,我们来阐述后者的操作方式,因为后者是最终结果有利于PCL库发展壮大,也是因为它有一点复杂(也就是,它包含几个附加的步骤)。对于前者,可以同样操作,只是不需要在PCL代码目录树中建立对应的文件组织形式,也不需要了解CMake的使用。假设我们想要新的算法成为PCL滤波库的一部分,我们开始先在代码树目录filters下新建3个不同的文件:(1)include/pcl/filters/bilateral.h——包含所有的定义和声明;(2)include/pcl/filters/impl/bilateral.hpp——包含模板类的具体实现;(3)src/bilateral.cpp——包含具体的不同点类型的模板类实例化。我们需要给新的类命名,把它称做BilateralFilter,PCL滤波器接口规定每个算法必须有两个声明和实现可供使用:一个操作PointCloud<T>,一个操作PointCloud2。本小节只讲前者操作PointCloud<T>的实现。1.bilateral.h前面提到过,bilateral.h头文件包含所有和BilateralFilter类相关的声明,下面是最小的框架:#ifndefPCL_FILTERS_BILATERAL_H_#definePCL_FILTERS_BILATERAL_H_#include<pcl/filters/filter.h>namespacepcl{template<typenamePointT>classBilateralFilter:publicFilter<PointT>//Filter类{};}#endif//PCL_FILTERS_BILATERAL_H_2.bilateral.hpp新建bilateral.hpp和bilateral.cpp两个文件,首先是bilateral.hpp:#ifndefPCL_FILTERS_BILATERAL_IMPL_H_#definePCL_FILTERS_BILATERAL_IMPL_H_#include<pcl/filters/bilateral.h>#endif//PCL_FILTERS_BILATERAL_H_这个应该是明确的,还没有给BilateralFilter声明任何方法,因此它没有任何具体实现。3.bilateral.cpp再编写bilateral.cpp文件:#include<pcl/filters/bilateral.h>#include<pcl/filters/impl/bilateral.hpp>因为在PCL(1.X)中编写模板代码,这里模板参数是点的类型,我们要显式地在bilateral.cpp中说明所有点类型对应模板类的定义,因此用户在使用我们定义的BilateralFilter编译代码的时候不必花费额外的编译时间。为此,我们需要添加include模板类实例化头文件(bilateral.hpp)。4.CMakeLists.txt把所有新建的文件增加到PCL滤波器的CMakeLists.txt(在\PCL源码根目录\filters\下)文件中,就可以开始编译链接过程,对于CMakeLists.txt的更改就完成了,剩下就是下面对类相关文件内容的填充了。#Find“set(srcs”,andaddanewentrythere,e.g.,set(srcssrc/conditional_removal.cpp#...src/bilateral.cpp//为该类而添加的语句)#Find“set(incs”,andaddanewentrythere,e.g.,set(incsincludepcl/${SUBSYS_NAME}/conditional_removal.h#...includepcl/${SUBSYS_NAME}/bilateral.h//为该类而添加的语句)将编写的类添加进源码#Find“set(impl_incs”,andaddanewentrythere,e.g.,set(impl_incsinclude/pcl/${SUBSYS_NAME}/impl/conditional_removal.hpp#...include/pcl/${SUBSYS_NAME}/impl/bilateral.hpp//为该类而添加的语句)3.2.3填写类的内容如果用户正确无误地编辑了以上所有文件,在适当的地方使用新的滤波器类重编译PCL源码应该没有问题。这一小节中开始填充每一个文件中的代码,我们从bilateral.cpp文件开始,因为它的内容是最短的。1.bilateral.cpp如前所述,我们准备为BilateralFilter类实例化并预编译实例化若干模板。尽管这可能增加PCL滤波库的编译时间,但是在用户编写的代码中使用该类的时候,能够使编译模板速度提高。为此最简单的做法像下面的在bilateral.cpp文件中手工添加模板实例化代码,每行对应一个需要手工预编译的模板实例:#include<pcl/point_tyepes.h>#include<pcl/filters/bilteral.h>#include<pcl/filters/impl/bilateral.hpp>templateclassPCL_EXPORTSpcl::BilateralFilter<pcl::PointXYZ>;templateclassPCL_EXPORTSpcl::BilateralFilter<pcl::PointXYZI>;templateclassPCL_EXPORTSpcl::BilateralFilter<pcl::PointXYZRGB>;//…然而,随着PCL支持的点的类型增加,上面一一对应的定义方式会使文件迅速增大,随后在维护PCL更新时变的更麻烦。因此,我们准备使用一个特殊的叫做PCL_INSTANTIATE的宏,并对以上代码做如下改变:#include<pcl/point_types.h>#include<pcl/impl/instantiate.hpp>#include<pcl/filters/bilateral.h>#include<pcl/filters/impl/bilateral.hpp>PCL_INSTANTIATE(BilateralFilter,PCL_XYZ_POINT_TYPES);这个例子可以对所有在point_types.h文件中定义的XYZ点类型对应的BilateralFilter模板类进行实例化,继而在编译类的时候就省去了PCL用户使用时的编译时间。仔细看例子中的代码,我们注意到像cloud->points[point_id].intensity这样的引用表明滤波器需要在点类型中有强度成员,因此,使用PCL_XYZ_POINT_TYPES就不起作用,因为不是所有声明了的类型都有强度数据。实际上,很容易注意到只有两个类型包含强度成员,也就是PointXYZI和PointXYZINormal,因此替换掉PCL_XYZ_POINT_TYPES,最后bilateral.cpp文件见本章源码文件1.0文件夹下的该文件。注意:现在还没有为BilateralFilter声明PCL_INSTANTIATE模板类,实际上也没有实现抽象类pcl::Filter中的纯虚函数,因此如果试图编译代码将产生如下错误:filters/src/bilateral.cpp:6:32:error:expectedconstructor,destructor,ortypeconversionbefore‘C’token…2.bilateral.h开始填充BilateralFilter类的内容,首先声明构造函数和它的成员变量。因为该双边滤波器算法有两个参数,存储起来作为类的成员,并按照PCL1.X的API范例来为它们实现各种设值函数(setters)和取值函数(getters),代码如下:…namespacepcl{template<typenamePointT>classBilateralFilter:publicFilter<PointT>{public:BilateralFilter():sigma_s_(0),sigma_r_(std::numeric_limits<double>::max()){}voidsetSigmaS(constdoublesigma_s){sigma_s_=sigma_s;}doublegetSigmaS(){return(sigma_s_);}voidsetSigmaR(constdoublesigma_r){sigma_r_=sigma_r;}doublegetSigmaR(){return(sigma_r_);}private:doublesigma_s_;doublesigma_r_;}}#endif//PCL_FILTERS_BILATERAL_H_都现在为止都是最普通的C++代码,除了构造函数BilateralFilter(),这里给了两个参数默认值。因为我们的类是从pcl::Filter继承的,而pcl::Filter是从pcl::PCLBase继承的,可以用setInputCloud()方法给算法传递输入数据(作为input_存储),因此下面增加了一个using声明:…template<typenamePointT>classBilateralFilter:publicFilter<PointT>{usingFilter<PointT>::input_;public:BilateralFilter():sigma_s_(0),…}使用using声明确保我们的类能够不需输入完整引用方式就可以访问成员其父类的成员变量input_,下面我们定义applyFilter函数,由于其是纯虚函数,所有从pcl::Filter继承的类都必须实现该方法成员,因此有如下声明:…usingFilter<PointT>::input_;typedeftypenameFilter<PointT>::PointCloudPointCloud;public:BilateralFilter():sigma_s_(0),sigma_r_(std::numeric_limits<double>::max()){}voidapplyFilter(PointCloud&output);…后面在bilateral.hpp文件中会给出applyFilter的具体实现。其中构造了一个类型定义(typedef)这样就不用输入整个引用就能使用PointCloud类。在例子中查看原始代码,我们注意到该算法是由对点云中每个点进行相同的操作组成的,为保持applyFilter清晰,我们声明一个叫做computePointWeight的方法:…voidapplyFilter(PointCloud&output);doublecomputePointWeight(constintpid,conststd::vector<int>&indices,conststd::vector<float>&distances);…此外,我们注意到例子中构造了一个Kd树对象用于获取给定点的近邻,因此我们增加下面的内容:#include<pcl/kdtree/kdtree.h>…usingFilter<PointT>::input_;typedeftypenameFilter<PointT>::PointCloudPointCloud;typedeftypenamepcl::KdTree<PointT>::PtrKdTreePtr;public:…voidsetSearchMethod(constKdTreePtr&tree){tree_=tree;}private:…KdTreePtrtree_;…最后,增加核心计算函数G(floatx,floatsigma)为内联(inline)函数,以便能够加速滤波的计算,因为该方法仅仅在该算法的环境里有用,我们把它设置为私有,完整的头文件见本章源码文件1.0文件夹下bilateral.h。3.bilateral.hpp最后需要编写的是.hpp文件,完成对声明函数的具体实现,这里需要实现两个方法,就是applyFilter和computePointWeight。template<typenamePointT>doublepcl::BilateralFilter<PointT>::computePointWeight(constintpid,conststd::vector<int>&indices,conststd::vector<float>&distances){doubleBF=0,W=0;

//Foreachneighborfor(size_tn_id=0;n_id<indices.size();++n_id){doubleid=indices[n_id];doubledist=std::sqrt(distances[n_id]);doubleintensity_dist=abs(input_->points[pid].intensity–input_->points[id].intensity);doubleweight=kernel(dist,sigma_s_)*kernel(intensity_dist,sigma_r_);BF+=weight*input_->points[id].intensity;W+=weight;}return(BF/W);}template<typenamePointT>voidpcl::BilateralFilter<PointT>::applyFilter(PointCloud&output){tree_->setInputCloud(input_);std::vector<int>k_indices;std::vector<float>k_distances;

output=*input_;

for(size_tpoint_id=0;point_id<input_->points.size();++point_id){tree_->radiusSearch(point_id,sigma_s_*2,k_indices,k_distances);output.points[point_id].intensity=computePointWeight(point_id,k_indices,k_distances);}}computePointWeight方法应该很简单因为它几乎和实例计算代码一样,通过传递一个要计算强度重量的point索引,索引所指示的点是由欧氏空间的邻域组成。在applyFilter中,我们首先利用输入数据构建Kd树,把所有输入数据复制到输出,然后开始计算新的点的强度,赋值于输出点云数据output。是时候为该类声明PCL_INSTANTIATE来实例化模板类了:#ifndefPCL_FILTERS_BILATERAL_IMPL_H_#definePCL_FILTERS_BILATERAL_IMPL_H_#include<pcl/filters/bilateral.h>…#definePCL_INSTANTIATE_BilateralFilter(T)templateclassPCL_EXPORTSpcl::BilateralFilter<T>;#endif//PCL_FILTERS_BILATERAL_H_需要做的另一件事就是检查错误:(1)是否给定了sigma_s_和sigma_r_两个参数;(2)是否设置了搜索方法的对象(例如,tree_)。对于前者,检查sigma_s_的值,它默认被设置成0,它对算法的行为有十分重要的意义(它实际上是定义了算法所支持滤波的范围大小),因此,如果代码执行的时候其值仍然是0,我们就用宏PCL_ERROR打印一个错误并返回。就搜索方法来说,我们可以做同样的操作或者为用户提供默认的选项,最好的默认选项如下:(1)如果点云是有序的,通过pcl::OrganizedDataIndex使用有序搜索方法。(2)如果点云是无序的,通过pcl::KdTreeFLANN使用通用的Kd树。#include<pcl/kdtree/kdtree_flann.h>#include<pcl/kdtree/organized_data.h>…template<typenamePointT>voidpcl::BilateralFilter<PointT>::applyFilter(PointCloud&output){if(sigma_s_=0){PCL_ERROR(“[pcl::BilateralFilter::applyFilter]Needasigma_svaluegivenbeforecontinuing.\n”);return;}if(!tree_){if(input_->isOrganized())tree_.reset(newpcl::OrganizedDataIndex<PointT>());elsetree_.reset(newpcl::KdTreeFLANN<PointT>(false));}tree_->setInputCloud(input_);…}这样该模板类完整的实现头文件见本章源码文件1.0文件夹下的bilateral.hpp。4.利用PCL其他机制点索引机制向PCL算法传递点云数据的标准方法是通过访问setInputCloud()。另外,PCL也可通过setIndices()传递用户感兴趣区域或点云集,而不是整个点云。所有的类都从PCLBase继承了以下行为:如果用户没有给出一点的索引,类就会建立一个虚的索引并且在算法的整个运行期间使用。这意味着我们能够很容易地改变上面的实现代码来对<cloud,indices>元组进行操作。这样的好处就是如果用户确实传递了点的索引,将会使用传递索引对应的点云,如果没有传递将使用整个点云。新的bilateral.hpp类就成为下面这样子:#include<pcl/kdtree/kdtree_flann.h>#include<pcl/kdtree/organized_data.h>…template<typenamePointT>voidpcl::BilateralFilter<PointT>::applyFilter(PointCloud&output){if(sigma_s_==0){PCL_ERROR(“[pcl::BilateralFilter::applyFilter]Needasigma_svaluegivenbeforecontinuing.\n”);return;}if(!tree_){if(input_->isOrganized())tree_.reset(newpcl::OrganizedDataIndex<PointT>());elsetree_.reset(newpcl::KdTreeFLANN<PointT>.(false));}tree_->setInputCloud(input_);…}模板类实现头文件就编写如本章源码文件2.0文件夹下bilateral.hpp。为了使indices_不用输入整个约束就能运行,需要给bilateral.h文件增加一行using语句来指示indices_的位置:…template<typenamePointT>classBilateralFilter:publicFilter<PointT>{usingFilter<PointT>::input_;usingFilter<PointT>::indices_;public:BilateralFilter():sigma_s_(0),….}3.2.4许可建议每一个文件包含一个描述代码作者的许可,这对于用户了解使用该代码会受到何种约束是十分有用的,PCL是100%的BSD许可的,我们在文件中以C++注释的形式嵌入该许可证,详细见本章源码文件夹下License.txt。如果需要声明其他的版权,添加其他类似的内容就行了(或者原始著作权被改变):*Copyright(c)XXX,respectiveauthors.3.2.5合理命名到目前为止,我们在例程中使用诸如setSigmaS或setSigmaR等简单的词汇来命名set和get功能的函数,在实际中应该使用更好的命名方法,以便能够真正表示对应参数的功能,在代码的最终版本中将重新把setters和getters命名成set/getHalfSize和set/getStdDev以及类似的名字。3.2.6代码注释PCL试图在用户和API文档方面保持高标准,支持Doxygen的文档生成的注释已经在上面的例子中删减掉。实际中,bilateral.h头文件类部分如下:…/**\briefcomputetheintensityaverageforasinglepoint*\param[in]pid*…*/doublecomputePointWeight(constintpid,conststd::vector<int>&indices,cosntstd::vector<float>&distances);/**\briefSetthehalfsizeoftheGaussianbilateralfilterwindow.*/inlinevoidsetHalfSize(constdoublesigma_s){sigma_s_=sigma_s;}…#endif//PCL_FILTERS_BILATERAL_H_很明显比上面的代码中的注释多了不少,并且都符合一定的格式,这样就是标准的PCL编码风格了,即方便代码的维护,又方便用户的使用和学习,bilateral.hpp文件部分如下:…//Copytheinputdataintotheoutputoutput=*input_;//Foralltheindicesgiven(equaltotheentirecloudifnonegiven)for(size_ti=0;i<indices_->size();++i){//Performaradiussearchtofindthenearestneighborstree_->radiusSearch((*indices_)[i],sigma_s_*2,k_indices,k_distances);…#endif//PCL_FILTERS_BILATERAL_H_}完成的bilateral.h和bilateral.hpp文件见本章源码文件夹下3.0文件夹。3.2.7测试新建的类测试新的类很容易,我们用上面提到的第一个代码段作为例子,转而使用pcl::BilateralFilter类利用光盘提供的CMakeLists.txt和testfilter.cpp文件,在cmake中建立工程文件,并生成相应的可执行文件,生成执行文件后就可以运行测试前面定义的类。需要重新编译Filter模块(将前面修改后的类包含进CMakeLists文件后重新编译),不然会提示errorC2664:“pcl::BilateralFilter<PointT>::setSearchMethod”:不能将参数1从“boost::shared_ptr<T>”转换重新编译(单独编译某一个模块即可,但是可能依然需要依赖库)之后,将编译生成的库文件替换原来的lib库文件Why?3.3PCL已有点类型介绍和增加自定义的点类型本小节不仅解释如何增加用户自己的PointT点类型,也介绍了PCL中的模板point类型以及它们的用户和定义。PCL从开始就伴随着各种预定义的point类型,从用于XYZ数据到更复杂的n维直方图表示法,例如PFH(点特征直方图)。这些类型应该足够支持在PCL中应用的算法及方法,然而,也有情况下用户希望定义新的类型。注意:由于PCL的快速更新,本节内容仅仅对PCL0.x和1.x版本兼容,撰写本文档的时候预期在PCL2.X中会有新的改变,但大的架构不变,只是添加些新定义的描述子等点类型之类的。3.3.1为什么用PointT类型PCL的PointT可以追溯到它在ROS中作为开源库被开发的时候,大家一致认为,点云是复杂的n维结构,它需要能表示不同类型的信息,然而用户应该知道并理解需要传送什么样的信息,为了使代码更易于调试,考虑优化等,下面给出一个例子,是对XYZ数据的简单操作,对带SSE功能的处理器最高效的方法是存储三维坐标为浮点型,紧跟着一个浮点型数据作为填补位数以满足存储对齐要求:structPointXYZ{floatx;floaty;floatz;floatpadding;}然而,例如当用户在嵌入式平台上寻找编译PCL的时候,增加额外的填补就是浪费存储空间了,因此,可以用一个简单的不带最后浮点数的PointXYZ结构来替代。此外,如果应用程序需要一个包含XYZ三维数据、RGB信息(颜色)和每个点的估计法线的PointXYZRGBNormal类型,定义包含以上所有内容的结构是很简单的,由于PCL中所有算法都是模板化的,除了更改的自定义结构之外不需要做其他的更改,增加了代码的重用性和可读性。3.3.2PCL中有哪些可用的PointT类型为了涵盖能想到的所有可能的情况,PCL中定义了大量的point类型。下面是一小段,在point_types.hpp中有完整目录,这个列表很重要,因为在定义用户自己的类型之前需要了解已有的类型,如果需要的类型已经存在于PCL,那么就不需要重复定义了。PointXYZ——成员变量:floatx,y,z;PointXYZ是使用最常见的一个点数据类型,因为它只包含三维XYZ坐标信息,这三个浮点数附加一个浮点数来满足存储对齐,用户可利用points[i].data[0]或者points[i].x访问点的x坐标值。union{floatdata[4];struct{floatx;floaty;floatz;};};2.PointXYZI——成员变量:floatx,y,z,intensity;PointXYZI是一个简单的XYZ坐标加上intensity的point类型,理想情况下,这4个变量将新建单独一个结构体,并且满足存储对齐,然而,由于point的大部分操作会把data[4]元素设置成0或1(用于变换),不能让intensity与XYZ在同一个结构体中,如果这样的话其内容将会被覆盖。例如,两个点的点积会把他们的第4个元素设置成0,否则该点积没有意义,等等。因此,对于兼容存储对齐用3个额外的浮点数来填充intensity,这样在存储方面效率较低,但是符合存储对齐要求,运行效率较高。union{floatdata[4];struct{floatx;floaty;floatz;};};union{struct{floatintensity;};floatdata_c[4];};3.PointXYZRGBA——成员变量:floatx,y,z;uint32_trgba;除了RGBA信息被包含在一个整型变量中,其他的和PointXYZI类似。union{floatdata[4];struct{floatx;floaty;floatz;};};union{struct{uint32_trgba;};floatdata_c[4];};4.PointXYZRGB——floatx,y,z,rgb;除了RGB信息被包含在一个浮点型变量中,其他的和PointXYZRGBA类似。RGB数据被压缩到一个浮点数里的原因在于早期PCL是作为ROS项目的一部分来开发的,那里RGB数据是用浮点数来传送的,PCL设计者希望所有遗留代码会重新更改(在PCL2.x中很可能这样做),可能取消此数据类型。union{floatdata[4];struct{floatx;floaty;floatz;};};union{struct{floatrgb;};floatdata_c[4];};5.PointXY——floatx,y;简单的二维x-ypoint结构代码如下:struct{floatx;floaty;};6.InterestPoint——floatx,y,z,strength;除了strength表示关键点的强度的测量值,其他的和PointXYZI类似。union{floatdata[4];struct{floatx;floaty;floatz;};};union{struct{floatstrength;};floatdata_c[4];}7.Normal——floatnormal[3],curvature;另一个最常用的数据类型,Normal结构体表示给定点所在样本曲面上的法线方向,以及对应曲率的测量值(通过曲面块特征值之间关系获得——查看NormalEstimation类API以便获得更多信息,后续章节有介绍),由于在PCL中对曲面法线的操作很普遍,还是用第4个元素来占位,这样就兼容SSE和高效计算,例如,用户访问法向量的第一个坐标,可以通过points[i].data_n[0]或者points[i].normal[0]或者points[i].normal_x,在此再一次强调,曲率不能被存储在用同一个结构体中,因为它会被普通的数据操作覆盖掉。union{floatdata_n[4];floatnormal[3];struct{floatnormal_x;floatnormal_y;floatnormal_z;};}union{struct{floatcurvature;};floatdata_c[4];};8.PointNormal——floatx,y,z;floatnormal[3],curvature;PointNormal是存储XYZ数据的point结构体,并且包括采样点对应法线和曲率。union{floatdata[4];struct{floatx;floaty;floatz;};};union{floatdata_n[4];floatnormal[3];struct{floatnormal_x;floatnormal_y;floatnormal_z;};};union{struct{floatcurvature;};floatdata_c[4];};9.PointXYZRGBNormal——floatx,y,z,rgb,normal[3],curvature;PointXYZRGBNormal存储XYZ数据和RGB颜色的point结构体,并且包括曲面法线和曲率。union{floatdata[4];struct{floatx;floaty;floatz;};};union{floatdata_n[4];floatnormal[3];struct{floatnormal_x;floatnormal_y;floatnormal_z;};}union{struct{floatrgb;floatcurvature;};floatdata_c[4];};10.PointXYZINormal——floatx,y,z,intensity,normal[3],curvature;PointXYZINormal存储XYZ数据和强度值的point结构体,并且包括曲面法线和曲率。union{floatdata[4];struct{floatx;floaty;floatz;};};union{floatdata_n[4];floatnormal[3];struct{floatnormal_x;floatnormal_y;floatnormal_z;};}union{struct{floatintensity;floatcurvature;};floatdata_c[4];};11.PointWithRange——floatx,y,z(unionwithfloatpoint[4]),range;PointWithRange除了range包含从所获得的视点到采样点的距离测量值之外,其他与PointXYZI类似。union{floatdata[4];struct{floatx;floaty;floatz;};};union{struct{floatrange;};floatdata_c[4];};12.PointWithViewpoint——floatx,y,z,vp_x,vp_y,vp_z;PointWithViewpoint除了vp_x、vp_y和vp_z以三维点表示所获得的视点之外,其他与PointXYZI一样。union{floatdata[4];struct{floatx;floaty;floatz;};};union{struct{floatvp_x;floatvp_y;floatvp_z;};floatdata_c[4];};13.MomentInvariants——floatj1,j2,j3;MomentInvariants是一个包含采样曲面上面片的3个不变矩的point类型,描述面片上质量的分布情况。查看MomentInvariantsEstimation以获得更多信息。struct{floatj1,j2,j3;};14.PrincipalRadiiRSD——floatr_min,r_max;PrincipalRadiiRSD是一个包含曲面块上两个RSD半径的point类型,查看RSDEstimation以获得更多信息。struct{floatr_min,r_max;};15.Boundary——uint8_tboundary_point;Boundary存储一个点是否位于曲面边界上的简单point类型,查看BoundaryEstimation以获得更多信息。struct{uint8_tboundary_point;}16.PrincipalCurvatures——floatprincipal_curvature[3],pc1,pc2;PrincipalCurvatures包含给定点主曲率的简单point类型。查看PrincipalCurvaturesEstimation以获得更多信息。struct{union{floatprincipal_curvature[3];struct{floatprincipal_curvature_x;floatprincipal_curvature_y;floatprincipal_curvature_z;};};floatpc1;floatpc2;};17.PFHSignaturel25——floatpfh[125];PFHSignaturel25包含给定点的PFH(点特征直方图)的简单point类型,查看PFHEstimation以获得更多信息。struct{floathistogram[125];};18.FPFHSignature33——floatfpfh[33];FPFHSignature33包含给定点的FPFH(快速点特征直方图)的简单point类型,查看FPFHEstimation以获得更多信息。struct{floathistogram[33];};19.VFHSignature308——floatvfh[308];VFHSignature308包含给定点VFH(视点特征直方图)的简单point类型,查看VFHEstimation以获得更多信息。struct{floathistogram[308];};20.Narf36——floatx,y,z,roll,pitch,yaw;floatdescriptor[36];Narf36包含给定点NARF(归一化对齐半径特征)的简单point类型,查看NARFEstimation以获得更多信息。struct{floatx,y,z,roll,pitch,yaw;floatdescriptor[36];};21.BorderDescription——intx,y;BorderTraitstraits;BorderDescription包含给定点边界类型的简单point类型,看BorderEstimation以获得更多信息。struct{intx,y;BorderTraitstraits;};22.IntensityGradient——floatgradient[3];IntensityGradient包含给定点强度的梯度point类型,查看IntensityGradientEstimation以获得更多信息。struct{union{floatgradient[3];struct{floatgradient_x;floatgradient_y;floatgradient_z;};};};23.Histogram——floathistogram[N];Histogram用来存储一般用途的n维直方图。template<intN>structHistogram{floathistogram[N];};24.PointWithScale——floatx,y,z,scale;PointWithScale除了scale表示某点用于几何操作的尺度(例如,计算最近邻所用的球体半径,窗口尺寸等),其他的和PointXYZI一样。struct{union{floatdata[4];struct{floatx;floaty;floatz;};};floatscale;};25.PointSurfel——floatx,y,z,normal[3],rgba,radius,confidence,curvature;PointSurfel存储XYZ坐标、曲面法线、RGB信息、半径、可信度和曲面曲率的复杂point类型。union{floatdata[4];struct{floatx;floaty;floatz;};};union{floatdata_n[4];floatnormal[3];struct{floatnormal_x;floatnormal_y;floatnormal_z;};};union{struct{uint32_trgba;floatradius;floatconfidence;floatcurvature;};floatdata_c[4];};3.3.3如何在模板类中使用这些point类型由于PCL模块较多,并且是一个模板库,在一个源文件里包含很多PCL算法会减慢编译过程,在撰写本文档的时候,大多数C++编译器仍然没有很好的优化来处理大量模板文件,尤其是涉及优化(-O2或者-O3)问题的时候。为了使包含和链接到PCL库的用户代码编译速度提高,我们使用显示的模板实例化,最终编译链接的库包括了所有可能的模板实例——在这些组合中使PCL中已经定义的point类型所有的模板类都能够直接调用,不需要重新编译,这意味着一旦PCL编译成库,任何用户代码都不需要编译模板化代码,这样就加速了用户编译过程。这个是通过在头文件中首先声明了我们的类和方法,再在模板类实现头文件中进行实现,配置在源文件中进行显示的实例化,最后在编译链接时分别实例化。举一个例子://foo.h#ifndefPCL_FOO_#definePCL_FOO_template<typenamePointT>classFoo{public:voidcompute(constpcl::PointCloud<PointT>&input,pcl::PointCloud<PointT>&output);}#endif//PCL_FOO_上面定义了模板类Foo,可以看到,它定义了方法和类,但是还没有实现任何功能。//impl/foo.hpp#ifndefPCL_IMPL_FOO_#definePCL_IMPL_FOO_#include“foo.h”template<typenamePointT>voidFoo::compute(constpcl::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”//InstantiationsofspecificpointtypesPCL_INSTANTIATE(Foo,PCL_XYZ_POINT_TYPES));最后,上面展示了在PCL中是如何进行显示实例化的,宏PCL_INSTANTIATE仅仅检查给定的类型清单并为每一个类型创建对应一个类实例。pcl/include/pcl/impl/instantitate.hpp中有如下代码://PCL_INSTANTIATE:calltoinstantiatetemplateTEMPLATEforall//POINT_TYPES#definePCL_INSTANTIATE_IMPL(r,TEMPLATE,POINT_TYPE)\BOOST_PP_CAT(PCL_INSTANTIATE_,TEMPLATE)(POINT_TYPE)#definePCL_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中)://DefineallpointtypesthatincludeXYZdata#definePCL_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这一个实例类,不需要使用宏,而只需要像下面这样简单地做://foo.cpp#include“pcl/point_types.h”#include“pcl/impl/instantiate.hpp”#include“foo.h”#include“impl/foo.hpp”templateclassFoo<pcl::PointXYZ>;注意:查看DavidVandervoorde和NicolaiM.Josuttis所著的C++Templates–TheCompleteGuide,可获得关于显示实例化的更多信息。3.3.4如何增加新的PointT类型为了增加新的point类型,首先需要进行定义,例如:structMyPointType{floattest;};然后,得确保自己的代码包含了PCL中特定的类/算法的模板头文件的实现,它将和新point类型MyPointType共同使用,例如,想使用pcl::PassThrough。只需要使用下面的代码即可:#include<pcl/filters/passthrough.h>#include<pcl/filters/impl/passthrough.hpp>//therestofthecodegoeshere如果自己的代码是库的一部分,可以被他人使用,需要为自己的MyPointType类型进行显示实例化。下面的代码段创建了包含XYZ数据的新point类型,连同一个的test的浮点型数据,这样满足存储对齐。#include<pcl/point_types.h>#include<pcl/point_cloud.h>#include<pcl/io/pcd_io.h>structMyPointType//定义点类型结构{

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论