(1)八叉树的基本概念与定义
(2)点云压缩中的八叉树体素构建方法及其在PCL中的应用
(3)八叉树在k邻域搜索、半径搜索及体素近邻搜索中的实现
(4)八叉树与kd-tree在k邻域搜索、半径搜索中的性能对比分析
(5)八叉树在空间变化检测中的应用场景与实现方法
八叉树(Octree)是一种用于三维空间数据组织的树状数据结构。以一个立方体为例,将其分割成尽可能均匀的小立方体,最少可以得到8个小立方体。假设在一个房间内寻找一枚金币,最高效的方法是将房间视为一个立方体,首先将其分割成8个小立方体,然后逐步排除不含金币的小立方体,继续对可能包含金币的小立方体进行八等分,如此反复,平均只需经过Log8(房间内物品总数)次操作即可找到目标。因此,八叉树主要用于三维场景管理,能够快速定位物体在3D空间中的位置,并检测物体间的碰撞情况或是否处于可视范围内。
以下是对八叉树相关概念的具体说明:
体素:如上图所示,一个立方体经过两次分割后形成64个等边长的小立方体,这些小立方体即为该八叉树的体素单元;
深度:深度计算公式为正方体被分割次数加1。如图所示,一个正方体经过两次分割,其深度为3;
分辨率:八叉树的分辨率指体素的边长。如图所示,若大正方体边长为1米,则体素边长为0.25米,即分辨率为0.25。注意,PCL中默认的点云距离单位为米,因此当点云压缩参数设为5厘米时,实际输入值应为0.05。
点云数据通常包含庞大的三维点集,这些点集通过距离、颜色、法线等附加信息描述空间中的三维坐标。如此庞大的数据集在创建时需要占用大量存储资源,而在存储或通过带宽受限的通信信道传输时,必须进行压缩编码以减少数据量。
PCL提供了点云压缩功能,支持对所有类型的点云进行压缩编码,包括具有无参考点、尺寸、分辨率、分布密度和点顺序等结构特征的无序点云。
PCL进行点云压缩编码时,首先将点云视为一个大型立方体,然后按照八叉树结构将其分割成多个小立方体,这些小立方体即为八叉树的叶子节点或体素。PCL的点云压缩编码正是通过对这些体素进行分别编码实现的。
PCL构建体素的方法如下:首先确定点云中x、y、z三个坐标的最小值点(xmin、ymin、zmin)作为参考点,然后以该点为起点沿三个坐标轴(x、y、z)正方向绘制直线段,形成大立方体的三条棱边。直线段的长度计算方法为:找出点云中x、y、z三个坐标的最大值点(xmax、ymax、zmax),分别计算:
xlen = xmax – xmin;
ylen = ymax – ymin;
zlen = zmax – zmin;
取最大值 len = max(xlen, ylen, zlen);
创建PCL的八叉树对象时,需传入一个参数“八叉树分辨率”(octreeResolution),该参数表示体素的小正方体棱长。PCL会计算八叉树的深度值n(n≥0,即大立方体被切割的层数,第1层为1个立方体变为8个,第2层为8个变为64个……),使其满足
octreeResolution * 2 ^ n ≥ len;
满足该公式的最小n值即为PCL创建的八叉树深度。
以下通过两个示例程序验证该过程:
创建Qt Console程序,
以上代码中,创建的点云压缩对象 pcl::io::OctreePointCloudCompression,其构造函数包含以下参数:
参数 compressionProfile_arg:压缩配置,代码中设置为 pcl::io::MANUAL_CONFIGURATION,表示手动设置参数而非使用默认值;
参数 showStatistics_arg:编码压缩过程中是否在控制台显示压缩信息;
参数 pointResolution_arg:体素编码压缩时的点分辨率,用于体素压缩编码,仅在参数 doVoxelGridDownDownSampling_arg 为false时有效;
参数 octreeResolution_arg:体素(八叉树叶子节点正方体的棱长)的长度;
参数 doVoxelGridDownDownSampling_arg:false-根据特定公式计算体素压缩编码后的点;true-取体素中心点作为体素压缩编码后的点;
参数 iFrameRate_arg:决定进行I帧编码的频率,若此数值为30,则每隔30帧进行一次I帧编码,其余帧进行P帧编码;
参数 doColorEncoding_arg:false-不进行颜色编码;true-进行颜色编码,若输入点云文件无颜色信息,则在解码时赋予默认颜色值;
参数 colorBitResolution_arg:表示颜色的RGB保留的有效位数。
根据代码中参数给定 pcl::io::OctreePointCloudCompression(pro, true, 0.01, 1, true);
doVoxelGridDownDownSampling_arg == true:不进行体素编码,即取体素中心点作为新点云的点;
octreeResolution_arg == 1:八叉树分辨率为1,即体素正方体棱长为1。
依据前文分析,代码中点云最小点(-1, -1, -1),最大点(1.5, 1.5, 1.5), len = 1.5 – (-1) = 2.5;octreeResolution=1,
满足式子 octreeResolution * 2 ^ n ≥ len 的最小 n == 2。
因此,该代码将以(-1, -1, -1)为参考点,将棱长为 (octreeResolution * 2 ^ n) == 4 的大正方体切割2层,形成64个棱长为1的体素,八叉树深度为n==2,然后因参数 doVoxelGridDownDownSampling_arg == true,压缩后的点云将取每个含点的体素中心点形成编码压缩后的点云。
根据 octreeResolution_arg == 1,可知源点云的3个点分别位于3个不同体素,而这3个体素的中心点分别为(-0.5, -0.5, -0.5)、(0.5, 0.5, 0.5)和(1.5, 1.5, 1.5),因此,该代码编码压缩后的新点云将由这3个点组成。
运行程序验证结果与预期一致:
基于八叉树的搜索与基于kd-tree的搜索在性能上差异不大,主要区别在于创建的搜索对象不同。
基于八叉树需创建对象 pcl::octree::OctreePointCloudSearch。
k邻域搜索调用 pcl::octree::OctreePointCloudSearch::nearestKSearch;
半径搜索调用 pcl::octree::OctreePointCloudSearch::radiusSearch;
体素近邻搜索调用 pcl::octree::OctreePointCloudSearch::voxelSearch。
以下通过一个示例演示这三种搜索函数的调用:
创建Qt Console程序,
以上程序的输出为:
接下来,通过编写一个小程序对比测试kd-tree与八叉树在k邻域搜索、半径搜索中的性能。
创建Qt Console程序,
1000个点的计算结果:
10,000个点的计算结果:
100,000个点的计算结果:
1,000,000个点的计算结果:
从4次计算结果来看,对于k近邻搜索和半径搜索,kd-tree比八叉树的速度更快,性能更优。
八叉树是一种用于管理稀疏三维数据的树状数据结构,可用于检测多个无序点云之间的空间变化,这些点云可能在尺寸、分辨率、密度和点顺序上存在差异。
通过递归比较八叉树的树结构,可以识别出由八叉树生成的体素组成之间的差异,从而反映空间变化。
以下通过一个小程序展示PCL的八叉树如何检测点云的体素变化:
创建Qt Console程序,
程序输出为:
根据代码中初始化的点云,cloundB形成的八叉树与clound形成的八叉树,在含有点的体素上,实际减少了一个体素(点(0.5,0.5,0.5)所在的体素)并增加了一个体素(点(2.5,2.5,2.5)所在的体素),但程序仅输出增加的体素,未显示减少的体素。可见,PCL基于八叉树的控件变化检测仅检测新点云比原点云增加的体素,而不检查减少的体素。