CGAL - 带信息的 Delaunay 三角剖分层次结构

CGAL - Delaunay Triangulation Hierarchy with Info

我有一个 PCL 点云(3D),我想将其转换为地面 TIN(2.5D),然后采样点(2D)以在投影到锡。为此,我一直在使用 CGAL Delaunay 三角剖分 classes,它在大多数情况下都运行良好!

我能够使用基于 Triangulation_vertex_base_with_info_2 构建的 Delaunay_triangulation_2 来实现这一点,并创建一个漂亮的 TIN。我还编写了一个函数,它使用 CGAL locate() 函数提取 2D space 中任意点的面和顶点,这样我就可以在投影到 TIN 上时插入点的高度。我需要信息字段来保存一个索引,让我可以将三角剖分中的顶点关联回 PCL 点云结构中的点。

然而,当使用基本三角剖分时 class locate() 函数很慢(从三角剖分中的任意顶点开始的随机游走),并且因为我必须为要插值的云(以估计预计高度)这是目前我整个管道中最慢的部分。所以我研究了使用三角剖分层次结构 class 来提高效率。

我不知道如何使 Triangulation_hierarchy class 与带有信息的顶点基一起工作,我认为我只是在做一些愚蠢的错误。这是一个最小的示例,显示了我使用简单的三角剖分结构(无层次结构)的缓慢解决方案,它确实有效:


#include <chrono>

#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Delaunay_triangulation_2.h>

typedef CGAL::Exact_predicates_inexact_constructions_kernel             K;
typedef CGAL::Triangulation_vertex_base_with_info_2<unsigned int, K>    Vb;
typedef CGAL::Triangulation_data_structure_2<Vb>                        Tds;
typedef CGAL::Delaunay_triangulation_2<K, Tds>                          Delaunay;
typedef Delaunay::Point_2                                               CGALPoint;
typedef Delaunay::Face_handle                                           Face_handle;


// This is templated on a PCL PointCloud Ptr - input cloud is basically a 3D vector of XYZ points
template <typename CloudType> 
void delaunayTriangulation(CloudType input_cloud, Delaunay& triangulation)
{ 
    std::cout << "Performing Delaunay triangulation on cloud of size " << input_cloud->points.size() << std::endl;
    // Convert ground minima cloud to CGAL vector of points
    std::vector< std::pair<CGALPoint, unsigned> > minima_vec;
    for(std::size_t i=0; i<input_cloud->points.size(); i++)
    {
        minima_vec.push_back(std::make_pair(CGALPoint(input_cloud->points[i].x,input_cloud->points[i].y), i));
    }
    // Generate Delaunay Triangulation for ground minima 
    triangulation = Delaunay(minima_vec.begin(), minima_vec.end());
    std::cout << "  Number of vertices in Delaunay: " << triangulation.number_of_vertices() << std::endl;
    std::cout << "  Number of faces in Delaunay: " << triangulation.number_of_faces() << std::endl; 
}

int main()
{
    // Generate a starting point cloud with random points
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    for(int i=0; i<500; i++)
        for(int j=0; j<500; j++)
        {
            // Generate points which are gridded + a bit of noise in XY, and random Z 
            pcl::PointXYZ point;
            point.x = i + (std::rand()%100)/100.0;
            point.y = j + (std::rand()%100)/100.0; 
            point.z = std::rand();
            cloud->points.push_back(point);
        }
    // Get the ground triangulation
    Delaunay triangulation;
    delaunayTriangulation(cloud, triangulation);
    // Locate the containing face for a bunch of random points
    std::cout << "Starting to search for faces..." << std::endl;
    auto start_time = std::chrono::high_resolution_clock::now();
    for(int i=0; i<3000000; i++)
    {
        // Random point with X and Y between 0 and 500
        CGALPoint test_point((std::rand() % 5000)/10.0, (std::rand() % 5000)/10.0);
        Face_handle face = triangulation.locate(test_point);
        // here we would do some math using the vertices located above
    }
    auto stop_time = std::chrono::high_resolution_clock::now();
    float duration = std::chrono::duration_cast<std::chrono::microseconds>( stop_time - start_time ).count();
    std::cout << "Finished searching for faces - took " << duration/1000000 << std::endl;
}

如果我转而使用 Triangulation_hierarchy_2 对象,构建在我的 Delaunay_Triangulation_2 类型之上,它不会让我插入包含信息字段的点对 - 它只会如果我自己使用点向量构建对象,则编译:


#include <chrono>

#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_hierarchy_2.h>

typedef CGAL::Exact_predicates_inexact_constructions_kernel             K;
typedef CGAL::Triangulation_vertex_base_with_info_2<unsigned int, K>    Vbb;
typedef CGAL::Triangulation_hierarchy_vertex_base_2<Vbb>                Vb;
typedef CGAL::Triangulation_data_structure_2<Vb>                        Tds;
typedef CGAL::Delaunay_triangulation_2<K, Tds>                          Delaunay;
typedef Delaunay::Point_2                                               CGALPoint;
typedef Delaunay::Face_handle                                           Face_handle;
typedef CGAL::Triangulation_hierarchy_2<Delaunay>                       Delaunay_hierarchy;

