KD 树原理以及在三维激光点云中的应用

2023-05-16

目录

1、介绍

2、原理

2.1、数据结构

2.2、构建KD树

2.3、实例

3、程序示例

4、参考链接


1、介绍

kd-tree简称k维树,是一种空间划分的数据结构。常被用于高维空间中的搜索,比如范围搜索和最近邻搜索。kd-tree是二进制空间划分树的一种特殊情况 。

在激光雷达SLAM中,一般使用的是三维点云。所以,kd-tree的维度是3。

由于三维点云的数目一般都比较大,所以,使用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树

kd-tree的构建就是按照某种顺序将无序化的点云进行有序化排列,方便进行快捷高效的检索。

构建算法:

Input:  无序化的点云,维度k
Output:点云对应的kd-tree
Algorithm:
1、初始化分割轴:对每个维度的数据进行方差的计算,取最大方差的维度作为分割轴,标记为r;
2、确定节点:对当前数据按分割轴维度进行检索,找到中位数数据,并将其放入到当前节点上;
3、划分双支:
    划分左支:在当前分割轴维度,所有小于中位数的值划分到左支中;
    划分右支:在当前分割轴维度,所有大于等于中位数的值划分到右支中。
4、更新分割轴:r = (r + 1) % k;
5、确定子节点:
    确定左节点:在左支的数据中进行步骤2;
    确定右节点:在右支的数据中进行步骤2;

这样的化,就可以构建出一颗完整的kd-tree了。

