【CGAL】圆柱体检测结果后处理

文章目录

  • 文章说明
  • 算法思路
  • 代码展示
  • 结果展示

文章说明

这篇文章主要介绍,对使用CGAL中的 Region Growing 算法爬取圆柱体的结果进行后处理,以获取位置、轴向量、半径都较为合理的单个圆柱体。

在之前的一篇文章中,使用了open3D生成的标准圆柱体测试了爬取圆柱的代码,结果并不好。结果中标准圆柱体被分了好几部分,也就是说,算法检测到了多个圆柱体,但实际上只有一个。其原因是,CGAL中圆柱体检测只是检测圆柱体的侧面,并不包含上下两个底面。而生成的标准圆柱体是带底面的,这对算法造成了干扰,导致结果不准确。

所以,我基于算法得到的结果,自己写了筛选并合并圆柱体的代码,以得到与真实圆柱体相近的圆柱体及其参数。

用途
这个可以用到物体参数化方面,当我们得到了真实世界类似圆柱体的三维模型,想要将它进行参数化,就可以使用检测圆柱+后处理的方法,来得到模型的参数。

算法思路

1、读取圆柱体结果,包括每个圆柱包含的点个数,半径、中心、轴方向,存储在自定义结构体中。

struct Cylinder_Param {
    uint32_t m_points_num;
    double m_radius;
    Point_3 m_center;
    DT m_direction;
    Cylinder_Param() = default;
    Cylinder_Param(uint32_t points_num, double radius, Point_3& center, DT& direction) :m_points_num(points_num), m_radius(radius), m_center(center), m_direction(direction){}
};

2、计算每个圆柱体的轴向量与其它各个圆柱体轴向量的角度差异,将角度差较小的圆柱体归为一组。在计算角度之前需要调整两个圆柱体的轴向,使其方向保持一致

3、找到包含点数最多的一组圆柱体,合并这一组圆柱体,得到新的单个圆柱体的各个参数。

合并规则
轴向量:各个圆柱体轴向量求平均后归一化
包含点个数:组中的每个圆柱体包含点个数之和
中心:这是可选的,既可以求所有圆柱中心的平均,也可以使用点数最多的圆柱的中心
半径:组中各个圆柱体的平均,或者直接使用包含点数最多的圆柱中心。

规则可以根据自己的实际情况调整。

代码展示

#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/property_map.h>
#include <CGAL/Shape_detection/Region_growing/Point_set.h>
#include <boost/iterator/function_output_iterator.hpp>
#include <CGAL/utils.h>
#include <fstream>

#define src_file_path "cylinder2_dense_normals.ply"

#define result_path "cylinder2_dense_all_result.ply"

#define param_path "cylinder2_dense_param.txt"

#define merge_param_path "cylinder2_dense_merge_param.txt"


// Typedefs.
using Kernel = CGAL::Simple_cartesian<double>;
using FT = Kernel::FT;
using Point_3 = Kernel::Point_3;
using Vector_3 = Kernel::Vector_3;
using Line_3 = Kernel::Line_3;
using Point_set = CGAL::Point_set_3<Point_3>;
using Point_map = typename Point_set::Point_map;
//using Normal_map = typename Point_set::Vector_map;
typedef std::pair<Point_3, Vector_3> Point_with_normal;
typedef std::vector<Point_with_normal> Pwn_vector;
using Neighbor_query = CGAL::Shape_detection::Point_set::K_neighbor_query_for_point_set<Point_set>;
using Region_type = CGAL::Shape_detection::Point_set::Least_squares_cylinder_fit_region_for_point_set<Point_set>;
using Region_growing = CGAL::Shape_detection::Region_growing<Neighbor_query, Region_type>;

// My
typedef CGAL::Direction_3<Kernel> DT;
typedef std::pair<std::vector<uint32_t>, std::vector<uint32_t>> Cylinders_Group; // first保存该组的圆柱索引,second保存总点数

