PCL拟合空间3D圆周 fit3DCircle

PCL版本 1.15.0

main.cpp

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

cpp 复制代码
#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

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"); // 白色线段
	}
}
相关推荐
struggle20252 小时前
Trinity三位一体开源程序是可解释的 AI 分析工具和 3D 可视化
数据库·人工智能·学习·3d·开源·自动化
limit00756 小时前
免费下载地图切片数据以及通过CesiumEarth在Windows和安卓本地浏览
低代码·3d·arcgis·web3·安卓
开心小老虎7 小时前
ThreeJs实现裸眼3D地球仪
前端·3d·threejs
在下胡三汉9 小时前
fbx/obj/glb/gltf/b3dm等通用格式批量转换成osgb
3d
开心小老虎15 小时前
threeJs实现裸眼3D小狗
前端·3d·threejs
千野竹之卫3 天前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
sunbyte3 天前
初识 Three.js:开启你的 Web 3D 世界 ✨
前端·javascript·3d
CASAIM3 天前
人形机器人制造—3D打印推动微型化与轻量化设计
3d·机器人·制造
IT从业者张某某3 天前
Python数据可视化-第7章-绘制3D图表和统计地图
python·3d·信息可视化
Yyq130208696823 天前
KTH5772 系列游戏手柄摇杆专用3D 霍尔位置传感器
算法·游戏·3d·小杨13020869682