PCL拟合空间3D圆周 fit3DCircle

news2025/4/18 8:44:39

PCL版本 1.15.0

main.cpp

#include<vector>
#include<iostream>
#include <array>
#include <string>
#include <windows.h>
#include <omp.h>
#include <charconv>  // C++17
#include <cstdlib>
#include<chrono>
#include"FitCircle3D.h"

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/sample_consensus/sac_model_circle3d.h>
#include <pcl/segmentation/sac_segmentation.h>

int main() {
	// 生成一个3D圆的点云(圆心(0,0,0),半径0.5,法向量(0,0,1))
	pcl::PointXYZ center(0, 0, 0);
	Eigen::Vector3f normal0(0, 0, 1);
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = generate3DCirclePoints(0.5, center, normal0, 200, 0, 0.25 * M_PI, 0.01);

	// 拟合并可视化
	pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
	pcl::ModelCoefficients::Ptr coefficients = fit3DCircle(cloud, inliers);

	center = pcl::PointXYZ(coefficients->values[0], coefficients->values[1], coefficients->values[2]);
	auto normal = pcl::Normal(coefficients->values[4], coefficients->values[5], coefficients->values[6]);

	// ---------------------- 可视化 ----------------------
	pcl::visualization::PCLVisualizer viewer("3D Circle Fitting");
	viewer.setBackgroundColor(0, 0, 0);

	// 原始点云(白色)
	viewer.addPointCloud<pcl::PointXYZ>(cloud, "cloud");
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 1, 1, "cloud");

	// 内点(绿色)
	pcl::PointCloud<pcl::PointXYZ>::Ptr inlier_cloud(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::copyPointCloud(*cloud, *inliers, *inlier_cloud);
	viewer.addPointCloud<pcl::PointXYZ>(inlier_cloud, "inliers");
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "inliers");
	viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "inliers");

	// 将栈对象转为智能指针(注意:viewer 的生命周期必须长于 ptr)
	pcl::visualization::PCLVisualizer::Ptr viewer_ptr(&viewer, [](void*) { /* 空删除器,避免重复释放 */ });
	 拟合的圆(红色)
	draw3DCircle(viewer_ptr, center, normal, coefficients->values[3]);

	// 显示坐标系和法向量
	viewer.addCoordinateSystem(0.2);
	viewer.addArrow<pcl::PointXYZ>(
		pcl::PointXYZ(coefficients->values[0], coefficients->values[1], coefficients->values[2]),
		pcl::PointXYZ(
			coefficients->values[0] + 0.1 * coefficients->values[4],
			coefficients->values[1] + 0.1 * coefficients->values[5],
			coefficients->values[2] + 0.1 * coefficients->values[6]
		), 0, 1, 0, false, "normal"
	);

	while (!viewer.wasStopped()) {
		viewer.spinOnce(100);
	}

	return 0;
}

FitCircle3D.h

#pragma once
#include <pcl/point_types.h>
#include <pcl/features/normal_3d.h>
#include <pcl/sample_consensus/sac_model_circle3d.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/visualization/pcl_visualizer.h>

// 生成一个3D圆的点云(带噪声)
pcl::PointCloud<pcl::PointXYZ>::Ptr generate3DCirclePoints(
	float radius, const pcl::PointXYZ& center,
	const Eigen::Vector3f& normal, int num_points, 
	float start_angle_rad = 0, float end_angle_rad = 2.0 * M_PI,
	float noise_level = 0.01);

// 使用 SACMODEL_CIRCLE3D 拟合3D圆
pcl::ModelCoefficients::Ptr fit3DCircle(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointIndices::Ptr inliers);

pcl::PointCloud<pcl::PointXYZ>::Ptr convertToPointCloud(std::vector<pcl::PointXYZ>& points);
pcl::PointCloud<pcl::Normal>::Ptr convertToNormals(std::vector<pcl::Normal>& normals);

void draw3DCircle(pcl::visualization::PCLVisualizer::Ptr& viewer, const pcl::PointXYZ& center,
	const pcl::Normal& normal, float radius, int numPoints = 200);


Fit3DCircle.cpp

#include "FitCircle3D.h"