void detec_and_save(Point_set& point_set, Neighbor_query& neighbor_query, Region_type& region_type)
{
    // Create an instance of the region growing class.
    Region_growing region_growing(
        point_set, neighbor_query, region_type);

    // Add maps to get a colored output.
    Point_set::Property_map<unsigned char>
        red = point_set.add_property_map<unsigned char>("red", 0).first,
        green = point_set.add_property_map<unsigned char>("green", 0).first,
        blue = point_set.add_property_map<unsigned char>("blue", 0).first;
    // Run the algorithm.
    //CGAL::Random random;
    std::size_t num_cylinders = 0;

    region_growing.detect(
        boost::make_function_output_iterator(
            [&](const std::pair< Region_type::Primitive, std::vector<typename Point_set::Index> >& region) {
                // Assign a random color to each region.
                const unsigned char r = static_cast<unsigned char>(rand() % 255);
                const unsigned char g = static_cast<unsigned char>(rand() % 255);
                const unsigned char b = static_cast<unsigned char>(rand() % 255);
                for (auto id : region.second) {
                    put(red, id, r);
                    put(green, id, g);
                    put(blue, id, b);
                }
                ++num_cylinders;
            }
        )
    );

    std::cout << "* number of found cylinders: " << num_cylinders << std::endl;
    // Save regions to a file.
    std::ofstream out(result_path);
    CGAL::IO::set_ascii_mode(out);
    out << point_set;
}

void detec_and_print_param(Point_set& point_set, Neighbor_query& neighbor_query, Region_type& region_type)
{
    // Create an instance of the region growing class.
    Region_growing region_growing(
        point_set, neighbor_query, region_type);

    std::vector<typename Region_growing::Primitive_and_region> regions;
    region_growing.detect(std::back_inserter(regions));

    // 打开输出文件
    std::ofstream outFile(param_path);

    for (size_t i = 0; i < regions.size(); i++)
    {
        const auto& primitive_and_region = regions[i];
        //const auto& region = primitive_and_region.second;
        const auto& cylinder_param = primitive_and_region.first;

        // 获取轴的方向
        const auto& dx = cylinder_param.axis.direction().dx();
        const auto& dy = cylinder_param.axis.direction().dy();
        const auto& dz = cylinder_param.axis.direction().dz();

        // 获取圆柱中心位置
        const auto& cx = cylinder_param.axis.point(0).x();
        const auto& cy = cylinder_param.axis.point(0).y();
        const auto& cz = cylinder_param.axis.point(0).z();

        // 获取圆柱半径
        const auto& r = cylinder_param.radius;

        outFile << r << " " << dx << " " << dy << " " << dz << " " << cx << " " << cy << " " << cz << "\n";

        std::cout << "圆柱半径:" << r << std::endl;
        std::cout << "圆柱轴方向:" << dx << ", " << dy << ", " << dz << std::endl;
        std::cout << "圆柱轴方向:" << cx << ", " << cy << ", " << cz << std::endl;
    }

    outFile.close();
}

// 保存合并后的圆柱参数
void save_merge_param(double radius, std::vector<double> center, Eigen::Vector3d direction)
{
    // 打开输出文件
    std::ofstream outFile(merge_param_path);
    outFile << radius << " " << direction[0] << " " << direction[1] << " " << direction[2] << " " << center[0] << " " << center[1] << " " << center[2] << " " << std::endl;

    outFile.close();
}

// 定义一个函数来纠正向量方向
void correctDirection(const Eigen::Vector3d& reference, Eigen::Vector3d& vec) {
    if (reference.dot(vec) < 0) {
        vec = -vec;
    }
}


