PCL 中 KdTree 的使用心得

Notes


为了促进同行业人员(特指 LiDAR 点云处理人员或相近行业)的技术交流,解决平时开发过程中遇到的技术性问题,博主建立一个QQ群,欢迎大家积极加入,共同引领点云行业的快速发展 ~

群名:LiDAR点云部落
群号:190162198

1. 背景


今天在优化自己的代码时,发现影响程序运行时间的罪魁祸首在 KdTree 的使用上,经过优化,将 30s 的程序优化到 7s,故记录下 KdTree 的优化心得

链接:PCL KdTree 官方教程

2. nearestKSearch() 和 radiusSearch() 谁快


首先从搜索方式来讲,一个是基于最近点的个数来搜索,一个是基于半径搜索;

从结果来讲,两者得到的都是搜索到的点及这些点到搜索点距离的额平方

两者的具体原理请自行查找;

那么在单线程的情况下,到底谁快?

其实这个没有绝对的好坏,并且也很难通过一定的方法对比好坏

可以明确的是,这两个方法在大多数场景下是没法替换的,因为选择使用哪一种搜索方式肯定与你的用途或目的有关,比如说,你要求邻域的协方差,你只能用 radiusSearch(),你是无法通过 nearestKSearch() 来控制邻域点的个数的;反之同理;

既然你根据你的想法确定了搜索方式,那么你会问,怎么最快?

3. nearestKSearch() 的加速


方法一:并行加速(如 omp ,或者用 GPU 加速)

这里推荐一篇 xuyiqiang87 写的非常好的 OMP 文章

首先我们以 nearestKSearch() 为例,讲解怎么加速:

#pragma omp parallel for
for(int i = 0; i < cloud->points.size(); ++i) {
    pcl::PointXYZ pSearch = cloud->points[i];
    int K = 10;
    std::vector<int> vecIdx(K);
    std::vector<float> vecDistance(K);
    if( kdtree.nearestKSearch(pSearch, K, vecIdx, vecDistance) > 0 ) {
        std::cout << vecIdx.size() << std::endl;
    }
}

值得注意的是,我把 std::vector vecIdx(K); 和 std::vector vecDistance(K); 写在了 for 循环里!!!否则在并行计算中程序会 crash!!!

方法二:预先设定 K 值

预先设定 K ,并且直接初始化 vecIdx(K) 和 vecDistance(K);,从理论上来讲,该操作一定程度上防止了 std::vector 在 capacity 不够的情况下导致的内存申请与释放;

但是在现实大部分场景中,你的 capacity 足够你用。也就是说,这并不会很明显的加快你的程序,或者说,在千万级别的点云处理中,基本无速度变化…上亿级别的估计会有吧…

所以这个方法无实质性作用

方法三:在保证正确率的情况下,减少或者抽稀要构建 KdTree 的点云中点的个数

为什么要这样做?假设你的点非常密集,那么 KdTree 在做 K 临近搜索时,要进行一定的排序比较操作,点非常密集,那么差别就小,比较起来比较耗时。所以从某种程度上讲,nearestKSearch() 更适合于点数适中的情况下。

但是记得前提:在保证正确率的情况下!!!

方法四:构建多个相同的 KdTree ,分别搜索???

放弃吧 … 这不是明智的选择

方法五:待挖掘

4. radiusSearch() 的加速


方法一:并行加速(如 omp )

原理同上

方法二:点云质量较好的情况下,将大半径搜索改为小半径搜索加聚类

这个详细说一下什么意思;

在点云质量较好的情况下,假设你原来是用的 float radius = 0.5f; 进行搜索,那么的话,速度会比较慢,半径越大,需要判断的点越多,需要计算的距离的平方也越多

那么的话,换一种想法,我们改成 float radius = 0.1f; 进行搜索,在搜索得到的点中,依次以这些点为搜索点,继续向外扩散,最终达到 radius = 0.5f 的效果;

