【CGAL】Region_Growing 检测平面并保存

目录

  • 说明
  • 一、算法原理
  • 二、代码展示
  • 三、结果展示

说明

本篇博客主要介绍CGAL库中使用Region_Growing算法检测平面的算法原理、代码以及最后展示结果。其中,代码部分在CGAL官方库中提供了例子。我在其中做了一些修改,使其可以读取PLY类型的点云文件,并在检测到平面后,为属于同一个平面的点赋予相同的颜色。最后再保存为PLY文件以方便我们查看检测结果。

在CGAL中,Region_Growing算法不仅可以用来检测平面,还可以检测圆、直线、圆锥等基本的几何。除此之外,用户也可以自定义模型并使用算法检测。

环境

  • Win10/Win11
  • VS2022
  • CGAL 5.6.1

上述环境仅为运行此代码时的电脑环境。

一、算法原理

Region_Growing算法应用“贪心”的思想,利用种子点与邻居点的曲率差异来筛选点。在平面检测时,除了利用曲率差异,还会使用当前拟合平面与当前点的距离作为评判标准。具体看算法流程。

算法流程

  1. 选取种子点,若不指定种子点,则按索引顺序选取。
  2. 搜索种子点的邻居,可以按球形范围搜索,也可以按邻居个数搜索。
  3. 包含满足条件的邻居点。
  4. 在包含点中重复1-3。
  5. 如果区域内点数不再增加,并且还有未分类的点,则在未分类点中重新选取种子点。

参数

  • search_sphere_radius - 球形邻域搜索半径
  • max_distance_to_plane - 当前点到拟合平面的最大距离
  • max_accepted_angle - 当前点与拟合平面的角度阈值
  • min_region_size - 形成一个平面的最小点数

二、代码展示

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/property_map.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing.h>
#include <CGAL/Shape_detection/Region_growing/Point_set.h>
#include <CGAL/Polygonal_surface_reconstruction.h>
#include <CGAL/IO/write_ply_points.h>

#include <fstream>
#include <CGAL/Timer.h>
#include <boost/range/irange.hpp>
typedef CGAL::Exact_predicates_inexact_constructions_kernel        Kernel;
typedef Kernel::FT       FT;
typedef Kernel::Point_3         Point;
typedef Kernel::Vector_3 Vector;
// Point with normal, and plane index.
typedef boost::tuple<Point, Vector, int> PNI;
typedef std::vector<PNI> Point_vector;
typedef CGAL::Nth_of_tuple_property_map<0, PNI>        Point_map;
typedef CGAL::Nth_of_tuple_property_map<1, PNI>        Normal_map;
typedef CGAL::Nth_of_tuple_property_map<2, PNI>        Plane_index_map;
using Point_map_region_growing = CGAL::Compose_property_map<CGAL::Random_access_property_map<Point_vector>, Point_map >;
using Normal_map_region_growing = CGAL::Compose_property_map<CGAL::Random_access_property_map<Point_vector>, Normal_map >;
using Region_type = CGAL::Shape_detection::Point_set::Least_squares_plane_fit_region<Kernel, std::size_t, Point_map_region_growing, Normal_map_region_growing>;
using Neighbor_query = CGAL::Shape_detection::Point_set::Sphere_neighbor_query<Kernel, std::size_t, Point_map_region_growing>;
using Region_growing = CGAL::Shape_detection::Region_growing<Neighbor_query, Region_type>;

//----------------------------------save_points_with_color_about-------------------------------------------
typedef std::array<unsigned char, 4> Color;
typedef std::tuple<Point, Vector, Color> PNC;
typedef CGAL::Nth_of_tuple_property_map<0, PNC> Save_Point_map;
typedef CGAL::Nth_of_tuple_property_map<1, PNC> Save_Normal_map;
typedef CGAL::Nth_of_tuple_property_map<2, PNC> Save_Color_map;
//----------------------------------save_points_with_color_about-------------------------------------------

// Define how a color should be stored
namespace CGAL {
    template< class F >
    struct Output_rep< ::Color, F > {
        const ::Color& c;
        static const bool is_specialized = true;
        Output_rep(const ::Color& c) : c(c)
        { }
        std::ostream& operator() (std::ostream& out) const
        {
            if (IO::is_ascii(out))
                out << int(c[0]) << " " << int(c[1]) << " " << int(c[2]) << " " << int(c[3]);
            else
                out.write(reinterpret_cast<const char*>(&c), sizeof(c));
            return out;
        }
    };
} // namespace CGAL