void detec_and_merge(Point_set& point_set, Neighbor_query& neighbor_query, Region_type& region_type)
{
    
    struct Cylinder_Param {
        uint32_t m_points_num;
        double m_radius;
        Point_3 m_center;
        DT m_direction;
        Cylinder_Param() = default;
        Cylinder_Param(uint32_t points_num, double radius, Point_3& center, DT& direction) :m_points_num(points_num), m_radius(radius), m_center(center), m_direction(direction){}
    };
    
    // Create an instance of the region growing class.
    Region_growing region_growing(
        point_set, neighbor_query, region_type);

    std::vector<typename Region_growing::Primitive_and_region> regions;
    region_growing.detect(std::back_inserter(regions));

    // 获取每个圆柱体参数
    std::vector<Cylinder_Param> cylinders;
    for (size_t i = 0; i < regions.size(); i++)
    {
        const auto& primitive_and_region = regions[i];
        const auto& region = primitive_and_region.second;     // 区域内点索引
        const auto& cylinder_param = primitive_and_region.first;

        uint32_t points_num = region.size();
        double radius = cylinder_param.radius;
        Point_3 center = primitive_and_region.first.axis.point(0);
        DT direction = primitive_and_region.first.axis.direction();
        
        Cylinder_Param c_p(points_num, radius, center, direction);
        cylinders.push_back(c_p);
    }

    // 将圆柱按照轴向角度差与半径分组
    std::vector<Cylinders_Group> cylinders_groups;
    std::vector<bool> region_is_used(regions.size(), false);
    uint32_t max_points_num(0);
    uint32_t max_group_idx(0);
    for (size_t i = 0; i < regions.size(); i++)
    {
        if (region_is_used[i] == true)
            continue;

        region_is_used[i] = true;

        std::vector<uint32_t> cylinders_idx;    // 当前组圆柱索引
        std::vector<uint32_t> cylinders_points_num;
        uint32_t points_num = 0;                // 当前组总点数
        cylinders_idx.push_back(i);
        cylinders_points_num.push_back(cylinders[i].m_points_num);
        points_num += cylinders[i].m_points_num;
        Eigen::Vector3d v1(cylinders[i].m_direction.dx(), cylinders[i].m_direction.dy(), cylinders[i].m_direction.dz());
        for (size_t j = 0; j < regions.size(); j++)
        {
            if (i == j || region_is_used[j] == true)
                continue;

            Eigen::Vector3d v2(cylinders[j].m_direction.dx(), cylinders[j].m_direction.dy(), cylinders[j].m_direction.dz());

            // 这里只使用了圆柱轴的角度条件,未使用半径条件。
            auto dot_result = v1.dot(v2);
            if (abs(dot_result) > 0.87/* diff_angle = 30 */)
            {
                cylinders_idx.push_back(j);
                cylinders_points_num.push_back(cylinders[j].m_points_num);
                points_num += cylinders[j].m_points_num;
                region_is_used[j] = true;
            }
        }

        // 将得到的圆柱组加入 groups
        cylinders_groups.emplace_back(cylinders_idx, cylinders_points_num);

        if (points_num > max_points_num) {
            max_points_num = points_num;
            max_group_idx = i;
        }
    }

    // 得到包含点数最多的圆柱组,将其中包含的圆柱纠正方向并计算平均向量和平均半径,以此作为代表圆柱
    Cylinders_Group real_cylinder_group = cylinders_groups[max_group_idx];

    // 找到纠正方向所用的参考向量(同组中包含点数最多的圆柱的轴方向)
    auto maxIt = std::max_element(real_cylinder_group.second.begin(), real_cylinder_group.second.end());
    int maxIndex = std::distance(real_cylinder_group.second.begin(), maxIt);
    uint32_t c_id = real_cylinder_group.first[maxIndex];
    Eigen::Vector3d reference_direction(cylinders[c_id].m_direction.dx(), cylinders[c_id].m_direction.dy(), cylinders[c_id].m_direction.dz());

    // 初始化平均向量
    Eigen::Vector3d average_vector = Eigen::Vector3d::Zero();
    for (size_t i = 0; i < real_cylinder_group.first.size(); i++)
    {
        
        uint32_t c_id = real_cylinder_group.first[i];

        Eigen::Vector3d c_direction(cylinders[c_id].m_direction.dx(), cylinders[c_id].m_direction.dy(), cylinders[c_id].m_direction.dz());

        Eigen::Vector3d correct_direction = c_direction;
        correctDirection(reference_direction, correct_direction);
        average_vector += correct_direction;
    }

    // 归一化平均向量
    average_vector.normalize();

    // 求真实圆柱组的平均半径与中心
    double radius_sum(0.0);
    std::vector<double> center_sum(3, 0);
    int r_c_size = real_cylinder_group.first.size();
    for (size_t i = 0; i < real_cylinder_group.first.size(); i++)
    {
        uint32_t c_id = real_cylinder_group.first[i];

        radius_sum += cylinders[c_id].m_radius;
        center_sum[0] += cylinders[c_id].m_center.x();
        center_sum[1] += cylinders[c_id].m_center.y();
        center_sum[2] += cylinders[c_id].m_center.z();
    }

    // TODO:这里合并后的中心也用了平均值,还有一个选项是用点数最多的圆柱中心
    for (auto& val : center_sum)
        val /= static_cast<double>(r_c_size);

    double average_radius = radius_sum / r_c_size;

    std::vector<double>& average_center = center_sum;
    save_merge_param(average_radius, average_center, average_vector);
}

