【CGAL】Region_Growing 检测平面并保存

news2024/11/16 18:04:48

目录

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

说明

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

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

环境

  • Win10/Win11
  • VS2022
  • CGAL 5.6.1

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

一、算法原理

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

算法流程

  1. 选取种子点,若不指定种子点,则按索引顺序选取。
  2. 使用种子点创建平面,平面法向量为该种子点法线
  3. 搜索种子点(包含点)的邻居,可以按球形范围搜索,也可以按邻居个数搜索。
  4. 计算邻居点法线与平面法线角度差异,计算邻居点到平面距离
  5. 将满足条件的邻居点加入平面,更新平面法向量。
  6. 在包含点中重复3-5。
  7. 如果区域内点数不再增加,并且还有未分类的点,则在未分类点中重新选取种子点。

参数

  • 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生成。也可以在这里下载。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OSPF状态机+SPF算法

OSPF状态机 1.点到点网络类型 down-->init-->(前提为可以建立邻接)exstart——>exchange-->若查看邻接的DBD 目录后发现不用进行LSA 直接进入ful。若查看后需要进行查询、应答先进入loading&#xff0c;在查询应答完后再进入 fuIl: 2.MA网络类型 down --&g…

269 基于matlab的四连杆机构动力学参数计算

基于matlab的四连杆机构动力学参数计算。将抽油机简化为4连杆机构&#xff0c;仿真出悬点的位移、速度、加速度、扭矩因数、游梁转角等参数&#xff0c;并绘出图形。程序已调通&#xff0c;可直接运行。 269机构动力学参数计算 位移、速度、加速度 - 小红书 (xiaohongshu.com)

煤炉Mecari防封攻略:如何降低封店概率?

不少卖家反馈&#xff0c;Mecari不少封店情况存在&#xff0c;今天就来整理一下常见原因及解决方法。 一、煤炉被封号的原因如下 1、IP不稳定&#xff1a;一定不要多次切换线路&#xff0c;IP跳动频繁&#xff0c;IP不纯净&#xff0c;多人共享&#xff0c;均会导致账号活动异…

linux开发之设备树六、linux下pinctrl子系统管理设置pin管脚的复用功能(一般原厂提供)

客户端的编写格式是固定的&#xff0c;不管哪家原厂的处理器&#xff0c;格式都是一样的 对于服务端部分是原厂提供&#xff0c;各个芯片肯定就不一样了&#xff0c;主要在于编写的格式不同 pinctrl客户端写法 使用pinctrl设置管脚复用 在kernel/arch/arm64/boot/dts/rockchi…

2022年全国职业院校技能大赛高职组“信息安全管理与评估”赛项第三阶段任务书

第三阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛-第三阶段试题。根据信息安全管理与评估项目技术文件要求&#xff0c;第三阶段为夺旗挑战CTF&#xff08;网络安全渗透&#xff09;。 本次比赛时间为180分钟。 介绍 夺旗挑战赛&#xff08;CTF&#xff09;的目标…

蓝桥杯第17135题 不完整的算式 C++ Java Python

目录 题目 思路和解题方法 步骤 1&#xff1a;识别缺失的部分 步骤 2&#xff1a;根据已知条件计算或推断 步骤 3&#xff1a;处理特殊情况和验证 c 代码 Java 版本 Python 版本&#xff08;仅供参考&#xff09; 代码和解题细节&#xff1a; 题目 题目链接&#xff…

科迅图书馆云平台 WebCloud.asmx SQL注入致RCE漏洞复现

0x01 产品简介 科迅图书馆云平台又称集群式图书管理系统是采用B/S架构的垂直管理模式,管理系统下设城市集群式图书馆管理系统+电子阅览室+门户网站,不仅实现了总馆对分馆的在线管理,而且实现了资源共享和建设图书馆联合服务体系,可以方便读者在图书馆门户网站或者其中任何…

CSS绘制圆弧

css绘制如图的圆弧&#xff1a; 这种矩形弧形的效果中&#xff0c;弧形的效果一般是由一条曲线拉伸出来的&#xff0c;这条曲线往往是属于一个椭圆的&#xff0c;所以可以绘制一个椭圆&#xff0c;截取部分可视区域实现效果。 <style> .wrapper{width: 400px;height: 60…

