PCL 中的 kd-tree ,使用的是 FLANN 项目中的数据结构,支持《快速最近邻搜索》。
kd-tree 简称k维树,它是一种空间划分数据结构,它将一组 k 维点存储在一个树结构中,可以理解是 k 维的二叉树。
kd-tree 可以实现有效的《范围搜索》和《最近邻搜索》。
《最近邻搜索》是处理点云数据的核心操作,可用于查找点组或特征描述符之间的对应关系,或者,定义一个或多个点周围的局部邻域。
PCL 中的 kd-tree 位于 common 模块(pcl_common.dll)。
使用时要引用以下头文件:
#include pcl/kdtree/kdtree_flann.h>
以下用一个例子练习一下如何使用 kd-tree 进行点云的《最近邻搜索》。
打开 QT Creator ,然后,新建 QT Application 项目。
在项目文件 .pro 文件中添加如下包含目录和引用的 lib 库
- INCLUDEPATH += C:\PCL1.12.1\include\pcl-1.12
- C:\PCL1.12.1\3rdParty\FLANN\include \
- C:\PCL1.12.1\3rdParty\Boost\include\boost-1_78 \
- C:\PCL1.12.1\3rdParty\Eigen\eigen3 \
- win32:LIBS += $$quote(C:\PCL1.12.1\lib\pcl_commond.lib)
- win32:LIBS += $$quote(C:\PCL1.12.1\3rdParty\FLANN\lib\flann_s.lib)
- win32:LIBS += $$quote(C:\PCL1.12.1\3rdParty\FLANN\lib\flann_cpp_s.lib)
在 main.cpp 的顶部添加头文件引用:
- #include
- #include
- #include
在 main.cpp 的 main 函数中输入如下代码:
- //创建6个点的点云
- pcl::PointCloudpcl::PointXYZ::Ptr clound(new pcl::PointCloudpcl::PointXYZ());
- clound->width = 6;
- clound->height = 1;
- clound->resize(clound->width *clound->height);
- for(int i = 0; i < clound->points.size(); ++i)
- {
- clound->points[i].x = (i * 3) + 1;
- clound->points[i].y = (i * 3) + 10;
- clound->points[i].z = (i * 3) + 100;
- }
- //打印点云
- qDebug() << "Clound : ";
- for(int i = 0; i < clound->points.size(); ++i)
- {
- qDebug() << clound->points[i].x << "," << clound->points[i].y << "," << clound->points[i].z;
- }
-
- //创建kd-tree对象,并将点云对象赋值给它
- pcl::KdTreeFLANNpcl::PointXYZ tree;
- tree.setInputCloud(clound);
-
- //最近邻搜索
-
- //要搜索的点
- pcl::PointXYZ pt;
- pt.x = 1.5;
- pt.y = 10.5;
- pt.z = 100.5;
-
- //k近邻搜索
- int K = 3;//最近的3个点
- std::vector<int> indices;//最近的点的索引
- std::vector<float> sqr_distance;//最近的点离搜索点的平方距离
- //执行k近邻搜索
- tree.nearestKSearch(pt, K, indices, sqr_distance);
- //打印搜索结果
- qDebug() << "Indices : ";
- for(int i = 0; i < indices.size(); ++i)
- {
- qDebug() << indices[i];
- }
- qDebug() << "sqr_distance : ";
- for(int i = 0; i < sqr_distance.size(); ++i)
- {
- qDebug() << sqr_distance[i];
- }
-
- //半径近邻搜索
- double raius = 5;//距离为5
- indices.clear();//最近的点的索引
- sqr_distance.clear();//最近的点离搜索点的平方距离
- //执行半径近邻搜索
- tree.radiusSearch(pt, raius, indices, sqr_distance);
- //打印搜索结果
- qDebug() << "Indices : ";
- for(int i = 0; i < indices.size(); ++i)
- {
- qDebug() << indices[i];
- }
- qDebug() << "sqr_distance : ";
- for(int i = 0; i < sqr_distance.size(); ++i)
- {
- qDebug() << sqr_distance[i];
- }
我使用的是编译器是MSC++Compiler16(VS2019):

用该编译器时,PCL中有1个位置有一个bug,对vs的编译器漏写了一个变量,编译项目时,会提示错误,这时,需要修改后才能通过编译:
(1)修改 dist.h 文件
文件路径:C:\PCL1.12.1\3rdParty\FLANN\include\flann\algorithms\dist.h。

另外,QT6.3 创建的项目,默认使用 C++17,.pro文件中有如下行:

PCL1.12.1 使用的 FLANN 项目代码中有2处被C++17弃用的STL函数,项目编译时也会提示错误,这时,需要手动修改:
(1)std::binary_function 函数
调用该函数的文件是:
C:\PCL1.12.1\3rdParty\FLANN\include\flann\util\heap.h

直接注释掉即可:

(2)std::random_shuffle 函数
调用该函数的文件有3个:
C:\PCL1.12.1\3rdParty\FLANN\include\flann\util\random.h
C:\PCL1.12.1\3rdParty\FLANN\include\flann\algorithms\kdtree_index.h
C:\PCL1.12.1\3rdParty\FLANN\include\flann\util\lsh_table.h
三个文件中都把 std::random_shuffle 函数调用改为 std::shuffle 函数调用即可,如下:
(2.1)修改 random.h
第一步,头文件添加 #include :

第二步,把 std::random_shuffle 函数调用改为 std::shuffle 函数调用:

(2.2)修改 kdtree_index.h
第一步,头文件添加 #include :

第二步,把 std::random_shuffle 函数调用改为 std::shuffle 函数调用:

(2.3)修改 lsh_table.h
第一步,头文件添加 #include :

第二步,把 std::random_shuffle 函数调用改为 std::shuffle 函数调用:

以上的例子编译成功后,运行可以得到如下输出:
- Clound :
- 1 , 10 , 100
- 4 , 13 , 103
- 7 , 16 , 106
- 10 , 19 , 109
- 13 , 22 , 112
- 16 , 25 , 115
- Indices :
- 0
- 1
- 2
- sqr_distance :
- 0.75
- 18.75
- 90.75
- Indices :
- 0
- 1
- sqr_distance :
- 0.75
- 18.75
如上所示,K近邻搜索得到,离搜索点最近的3个点的平方距离是 0.75、18.75、90.75 ,可见之后的半径为 5 的半径搜索只能搜索到最大 5*5=25 距离的点,因此,结果就是如图所示的 0.75 和 18.75 这 2 个点。