int main(int argc, char* argv[])
{
    Point_vector points;
    // Load point set from a file.
    const std::string input_file = "cube_point_cloud.ply";
    std::ifstream input_stream(input_file.c_str());
    if (input_stream.fail()) {
        std::cerr << "Failed open file \'" << input_file << "\'" << std::endl;
        return EXIT_FAILURE;
    }
    input_stream.close();
    std::cout << "Loading point cloud: " << input_file << "...";
    CGAL::Timer t;
    t.start();
    if (!CGAL::IO::read_points(input_file.c_str(), std::back_inserter(points),
        CGAL::parameters::point_map(Point_map()).normal_map(Normal_map()))) {
        std::cerr << "Error: cannot read file " << input_file << std::endl;
        return EXIT_FAILURE;
    }
    else
        std::cout << " Done. " << points.size() << " points. Time: "
        << t.time() << " sec." << std::endl;
    // Shape detection.
    // Default parameter values for the data file cube.pwn.
    const FT          search_sphere_radius = FT(2) / FT(100);
    const std::size_t k = 12;
    const FT          max_distance_to_plane = FT(0.5) / FT(100);
    const FT          max_accepted_angle = FT(30);
    const std::size_t min_region_size = 500;
    Point_map_region_growing point_map_rg(CGAL::make_random_access_property_map(points));
    Normal_map_region_growing normal_map_rg(CGAL::make_random_access_property_map(points));
    // Create instances of the classes Neighbor_query and Region_type.
    Neighbor_query neighbor_query(
        boost::irange<std::size_t>(0, points.size()), CGAL::parameters::sphere_radius(search_sphere_radius).point_map(point_map_rg));
    //Neighbor_query neighbor_query(
    //    boost::irange<std::size_t>(0, points.size()), CGAL::parameters::k_neighbors(k).point_map(point_map_rg));
    Region_type region_type(
        CGAL::parameters::
        maximum_distance(max_distance_to_plane).
        maximum_angle(max_accepted_angle).
        minimum_region_size(min_region_size).
        point_map(point_map_rg).
        normal_map(normal_map_rg));
    // Create an instance of the region growing class.
    Region_growing region_growing(
        boost::irange<std::size_t>(0, points.size()), neighbor_query, region_type);
    std::cout << "Extracting planes...";
    std::vector<typename Region_growing::Primitive_and_region> regions;
    t.reset();
    region_growing.detect(std::back_inserter(regions));
    std::cout << " Done. " << regions.size() << " planes extracted. Time: "
        << t.time() << " sec." << std::endl;
    // Stores the plane index of each point as the third element of the tuple.
    for (std::size_t i = 0; i < points.size(); ++i)
        // Uses the get function from the property map that accesses the 3rd element of the tuple.
        points[i].get<2>() = static_cast<int>(get(region_growing.region_map(), i));

    // 随机生成颜色(要保证颜色种类大于提取的平面个数)
    std::vector<Color> rand_colors;
    for (size_t i = 0; i < regions.size() + 1; i++)
    {
        Color p_color = {
            static_cast<unsigned char>(rand() % 256),
            static_cast<unsigned char>(rand() % 256),
            static_cast<unsigned char>(rand() % 256), 255 };

        rand_colors.push_back(p_color);
    }

    std::vector<PNC> points_with_color;
    // 为所属平面相同的点赋予相同的颜色
    for (std::size_t i = 0; i < points.size(); i++)
    {
        // 获取单个点坐标
        Point point = points[i].get<0>();

        // 获得单个点法线
        Vector normal = points[i].get<1>();

        // 获取点对应的颜色,所属平面相同的点颜色相同
        int plane_index = points[i].get<2>();

        Color p_color;
        if (plane_index == -1)    // 未分配平面的点为白色
            p_color = { 255, 255, 255, 255 };
        else
            p_color = rand_colors[plane_index];

        points_with_color.push_back(std::make_tuple(point, normal, p_color));
    }

    std::ofstream f("result.ply", std::ios::binary);
    CGAL::IO::set_binary_mode(f); // The PLY file will be written in the binary format
    CGAL::IO::write_PLY_with_properties(f, points_with_color,
        CGAL::make_ply_point_writer(Save_Point_map()),
        CGAL::make_ply_normal_writer(Save_Normal_map()),
        std::make_tuple(Save_Color_map(),
            CGAL::IO::PLY_property<unsigned char>("red"),
            CGAL::IO::PLY_property<unsigned char>("green"),
            CGAL::IO::PLY_property<unsigned char>("blue"),
            CGAL::IO::PLY_property<unsigned char>("alpha")));

    return EXIT_SUCCESS;
}

