上一次介绍了KD树及应用,这次介绍一下八叉树,主要从定义、结构、作用及应用几个方面进行理解。
八叉树的定义
八叉树是在描述三维空间坐标场景中常用的一种数据结构。如下图所示,一个空间自身作为根节点,当需要细分内部区域时,将空间划分为八个小空间,即八个子节点,若某个小节点还需要细分,则继续往下划分八个子节点,就这样将一个空间不断的划分为子空间,都用根节点和其八个子节点连接起来,这样的树状存储结构就是八叉树。
特别注意:树中任一节点的子节点恰好只会有八个,或零个,也就是子节点不会有0与8以外的数目。
如何搭建一颗八叉树
1、设定最大递归深度。这决定了最小子空间的包围盒大小
2、以尺寸最大的场景作为根节点。可以是立方体或长方体
3、依序将单位元元素丢入能包含且没有子节点的立方体。是指将你想要存储的坐标归类到所属子节点的包围盒。
4、若没达到最大递归深度,就进行细分八等份,再讲该立方体所装的单位元元素全部分组给八个子立方体。
5. 若发现子立方体所分配到的单位元元素数量不为零且跟父立方体一样,则该子立方体停止细分
(因为根据空间分割理论,细分的空间所得到的分配必定较少,若是一样的数目,再怎么切,数目还是一样)
6、重复3,直到达最大递归深度。
八叉树的作用
八叉树的结构决定了八叉树具有的功能,由于每个元素都在树结构中,可以快速定位到各个元素,并且时间复杂度很小。可以用来碰撞检测、邻域检索、空间变化检测、压缩等功能。
优点,使用八叉树可以快速进行三维目标的集合运算,如交、并、补、差等,亦可快速进行最邻近区域或点的搜索。
缺点,存储空间消耗。
八叉树的实际应用
1、八叉树在PCL中的压缩例子
// 用于显示数据
pcl::visualization::CloudViewer viewer;
// 用于压缩、解压点云数据
pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA>* PointCloudEncoder;
pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA>* PointCloudDecoder;
// 用于接收到设备数据时的回调响应
void cloud_callback(const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr &cloud);
// interface->start ();之后,设备获取一帧数据,就回调一次次函数,不断刷新压缩后的点云
void cloud_callback(const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr &cloud)
{
if (!viewer.wasStopped())
{
// 存储压缩点云的字节流对象
std::stringstream compressedData;
// 存储输出点云
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloudOut(new pcl::PointCloud<pcl::PointXYZRGBA>());
// 压缩点云
PointCloudEncoder->encodePointCloud(cloud, compressedData);
// 解压缩点云
PointCloudDecoder->decodePointCloud(compressedData, cloudOut);
// 可视化解压缩的点云
viewer.showCloud(cloudOut);
}
}
int main()
{
// 压缩选项详情在: /io/include/pcl/compression/compression_profiles.h
pcl::io::compression_Profiles_e compressionProfile = pcl::io::MED_RES_ONLINE_COMPRESSION_WITH_COLOR;
// 初始化压缩和解压缩对象 其中压缩对象需要设定压缩参数选项,解压缩按照数据源自行判断
PointCloudEncoder = new pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA> (compressionProfile, true);
PointCloudDecoder = new pcl::io::OctreePointCloudCompression<pcl::PointXYZRGBA> ();
//创建从OpenNI获取点云的抓取对象
pcl::Grabber* interface = new pcl::OpenNIGrabber ();
// 建立回调函数
boost::function<void
(const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr&)> f = boost::bind (&SimpleOpenNIViewer::cloud_callback, this, _1);
//建立回调函数和回调信息的绑定
boost::signals2::connection c = interface->registerCallback (f);
// 开始接受点云的数据流
interface->start ();
while (!viewer.wasStopped ())
{
sleep (1);
}
interface->stop ();
// 删除压缩与解压缩的实例
delete (PointCloudEncoder);
delete (PointCloudDecoder);
return 0;
}
2、体素内邻近搜索、K邻近搜索、半径内邻近搜索
void main ()
{
pcl::PintCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<PointXYZ>);
cloud->width=1000;
cloud->height=1;
cloud->pionts.resize(cloud->width*cloud->height);
for(int i=0;i<cloud->points.size();i++)
{
cloud->points[i].x=1024.0f*rand()/(RAND_MAX+1.0f);
cloud->points[i].y=1024.0f*rand()/(RAND_MAX+1.0f);
cloud->points[i].z=1024.0f*rand()/(RAND_MAX+1.0f);
}
pcl::octree::OctreePointCloudSearch<pcl::PointXYZ>octree(128.0f)//初始化octree,分辨率为128.0
octree.setInputCloud(cloud);
octree.addPointsFromInputCloud();// 根据点云搭建octree
vector<int>piontIndexVec;// 存储体素邻近的点的索引
//体素内邻近搜索
if(octree.voxelSearch(searchPoint,piontIndexVec))//searchPoint为搜索的关键点
{
for(int i=0;i<piontIndexVec.size();i++)
{
// 每个点的操作
}
}
//K邻近搜索
int k=10;
vector<int>knnIndex;
vector<float>knnDistance;
if(octree.nearestKSearch(searchPoint,k,knnIndex,knnDistance)>0)
{
// 相关操作
}
//半径内邻近搜索
float radius=256.0f;
vector<int>radiusIndex;
vector<float>radiusDistance;
if(octree.radiusSearch(searchPoint,radius,radiusIndex,radiusDistance)>0)
{
// 相关操作
}
}
3、无序点云数据集的空间变化检测
void main ()
{
pcl::PintCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<PointXYZ>);
cloud->width=1000;
cloud->height=1;
cloud->pionts.resize(cloud->width*cloud->height);
for(int i=0;i<cloud->points.size();i++)
{
cloud->points[i].x=1024.0f*rand()/(RAND_MAX+1.0f);
cloud->points[i].y=1024.0f*rand()/(RAND_MAX+1.0f);
cloud->points[i].z=1024.0f*rand()/(RAND_MAX+1.0f);
}
//OctreePointCloudChangeDetector 这个对象允许同时在内存中保管两个八叉树,内部使用内存池管理
pcl::octree::OctreePointCloudChangeDetector<pcl::PointXYZ>octree(128.0f)//初始化octree,分辨率为128.0
octree.setInputCloud(cloud);
octree.addPointsFromInputCloud();// 根据点云搭建octree
octree.switchBuffers();// 交换缓冲区,使用新的八叉树结构
pcl::PintCloud<pcl::PointXYZ>::Ptr cloudB(new pcl::PointCloud<PointXYZ>);
cloudB->width=1000;
cloudB->height=1;
cloudB->pionts.resize(cloudB->width*cloudB->height);
for(int i=0;i<cloudB->points.size();i++)
{
cloudB->points[i].x=1024.0f*rand()/(RAND_MAX+1.0f);
cloudB->points[i].y=1024.0f*rand()/(RAND_MAX+1.0f);
cloudB->points[i].z=1024.0f*rand()/(RAND_MAX+1.0f);
}
octree.setInputCloud(cloudB);
octree.addPointsFromInputCloud();// 根据点云cloudB搭建octree
//getPointIndicesFromNewVoxels()探测两个点云中体素间的不同,只能检测增加的点,不能检测减少的点
vector<int>newPointIndex;
octree.getPointIndicesFromNewVoxels(newPointIndex);//cloudB对比于原始cloud,添加了哪些点,这些点是在cloudB中
for(int i=0;i<newPointIndex.size();i++)
{
// ~~~相关操作
}
}