工业相机识别电路板元器件:彩色与黑白的区别

工业相机用于识别电路板上的元器件时&#xff0c;选择彩色相机或黑白相机取决于具体应用需求和条件。彩色相机能提供更丰富的信息&#xff0c;但处理复杂度较高&#xff1b;黑白相机则在处理速度和精度上具有优势。理解它们的区别和各自的优缺点&#xff0c;有助于在具体项目中…

PromptIR论文阅读笔记

MZUAI和IIAI在NIPS2023上的一篇论文&#xff0c;用prompt来编码degradation&#xff0c;然后用来guide restoration network&#xff0c;使得模型能够泛化到不同degradation types and levels&#xff0c;也就是说是一个模型一次训练能够应对多种degradation的unified model。文…

Satellite Stereo Pipeline学习

1.在Anaconda某个环境中安装s2p pip install s2p 2.在Ubuntu系统中安装s2p源代码 git clone https://github.com/centreborelli/s2p.git --recursive cd s2p pip install -e ".[test]" 3.在s2p中进行make all处理 中间会有很多情况&#xff0c;基本上哪个包出问题…

【机器学习】Adaboost: 强化弱学习器的自适应提升方法

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Adaboost: 强化弱学习器的自适应提升方法引言Adaboost基础概念弱学习器与强学习…

Docker 图形化界面管理工具 Portainer | 让你更轻松的管理 Docker

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 Portainer 是一个 Docker 图形化管理工具&#xff0c;可以通过 Web UI 轻松的管理容器、镜像、网络、卷。与 Dockge 相比功能更加的完善&#xff0c;同时上手难度也更大一些 Portainer 分为社区版和商业版…

实战16:基于apriori关联挖掘FP-growth算法挖掘关联规则的手机销售分析-代码+数据

直接看视频演示: 基于apriori关联挖掘关联规则的手机销售分析与优化策略 直接看结果: 这是数据展示: 挖掘结果展示: 数据分析展示:

el-table中的信息数据过长 :show-overflow-tooltip=‘true‘**

可以在 el-table-column中添加 :show-overflow-tooltip‘true’

【机器学习】AI大模型的探索—浅谈ChatGPT及其工作原理

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 &#x1f4da;介绍ChatGPT 1.1 什么是ChatGPT 1.2 ChatGPT的应用场景 &#x1f4a1;基础概念 1. 人工智能和机器学习 1.1 人工智能&#xff08;AI&#xff09;简介 1.2 机器学习&#xff08;ML&#xff09;简…

RAG技术探索

什么是RAG 1 RAG原理 RAG&#xff08;Retrieval Augmented Generation, 检索增强生成&#xff09;&#xff0c;即LLM在回答问题或生成文本时&#xff0c;先会从大量文档中检索出相关的信息&#xff0c;然后基于这些信息生成回答或文本&#xff0c;从而提高预测质量。RAG模型尤…

生态融合促发展 YashanDB与丰图科技完成兼容性认证

近日&#xff0c;深圳计算科学研究院崖山数据库系统YashanDB V23与丰图科技智域城市数字孪生平台顺利完成兼容性互认证。经严格测试&#xff0c;双方产品完全兼容&#xff0c;稳定运行&#xff0c;充分满足企事业单位在高性能、高可用性、高稳定性及高可控性方面的核心需求&…

Redis 和 Mysql 如何保证两者数据一致性

文章目录 概述解决方案消息队列异步重试 基于 RocketMQ 的可靠性消息通信&#xff0c;来实现最终一致Canal 组件&#xff0c;监控 Mysql 中 binlog 的日志&#xff0c;把更新后的数据同步到 Redis 里面延时双删弱一致性和强一致性Canal详解 概述 在分布式系统中&#xff0c;保…

YoloV8改进策略:卷积篇|基于PConv的二次创新|附结构图|性能和精度得到大幅度提高(独家原创)

摘要 在PConv的基础上做了二次创新,创新后的模型不仅在精度和速度上有了质的提升,还可以支持Stride为2的降采样。 改进方法简单高效,需要发论文的同学不要错过! 论文指导 PConv在论文中的描述 论文: 下面我们展示了可以通过利用特征图的冗余来进一步优化成本。如图3所…