第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指示语句用双引号,在其他情况下则用尖括号,例如:#includepcl/module_name/file_name.h#includepcl/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::listintpid_list;std::listint::iteratorpid_it;//指示迭代的对象为点的索引(2)常量命名。常量的名字应该是全大写,例如:conststaticintMY_CONSTANT=1000;(3)成员变量命名。类的成员变量命名单词之间用下划线隔开并且以下划线结尾,例如:intexample_int_;//对阅读PCL源码很有帮助,可明显区分成员变量与局部变量9.Return语句return语句需要在圆括号中设返回值,即规定return语句必须有返回值,大家知道,return如果没有返回值也会编译,例如:intmain(){return(0);}3.1.2PCL推荐的缩进与格式在PCL中每个代码块的标准缩进是两个空格,在任何情况下可以用制表符或者其他空格间隔的方式进行格式化代码,PCL利用多样化的GNU类型的格式。1.命名空间缩进格式在头文件里,命名空间的内容应该缩进两个空格,例如:namespacepcl{classFoo{…};}在一个实现文件里,对每一个类成员函数或函数的命名必须添加命名空间限定,例如:voidpcl::Foo::bar(){…}2.类格式一个模板类的模板参数必须与类定义在不同行,例如:templatetypenameTclassFoo{…}3.函数/类成员函数格式每一个函数的返回类型声明必须与函数声明放在不同的行,例如:voidbar();在函数实现的时候也一样,返回类型声明必须与函数声明放在不同的行,例如:voidbar(){…}或者:voidFoo::bar(){…}或者:templatetypenameTvoidFooT::bar(){…}4.花括号花括号成对出现,与上一句代码另起一行定义,必须闭合才组成合理的程序块,例如:if(ab){…}else{…}下面的情况花括号可以省略,例如:if(ab)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滤波器接口规定每个算法必须有两个声明和实现可供使用:一个操作PointCloudT,一个操作PointCloud2。本小节只讲前者操作PointCloudT的实现。1.bilateral.h前面提到过,bilateral.h头文件包含所有和BilateralFilter类相关的声明,下面是最小的框架:#ifndefPCL_FILTERS_BILATERAL_H_#definePCL_FILTERS_BILATERAL_H_#includepcl/filters/filter.hnamespacepcl{templatetypenamePointTclassBilateralFilter:publicFilterPointT//Filter类{};}#endif//PCL_FILTERS_BILATERAL_H_2.bilateral.hpp新建bilateral.hpp和bilateral.cpp两个文件,首先是bilateral.hpp:#ifndefPCL_FILTERS_BILATERAL_IMPL_H_#definePCL_FILTERS_BILAT