KdTree search using oneapi KdTree FLANN¶
OneAPI KdTree is similar to pcl::KdTreeFLANN
, except OneAPI KdTree can search the entire point cloud
in a single call, while pcl::KdTreeFLANN
performs a search for one point at a time. The OneAPI
version returns a two-dimensional vector of indices and distances for the entire point cloud search
or single dimensional vector of indices, distances, splits for entire point cloud search.
Both nearestKSearch
and radiusSearch
are supported.
OneAPI includes an additional higher-performance fixed-radius search, which performs a radius search
from a hash table. Unlike pcl::KdTreeFLANN
’s radius search, which supports searching with different
radius values once a KdTree has been built, the fixed-radius search requires specifying a radius to
build the hash table. The hash table can only search for a fixed radius. The time required to build
a fixed-radius hash table is much shorter than that of FLANN KdTree. Another difference is that only
FLANN KdTree supports returning K elements from radius search, while fixed-radius search always
returns all elements. For the same radius value, the results of both FLANN and fixed-radius search
should match.
This document will not describe KdTree in detail, and please refer to original kdtree_search for more detail KdTree.
Note
This tutorial can be run both inside and outside a Docker* image. We assume that the pcl-oneapi-tutorial Deb package has been installed, and the user has copied the tutorial directory from /opt/intel/pcl/oneapi/tutorials/ to a user-writable directory.
Prepare the environment:
oneapi_kdtree.cpp
should be in the directory with following content:1#include <pcl/oneapi/search/kdtree.h> // for KdTree 2#include <pcl/point_cloud.h> 3 4#include <vector> 5#include <iostream> 6 7int 8main (int argc, char** argv) 9{ 10 srand (time (NULL)); 11 12 std::cout << "Running on device: " << dpct::get_default_queue().get_device().get_info<sycl::info::device::name>() << "\n"; 13 14 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); 15 16 // Generate pointcloud data 17 cloud->width = 1000; 18 cloud->height = 1; 19 cloud->points.resize (cloud->width * cloud->height); 20 21 for (std::size_t i = 0; i < cloud->size (); ++i) 22 { 23 (*cloud)[i].x = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0)); 24 (*cloud)[i].y = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0)); 25 (*cloud)[i].z = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0)); 26 } 27 28 pcl::PointCloud<pcl::PointXYZ>::Ptr searchPoints (new pcl::PointCloud<pcl::PointXYZ>); 29 30 // Generate pointcloud data 31 searchPoints->width = 3; 32 searchPoints->height = 1; 33 searchPoints->points.resize (searchPoints->width * searchPoints->height); 34 35 for (std::size_t i = 0; i < searchPoints->size (); ++i) 36 { 37 (*searchPoints)[i].x = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0)); 38 (*searchPoints)[i].y = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0)); 39 (*searchPoints)[i].z = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0)); 40 } 41 42 pcl::oneapi::search::KdTree<pcl::PointXYZ> kdtree; 43 kdtree.setInputCloud (cloud); 44 45 // K nearest neighbor search 46 int K = 5; 47 48 std::vector< std::vector< float > > pointsSquaredDistance (searchPoints->size()) ; 49 std::vector< pcl::Indices > pointsIdxKnnSearch (searchPoints->size()); 50 51 kdtree.nearestKSearch(*searchPoints, K, pointsIdxKnnSearch, pointsSquaredDistance); 52 53 for (std::size_t j = 0; j < pointsIdxKnnSearch.size(); ++j) 54 { 55 std::cout << "K=" << K << " neighbors from (" << (*searchPoints)[j].x << "," 56 << (*searchPoints)[j].y << "," 57 << (*searchPoints)[j].z << ")" << std::endl; 58 for (std::size_t i = 0; i < pointsIdxKnnSearch.at(j).size(); ++i) 59 { 60 std::cout << " " << (*cloud)[ pointsIdxKnnSearch.at(j)[i] ].x 61 << " " << (*cloud)[ pointsIdxKnnSearch.at(j)[i] ].y 62 << " " << (*cloud)[ pointsIdxKnnSearch.at(j)[i] ].z 63 << " (squared distance: " << pointsSquaredDistance.at(j)[i] << ")" << std::endl; 64 } 65 } 66 67 // Neighbors within radius search 68 float radius = 100.f; 69 70 std::vector< std::vector< float > > pointsRadiusSquaredDistance (searchPoints->size()) ; 71 std::vector< pcl::Indices > pointsIdxRadiusSearch (searchPoints->size()); 72 73 kdtree.radiusSearch(*searchPoints, radius, pointsIdxRadiusSearch, pointsRadiusSquaredDistance, 10); 74 75 for (std::size_t j = 0; j < pointsIdxRadiusSearch.size(); ++j) 76 { 77 std::cout << "Kdtree Radius Search Radius=" << radius << " neighbors from (" << (*searchPoints)[j].x << "," 78 << (*searchPoints)[j].y << "," 79 << (*searchPoints)[j].z << ")" << std::endl; 80 for (std::size_t i = 0; i < pointsIdxRadiusSearch.at(j).size(); ++i) 81 { 82 std::cout << " " << (*cloud)[ pointsIdxRadiusSearch.at(j)[i] ].x 83 << " " << (*cloud)[ pointsIdxRadiusSearch.at(j)[i] ].y 84 << " " << (*cloud)[ pointsIdxRadiusSearch.at(j)[i] ].z 85 << " (squared distance: " << pointsRadiusSquaredDistance.at(j)[i] << ")" << std::endl; 86 } 87 } 88 89 // Fixed radius search 90 // Only support radius use to build the table 91 92 std::vector<int> pointsFixedRadiusIdx; 93 std::vector<float> pointsFixedRadiusSquaredDistance; 94 std::vector<int> pointsFixedRadiusSplit; 95 96 kdtree.setInputCloud(cloud, radius); 97 98 kdtree.fixedRadiusSearch(searchPoints, pointsFixedRadiusIdx, pointsFixedRadiusSquaredDistance, 99 pointsFixedRadiusSplit); 100 101 for (std::size_t j = 0; j < pointsFixedRadiusSplit.size() - 1; ++j) 102 { 103 std::cout << "Fixed Radius Search Radius=" << radius << " neighbors from (" << (*searchPoints)[j].x << "," 104 << (*searchPoints)[j].y << "," 105 << (*searchPoints)[j].z << ")" << std::endl; 106 107 std::size_t cur_idx = pointsFixedRadiusSplit[j]; 108 std::size_t elements = pointsFixedRadiusSplit[j+1] - cur_idx; 109 for (std::size_t i = 0; i < elements; ++i) 110 { 111 std::cout << " " << (*cloud)[pointsFixedRadiusIdx.at(cur_idx+i)].x 112 << " " << (*cloud)[ pointsFixedRadiusIdx.at(cur_idx+i)].y 113 << " " << (*cloud)[ pointsFixedRadiusIdx.at(cur_idx+i)].z 114 << " (squared distance: " << pointsFixedRadiusSquaredDistance.at(cur_idx+i) << ")" << std::endl; 115 116 } 117 } 118 119 return 0; 120}
Source the Intel® oneAPI Base Toolkit environment:
(Optional) Setup proxy setting to download test data:
Build the code:
Run the binary:
Expected results example:
K=5 neighbors from (911.933,321.699,128.681) 870.884 350.103 160.644 (squared distance: 3513.43) 856.565 374.562 132.514 (squared distance: 5874.84) 960.284 267.917 173.756 (squared distance: 7262) 987.681 359.434 104.805 (squared distance: 7731.77) 929.787 396.96 84.1415 (squared distance: 7966.85) K=5 neighbors from (39.0119,923.789,113.278) 36.4024 943.423 84.1814 (squared distance: 1238.91) 68.2891 927.112 150.885 (squared distance: 2282.5) 79.0735 887.262 63.1519 (squared distance: 5451.72) 119.306 1021.4 127.402 (squared distance: 16175.4) 116.103 800.647 145.881 (squared distance: 22169.9) K=5 neighbors from (542.853,566.777,4.23339) 481.511 597.7 27.2575 (squared distance: 5249.24) 570.652 585.804 69.9711 (squared distance: 5456.25) 574.454 498.665 37.4542 (squared distance: 6741.42) 569.49 480.913 6.70625 (squared distance: 8088.2) 649.963 597.5 25.601 (squared distance: 12872.9) Kdtree Radius Search Radius=100 neighbors from (911.933,321.699,128.681) 870.884 350.103 160.644 (squared distance: 3513.43) 856.565 374.562 132.514 (squared distance: 5874.84) 960.284 267.917 173.756 (squared distance: 7262) 987.681 359.434 104.805 (squared distance: 7731.77) 929.787 396.96 84.1415 (squared distance: 7966.85) 967.947 352.763 64.8979 (squared distance: 8170.85) 974.103 248.606 116.676 (squared distance: 9351.69) 842.968 389.302 109.632 (squared distance: 9689.34) Kdtree Radius Search Radius=100 neighbors from (39.0119,923.789,113.278) 36.4024 943.423 84.1814 (squared distance: 1238.91) 68.2891 927.112 150.885 (squared distance: 2282.5) 79.0735 887.262 63.1519 (squared distance: 5451.72) Kdtree Radius Search Radius=100 neighbors from (542.853,566.777,4.23339) 481.511 597.7 27.2575 (squared distance: 5249.24) 570.652 585.804 69.9711 (squared distance: 5456.25) 574.454 498.665 37.4542 (squared distance: 6741.42) 569.49 480.913 6.70625 (squared distance: 8088.2) Fixed Radius Search Radius=100 neighbors from (911.933,321.699,128.681) 870.884 350.103 160.644 (squared distance: 3513.43) 856.565 374.562 132.514 (squared distance: 5874.84) 960.284 267.917 173.756 (squared distance: 7262) 987.681 359.434 104.805 (squared distance: 7731.77) 929.787 396.96 84.1415 (squared distance: 7966.85) 967.947 352.763 64.8979 (squared distance: 8170.85) 974.103 248.606 116.676 (squared distance: 9351.69) 842.968 389.302 109.632 (squared distance: 9689.34) Fixed Radius Search Radius=100 neighbors from (39.0119,923.789,113.278) 36.4024 943.423 84.1814 (squared distance: 1238.91) 68.2891 927.112 150.885 (squared distance: 2282.49) 79.0735 887.262 63.1519 (squared distance: 5451.72) Fixed Radius Search Radius=100 neighbors from (542.853,566.777,4.23339) 481.511 597.7 27.2575 (squared distance: 5249.24) 570.652 585.804 69.9711 (squared distance: 5456.25) 574.454 498.665 37.4542 (squared distance: 6741.42) 569.49 480.913 6.70625 (squared distance: 8088.2)
Code Explanation¶
Compare to original version, the OneAPI version requires to use the OneAPI KdTree header.
The following code first seeds the ‘rand()’ function with the system time and then creates and fills a PointCloud with random data. Another set of search points with random coordinates is created as well. The objective here is to perform a search for 3 coordinates using a single call.
srand (time (NULL));
std::cout << "Running on device: " << dpct::get_default_queue().get_device().get_info<sycl::info::device::name>() << "\n";
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->size (); ++i)
{
(*cloud)[i].x = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0));
(*cloud)[i].y = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0));
(*cloud)[i].z = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0));
}
pcl::PointCloud<pcl::PointXYZ>::Ptr searchPoints (new pcl::PointCloud<pcl::PointXYZ>);
// Generate pointcloud data
searchPoints->width = 3;
searchPoints->height = 1;
searchPoints->points.resize (searchPoints->width * searchPoints->height);
for (std::size_t i = 0; i < searchPoints->size (); ++i)
{
(*searchPoints)[i].x = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0));
(*searchPoints)[i].y = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0));
(*searchPoints)[i].z = static_cast<float>(1024.0f * rand () / (RAND_MAX + 1.0));
}
This section of code initializes the KdTree object and sets the randomly created cloud as the input.
An integer variable is created (set to 5), along with two two-dimensional vectors designed to store the K nearest neighbors from the search.
Assuming that the KdTree returns more than 0 closest neighbors, it proceeds to print out the
locations of all 5 closest neighbors to the random searchPoints
, which have been stored in the
previously created vectors.
for (std::size_t j = 0; j < pointsIdxKnnSearch.size(); ++j)
{
std::cout << "K=" << K << " neighbors from (" << (*searchPoints)[j].x << ","
<< (*searchPoints)[j].y << ","
<< (*searchPoints)[j].z << ")" << std::endl;
for (std::size_t i = 0; i < pointsIdxKnnSearch.at(j).size(); ++i)
{
std::cout << " " << (*cloud)[ pointsIdxKnnSearch.at(j)[i] ].x
<< " " << (*cloud)[ pointsIdxKnnSearch.at(j)[i] ].y
<< " " << (*cloud)[ pointsIdxKnnSearch.at(j)[i] ].z
<< " (squared distance: " << pointsSquaredDistance.at(j)[i] << ")" << std::endl;
}
}
The code then demonstrates the process of finding all neighbors to the given searchPoints
within a
specified radius. Two vectors are created to store information about these neighbors.
// Neighbors within radius search
float radius = 100.f;
std::vector< std::vector< float > > pointsRadiusSquaredDistance (searchPoints->size()) ;
std::vector< pcl::Indices > pointsIdxRadiusSearch (searchPoints->size());
kdtree.radiusSearch(*searchPoints, radius, pointsIdxRadiusSearch, pointsRadiusSquaredDistance, 10);
Similar to the previous scenario, if the KdTree returns more than 0 neighbors within the specified radius, it prints out the coordinates of these points, which have been stored in the vectors.
for (std::size_t j = 0; j < pointsIdxRadiusSearch.size(); ++j)
{
std::cout << "Kdtree Radius Search Radius=" << radius << " neighbors from (" << (*searchPoints)[j].x << ","
<< (*searchPoints)[j].y << ","
<< (*searchPoints)[j].z << ")" << std::endl;
for (std::size_t i = 0; i < pointsIdxRadiusSearch.at(j).size(); ++i)
{
std::cout << " " << (*cloud)[ pointsIdxRadiusSearch.at(j)[i] ].x
<< " " << (*cloud)[ pointsIdxRadiusSearch.at(j)[i] ].y
<< " " << (*cloud)[ pointsIdxRadiusSearch.at(j)[i] ].z
<< " (squared distance: " << pointsRadiusSquaredDistance.at(j)[i] << ")" << std::endl;
}
}
Additionally, a fixed radius search method is provided through a hash table. As this is a fixed radius search, it is necessary to provide the radius to build the table.
The fixed radius search returns a one-dimensional vector containing both indices and squared
distances for the neighbors. To access the respective searchPoints
, the returned split vector is
used. The example below demonstrates how to access each searchPoint
.
kdtree.fixedRadiusSearch(searchPoints, pointsFixedRadiusIdx, pointsFixedRadiusSquaredDistance,
pointsFixedRadiusSplit);
for (std::size_t j = 0; j < pointsFixedRadiusSplit.size() - 1; ++j)
{
std::cout << "Fixed Radius Search Radius=" << radius << " neighbors from (" << (*searchPoints)[j].x << ","
<< (*searchPoints)[j].y << ","
<< (*searchPoints)[j].z << ")" << std::endl;
std::size_t cur_idx = pointsFixedRadiusSplit[j];
std::size_t elements = pointsFixedRadiusSplit[j+1] - cur_idx;
for (std::size_t i = 0; i < elements; ++i)
{
std::cout << " " << (*cloud)[pointsFixedRadiusIdx.at(cur_idx+i)].x
<< " " << (*cloud)[ pointsFixedRadiusIdx.at(cur_idx+i)].y
<< " " << (*cloud)[ pointsFixedRadiusIdx.at(cur_idx+i)].z
<< " (squared distance: " << pointsFixedRadiusSquaredDistance.at(cur_idx+i) << ")" << std::endl;
}
}