int main(int argc, char** argv) {
    // Load ply data either from a local folder or a user-provided file.

    const std::string input_file = src_file_path;

    std::ifstream in(CGAL::data_file_path(input_file));
    CGAL::IO::set_ascii_mode(in);
    //CGAL::IO::set_binary_mode(in);
    if (!in) {
        std::cerr << "ERROR: cannot read the input file!" << std::endl;
        return EXIT_FAILURE;
    }
    Point_set point_set;
    in >> point_set;
    in.close();
    std::cout << "* number of input points: " << point_set.size() << std::endl;
    //assert(!is_default_input || point_set.size() == 1813);
    assert(point_set.has_normal_map()); // input should have normals
    // Default parameter values for the data file cuble.pwn.
    const std::size_t k = 10;
    const FT          max_distance = FT(1) / FT(500);
    const FT          max_angle = FT(60);
    const std::size_t min_region_size = 200;
    // Create instances of the classes Neighbor_query and Region_type.
    Neighbor_query neighbor_query = CGAL::Shape_detection::Point_set::make_k_neighbor_query(point_set, CGAL::parameters::k_neighbors(k));
    Region_type region_type = CGAL::Shape_detection::Point_set::make_least_squares_cylinder_fit_region(
        point_set,
        CGAL::parameters::
        maximum_distance(max_distance).
        maximum_angle(max_angle).
        minimum_region_size(min_region_size));

    // 检测圆柱,对属于不同圆柱的点赋予不同颜色
    //detec_and_save(point_set, neighbor_query, region_type);

    // 检测圆柱,输出每个圆柱的参数
    //detec_and_print_param(point_set, neighbor_query, region_type);

    // 检测 & 合并
    detec_and_merge(point_set, neighbor_query, region_type);

    return EXIT_SUCCESS;
}

如果还没有圆柱的检测结果,需要先运行
detec_and_print_param(point_set, neighbor_query, region_type);

结果展示

说明
初始圆柱是我用open3D生成的,半径为0.1,高度为0.5,轴向量与z轴平行。

在上一篇文章的最后,展示了带底面的圆柱的检测结果。
在这里插入图片描述
我在blender中使用python脚本画出了检测到的圆柱的真实样貌,检测到的圆柱参数如下:

number of input points: 10000
圆柱半径:0.427617
圆柱轴方向:0.0883206, 0.996081, 0.00471564
圆柱中心:0.0386407, 0.00583636, -0.176611
圆柱半径:0.0984349
圆柱轴方向:0.173551, -0.0677968, 0.982489
圆柱中心:-0.00130926, -0.000494991, 0.2048
圆柱半径:0.08501
圆柱轴方向:0.999531, 0.0304522, -0.00314423
圆柱中心:0.000517605, -0.000217548, -0.179827
圆柱半径:0.0551355
圆柱轴方向:0.133879, 0.989484, 0.0547502
圆柱中心:-0.0453276, -0.00894591, 0.298277
圆柱半径:0.099298
圆柱轴方向:0.00462129, 0.00957155, 0.999944
圆柱中心:-2.68408e-05, 1.09269e-05, -0.0189

