KdTree-1 PCL学习记录-8 KdTree原理及使用kdTree进行范围搜索和最近邻域搜索方法

引: 通过3D相机(雷达、激光扫描、立体相机)获取到的点云,一般数据量较大,分布不均匀等特点,数据主要表征了目标物表面的大量点的集合(无序),不具备传统实体网格数据的几何拓扑信息

这些离散的点如果希望实现基于邻域关系的快速查找比对功能,就必须对这些离散的点之间建立拓扑关系,所以点云的数据处理中,最为核心的问题就是建立离散点之间的拓扑关系,以便于实现基于邻域关系的快速查找。

常见的空间索引一般是自上而下逐级划分空间的各种索引结构,包括BSP树,k-d tree、KDB tree、R tree、CELL tree、八叉树等。有了这些关系,我们就可以实现点云的降采样,计算特征向量,点云匹配,点云拆分等功能。引用自:http://robot.czxy.com/docs/pcl/chapter01/decomposition/

    k-d树 (k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。K-D树是二进制空间分割树的特殊的情况。用来组织表示K维空间中点的几何,是一种带有其他约束的二分查找树,为了达到目的,通常只在三个维度中进行处理因此所有的kd_tree都将是三维的kd_tree,kd_tree的每一维在指定维度上分开所有的字节点,在树 的根部所有子节点是以第一个指定的维度上被分开。

  Kd-Tree分为Kd-Tree分割原理,及基于Kd-Tree的算法(一部分是关于kd-Tree本身这种数据结构的建立算法,另一部分是建立在KD-Tree基础上的最邻近查找算法),以下进行分别介绍。

0.关于KdTree的元素名称

域名数据类型描述

Node-data

数据矢量

数据集中某个数据点,是n维矢量(这里也就是k维)

Range

空间矢量

该节点所代表的空间范围

split

整数

垂直于分割超平面的方向轴序号

Left

k-d树

由位于该节点分割超平面左子空间内所有数据点所构成的k-d树

Right

k-d树

由位于该节点分割超平面右子空间内所有数据点所构成的k-d树

parent

k-d树

父节点

一. Kd-tree分割算法介绍

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

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

(1)确定split域的首先该取的值(确定x方向还是y方向方差大,以便于确认分割起点是x轴还是y轴)

        分别计算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)}。

 (4)k-d树的构建是一个递归的过程。

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

总结:关于Kd-Tree分割算法的总结:Kd-Tree的分割算法,主要过程为:1.首先找到x,y方向上,哪个方向的方差比较大,就从那个方向开始分割(x or y?) 2. 如果第一步是沿着x轴进行的分割,那么下一次分割将沿着下一个点的y坐标进行分割。3. 第二个点的找法与第一个类似,在第一步分割后的其中一部分,找到在这一堆点的中,y坐标位于居中位置的点,令其当作第二个分割点。4. 沿着第二个点的y坐标进行分割........循环上边的过程,直至全部点分割结束为止。(实际上可以理解成:按照x-y-z-x-y-z...顺序向下分割)

二. Kd-tree查找算法介绍 (查找在坐标系内,与某一个指定点之间,距离最近点的坐标)

img

上边的动图表示了通过KD-TREE寻找最近点的过程:

1.首先在起点A;

2.然后分别计算分支点B和点C距离待测点的距离,比较得到哪个点距离最近;(图中:B点距离带测点距离近,故选择B点);

3.然后以B点为起点,分别计算D点和E点距离带测点之间的距离,取出这两个点之间,距离带测点之间最近的的那个点;

4.当KD-TREE已经检索到末端时,可以看到E点距离待测点较近,故可以先认E点是距离最近点;

5.进行核算:以待测点为圆心,以待测点到E点距离为半径,做圆,查看这个圆是否与其他Kd-tree的分区有交割,如果有的话,需要退回到前一个节点,然后去验算另一个分支内有无与待测点距离更近的点,若没有,那么就可以认为E点是距离待测点最近的点,点已找到。

更多的查找案例:http://robot.czxy.com/docs/pcl/chapter01/decomposition/

三.Kd-Tree 查找案例 (包含查找N个相邻点+查找半径范围内的邻域点)

#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));

//step1:创建仿真的点云数据
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);

cloud->width =1000;
cloud->height =1;
cloud->points.resize(cloud->width*cloud->height);

for(std::size_t i=0;i<cloud->size();++i){
    (*cloud)[i].x= 1024.0f* rand()/ (RAND_MAX + 1.0f);
    (*cloud)[i].y= 1024.0f* rand()/ (RAND_MAX + 1.0f);
    (*cloud)[i].z= 1024.0f* rand()/ (RAND_MAX + 1.0f);
}

//step2:创建KDTREE相关对象
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(cloud);

//Step3:设置将被查询的点的信息
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);

//step4:设置查询邻域点的数量,并且设置存储点的编号及点的距离的Vector对象
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;


//step5: 使用kdtree寻找邻域点
if(kdtree.nearestKSearch(searchPoint, K,pointIdxNKNSearch ,pointNKNSquaredDistance ) > 0 ){
    for(std::size_t i=0; i<pointIdxNKNSearch.size(); ++i){
        std::cout << "    "  <<   (*cloud)[ pointIdxNKNSearch[i] ].x 
                  << " " << (*cloud)[ pointIdxNKNSearch[i] ].y 
                  << " " << (*cloud)[ pointIdxNKNSearch[i] ].z 
                  << " (squared distance: " << pointNKNSquaredDistance[i] << ")" << std::endl;
  }
}
/*
以上为查找10个邻域点的方法;下边是查找预订点半径之内的点的数量
*/

//step1: 设置用于存放点的Vector+存放距离的Vector
std::vector<int> pointIdxRadiusSearch;
std::vector<float> pointRadiusSquaredDistance;

//Step2:设置查找半径信息
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;

//Step3.使用Kdtree进行检查
if ( kdtree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0 )
  {
    for (std::size_t i = 0; i < pointIdxRadiusSearch.size (); ++i)
      std::cout << "    "  <<   (*cloud)[ pointIdxRadiusSearch[i] ].x 
                << " " << (*cloud)[ pointIdxRadiusSearch[i] ].y 
                << " " << (*cloud)[ pointIdxRadiusSearch[i] ].z 
                << " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std::endl;
  }
  return 0;



}







运行结果如下,可以看到

1. 距离预设点的最近的10个点的坐标,以及相距预设点的距离。

2. 距离预设点的半径87mm之内的全部点坐标,以及相距预设点的距离:

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值