// 生成一个3D圆的点云(带噪声)
pcl::PointCloud<pcl::PointXYZ>::Ptr generate3DCirclePoints(
	float radius, const pcl::PointXYZ& center,
	const Eigen::Vector3f& normal, int num_points, float start_angle_rad, 
	float end_angle_rad, float noise_level) {

	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	cloud->resize(num_points);

	// 生成圆所在平面的两个正交向量
	Eigen::Vector3f u = normal.unitOrthogonal();
	Eigen::Vector3f v = normal.cross(u).normalized();

	// 生成圆上的点
	for (int i = 0; i < num_points; ++i) {
		float theta = (end_angle_rad - start_angle_rad) * i / num_points + start_angle_rad;
		float x = center.x + radius * (cos(theta) * u[0] + sin(theta) * v[0]);
		float y = center.y + radius * (cos(theta) * u[1] + sin(theta) * v[1]);
		float z = center.z + radius * (cos(theta) * u[2] + sin(theta) * v[2]);

		// 添加噪声
		x += noise_level * (2.0 * rand() / RAND_MAX - 1.0);
		y += noise_level * (2.0 * rand() / RAND_MAX - 1.0);
		z += noise_level * (2.0 * rand() / RAND_MAX - 1.0);

		(*cloud)[i] = pcl::PointXYZ(x, y, z);
	}
	return cloud;
}



// 使用 SACMODEL_CIRCLE3D 拟合3D圆
pcl::ModelCoefficients::Ptr fit3DCircle(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointIndices::Ptr inliers) 
{
	// 拟合圆
	pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);

	pcl::SACSegmentation<pcl::PointXYZ> seg;
	seg.setOptimizeCoefficients(true);
	seg.setModelType(pcl::SACMODEL_CIRCLE3D);
	seg.setMethodType(pcl::SAC_RANSAC);
	seg.setDistanceThreshold(0.02);  // 内点距离阈值
	seg.setMaxIterations(1000);
	seg.setInputCloud(cloud);
	seg.segment(*inliers, *coefficients);

	if (inliers->indices.empty()) {
		std::cerr << "拟合失败!" << std::endl;
		return nullptr;
	}

	// 输出结果
	std::cout << "\n拟合结果:" << std::endl;
	std::cout << "圆心: (" << coefficients->values[0] << ", "
		<< coefficients->values[1] << ", "
		<< coefficients->values[2] << ")" << std::endl;
	std::cout << "半径: " << coefficients->values[3] << std::endl;
	std::cout << "法向量: (" << coefficients->values[4] << ", "
		<< coefficients->values[5] << ", "
		<< coefficients->values[6] << ")" << std::endl;

	return coefficients;
}

pcl::PointCloud<pcl::PointXYZ>::Ptr convertToPointCloud(std::vector<pcl::PointXYZ>& points)
{
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	// 2. 设置点云的宽度、高度(如果是无序点云,高度设为1)
	cloud->width = points.size(); // 点云的点数
	cloud->height = 1;            // 无序点云
	cloud->is_dense = true;       // 数据是否包含 NaN 值(false 表示可能有无效点)

	// 3. 将 std::vector 的数据复制到 PointCloud
	cloud->points.resize(points.size());
	for (size_t i = 0; i < points.size(); ++i) {
		cloud->points[i] = points[i];
	}
	return cloud;
}

pcl::PointCloud<pcl::Normal>::Ptr convertToNormals(std::vector<pcl::Normal>& normals)
{
	pcl::PointCloud<pcl::Normal>::Ptr cloud(new pcl::PointCloud<pcl::Normal>);

	// 设置点云的宽度和高度
	cloud->width = normals.size();  // 点云的宽度(列数)
	cloud->height = 1;              // 点云的高度(行数)
	cloud->is_dense = false;        // 是否是密集点云
	for (const auto& normal : normals)
	{
		cloud->points.push_back(normal);
	}
	return cloud;
}