2.3、实例

        先以一个简单直观的实例来介绍k-d树算法。假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点位于二维空间内(如图1中黑点所示)。k-d树算法就是要确定图1中这些分割空间的分割线(多维空间即为分割平面,一般为超平面)。下面就要通过一步步展示k-d树是如何确定这些分割线的。

                                                                         图1

        k-d树算法可以分为两大部分,一部分是有关k-d树本身这种数据结构建立的算法,另一部分是在建立的k-d树上如何进行最邻近查找的算法。

        k-d树生成过程

 由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。

  (1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向;

  (2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7;

  (3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如图2所示。x < =  7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。

                                                                         图2

如算法所述,k-d树的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是左右子空间的'根'节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,如图1所示。最后生成的k-d树如图3所示。

                                                                        图3

注意:每一级节点旁边的'x'和'y'表示以该节点分割左右子空间时split所取的值

       k-d tree 最近邻查找

在k-d树中进行数据的查找也是特征匹配的重要环节,其目的是检索在k-d树中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。

  星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行'回溯'操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中先从(7,2)点开始进行二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为<(7,2),(5,4),(2,3)>,首先以(2,3)作为当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如图4所示。发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。

                                                                        图4

再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。

  一个复杂点了例子如查找点为(2,4.5)。同样先进行二叉查找,先从(7,2)查找到(5,4)节点,在进行查找时是由y = 4为分割超平面的,由于查找点为y值为4.5,因此进入右子空间查找到(4,7),形成搜索路径<(7,2),(5,4),(4,7)>,取(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。然后回溯到(5,4),计算其与查找点之间的距离为3.041。以(2,4.5)为圆心,以3.041为半径作圆,如图5所示。可见该圆和y = 4超平面交割,所以需要进入(5,4)左子空间进行查找。此时需将(2,3)节点加入搜索路径中得<(7,2),(2,3)>。回溯至(2,3)叶子节点,(2,3)距离(2,4.5)比(5,4)要近,所以最近邻点更新为(2,3),最近距离更新为1.5。回溯至(7,2),以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如图6所示。至此,搜索路径回溯完。返回最近邻点(2,3),最近距离1.5。

                                                                        图5 

                                                                         图6

 上述两次实例表明,当查询点的邻域与分割超平面两侧空间交割时,需要查找另一侧子空间,导致检索过程复杂,效率下降。研究表明N个节点的K维k-d树搜索过程时间复杂度为:

3、程序示例

        kd-tree在点云处理中常用于最近邻搜索和距离范围搜索,当k为1时,就是最近邻搜索,当

大于1的时候,就是多个最近邻搜索例如距离范围搜索。请看下列示例

ps: 修改k 的值可以更深刻的理解一下kd树。

#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 data
  cloud->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 search

  int 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 search

  std::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;
}

对应的cmakelists 给出

cmake_minimum_required(VERSION 3.1)
project(test)

set(CMAKE_CXX_STANDARD 11)

find_package(PCL 1.8 REQUIRED COMPONENTS )
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable(test main.cpp)
target_link_libraries(test ${PCL_LIBRARIES})

4、参考链接

KD-Tree原理详解 - 知乎

k-d tree算法详解 - maxlpy - 博客园

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

KD 树原理以及在三维激光点云中的应用 的相关文章

  • 【OpenFOAM】-olaFlow-算例3- currentWaveFlume

    算例路径 olaFlow tutorials currentWaveFlume 算例描述 波流耦合模拟 该算例提供了四种工况 1 Waves and forward current 2 Waves and backward current
  • 【OpenFOAM】-olaFlow-算例4- irreg45degTank

    算例路径 olaFlow tutorials irreg45degTank 算例描述 不规则波浪模拟 学习目标 不规则波浪模拟 olaFlow中单向不规则波采用线性波浪叠加法生成 基本原理如图2所受 需要提供对应波谱的周期 波高和相位的离散
  • 【OpenFOAM】-olaFlow-算例5- oppositeSolitariesFlume

    算例路径 olaFlow tutorials oppositeSolitariesFlume 算例描述 两列反向的孤立波相互作用 学习目标 熟练掌握olaFlow的造波设置 波浪方向与消波方向设置 算例快照 图1 两列反向孤立波相互作用 文
  • 【OpenFOAM】-olaFlow-算例6- waveFloatingObject

    算例路径 olaFlow tutorials waveFloatingObject 算例描述 波浪作用下的浮体的刚体运动 属于流固耦合 FSI 问题 学习目标 动网格设置和使用 网格变形控制 浮体的物理参数设置 浮体做刚体运动的约束设置 算
  • 【OpenFOAM】-olaFlow-算例7-波面自适应网格

    算例路径 none 算例描述 波面附近采用自适应网格划分 学习目标 动网格设置和使用 dynamicFvMesh dynamicRefineFvMesh 的各参数含义 学习体会 1 在结构附近的加密网格 自适应网格依然会对细网格进一步细化
  • 【OpenFOAM】-olaFlow-算例8-setOlaFlume

    算例路径 olaFlow tutorials setOlaFlume 算例描述 不规则底部的二维波浪水槽 且波场被 setOla 工具设置为初始条件 学习目标 使用 setOla 工具设置初始条件为波浪场 不规则底部数值波浪水槽的设置 学习
  • 【OpenFOAM】-olaFlow-算例9-pistonFlumeABS

    算例路径 olaFlow tutorials pistonFlumeABS 算例描述 采用 piston 形式的动边界进行消波 学习目标 了解 olaDyMFlow 的使用 理解动网格使用和参数设置 理解 dynamicMotionSolv
  • 【OpenFOAM】-olaFlow-算例10-wavemakerTank

    算例路径 olaFlow tutorials wavemakerTank 算例描述 采用 Flap和Piston两种方式的动网格进行造波 学习目标 了解 olaDyMFlow 的使用 理解动网格使用和参数设置 理解 dynamicMotio
  • 【OpenFOAM】-算例解析合集(备份目录)

    OpenFOAM 算例解析合集 OlaFlowinterFoampimpleFoam OlaFlow OpenFOAM olaFlow 算例1 baseWaveFlume OpenFOAM olaFlow 算例2 breakwater Op
  • 关于MATLAB中使用Link函数和SerialLink建模

    关于MATLAB中使用Link函数和SerialLink建模 Link函数默认使用的是标准D H法建立模型 xff0c 如果想用改进D H法建立模型 xff0c 则应在参数后添加 modified 如下所示 xff1a 建立机器人模型 th
  • 【OpenFOAM】-interFoam-laminar-算例11-wave

    算例路径 OpenFOAM 8 tutorials multiphase interFoam laminar wave 算例描述 使用 interFoam 求解器的造波功能 学习目标 extrudeMesh 网格操作 了解 setWaves
  • 解决Ubunt20.04安装Sogou输入法失败进不去桌面 及 中文输入法安装

    目录 解决Ubunt20 04安装Sogou输入法失败进不去桌面中文输入法安装解决wps无法输入中文 解决Ubunt20 04安装Sogou输入法失败进不去桌面 问题 xff1a Ubuntu20 04 安装了 fcitx 和 sogou
  • 【OpenFOAM】-pimpleFoam-RAS-算例12-wingMotion

    算例路径 OpenFOAM 8 tutorials incompressible pimpleFoam RAS wingMotion 算例描述 该路径下包含三个目录 分别为 1 wingMotion snappyHexMesh 使用 sna
  • 【OpenFOAM】-算例解析合集

    OpenFOAM 算例解析合集 OlaFlow interFoam pimpleFoam OlaFlow OpenFOAM olaFlow 算例1 baseWaveFlume OpenFOAM olaFlow 算例2 breakwater
  • 我的第一篇博客

    我的第一篇博客 很高兴来到这里 xff0c 加油 xff01 我会写更多有用的文章的 xff01
  • 为Termux安装图形化界面

    在学校闲着没事就逛逛论坛 博客 以及贴吧 突然发现一个好玩的东西 xff0c 它就是 Termux 也是咕哝了好久 xff0c 在贴吧看到Termux可以装xfce桌面 于是便有此篇文章留作纪念 xff0c 也同时感谢大佬们的默默努力 xf
  • 在华为平板的Termux上安装Debian Linux图形化界面的详细教程,向生产力更近一步。

    Termux 安装 Debian Linux 图形化界面 文章目录 前言一 准备材料二 安装Debian Linux步骤1 进入Termux安装Debian Linux2 开启远程桌面 xff08 两种方式选一种即可 xff09 总结 前言
  • 在Termux的Debian Linux中设置中文界面

    在Termux的Debian Linux容器中设置中文界面 文章目录 前言Debian汉化 前言 上次在平板中安装了Debian Linux 并可以连接远程xfce桌面 xff0c 有兴趣的可以去看这里 xff0c 但是系统界面确是英文实在
  • 在Termux的Debian Linux中安装VScode

    文章目录 前言安装VScode 前言 有兴趣的伙伴可以看上次安装Debian这里和汉化Debian的文章这里 安装VScode 1 下载火狐浏览器 span class token function sudo span span class

随机推荐

  • 一步一步教你使用uCOS-II

    第一篇 UCOS介绍 第一篇 UCOS介绍 这个大家都知道 呵呵 考虑到咱们学习的完整性还是在这里唠叨一下 让大家再熟悉一下 高手们忍耐一下吧 xff01 uC OS II Micro Control Operation System Tw
  • MATLAB绘制空间曲线和曲面图像

    MATLAB绘制空间曲线和曲面图像 之前考研的时候做到2010年数一试卷第19题时 xff0c 一直无法想象 Sigma 的图像到底是什么样的 当时由于时间紧迫且不知道如何用MATLAB画图 xff0c 因此就这么草草了事 现在正好学到了这
  • 学习笔记|元学习(Meta-learning)——让机器学习如何学习

    文章目录 1 元学习概述2 MAML2 1 MAML概述2 2 MAML的训练 3 元学习在N ways K shot上的应用 1 元学习概述 元学习的意思即 学会如何学习 在机器学习中 xff0c 工作量最大也是最无聊的事情就是调参 我们
  • 串口调试助手 安卓版 附下载地址

    平时工作中和硬件同事对接的比较多 xff0c 软件和硬件的通讯 xff0c 串口用的也比较多的 在网上找了很多串口调试工具 xff0c 大都年代久远 xff0c 没有继续更新维护的了 于是 xff0c 自己抽空写了一个 xff1a 串口调试
  • cv_bridge 与opencv 版本不匹配的解决

    问题描述 xff1a ubuntu18 04安装的ros 默认的opencv版本和cv bridge 版本为3 2 0 但是在使用其他程序包的时候有时候需要用到其他版本的opencv 再调用cv bridge的时候会发生调用冲突 xff1b
  • ROS包nmea_navsat_driver读取GPS、北斗定位信息笔记

    硬件 ATGM332D 43 串口调试工具 43 GPS 天线 软件 xff1a ubunutu 18 04 43 ros 1 串口 读取数据 sudo apt install cutecom sudo cutecom 设置 波特率9600
  • realsense 选型大对比D455 D435i D415 T265 3D硬件对比

    Intel Realsense D455 D435i D415 T265 3D实感硬件对比 xiaodeng6185的博客 CSDN博客 体感摄像头 realsense 系列硬件资料 果匠 CSDN博客 体感摄像头 哪款适合你 xff1f
  • ImportError: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29‘ not found

    解决方案 下载地址 xff1a http ftp gnu org pub gnu glibc 下载 xff1a wget http ftp gnu org gnu glibc glibc 2 29 tar gz 过程有些慢 解压安装 xff
  • 解决ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26‘ not found

    报错信息如题所示 原因 xff1a 这个是默认路径下的libstdc 43 43 so 6缺少GLIBCXX 3 4 26 xff0c 你有可能缺少其它版本的比如3 4 26 xff0c 解决方法一样 xff0c 如下所示 xff1a xf
  • Eigen 库常用基本用法 备忘

    ps xff1a eigen 看到的时候较多 xff0c 自己写的时候总有一些用法想不起来具体函数名 xff0c 特此总结一下以备忘 官方doc eigen 官网最权威 目录 Eigen 矩阵定义 Eigen 基础使用 Eigen 特殊矩阵
  • Hector slam算法原理解析与代码详解

    写了markdown 上传 xff0c 公式都乱码 xff0c 无果 xff0c 截图上传吧 目录 1 hector 原理解析 1 4 多重分辨率地图 2 代码框架 2 1 回调函数 2 2 更新 3 扫描匹配 3 1 多分辨率匹配 3 2
  • Logistic映射的简单理解

    Logistic映射 在看论文时看到了这个概念 xff0c 于是就去简单了解了一下 参考博客 1 前言 谈到Logistic映射就要先谈一谈什么是混沌系统 百度百科上的解释是 xff0c 混沌系统是指在一个确定性系统中 xff0c 存在着貌
  • _findnext 报错

    ps 编译环境 qt 43 mingw32 编译没问题 xff1b 换到qt 43 msvc 2017 64 就出现问题 xff1b 报错信息 xff1a Stopped in thread 0 by Exception at 0x7ffb
  • bug解决: ffmpeg 在window下使用 PRId64 报错

    在timestamp h 中 调用 av ts make string报错 error expected before PRId64 原因 xff1a 该宏定义给c用的 xff0c C 43 43 要用它 xff0c 就要定义一个 STDC
  • qt: error: C2001: 常量中有换行符

    PS 这两天搞工程系统移植 xff0c 搞得疯掉了 xff0c 代码复用还不如重写呢 如下一句带有中文的程序 xff0c mingw 43 linux 运行没有任何问题 xff0c window下msvc 运行就报错C2001 time s
  • Eigen内存分配器aligned_allocator

    在使用Eigen的时候 xff0c 如果STL容器中的元素是Eigen数据库结构 xff0c 比如下面用vector容器存储Eigen Matrix4f类型或用map存储Eigen Vector4f数据类型时 xff1a vector lt
  • Ubuntu 升级cmake 版本

    PS 在编译一些包时需要更高的版本 xff0c 需要升级 cmake 千万别执行下面的命令 xff0c 这样会把之前用 cmake 编译好的包都给卸载掉 xff0c 包括ros sudo apt get autoremove cmake 比
  • 视觉slam十四讲(ch6) Ubuntu18.04安装 g2o库 报错error: FixedArray ... has no member named ‘fill’

    ps 再学习14讲第二版的时候 xff0c 运行g2o 报错 error FixedArray aka class ceres internal FixedArray lt double 6 gt has no member named f
  • 无人驾驶学习笔记-NDT 配准

    目录 1 NDT 的算法处理流程 2 NDT 公式推导 3 NDT 实例 3 1 常规NDT的位姿估计 3 2 front end node 1 ROS常规初始化 2 初始化操作 xff1a 读取传感器数据 获取lidar to imu变换
  • KD 树原理以及在三维激光点云中的应用

    目录 1 介绍 2 原理 2 1 数据结构 2 2 构建KD树 2 3 实例 3 程序示例 4 参考链接 1 介绍 kd tree简称k维树 xff0c 是一种空间划分的数据结构 常被用于高维空间中的搜索 xff0c 比如范围搜索和最近邻搜