将其存放到txt文档中,在blender中新建工程,使用python读取后创建相应圆柱体。
在这里插入图片描述

执行圆柱体合并后的结果:

得到的圆柱参数

半径: 0.0988664
轴向量:0.0894765 -0.0292402 0.99556
中心:-0.000668052 -0.000242032 0.0929501

可以看到合并后得到的圆柱,与原始圆柱的参数基本吻合。
其中,半径≈0.01,轴向量与Z轴基本平行

在blender中画出合并后的圆柱
在这里插入图片描述

  • 左图为合并后的结果与之前检测到的圆柱一起堆放,黄色线为合并结果的轮廓。
  • 右图为合并结果单独放置。

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

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

相关文章

560亿美元薪酬获批!马斯克:特斯拉未来市值将不止5万亿美元

KlipC报道&#xff1a;6月13日&#xff0c;美国电动汽车制造商特斯拉公司举办年度股东大会&#xff0c;其CEO马斯克对特斯拉生产销售、未来车型计划和在无人驾驶能等领域的发展进行了报告。此外&#xff0c;特斯拉股东批准了马斯克的560亿美元薪酬方案以及特斯拉总部迁至得克萨…

基于Verilog表达的FSM状态机

基于Verilog表达的FSM状态机 1 FSM1.1 Intro1.2 Why FSM?1.3 How to do 在这里聚焦基于Verilog的三段式状态机编程&#xff1b; 1 FSM 1.1 Intro 状态机是一种代码实现功能的范式&#xff1b;一切皆可状态机&#xff1b; 状态机编程四要素&#xff1a;– 1.状态State&#…

深入理解计算机系统 家庭作业6.22

每条磁道存 位 有r-xr条磁道 二者相乘就是我们要求的容量) 所以最大值x0.5

java-多态数组的多态参数

