Fast Triangulation of Unordered Point Clouds (OpenMP Version)¶
This tutorial explains how to run a greedy surface triangulation algorithm (OpenMP version) on a PointCloud with normals, to obtain a triangle mesh based on projections of the local neighborhoods. The OpenMP version will break the point cloud into multiple segments and call the original version of the greedy surface triangulation based on the segment count. By running the segments independently and concurrently, there will be a disjoint result between each segment. To overcome the disjoint issue, additional overlap segments will be created to go through greedy surface triangulation and combine with other processed segments.
Note
The output of OpenMP version may differ from serial version of greedy projection triangulation.
Background: Algorithm and Parameters¶
Refer to original greedy_triangulation
for more detail original greedy triangulation parameters. All greedy triangulation parameters are supported except getPointStates()
,
getPartIDs()
, getSFN()
and getFFN()
.
Additional parameters are defined to provide control segments and overlap.
setNumberofThreads(unsigned) controls how many segments are created from the input point cloud. Each segment size is determined using the formula (maximum x - minimum x) / number of threads, and all segment sizes are equal. Each segment will be assigned to a CPU thread/core. It is recommended to obtain the number of threads/cores using
omp_get_max_threads()
.setBlockOverlapPercentage(double) controls how wide to create the overlap region among two segments. The overlap region is this parameter percentage multiply the size of segment. If there is still gap between segments, can overcome by increase the overlap percentage.
setRemoveDuplicateMesh(bool) controls whether to clean up the duplicate point cloud and polygons after combining all the segments output from GP3.
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:
greedy_projectoin.cpp
should be in the directory with following content:1#include <pcl/point_types.h> 2#include <pcl/io/pcd_io.h> 3#include <pcl/io/vtk_io.h> 4#include <pcl/search/kdtree.h> // for KdTree 5#include <pcl/features/normal_3d.h> 6#include <pcl/oneapi/surface_omp/gp3.h> 7 8int 9main () 10{ 11 // Load input file into a PointCloud<T> with an appropriate type 12 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); 13 pcl::PCLPointCloud2 cloud_blob; 14 pcl::io::loadPCDFile ("bun0.pcd", cloud_blob); 15 pcl::fromPCLPointCloud2 (cloud_blob, *cloud); 16 //* the data should be available in cloud 17 18 // Normal estimation* 19 pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n; 20 pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>); 21 pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>); 22 tree->setInputCloud (cloud); 23 n.setInputCloud (cloud); 24 n.setSearchMethod (tree); 25 n.setKSearch (20); 26 n.compute (*normals); 27 //* normals should not contain the point normals + surface curvatures 28 29 // Concatenate the XYZ and normal fields* 30 pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals (new pcl::PointCloud<pcl::PointNormal>); 31 pcl::concatenateFields (*cloud, *normals, *cloud_with_normals); 32 //* cloud_with_normals = cloud + normals 33 34 // Create search tree* 35 pcl::search::KdTree<pcl::PointNormal>::Ptr tree2 (new pcl::search::KdTree<pcl::PointNormal>); 36 tree2->setInputCloud (cloud_with_normals); 37 38 // Initialize objects 39 pcl::GreedyProjectionTriangulationOMP<pcl::PointNormal> gp3; 40 pcl::PolygonMesh triangles; 41 42 // Set the maximum distance between connected points (maximum edge length) 43 gp3.setSearchRadius (0.025); 44 45 // Set typical values for the parameters 46 gp3.setMu (2.5); 47 gp3.setMaximumNearestNeighbors (100); 48 gp3.setMaximumSurfaceAngle(M_PI/4); // 45 degrees 49 gp3.setMinimumAngle(M_PI/18); // 10 degrees 50 gp3.setMaximumAngle(2*M_PI/3); // 120 degrees 51 gp3.setNormalConsistency(false); 52 gp3.setNumberOfThreads(3); 53 gp3.setBlockOverlapPercentage(0.04); 54 gp3.setRemoveDuplicateMesh(true); 55 56 // Get result 57 gp3.setInputCloud (cloud_with_normals); 58 gp3.setSearchMethod (tree2); 59 gp3.reconstruct (triangles); 60 61 pcl::io::saveVTKFile("mesh.vtk", triangles); 62 63 // Finish 64 return (0); 65}
Source the Intel® oneAPI Base Toolkit environment:
(Optional) Setup proxy setting to download test data:
Build the code:
Run the binary:
Output:
The program will save the output as “
mesh.vtk
”. View the VTK file by:
Code Explanation¶
As the example PCD has only XYZ coordinates, load it into a PointCloud<PointXYZ>.
The method requires normals, so normals are estimated using the standard method from PCL.
// Normal estimation*
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n;
pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud (cloud);
n.setInputCloud (cloud);
n.setSearchMethod (tree);
n.setKSearch (20);
n.compute (*normals);
Since coordinates and normals need to be in the same PointCloud, a PointNormal type point cloud is created.
The lines below deal with the initialization of the required objects. In the OpenMP version, include <pcl/oneapi/surface_omp/gp3.h> and declare the class by appending OMP to the original class.”
The lines below set the parameters, as explained above, to configure the algorithm.
// Set the maximum distance between connected points (maximum edge length)
gp3.setSearchRadius (0.025);
// Set typical values for the parameters
gp3.setMu (2.5);
gp3.setMaximumNearestNeighbors (100);
gp3.setMaximumSurfaceAngle(M_PI/4); // 45 degrees
gp3.setMinimumAngle(M_PI/18); // 10 degrees
gp3.setMaximumAngle(2*M_PI/3); // 120 degrees
gp3.setNormalConsistency(false);
gp3.setNumberOfThreads(3);
gp3.setBlockOverlapPercentage(0.04);
gp3.setRemoveDuplicateMesh(true);
The lines below set the input objects and perform the actual triangulation.