目录
先说一下kd-tree
1 kd-tree
1.1 定义
K-dimensional tree,一种高维索引树形数据结构,经常用于在大规模的高维数据空间进行最近邻查找
类似于K维二叉树,树中存储一些K维数据
1.2 步骤
1.3 缺点
-
数据为多维结构,不同树节点根据不同维度进行数据划分
-
父节点将所有数据划分至左右子树,叶子节点包含所有数据
-
使用于动态程度不高的数据,插入删除不方便
-
非动态增量式结构,每次都要重新构建kdtree
-
深度影响搜索速度,图的结构可能不平衡
理解:kd-tree的搜索速度与其深度有关,然而,很多SLAM方案都需要kd-tree来维护一个动态的local map,因此必然需要不断的插入新点,并删除过远过旧的点,导致kd-tree的平衡性遭到破坏。换言之,在kd-tree总点数不变的情况下,由于一些位置的点被删除、其他位置的点有新增,导致整个kd-tree树不平衡,进而导致搜索效率下降
针对上述现象,如果能构建一个增量式的动态kd-tree结构,能够自行调整树的结构、始终保持动态平衡,那问题就会迎刃而解。ikd-tree就做到了。
2 ikd-tree
2.1 原理
2.2 节点数据结构
Struct TreeNode: PointType point;//点坐标、强度信息 TreeNode *leftchild,*rightchild;//左右节点指针 int axis;//分割轴 // 1.treesize:tree中所有节点的数量(树的大小) // 2.incailnum:tree中被标记为"删除"的节点数 int treesize,incailnum; // 1.deleted:当前节点是否被标记为删除 // 2.treedeleted:当前tree是否被标记为删除 // 注意:是累积一定数量的点后统一删除 bool deleted,treedeleted; // 子树上点的范围(长方体空间) // 用于排除不必要的点 CuboidVertices ranges; end
树的构建:与kdtree一样,都是根据对比均值方差,进行初始的构建
2.3 功能实现
2.3.1 插入
输入:下采样分辨率,新插入点P,是否重构建
Start
-
根据点P及其分辨率,找到该点在哪个体素里,记为C_D
-
找到体素的中心点P_center
-
将体素C_D内所有点记为V
-
将新点P放入V中
-
近邻搜索,找到体素内距离P——center最近的点
-
判断最近点是否为新点
Y:插入,并删除其余点
流程:
-
将整个空间体素化,并明确新点落入哪个体素(目标体素)
-
向ikd-Tree查询目标体素内是否已经有点以及有哪些点(查询过程参考box-wise delete)
-
如果已经有点了,将已有点和新点一起排序,找出离体素中心最近的那个点,然后做判断:【如果最近的点是已有点,意味着新点无必要再插入了,结束处理;如果最近的点是新点,则把已有点全部标记删除,并插入新点】
-
如果体素内尚不存在点,则直接插入新点
2.3.2 删除
策略
(1)删除:删除一个中间节点意味着该节点下方所以节点都要重构subtree,低效,所以增量式kd-tree基本采用lazy delete策略。
(2)lazy delete策略:删除一个节点时,先将其标记为“删除”,这些节点在KNN搜索时会被跳过,但在树形结构中它依旧存在,只有当tree结构被彻底重构时(低频),才会真的将这些节点从树结构中删除。
(3)box-wise delete策略:按区域批量删除点
-
为ikd-tree框定一个立方体区域,用于删除区域内所有的点
-
一旦机器人运动到区域边界,就将立方体沿着运动方向移动一定距离
-
实现删除区域外的点,引进新点
-
判断是否处于灰色区域
- 判断range是否与删除区域有交叉,有交叉就直接剪枝
-
下图为算法实现流程
1. 从根节点遍历每个节点,判断是否处于灰色区域内
Y:标记删除
2. 判断两个子节点的range是否与删除区域有交叉
N:剪枝
3. 重复上述操作,直至遍历完成,
4. 更新treesize、incalidnum、range、treedeleted
5. 在re-balancing环节再真正删除
2.3.3 搜索
(1)KNN & ranged-KNN搜索基本与常规 kd-tree相同,此处不再赘述
(2)补充:ikd-Tree 中充分利用了数据结构部分提到的 range信息 来进行剪枝加速,也即在每个节点处,除了计算节点本身与查询点的距离之外,也会分别判断左右两个子树的range 是否与目标解空间有重叠,只有有重叠的子树才会被继续递归搜索,没重叠的子树将直接被剪枝掉,实现搜索加速
2.3.4 re-balancing再平衡
目的:经过多次删除,树的结构可能变得不平衡。由于深度影响搜索速度,于是通过re-balancing平衡树的结构、减小深度
树的平衡性判断准则
-
左右两侧最大不平衡容忍度
-
任意一个子树的规模不能大于特定比例
-
值越趋近于0.5,越容易被判定为不平衡
-
最多容忍subtree中被标记为"删除"(无效节点的数量)的占比
-
任意一个子树中标记删除点的规模不能超过特定比例
-
值越小,容忍标记为删除点的比例越低
2.3.5 再平衡算法(重构)
在判断为不平衡的基础上,触发对subtree的重建。
(1)子树规模较小
(2)子树规模较大
当子树规模较大,重建时间花销不可忽略,若在主线程中执行重建,就会导致ikd-tree一直被占用,外部的SLAM算法无法访问ikd-tree执行查询或增删操作 —— 导致算法阻塞。
-
重建时间花销可以忽略,在主线程中直接重建
-
流程:重建时先把subtree的所有节点打乱重排,在这一过程中会拿掉标记删除的点;然后,对剩下的点按中值点法执行常规的kdtree构建过程;构建完成后,把新的子树替换到原来的位置
-
采用双线程策略,即将正在重建的tree的增删操作额外缓存到一个容器中,待额外线程中完成了重建后,再从容器把增删操作挪到新tree上。【主线程中依旧可以进行增删操作】