本文参考网址为CGAL 6.1.1 - 3D Point Set: User Manual我整理了这个网址上的知识点对其中的example做了详细注释Point_set_3是用来存储点云的容器是点云操作的基础数据结构对一些需要使用点云作为数据输入的工程至关重要。说明本文中案例涉及的.ply.pwn.xyz文件均在CGAL的examples的文件夹下面下载地址为https://github.com/CGAL/cgal/releases/download/v6.1.1/CGAL-6.1.1-examples.zip1 引言CGAL::Point_set_3Point,Vector 是一种基于向量的数据结构包含一个用于存储点坐标的默认属性名称为point。 用户所需的任意属性均可在运行时轻松添加、修改与删除。每个属性由唯一名称与数据类型进行标识。该结构提供便捷方法来处理法向量对应名称为normal的属性而法向量是点集中十分常用的一类属性。 为优化内存的分配与释放效率每个点都会关联一个索引。删除点时仅会将对应索引标记为已删除。在底层逻辑中此举可避免每次删除操作都修改属性向量同时新增点时能够复用已标记删除的索引。这也就意味着在执行删除操作后新增的点其部分属性可能未按默认规则初始化。若用户需要彻底释放内存可通过调用Point_set_3::garbage_collect()将所有标记为已删除的元素从内存中真正清除。2 简单使用该数据结构在使用属性时即便存在潜在复杂性也做了易用化设计。系统提供多种便捷方法可直接处理点与法向量无需操作属性本身。 以下示例演示如何填充点集、添加法向量属性、设置法向量数值以及完成点的新增与删除操作。#includeCGAL/Exact_predicates_inexact_constructions_kernel.h #includeCGAL/Point_set_3.h #includefstream #includelimits using namespace std; typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef Kernel::FT FT; typedef Kernel::Point_3 Point; typedef Kernel::Vector_3 Vector; typedef CGAL::Point_set_3Point Point_set; void Print_point_set(const Point_set pset) { cout endl; cerr Content of point set endl; for (Point_set::const_iterator it pset.begin(); it ! pset.end();it) { cerr *Point *it : pset.point(*it) with normal pset.normal(*it) endl; } cout endl; } void test1(int , char**) { Point_set point_set; //插入3个点 point_set.insert(Point(0,0,0)); point_set.insert(Point(0,0,1)); point_set.insert(Point(0,1,0)); //为插入的几个点添加默认的法向量 point_set.add_normal_map(); Print_point_set(point_set); //拿到点的迭代器 Point_set::iterator it point_set.begin(); //为每个点手动设置法向量 point_set.normal(*(it)) Vector(1,0,0); point_set.normal(*(it)) Vector(0,1,0); point_set.normal(*(it)) Vector(0,0,1); //插入点并为其设置法向量 point_set.insert(Point(1,2,3),Vector(4,5,6)); Print_point_set(point_set); //插入新点 Point_set::iterator new_item point_set.insert(Point(7,8,9)); //为新点设置法向量 point_set.normal(*new_item) Vector(10,11,12); Print_point_set(point_set); //依次删除下标为3-5的点逻辑删除 point_set.remove(point_set.begin()2,point_set.begin()4); Print_point_set(point_set); cout Number of removed points: point_set.number_of_removed_points() endl; //垃圾回收 point_set.collect_garbage(); //物理删除 cout After garbage collection point_set.number_of_removed_points() endl; }3 使用附加属性点集中的所有信息均为属性。原始点集仅自带point属性。正如我们在上一个示例中所见用户可轻松添加法向量属性。且该机制可通用于所有类型的属性。 下述示例将演示如何定义颜色属性与强度属性以及如何依据这些属性修改点集。#includeCGAL/Exact_predicates_inexact_constructions_kernel.h #includeCGAL/Point_set_3.h #includeCGAL/Random.h #includefstream #includelimits using namespace std; typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef Kernel::FT FT; typedef Kernel::Point_3 Point_3; typedef Kernel::Vector_3 Vector_3; typedef std::arrayunsigned char, 3 Color; typedef CGAL::Point_set_3Point_3 Point_set; typedef Point_set::Property_mapColor Color_map; typedef Point_set::Property_mapFT FT_map; void print_point_set(const Point_set p_set) { //获取点集的颜色属性和强度属性,Color_map和FT_map是可以操作的属性映射 Color_map color p_set.property_mapColor(color).value(); FT_map intensity p_set.property_mapFT(intensity).value(); cout content of point set endl; for (Point_set::const_iterator it p_set.begin(); it ! p_set.end();it) { cout location of point p_set.point(*it) endl with color [ static_castint(color[*it][0]) static_castint(color[*it][1]) static_castint(color[*it][2]) ] endl with intensity intensity[*it] endlendl; } cout endl; } void test2() { Point_set point_set; Color black { 0,0,0 }; bool success false; Color_map color; //自定义属性映射添加颜色属性 //add_property_map函数返回值为std::pairProperty_mapType, bool其中Property_mapType是一个属性映射对象bool表示是否成功添加属性映射 tie(color, success) point_set.add_property_mapColor(color, black); assert(success); FT_map intensity; //自定义属性映射添加强度属性 tie(intensity, success) point_set.add_property_mapFT(intensity, 0); assert(success); point_set.reserve(10); //遍历插入10个点并为每个点设置随机颜色和强度 for (size_t i 0; i 10;i) { Point_set::iterator it point_set.insert(Point_3(double(i),double(i),double(i))); Color c { CGAL::get_default_random().get_int(0,255), CGAL::get_default_random().get_int(0,255), CGAL::get_default_random().get_int(0,255) }; color[*it] c; intensity[*it] rand() / static_castdouble(RAND_MAX); } print_point_set(point_set); //删除强度小于0.5的点 Point_set::iterator it point_set.begin(); while (it!point_set.end()) { if (intensity[*it] 0.5) point_set.remove(it); else it; } print_point_set(point_set); }4 应用CGAL算法大多数CGAL算法允许用户自主选择输入数据结构后续可通过范围与属性映射来访问点数据及相关属性。CGAL::Point_set_3类是一种可提供属性映射的范围类型能够便捷调用各类CGAL算法。 由于点集处理算法会借助命名参数来管理属性映射系统提供了CGAL::Point_set_3::parameters()方法该方法可返回一个命名参数对象内含适配点集对象读写操作的点映射与法向量映射。 此外点集处理模块的所有输入输出函数均已完成重载用户仅需传入CGAL::Point_set_3对象作为参数即可完成调用。4.1 点集处理以下示例展示如何借助点集对象应用CGAL库中的部分算法在球体周边生成点集 通过CGAL::jet_estimate_normals()估算法线 利用CGAL::grid_simplify_point_set()简化点集 借助CGAL::Shape_detection::Efficient_RANSAC检测球体形状/** 这个cpp主要做的事 在一个虚拟球面上生成 10000 个随机点 → 计算每个点的法向量 → 精简点云加速→ 用 RANSAC 算法自动把 “球面” 这个几何形状识别出来 */ #define CGAL_EIGEN3_ENABLED 1 #define CGAL_USE_BASIC_VIEWER #includeCGAL/Exact_predicates_inexact_constructions_kernel.h #includeCGAL/Point_set_3.h #includeCGAL/draw_point_set_3.h #includeCGAL/jet_estimate_normals.h #includeCGAL/grid_simplify_point_set.h #includeCGAL/point_generators_3.h #includeCGAL/Shape_detection/Efficient_RANSAC.h #includefstream #includelimits using namespace std; typedef CGAL::Exact_predicates_inexact_constructions_kernel kernel; typedef kernel::FT FT; typedef kernel::Point_3 Point; typedef kernel::Vector_3 Vector; typedef CGAL::Random_points_on_sphere_3Point Point_generator; typedef CGAL::Point_set_3Point Point_set; typedef CGAL::Shape_detection::Efficient_RANSAC_traitskernel, Point_set, Point_set::Point_map, Point_set::Vector_map Traits; typedef CGAL::Shape_detection::Efficient_RANSACTraits Efficient_ransac; typedef CGAL::Shape_detection::SphereTraits Sphere; typedef CGAL::Shape_detection::PlaneTraits Plane; void test3() { Point_set point_set; //半径为1的球面生成器 Point_generator generator(1); //提前预留内存 size_t nb_pts 10000; point_set.reserve(nb_pts); //在虚拟空间里 “撒” 10000 个点这些点严格落在半径为 1、球心在原点的球面上。 //模拟数据 for (size_t i 0; i nb_pts;i) { point_set.insert(*(generator )); } //添加并评估法向量 //用这个点周围的12个邻居点用数学方法拟合出一个局部曲面曲面的垂直方向就是法向量 point_set.add_normal_map(); CGAL::jet_estimate_normalsCGAL::Sequential_tag( point_set, 12, //每个点用12个邻居来计算法向量 point_set.parameters().degree_fitting(2)); //用2介多项式拟合 //点云精简减少采样点 //原理在空间里画一个边长为 0.1 的立方体网格每个网格中只保留一个 CGAL::grid_simplify_point_set(point_set,0.1); CGAL::draw(point_set); //打印点云属性 vectorstring properties point_set.properties(); cout Properties: endl; for (size_t i 0; i properties.size();i) { cout * properties[i] endl; } //初始化RANSAC Efficient_ransac ransac; ransac.set_input( point_set, //输入点云 point_set.point_map(), //告诉其坐标在哪 point_set.normal_map()); //告诉其法向量在哪 //告诉ransac找求面形状 ransac.add_shape_factorySphere(); //设置RANSAC的参数 Efficient_ransac::Parameters parameters; parameters.probability 0.05; //置信度 parameters.min_points size_t(point_set.size()/3); //一个形状至少要有多少个点 parameters.epsilon 0.01; //点到形状的最大允许距离 parameters.cluster_epsilon 0.5; //聚类距离 parameters.normal_threshold 0.9; //法向量一致性阈值 ransac.detect(parameters); //开始检测 for (shared_ptrEfficient_ransac::Shape shape : ransac.shapes()) if (Sphere* sphere dynamic_castSphere*(shape.get())) cout Detected sphere of center sphere-center() and of radius sphere-radius() endl; }我们使用CGAL basic viewer输出这个球面注意这个basic_viewer默认情况下是不显示点的按V即可显示点。4.2 输入/输出以下示例演示如何读取XYZ格式的点集、对法向量进行归一化与反向处理并将结果以OFF格式保存。#includeCGAL/Exact_predicates_inexact_constructions_kernel.h #includeCGAL/Point_set_3.h #includefstream #includelimits using namespace std; typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef Kernel::FT FT; typedef Kernel::Point_3 Point; typedef Kernel::Vector_3 Vector; typedef CGAL::Point_set_3Point Point_set; void test5() { string fname D:/CGAL_examples/data/points_3/oni.pwn; Point_set point_set; if (!CGAL::IO::read_XYZ(fname,point_set)) { cout cant read input file endl; return; } if (point_set.has_normal_map()) { for (Point_set::iterator it point_set.begin(); it ! point_set.end();it) { Vector n point_set.normal(*it); n -n / sqrt(n*n); point_set.normal(*it) n; } } //输出点云数据OFF格式CGAL::parameters::stream_precision(17)设置输出精度为17位小数 if (!CGAL::IO::write_OFF(normalized_normal.off,point_set,CGAL::parameters::stream_precision(17))) { return; } }PLY格式是需要存储任意数量的点附加属性时的常用选择。CGAL提供了函数read_PLY() 只要实现适配的PLY解析器用户便可读取所需的任意PLY属性。CGAL::Point_set_3对象可载入PLY输入文件中所有可读取的属性。每个PLY属性都会被读取并以名称、类型一致的属性形式进行存储。#includeCGAL/Exact_predicates_inexact_constructions_kernel.h #includeCGAL/Point_set_3.h #includeCGAL/draw_point_set_3.h #includefstream #includelimits using namespace std; typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef Kernel::FT FT; typedef Kernel::Point_3 Point_3; typedef Kernel::Vector_3 Vector_3; typedef CGAL::Point_set_3Point_3 Point_set; void test6(int argc,char** argv) { //读取点云数据PLY格式二进制模式 ifstream f(argc1?argv[1]:D:/CGAL_examples/data/points_3/oni.ply,ios_base::binary); Point_set point_set; if (!CGAL::IO::read_PLY(f,point_set)) { cout cant read the file endl; return; } vectorstring properties point_set.properties(); cout Properties: endl; for (size_t i 0; i properties.size();i) { cout * properties[i] endl; } //查询点云是否有一个叫“label”的整数属性如果有打印它的值 optionalPoint_set::Property_mapint32_t label_prop point_set.property_mapint32_t(label); if (label_prop.has_value()) { cout Point set has an integer\label\property with values: endl; for (Point_set::iterator it point_set.begin(); it ! point_set.end(); it) cout * (*label_prop)[*it] endl; } //输出点云数据PLY格式文本模式 if (argc2strcmp(argv[2],-b)0) { CGAL::IO::write_PLY(out.ply,point_set,CGAL::parameters::stream_precision(17).use_binary_mode(false)); } }4.3 高级用法上一个案例使用的是CGAL::IO::read_PLY(),下面的案例使用的是CGAL::IO::read_points(),它不是简单读点而是同时读取点坐标 法向量并且用 最底层的高级接口。读取一个 OFF 文件 → 把点 法向量 一起读进 Point_set_3#include CGAL/Exact_predicates_inexact_constructions_kernel.h #include CGAL/Point_set_3.h #include CGAL/IO/read_points.h #include fstream #include limits typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef Kernel::FT FT; typedef Kernel::Point_3 Point; typedef Kernel::Vector_3 Vector; typedef CGAL::Point_set_3Point Point_set; int main (int argc, char** argv) { const std::string filename argc 1 ? argv[1] : CGAL::data_file_path(meshes/camel.off); Point_set point_set; point_set.add_normal_map(); // Reading input in OFF format if(!CGAL::IO::read_points(filename, point_set.index_back_inserter(), CGAL::parameters::point_map(point_set.point_push_map()) .normal_map(point_set.normal_push_map()))) { std::cerr Cant read input file std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }read_points()CGAL 最底层的通用点读取函数能读OFF、PLY、XYZ、LAS 等所有格式point_set.index_back_inserter():把读取到的点往 Point_set_3 里追加并自动分配索引point_map(point_set.point_push_map())告诉 read_points()将读到的点坐标存到 point_set 的 point 里面normal_map(point_set.normal_push_map())告诉 read_points()将读到的法向量 存到 point_set 的 normal 里面4.4 绘制点集使用CGAL_USE_BASIC_VIEWER绘制点集#define CGAL_USE_BASIC_VIEWER #include CGAL/Exact_predicates_inexact_constructions_kernel.h #include CGAL/Point_set_3.h #include CGAL/draw_point_set_3.h #include fstream typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef Kernel::FT FT; typedef Kernel::Point_3 Point; typedef Kernel::Vector_3 Vector; typedef CGAL::Point_set_3Point Point_set; void test8(int argc, char** argv) { const std::string filename D:/bun_zipper.ply; Point_set point_set; if (!CGAL::IO::read_point_set(filename, point_set)) { std::cerr Cant read input file filename std::endl; return; } CGAL::draw(point_set); } int main(int argc, char** argv) { test8(argc,argv); return 0; }展示下这是一个兔子的点集左边是cgal basic viewer的展示右图为meshlab打开的ply文件。CGAL_basic_view的使用需要依赖QT才能运行参见Win11下载CGAL6.0并使用VS2022搭载 Qt,可视化CGAL几何对象 _cgal visual-CSDN博客Point_set_3成员函数自查表参见CGAL::Point_set_3 成员函数自查表-CSDN博客