KD-Tree原理详解
目录
1、简介
2、原理
2.1、数据结构
2.2、构建kd-tree
例子
2.3、最近邻检索
3、常用函数
4、参考文献
kd-tree是在进行激光点云编程中经常使用的一个工具,我们平时使用的时候可能就是使用其中的一小部分的内容,并未对其进行很深的思考。那么kd-tree的原理到底是什么呢?我们又经常使用哪些常用函数呢?
本片文章将对kd-tree进行细致的整理,方便以后的使用和阅读。
1、简介
kd-tree简称k维树,是一种空间划分的数据结构。常被用于高维空间中的搜索,比如范围搜索和最近邻搜索。kd-tree是二进制空间划分树的一种特殊情况 。
在激光雷达SLAM中,一般使用的是三维点云。所以,kd-tree的维度是3。
由于三维点云的数目一般都比较大,所以,使用kd-tree来进行检索,可以减少很多的时间消耗,可以确保点云的关联点寻找和配准处于实时的状态。
本篇文章将从原理层面讲解kd-tree,以便大家在使用中,可以有着更深刻的理解。
2、原理
2.1、数据结构
kd-tree,是k维的二叉树。其中的每一个节点都是k维的数据,数据结构如下所示 :
struct kdtree{Node-data - 数据矢量 数据集中某个数据点,是n维矢量(这里也就是k维)Range - 空间矢量 该节点所代表的空间范围split - 整数 垂直于分割超平面的方向轴序号Left - kd树 由位于该节点分割超平面左子空间内所有数据点所构成的k-d树Right - kd树 由位于该节点分割超平面右子空间内所有数据点所构成的k-d树parent - kd树 父节点
}
上面的数据在进行算法解析中,并不是全部都会用到。一般情况下,会用到的数据是{数据矢量,切割轴号,左支节点,右支节点}。这些数据就已经满足kd-tree的构建和检索了。
2.2、构建kd-tree
kd-tree的构建就是按照某种顺序将无序化的点云进行有序化排列,方便进行快捷高效的检索。
构建算法:
Input: 无序化的点云,维度k
Output:点云对应的kd-tree
Algorithm:
1、初始化分割轴:对每个维度的数据进行方差的计算,取最大方差的维度作为分割轴,标记为r;
2、确定节点:对当前数据按分割轴维度进行检索,找到中位数数据,并将其放入到当前节点上;
3、划分双支:划分左支:在当前分割轴维度,所有小于中位数的值划分到左支中;划分右支:在当前分割轴维度,所有大于等于中位数的值划分到右支中。
4、更新分割轴:r = (r + 1) % k;
5、确定子节点:确定左节点:在左支的数据中进行步骤2;确定右节点:在右支的数据中进行步骤2;
这样的化,就可以构建出一颗完整的kd-tree了。
例子
拿个例子说明为:
二维样例:{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}
构建步骤:
1、初始化分割轴:
发现x轴的方差较大,所以,最开始的分割轴为x轴。
2、确定当前节点:
对{2,5,9,4,8,7}找中位数,发现{5,7}都可以,这里我们选择7,也就是(7,2);
3、划分双支数据:
在x轴维度上,比较和7的大小,进行划分:
左支:{(2,3),(5,4),(4,7)}
右支:{(9,6),(8,1)}
4、更新分割轴:
一共就两个维度,所以,下一个维度是y轴。
5、确定子节点:
左节点:在左支中找到y轴的中位数(5,4),左支数据更新为{(2,3)},右支数据更新为{(4,7)}
右节点:在右支中找到y轴的中位数(9,6),左支数据更新为{(8,1)},右支数据为null。
6、更新分割轴:
下一个维度为x轴。
7、确定(5,4)的子节点:
左节点:由于只有一个数据,所以,左节点为(2,3)
右节点:由于只有一个数据,所以,右节点为(4,7)
8、确定(9,6)的子节点:
左节点:由于只有一个数据,所以,左节点为(8,1)
右节点:右节点为空。
最终,就可以构建整个的kd-tree了。
示意图如下所示 :
二维空间表示:
二维坐标系下的分割示意图
kd-tree表示:
构建kd-tree
2.3、最近邻检索
在构建了完整的kd-tree之后,我们想要使用他来进行高维空间的检索。所以,这里讲解一下比较常用的最近邻检索,其中范围检索也是同样的道理。
最近邻搜索,其实和之前我们曾经学习过的KNN很像。不过,在激光点云章,如果使用常规的KNN算法的话,时间复杂度会空前高涨。因此,为了减少时间消耗,在工程上,一般使用kd-tree进行最近邻检索。
由于kd-tree已经按照维度进行划分了,所以,我们在进行比较的时候,也可以通过维度进行比较,来快速定位到与其最接近的点。由于可能会进行多个最近邻点的检索,所以,算法也可能会发生变化。因此,我将从最简单的一个最近邻开始说起。
- 一个最近邻
一个最近邻其实很简单,我们只需要定位到对应的分支上,找到最接近的点就可以了。
举个例子:查找(2.1,3.1)的最近邻。
- 计算当前节点(7,2)的距离,为6.23,并且暂定为(7,2),根据当前分割轴的维度(2.1 < 7),选取左支。
- 计算当前节点(5,4)的距离,为3.03,由于3.03 < 6.23,暂定为(5,4),根据当前分割轴维度(3.1 < 4),选取左支。
- 计算当前节点(2,3)的距离,为0.14,由于0.14 < 3.03,暂定为(2,3),根据当前分割轴维度(2.1 > 2),选取右支,而右支为空,回溯上一个节点。
- 计算(2.1,3.1)与(5,4)的分割轴{y = 4}的距离,如果0.14小于距离值,说明就是最近值。如果大于距离值,说明,还有可能存在值与(2.1,3.1)最近,需要往右支检索。
由于0.14 < 0.9,我们找到了最近邻的值为(2,3),最近距离为0.14。
- 多个最近邻
多个近邻其实和一个最近邻类似,不过是存储区间变为了多个,判定方法还是完全一样。
由于篇幅的原因,这里就不在赘述。这篇博客讲的很详细,有兴趣的同学可以去学习一下:
加载中 www.joinquant.com/view/community/detail/c2c41c79657cebf8cd871b44ce4f5d97
3、常用函数
kd-tree在日常使用中,一般会在两个方面使用:
- 最近邻搜索
- 距离范围搜索
距离范围搜索的原理和最近邻搜索的差不多,把满足距离的全部放进去就可以了。
最近邻搜索的函数在激光点云匹配中找最近点的时候用的比较多:
//头文件
#include <pcl/kdtree/kdtree_flann.h>
//设定kd-tree的智能指针
pcl::KdTreeFLANN<pcl::PointXYZI>::Ptr kdtreeCornerLast(new pcl::KdTreeFLANN<pcl::PointXYZI>());
//输入三维点云,构建kd-tree
kdtreeCornerLast->setInputCloud(laserCloudCornerLast);
//在点云中寻找点searchPoint的k近邻的值,返回下标pointSearchInd和距离pointSearchSqDis
kdtreeCornerLast->nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance);
其中,当k为1的时候,就是最近邻搜索。当k大于1的时候,就是多个最近邻搜索。
距离范围搜索:
//在点云中寻找和点searchPoint满足radius距离的点和距离,返回下标pointIdxRadiusSearch和距离pointRadiusSquaredDistance
kdtreeCornerLast->radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance)
其完整的使用代码可以参考PCL的官方文件 :
#include <pcl/point_cloud.h>
#include <pcl/kdtree/kdtree_flann.h>#include <iostream>
#include <vector>
#include <ctime>int
main (int argc, char** argv)
{srand (time (NULL));pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);// Generate pointcloud datacloud->width = 1000;cloud->height = 1;cloud->points.resize (cloud->width * cloud->height);for (std::size_t 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::KdTreeFLANN<pcl::PointXYZ> kdtree;kdtree.setInputCloud (cloud);pcl::PointXYZ searchPoint;searchPoint.x = 1024.0f * rand () / (RAND_MAX + 1.0f);searchPoint.y = 1024.0f * rand () / (RAND_MAX + 1.0f);searchPoint.z = 1024.0f * rand () / (RAND_MAX + 1.0f);// K nearest neighbor searchint K = 10;std::vector<int> pointIdxNKNSearch(K);std::vector<float> pointNKNSquaredDistance(K);std::cout << "K nearest neighbor search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z<< ") with K=" << K << std::endl;if ( kdtree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0 ){for (std::size_t i = 0; i < pointIdxNKNSearch.size (); ++i)std::cout << " " << cloud->points[ pointIdxNKNSearch[i] ].x << " " << cloud->points[ pointIdxNKNSearch[i] ].y << " " << cloud->points[ pointIdxNKNSearch[i] ].z << " (squared distance: " << pointNKNSquaredDistance[i] << ")" << std::endl;}// Neighbors within radius searchstd::vector<int> pointIdxRadiusSearch;std::vector<float> pointRadiusSquaredDistance;float radius = 256.0f * rand () / (RAND_MAX + 1.0f);std::cout << "Neighbors within radius search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z<< ") with radius=" << radius << std::endl;if ( kdtree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0 ){for (std::size_t i = 0; i < pointIdxRadiusSearch.size (); ++i)std::cout << " " << cloud->points[ pointIdxRadiusSearch[i] ].x << " " << cloud->points[ pointIdxRadiusSearch[i] ].y << " " << cloud->points[ pointIdxRadiusSearch[i] ].z << " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std::endl;}return 0;
}
4、参考文献
[1] https://en.wikipedia.org/wiki/K-d_tree
[2] https://www.cnblogs.com/maxlpy/p/4297254.html
[3] http://pointclouds.org/documen
KD-Tree原理详解相关推荐
- Kd tree原理详解
目录 一.kd-tree简介 二.kd-tree的实现过程 1. kd-tree的创建 2. 最近邻搜索 三.kd-tree代码实现 一.kd-tree简介 kd-tree(全称为k-dimensi ...
- K-D Tree 算法详解及Python实现
K-D Tree 算法 k−d treek−dtree\mathrm{k-d\ tree}即k−dimensional treek−dimensionaltree\mathrm{k-dimension ...
- Kd Tree算法详解
kd树(k-dimensional树的简称),是一种分割k维数据空间的数据结构,主要应用于多维空间关键数据的近邻查找(Nearest Neighbor)和近似最近邻查找(Approximate Nea ...
- Boosted Tree原理详解
简述 boosted Tree算法简要描述: 不断地添加树,不断地进行特征分裂来生长一棵树.每次添加一个树,其实是学习一个新函数,去拟合上次预测的残差.一个树在生长的过程中,挑选一个最佳特征的最佳分裂 ...
- K近邻算法和KD树详细介绍及其原理详解
相关文章 K近邻算法和KD树详细介绍及其原理详解 朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解 决策树算法和CART决策树算法详细介绍及其原理详解 线性回归算法和逻辑斯谛回归算法详细介绍及其原理详 ...
- 决策树算法和CART决策树算法详细介绍及其原理详解
相关文章 K近邻算法和KD树详细介绍及其原理详解 朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解 决策树算法和CART决策树算法详细介绍及其原理详解 线性回归算法和逻辑斯谛回归算法详细介绍及其原理详 ...
- 深入剖析Redis系列(三) - Redis集群模式搭建与原理详解
前言 在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态.Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 ...
- 扩展Euclidean算法求乘法逆原理详解与算法实现
[利用扩展Euclidean算法求乘法逆] 1. Equipment (1) operating system version :WIN 10 (2) CPU instruction set: x 6 ...
- 朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解
相关文章 K近邻算法和KD树详细介绍及其原理详解 朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解 决策树算法和CART决策树算法详细介绍及其原理详解 线性回归算法和逻辑斯谛回归算法详细介绍及其原理详 ...
- ntp如何确认与服务器偏差_NTP时间同步服务器(NTP服务器)原理详解
NTP时间同步服务器(NTP服务器)原理详解 NTP时间同步服务器(NTP服务器)原理详解 1.前言 由计算机网络系统组成的分布式系统,若想协调一致进行:IT行业的"整点开拍".& ...
最新文章
- 值传递 引用传递(传地址,传引用)的区别
- 网络配置及shell基础
- 函数和构造函数的区别
- boost::type_erasure模块实现类型安全的 printf的测试程序
- 有向无环图DAG 拓扑排序 代码解释
- 51单片机配合超声波测距以及用1602液晶进行显示
- JS concat() 方法
- Spring Boot (一)快速入门
- python输入一个区间_Python 学习笔记:根据输入年月区间,返回期间所有的月份...
- otc机器人氩弧焊机_历经100年,这家公司成为弧焊机器人领域的世界王者
- php代挂程序什么原理,小新云全套代挂加速 来源于网络资源简介:01、本源码调用官方接口 联合开发网 - pudn.com...
- 实验七 TCP/IP协议分析
- Automated SQL Injection Detection
- 多台服务器搭建Nacos集群
- ffmpeg解复用FLV文件
- Java将html转换成pdf、html转换成图片
- WDF开发USB设备驱动教程(2)
- 网络渗透——CTF实践
- 循环和switch语句中的continue、break
- filezilla定时上传_FileZilla的使用和注意事项
热门文章
- UFT QTP 12 试用
- [2019杭电多校第三场][hdu6609]Find the answer(线段树)
- SpringMVC详解(四)------SSM三大框架整合之登录功能实现
- lucene 多字段查询-MultiFieldQueryParser
- Ubuntu下设置root用户
- 微信小程序——律师事务所微官网
- 字号计算,字体大小随窗口高度变化
- 如何将Eclipse中Web项目打成war包
- MEncoder的基础用法——6.9. 将DVD子标题提取到VOBsub文件
- plsql设置代码提示和自动补全