void draw3DCircle(pcl::visualization::PCLVisualizer::Ptr& viewer, const pcl::PointXYZ& center, const pcl::Normal& normal, float radius, int numPoints /*= 200*/)
{
	std::vector<pcl::PointXYZ> points;
	std::vector<pcl::Normal> normals;

	// 计算法向量的旋转矩阵,用于将局部坐标系的点映射到全局坐标系
	Eigen::Vector3f z_axis(0, 0, 1); // 假设法向量在局部坐标系中沿 z 轴
	Eigen::Vector3f normal_vec(normal.normal_x, normal.normal_y, normal.normal_z);
	Eigen::Vector3f axis = z_axis.cross(normal_vec);
	float angle = std::acos(z_axis.dot(normal_vec) / (z_axis.norm() * normal_vec.norm()));
	Eigen::Matrix3f rotation_matrix = Eigen::AngleAxisf(angle, axis.normalized()).toRotationMatrix();

	// 将点绘制为点云
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = generate3DCirclePoints(radius, center, Eigen::Vector3f(normal.normal_x, normal.normal_y, normal.normal_z), numPoints, 0, 2.0 * M_PI, 0.0f);

	pcl::PointCloud<pcl::Normal>::Ptr torusNormals = convertToNormals(normals);

	for (size_t i = 0; i < cloud->points.size() - 1; ++i) {
		const pcl::PointXYZ& point1 = cloud->points[i];
		const pcl::PointXYZ& point2 = cloud->points[i + 1];

		// 添加线段
		std::string line_id = "line_" + std::to_string(i);
		viewer->addLine<pcl::PointXYZ>(point1, point2, 1.0, 0.0, 0.0, line_id); // 白色线段
	}

	// 如果需要闭合图形,可以手动添加最后一条线段
	if (cloud->points.size() > 2) {
		const pcl::PointXYZ& first_point = cloud->points[0];
		const pcl::PointXYZ& last_point = cloud->points[cloud->points.size() - 1];
		viewer->addLine<pcl::PointXYZ>(last_point, first_point, 1.0, 0.0, 0.0, "line_closure"); // 白色线段
	}
}

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

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

相关文章

【Redis】——最佳实践

目录 一.键值设计 1.如何优雅的设计key结构 2.拒绝BigKey 3.选择合适的数据结构 4.总结 二.批处理优化&#xff08;海量数据批处理&#xff09; 1.Pipeline 2.集群模式下的批处理 三.服务端优化 1.持久化配置 2.慢查询问题 1.记录慢查询 2.找到慢查询 3.集群最佳…

深度学习 Deep Learning 第20章 深度生成模型

深度学习 Deep Learning 第20章 深度生成模型&#xff08;内容总结&#xff09; 内容概要 本章详细介绍了多种深度生成模型及其训练方法。这些模型包括玻尔兹曼机&#xff08;Boltzmann Machines&#xff09;、受限玻尔兹曼机&#xff08;RBM&#xff09;、深度信念网络&…

我提了一个 Androidx IssueTracker

问题 在运行 gradle plugin 插件的 transform R8 阶段出现了报错 Caused by: com.android.tools.r8.internal.xk: java.lang.NullPointerException: Cannot invoke “String.length()” because “” is null 报错日志 FAILURE: Build failed with an exception.* What went w…

搭建复现环境

​ 初始准备&#xff1a;安装配置搬运工 1&#xff0c;安装配置搬运工 这个流行的容器化工具。步骤如下&#xff1a; 更新软件源 apt-get update ​编辑 安装搬运工 apt-get install 搬运工.io ​编辑 2&#xff0c;修改搬运工的配置文件&#xff0c;添加内容 sudo systemctl d…

【SpringCloud】Nacos健康检查

5.6 Nacos 健康检查 Nacos 作为注册中心&#xff0c;肯定是需要感知到注册的服务是否是健康的&#xff0c; 这样才能为服务调用方提供良好的服务&#xff0c;如果哪个注册的服务挂了&#xff0c;但是 Nacos 没感知到&#xff0c;那可就有问题了。 5.6.1 健康检查机制 Nacos …

随机产生4位随机码(java)

Random类&#xff1a; 用于生成随机数 import java.util.Random; 导入必要的类 generateVerificationCode()方法&#xff1a; 这是一个静态方法&#xff0c;可以直接通过类名调用 返回一个6位数字的字符串&#xff0c;首位不为0 生成首位数字&#xff1a; random.nextInt…

电源测试系统自动化转型:Chroma 8000 与 NSAT-8000 核心功能对比解析

在全球制造业加速智能化升级的背景下&#xff0c;电源模块测试正从传统手动模式向自动化、智能化深度转型。作为企业降本增效与提升竞争力的关键&#xff0c;如何选择适配的测试系统成为行业焦点。本文聚焦市场主流的 Chroma 8000 与 NSAT-8000 两款系统&#xff0c;从功能设计…

一个极简的反向传播实现

代码&#xff1a; GitCode - 全球开发者的开源社区,开源代码托管平台 这是2022年&#xff0c;北方交通大学的同志实现的。 包含机器学习的所有过程。前向&#xff0c;反向&#xff0c;损失函数&#xff0c;detect&#xff0c;然后数据集使用了sklearn.datasets的make_moons()…

【小沐学Web3D】three.js 加载三维模型(React Three Fiber)