三、结果展示

源文件:由点云组成的立方体
由点云组成的立方体
平面检测结果:
在这里插入图片描述

该立方体点云文件可以使用open3D生成。也可以在这里下载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/651348.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解析边缘计算网关的优势-天拓四方

随着信息化、智能化浪潮的持续推进&#xff0c;计算技术正以前所未有的速度发展&#xff0c;而边缘计算网关作为其中的重要一环&#xff0c;以其独特的优势正在逐步改变我们的生活方式和工作模式。本文将详细解析边缘计算网关的优势。 首先&#xff0c;边缘计算网关具有显著的…

Python 文件操作指南:使用 open 和 with open 实现高效读写

&#x1f340; 前言 博客地址&#xff1a; CSDN&#xff1a;https://blog.csdn.net/powerbiubiu &#x1f44b; 简介 本系列文章主要分享文件操作&#xff0c;了解如何使用 Python 进行文件的读写操作&#xff0c;介绍常见文件格式的读取和写入方法&#xff0c;包括TXT、 CS…

开源博客项目Blog .NET Core源码学习(27:App.Hosting项目结构分析-15)

本文学习并分析App.Hosting项目中后台管理页面的角色管理页面。   角色管理页面用于显示、检索、新建、编辑、删除角色数据同时支持按角色分配菜单权限&#xff0c;以便按角色控制后台管理页面的菜单访问权限。角色管理页面附带一新建及编辑页面&#xff0c;以支撑新建和编辑…

【MATLAB源码-第215期】基于matlab的8PSK调制CMA均衡和RLS-CMA均衡对比仿真,对比星座图和ISI。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 CMA算法&#xff08;恒模算法&#xff09; CMA&#xff08;Constant Modulus Algorithm&#xff0c;恒模算法&#xff09;是一种自适应盲均衡算法&#xff0c;主要用于消除信道对信号的码间干扰&#xff08;ISI&#xff09;…

黑灰产攻防对抗——中睿天下代理秒拨IP防护研究

01网络攻击代理现状 随着攻击者防溯源和绕过防护意识的加强&#xff0c;攻击者进行网络攻击时常使用代理IP进行攻击&#xff0c;防止非法攻击时被追踪到身份信息。目前常见的代理方式有socks代理、HTTP/HTTPS代理、VPN、秒拨等。 对于企业来说&#xff0c;通过SOAR类平台可实现…

【爬虫软件】2024最新短视频评论区抓取工具

一、背景说明 1.0 采集目标 采集DOU音评论数据对引流截流和获客有很多好处。首先&#xff0c;通过分析DOU音评论数据&#xff0c;我们可以更好地了解用户对于产品或内容的喜好和需求&#xff0c;从而调整营销策略&#xff0c;吸引更多用户关注和点击。其次&#xff0c;评论数据…

Delft3D水动力-富营养化模型(水质模型)教程

原文链接&#xff1a;Delft3D水动力-富营养化模型&#xff08;水质模型&#xff09;教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247605459&idx5&sn105e94f09f0589172835ce8204519971&chksmfa821d34cdf59422b4f6c39b243373a23836d79841a1fcd19f9…

Java EE-Spring AOP 面向切面编程

Spring AOP https://www.cnblogs.com/joy99/p/10941543.html 超级详细版&#xff1a;Chapter 6. 使用Spring进行面向切面编程&#xff08;AOP&#xff09; AOP 原理 面向切面 ( Aspect Orient Programming ) 面向切面编程&#xff0c;是面向对象编程(OOP) 的一种补充。 在…