// This is templated on a PCL PointCloud Ptr - input cloud is basically a 3D vector of XYZ points
template <typename CloudType> 
void delaunayTriangulation(CloudType input_cloud, Delaunay_hierarchy& triangulation)
{ 
    std::cout << "Performing Delaunay triangulation on cloud of size " << input_cloud->points.size() << std::endl;
    // Convert ground minima cloud to CGAL vector of points
    std::vector<CGALPoint> minima_vec_simple;
    for(std::size_t i=0; i<input_cloud->points.size(); i++)
    {
        minima_vec_simple.push_back(CGALPoint(input_cloud->points[i].x,input_cloud->points[i].y));
    }
    // Generate Delaunay Triangulation for ground minima 
    triangulation = Delaunay_hierarchy(minima_vec_simple.begin(), minima_vec_simple.end());
    std::cout << "  Number of vertices in Delaunay: " << triangulation.number_of_vertices() << std::endl;
    std::cout << "  Number of faces in Delaunay: " << triangulation.number_of_faces() << std::endl; 
}

int main()
{
    // Generate a starting point cloud with random points
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    for(int i=0; i<500; i++)
        for(int j=0; j<500; j++)
        {
            // Generate points which are gridded + a bit of noise in XY, and random Z 
            pcl::PointXYZ point;
            point.x = i + (std::rand()%100)/100.0;
            point.y = j + (std::rand()%100)/100.0; 
            point.z = std::rand();
            cloud->points.push_back(point);
        }
    // Get the ground triangulation
    Delaunay_hierarchy triangulation;
    delaunayTriangulation(cloud, triangulation);
    // Locate the containing face for a bunch of random points
    std::cout << "Starting to search for faces..." << std::endl;
    auto start_time = std::chrono::high_resolution_clock::now();
    for(int i=0; i<3000000; i++)
    {
        // Random point with X and Y between 0 and 500
        CGALPoint test_point((std::rand() % 5000)/10.0, (std::rand() % 5000)/10.0);
        Face_handle face = triangulation.locate(test_point);
        // here we would do some math using the vertices located above
    }
    auto stop_time = std::chrono::high_resolution_clock::now();
    float duration = std::chrono::duration_cast<std::chrono::microseconds>( stop_time - start_time ).count();
    std::cout << "Finished searching for faces - took " << duration/1000000 << std::endl;
}

CGAL 中的所有 typedef 和模板层对我来说都有点不透明 - 有没有什么方法可以设置这些,而不是让我可以基于 Triangulation_hierarchy_2 构建类似 Triangulation_hierarchy_2 的东西=28=]_vertex_base_with_info_2?第二种看起来不存在。

谢谢!

好的 - 我最终尝试了几条路线,并对每条路线进行了一些基本的执行时间基准测试。这些是基于我问题中代码中的设置:

  • 具有 25,000 个点的 TIN,主要分布在 XY 中从 0 到 500 的网格上,XY 中有一点噪声,z 值是随机的
  • 在 X/Y
  • 中包含 3,000,000 个随机值介于 0 和 500 之间的点的测试云

我尝试了什么:

  1. CGAL locate() 使用简单的三角结构,任意起始面 --> 79.3 s
  2. CGAL locate() 使用 Triangulation Hierarchy 结构,任意起始面 --> 4.74 s
  3. CGAL locate() 使用由最近邻顶点给出的起始面,在输入的顶点云上使用 K-D 树找到 --> 3.41 s

为此,在创建三角剖分后,我遍历面并在输入云顶点索引和三角剖分面句柄之间建立映射:


std::vector<Face_handle> face_mapping(cloud->points.size());
std::vector<bool> faces_filled(cloud->points.size(), false);
auto start_time_list = std::chrono::high_resolution_clock::now();
// Iterate over all faces in triangulation
for (Face_handle face : triangulation.finite_face_handles())
    // Iterate over 3 vertices for each face
    for(int i=0; i<3; i++)
    {
        int index = uint32_t(face->vertex(i)->info());
        if(!faces_filled[index])
            face_mapping[index] = face;
    }

然后我们运行点定位搜索的时候可以这样:


std::vector<int> nearest_indices;
std::vector<float> nearest_dists;
pcl::KdTreeFLANN<pcl::Point2DGround> tree;
pcl::PointCloud<pcl::Point2DGround>::Ptr cloud_2d(new pcl::PointCloud<pcl::Point2DGround>);
copyPointCloud3D(cloud, cloud_2d);
tree.setInputCloud(cloud_2d);
for(int i=0; i<3000000; i++)
{
    // Random point with X and Y between 0 and 500
    CGALPoint test_point((std::rand() % 5000)/10.0, (std::rand() % 5000)/10.0);
    
    // Get closest vertex in triangulation
    pcl::Point2DGround pcl_point;
    pcl_point.x = test_point.x();
    pcl_point.y = test_point.y();
    tree.nearestKSearch(pcl_point, 1, nearest_indices, nearest_dists);

    Face_handle face = triangulation.locate(test_point, face_mapping[nearest_indices[0]]);

    // here we would do some math using the vertices located above
}

其中 pcl::Point2DGround 必须是一些自定义点类型,其中表示仅为 2D(以使搜索树正常工作)。

我最终没有尝试对要测试的传入点进行任何类型的排序,因为上面的方法有效并且因为我要定位()的测试点总是比 TIN 顶点多得多他们自己,所以我认为对他们强加命令可能会更昂贵。

我认为这是针对我的案例的 simplest/fastest 解决方案,所以我将采用它!感谢@marcglisse 和@andreasfabri 的评论。