介绍 代码 employer父类 package hansunping;public class employer {private String name;private double salary;public employer(String name,double salary) {this.namename;this.salarysalary;// TODO Auto-generated constructor stub}public double getsalary() {retu…

GlusterFS企业分布式存储

GlusterFS 分布式文件系统代表-nfs常见分布式存储Gluster存储基础梳理GlusterFS 适合大文件还是小文件存储&#xff1f; 应用场景术语Trusted Storage PoolBrickVolumes Glusterfs整体工作流程-数据访问流程GlusterFS客户端访问流程 GlusterFS常用命令部署 GlusterFS 群集准备环…

职称申报总是不通过的五大原因,竟然在这里

职称评审每年都是有人通过&#xff0c;有人不能通过&#xff0c;而且有的人每年申报&#xff0c;但还是不通过&#xff0c;不通过其实都是有原因&#xff0c;抛开运气&#xff0c;有的人确实运气不好&#xff0c;不通过&#xff0c;这种没办法&#xff0c;但是大部分人申报没有…

Spring Cloud Gateway 详解:构建高效的API网关解决方案

Spring Cloud Gateway 详解&#xff1a;构建高效的API网关解决方案 Spring Cloud Gateway 是 Spring Cloud 生态系统中用于构建 API 网关的核心组件。它基于 Spring WebFlux 构建&#xff0c;旨在提供简单且有效的方式来路由和增强 API 请求。以下是 Spring Cloud Gateway 的详…

【Oracle篇】rman时间点异机恢复:从RAC环境到单机测试环境的转移(第六篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

VRRP跟踪接口及认证(华为)

#交换设备 VRRP跟踪接口及认证 一、相关概念 1.VRRP跟踪接口 当 VRRP 的 Master 设备的上行接口出现问题, 而 Master 设备一直保持 Active 状态&#xff0c;那么就会导致网络出现中断&#xff0c;所以必须要使得 VRRP 的运行状态和上行接口能够关联。在配置了 VRRP 元余的网…

经典的网站系统架构(入门级)

从开发到部署&#xff0c;从用户访问到底层数据库&#xff0c;介绍搭建网站系统的经典架构的10个核心部分。 &#xff08;图转自bytebytego&#xff0c;翻译整理by dogstar&#xff09; 1、使用Git管理和协同源代码&#xff0c;通过CI/CD或Git的Webhook方式自动同步更新部署到服…

AI赋能数据安全体系化落地,出席网安标委2024年第一次标准周“数据安全标准与能力建设研讨会”

6月13日&#xff0c;全国网络安全标准化技术委员会&#xff08;以下简称“网安标委”&#xff09;2024年第一次标准周“数据安全标准与能力建设研讨会”在南昌召开。中央网信办网络数据管理局范雪炜、工业和信息化部网络安全管理局周睿康、国家信息中心外网办安全管理处处长罗海…

红酒保存中的氧气管理:适度接触与避免过度氧化

在保存云仓酒庄雷盛红酒的过程中&#xff0c;我们不得不面对一个微妙的问题&#xff1a;氧气管理。氧气&#xff0c;这个我们生活中无处不在的气体&#xff0c;对于红酒的保存却有着至关重要的影响。适度接触氧气对红酒的陈年过程和品质维护具有积极作用&#xff0c;然而过度氧…

【APP移动端自动化测试】第四节.元素操作的API

文章目录 前言一、点击&输入&清空操作 1.1 点击元素 1.2 输入&清空元素二、获取文本内容&位置&大小操作 2.1 获取文本内容 2.2 获取位置&大小三、根据属性名获取属性值操作四、滑动和拖拽操作 4.1 _swipe 4.2 _scroll …

Threejs-12、场景的线性雾和指数雾

1、创建场景雾 //创建场景雾 scene.fog new THREE.Fog(0x999999,0.1,50);2、创建场景指数雾 scene.fog new THREE.FogExp2(0x999999,0.05);3、 设置场景背景颜色 scene.background new THREE.Color(0x999999);完整代码 <script setup> // 导入threejs import * as…

string类小贴士:让你的C++字符串处理更高效

目录 ​编辑 一、为什么要学习string类 1.1 C语言中的字符串 1.2 面试题 &#x1f333;字符串相加https://leetcode.cn/problems/add-strings/description/ 二、标准库中的string类 2.1 string类 2.2 string类的常用接口说明 1. string类对象的常见构造 2. string类对…

精准定位,智慧提纯:高级数据提取策略

在数据驱动的时代&#xff0c;高级数据提取策略成为企业决策、科学研究以及各类项目成功的关键。数据提取&#xff0c;不仅仅是简单地收集信息&#xff0c;而是需要精准定位目标数据&#xff0c;并通过智慧提纯方法&#xff0c;从海量数据中提取出有价值、有深度的信息。本文将…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第40课-实时订阅后端数据

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第40课-实时订阅后端数据 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引…

程序员要学会偷懒--excel转建表语句

如下需求&#xff1a;一个复杂的统战系统&#xff0c;用户信息字段总共有26个sheet&#xff0c;54张小表&#xff0c;没有什么复杂的业务逻辑&#xff0c;纯增删改查。字段目测在1000个以上。建表54张。领导交待了一句这周末完成&#xff0c;我&#xff1f;&#xff1f;&#x…

【数组】【快慢双指针】删除有序数组中的重复项+移除元素+删除有序数组中的重复项II

今天趁热打铁&#xff0c;接着刷了几道标签是【数组】的题&#xff0c;基本都是双指针就能解决。 1、删除有序数组中的重复项 该题对应力扣网址 class Solution { public:int removeDuplicates(vector<int>& nums) {int i,j;int count 0;int n nums.size();nums.…

vue3+ Element-Plus 点击勾选框往input中动态添加多个tag

实现效果&#xff1a; template&#xff1a; <!--产品白名单--><div class"con-item" v-if"current 0"><el-form-item label"平台名称"><div class"contaion" click"onclick"><!-- 生成的标签 …