【海外app制作】海外短剧app系统搭建部署指南:多平台推广策略助力业务拓展,支付接口搭建部署。

海外短剧系统&#xff0c;顾名思义就是短剧系统海外版本。 教你三步完成一个海外短剧平台系统 首先我们看一下海外系统前端界面 首页就是我们展示剧的地方&#xff0c;可以根据地区不同&#xff0c;一键切换地区&#xff0c;比如中文切换为英文。那么界面语言就会变。可以设置…

字典推导式

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 使用字典推导式可以快速生成一个字典&#xff0c;它的表现形式和列表推导式类似。例如&#xff0c;我们可以使用下面的代码生成一个包含4个随机数的字…

DBeaver怎么将编辑栏内容放大

1、窗口–》编辑器–》放大 2、ctrl 3、页面结果展示

PS系统教学01

在前面几节内容基本介绍了PS的基本作用&#xff0c;简单的对PS中的某些基础功能进行介绍应用。 接下来我们进行系统的分享。 本次分享内容 基础的视图操作 接下来我们是对于PS工作区域的每个图标工具进行详细的分享 抓手工具缩放工具 这个图标是将工具栏由一列变成两列 一…

期权开户要多久的时间?能当天开好交易吗?

今天期权懂带你了解期权开户要多久的时间&#xff1f;能当天开好交易吗&#xff1f;期权&#xff0c;作为一种金融衍生品&#xff0c;它赋予了持有人在未来某个时间内购买或出售特定资产的权利&#xff0c;近年来在全球范围内得到了广泛的关注和应用。 期权开户要多久的时间&am…

Python操作MySQL数据库的工具--sqlalchemy

文章目录 一、pymysql和sqlalchemy的区别二、sqlalchemy的详细使用1.安装库2.核心思想3.整体思路4.sqlalchemy需要连接数据库5.使用步骤1.手动提前创建数据库2.使用代码创建数据表3.用代码操作数据表3.1 增加数据3.2 查询数据3.3 删除数据3.4 修改数据 一、pymysql和sqlalchemy…

服务高峰期gc,导致服务不可用

随着应用程序的复杂性和负载的不断增加&#xff0c;对JVM进行调优&#xff0c;也是保障系统稳定性的一个重要方向。 需要注意&#xff0c;调优并非首选方案&#xff0c;一般来说解决性能问题还是要从应用程序本身入手&#xff08;业务日志&#xff0c;慢请求等&#xff09;&am…

Python 机器学习 基础 之 模型评估与改进 【网格搜素】的简单说明

Python 机器学习 基础 之 模型评估与改进 【网格搜素】的简单说明 目录 Python 机器学习 基础 之 模型评估与改进 【网格搜素】的简单说明 一、简单介绍 二、网格搜索 1、简单网格搜索 2、参数过拟合的风险与验证集 3、带交叉验证的网格搜索 附录 一、参考文献 一、简单…

【Web】2024 京麒CTF ezjvav题解

目录 step 0 step 1 step 2 EXP1 EXP2 step 0 进来是一个登录框 admin/admin成功登录 访问./source jwt伪造 带着伪造的jwt访问./source&#xff0c;拿到题目源码jar包 step 1 pom依赖有spring、fj、rome 反序列化入口在./Jsrc路由 有两层waf&#xff0c;一个是明…

深入了解python的关键字“break”与循环退出策略

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、break关键字的基础理解与用途 二、通过案例理解break的应用 三、总结break在编程中的实…

Linux中ftp配置

一、ftp协议 1、端口 ftp默认使用20、21端口 20端口用于建立数据连接 21端口用于建立控制连接 2、ftp数据连接模式 主动模式&#xff1a;服务器主动发起数据连接 被动模式&#xff1a;服务器被动等待数据连接 二、ftp安装 yum install -y vsftpd #---下…

django-celery-beat自动调度异步任务

Celery是一个简单、灵活且可靠的分布式系统&#xff0c;专门用于处理大量消息的实时任务调度。它支持使用任务队列的方式在分布的机器、进程、线程上执行任务调度。Celery不仅支持异步任务&#xff08;如发送邮件、文件上传、图像处理等耗时操作&#xff09;&#xff0c;还支持…