这个思想非常类似于聚类中的 DBSCAN(网上有很多介绍);

这种思想非常适用于区域增长,或者是基于一定条件聚类的算法中,配合上 OMP 的加速,可以达到相当好的效果;

伪代码大概为:

#pragma omp parallel for
for(int i = 0; i < cloud->points.size(); ++i) {
    pcl::PointXYZ pSearch = cloud->points[i];

    // 使用 std::queue
    std::queue<pcl::PointXYZ> q;
    std::vector<int> vecIdx;
    std::vector<float> vecDistance;
    float radius = 0.1f;
    if( kdtree.radiusSearch(pSearch, radius, vecIdx, vecDistance) > 0 ) {
        for(int j = 0; j < vecIdx.size(); ++j) {
            q.push(cloud->points[vecIdx[j]]);
        }
    }
    // 开始新的遍历
    while(q.size() > 0) {
        // TODO ...
    }
    // ...
}

方法三:点云质量较差的情况下,用 nearestKSearch() 代替

这个就不多说了,经验之谈,写多了自然就会了 …

方法四:待挖掘

5. 总结


实践出真知

### PCLKDTree使用方法及示例 #### 关于 KDTree KDTree 是一种高效的数据结构,用于多维键数组的空间分割。这种数据结构特别适合执行快速的最近邻查询和其他类型的范围搜索操作。在三维空间中,KDTree 被广泛应用于点云处理领域,能够显著提高搜索效率[^3]。 #### 最近邻搜索功能 通过构建 KDTree 数据结构,在低维度数据集中可以实现高效的 k 近邻 (k-NN) 和范围搜索。这使得 KDTree 成为了许多机器学习算法和计算机视觉任务的核心组件之一。此外,该结构支持动态更新节点,允许实时插入或移除数据点而不影响整体性能[^2]。 #### 复杂度分析 对于一次成功的查找操作而言,平均情况下时间复杂度接近 O(log n),其中 n 表示存储在树内的元素数量;最坏情况下的时间复杂度则取决于输入分布特性,可能退化至线性级别即 O(n)[^1]。 #### C++ 代码实例展示如何创建并利用 PCL 库中的 `pcl::KdTreeFLANN` 类来进行简单的最近邻居检索: ```cpp #include <iostream> #include <vector> #include <pcl/point_cloud.h> #include <pcl/kdtree/kdtree_flann.h> int main () { // 创建一个 PointXYZ 点云对象,并向其添加一些随机生成的数据点. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // 填充点云... (*cloud).width = 5; (*cloud).height = 1; (*cloud).points.resize ((*cloud).width * (*cloud).height); for (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); } // 定义 KdTree 对象并与上述点云关联起来. pcl::KdTreeFLANN<pcl::PointXYZ> kdtree; // 将点云传递给 KdTree 实例. kdtree.setInputCloud(cloud); // 设置测试用的目标点及其对应的索引变量. pcl::PointXYZ searchPoint; int K = 1; // 寻找离目标点最近的一个邻居. // 执行实际的搜索过程. std::vector<int> pointIdxNKNSearch(K); // 存储找到的结果点下标的容器. std::vector<float> pointNKNSquaredDistance(K); // 记录各匹配项之间的平方差值. // 测试: 查找距离指定位置最近的那个点. if(kdtree.nearestKSearch(searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance)>0) { printf("\nK nearest neighbor(s) found:"); for(size_t i=0;i<pointIdxNKNSearch.size();++i) printf("\nIndex=%d Distance Squared=%.9lf",pointIdxNKNSearch[i],pointNKNSquaredDistance[i]); } } ``` 此段程序展示了怎样初始化一个点集、建立相应的 KDTree 结构体以及调用 `nearestKSearch()` 方法来定位特定坐标附近最相近的一组样本点。注意这里只选取了一个临近点作为例子,但在实践中可以根据具体应用场景调整参数 K 来获取更多候选者。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值