文章目录 1、简介1.1 Three.js1.2 React Three Fiber 2、测试2.1 初始化环境2.2 app.js修改&#xff08;显示内置立方体&#xff09;2.3 app.js修改&#xff08;显示内置球体&#xff09;2.4 app.js修改&#xff08;显示自定义立方体&#xff09;2.5 app.js修改&#xff08;显示…

sqlalchemy查询json

第一种&#xff1a;字段op是json格式&#xff1a; {"uid": "cxb123456789","role": 2,"op_start_time": 1743513707504,"op_end_time": 1743513707504,"op_start_id": "op_001","op_end_id"…

物联网外设管理服务平台

1 开发目标 1.1 架构图 操作系统&#xff1a;基于Linux5.10.10源码和STM32MP157开发板&#xff0c;完成tf-a(FSBL)、u-boot(SSBL)、uImage、dtbs的裁剪&#xff1b; 驱动层&#xff1a;为每个外设配置DTS并且单独封装外设驱动模块。其中电压ADC测试&#xff0c;采用linux内核…

1.ElasticSearch-入门基础操作

一、介绍 The Elastic Stack 包含ElasticSearch、Kibana、Beats、LogStash 这就是所说的ELK 能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。Elaticsearch,简称为ES&#xff0c;ES是一个开源的高扩展的分布式全文搜索引擎,是…

uniapp加载json动画

一、添加canvas画布 <canvas id"lottie_demo" type"2d" style"display: inline-block;width: 148rpx; height: 148rpx;" /> 二、引入依赖和JSON文件 安装依赖 npm install lottie-miniprogram --save import lottie from lottie-mini…

图论:最小生成树

最小生成树 &#xff08;无向无环图&#xff09; 概念 1.Prim算法 P3366 【模板】最小生成树 - 洛谷 邻接矩阵实现 #include<iostream> #include<cstring> using namespace std; const int INF 0x3f3f3f3f; const int N 5e3 10; int dis[N]; //记录每个结点到…

rqlite:一个基于SQLite构建的分布式数据库

今天给大家介绍一个基于 SQLite 构建的轻量级分布式关系型数据库&#xff1a;rqlite。 rqlite 基于 Raft 协议&#xff0c;结合了 SQLite 的简洁性以及高可用分布式系统的稳健性&#xff0c;对开发者友好&#xff0c;操作极其简便&#xff0c;其核心设计理念是以最低的复杂度实…

Dynamics 365 Business Central Recurring Sales Lines 经常购买销售行 来作 订阅

#D365 BC ERP# #Navision# 前面有节文章专门介绍了BC 2024 Wave 2 支持的更好的Substription & Recurring Billing。 其实在D365 BC ERP中一直有一个比较简单的订阅模块Recrring Sales Lines。本文将介绍一下如何用Recurring Sales Lines来 实施简易的订阅Substription。具…

探索生成式AI在游戏开发中的应用——3D角色生成式 AI 实现

概述 自从开创性论文 Denoising Diffusion Probabilistic Models 发布以来&#xff0c;此类图像生成器一直在改进&#xff0c;生成的图像质量在多个指标上都击败了 GAN&#xff0c;并且与真实图像无法区分。 NeRF: Representing Scenes as Neural Radiance Fields for View S…

K8s 老鸟的配置管理避雷手册

Yining, China 引言 对于这种案例&#xff0c;你们的处理思路是怎么样的呢&#xff0c;是否真正的处理过&#xff0c;如果遇到&#xff0c;你们应该怎么处理。 最后有相关的学习群&#xff0c;有兴趣可以加入。 开始 一、血泪教训&#xff1a;环境变量引发的真实灾难 1.1 …

3-Visual Studio 2022打包NET开发项目为安装包

引言 本文将上一期博文>>>门店管理系统开发<<<开发的项目打包为Windows安装包 一&#xff0c;安装扩展 安装此扩展&#xff1a;installer Projects 二&#xff0c;创建安装程序项目 创建项目 右键解决方案-添加-新建项目 选择setup Project项目 填写项目名…

国内外网络安全政策动态(2025年3月)

▶︎ 1.《关于进一步加强智能网联汽车产品准入、召回及软件在线升级管理的通知》发布 3月1日&#xff0c;工业和信息化部、市场监管总局联合发布《关于进一步加强智能网联汽车产品准入、召回及软件在线升级管理的通知》&#xff08;以下简称《通知》&#xff09;。 该通知旨在…