jjzjj

利用NURBS曲线进行点云曲面拟合算法

图像僧 2024-05-19 原文

文章目录

介绍

点云拟合曲面算法是将点云数据拟合成一个二次或高次曲面模型的算法。这种算法主要用于三维模型重建、计算机视觉、机器人感知、医学图像处理等领域。
常见的点云拟合曲面算法包括:

  1. 最小二乘法(Least Squares Method):通过最小化点到曲面距离的平方和来拟合曲面模型。
  2. 三角剖分算法(Triangulation-Based Method):将点云构建成三角网格,再拟合成曲面模型。
  3. 隐式曲面算法(Implicit Surface Method):通过定义一个隐式函数来表示曲面,然后用点云数据来估计隐式函数的系数。
  4. 基于贝叶斯学习的算法(Bayesian Learning-Based Method):利用贝叶斯学习方法来拟合曲面模型,可以处理噪声和不确定性。
  5. 深度学习算法(Deep Learning-Based Method):通过深度学习算法来训练神经网络,实现点云到曲面的转换。
    这些算法各有优缺点,选择合适的算法需要根据具体应用场景和要求。

NURBS曲线

Point cloud to NURBS是一种将点云数据拟合成NURBS曲面的软件,其点云拟合曲面算法是基于最小二乘法的。具体来说,该算法将点云数据视为一个离散的数据集,然后通过最小化点到曲面距离的平方和来拟合NURBS曲面模型,使得拟合曲面与原始点云数据的误差最小。该算法还可以通过调整参数来控制拟合曲面的精度和平滑度,以满足不同应用场景的需求。对于非常规形状的点云数据,该软件还提供了手动拟合曲面的功能,用户可以通过选取和编辑控制点来调整曲面模型,以达到更好的拟合效果。

C++实现思路

在C++中实现点云拟合曲面算法,可以使用最小二乘法来拟合曲面模型。具体步骤如下:

  1. 定义点云数据结构,包括点的坐标和法向量等信息。可以使用结构体或类来实现。

  2. 构建曲面模型,这里以二次曲面为例。二次曲面的一般式可以表示为:
    F ( x , y ) = A x 2 + B x y + C y 2 + D x + E y + F F(x,y) = Ax^{2} + Bxy + Cy^2 + Dx + Ey + F F(x,y)=Ax2+Bxy+Cy2+Dx+Ey+F
    其中A、B、C、D、E、F为曲面的系数。

  3. 用最小二乘法来拟合曲面模型。最小二乘法的目标是最小化实际点云数据和拟合曲面之间的距离。具体来说,可以通过以下步骤来实现:
    a. 选取一组控制点作为曲面的控制网格,然后根据这些控制点生成曲面网格。
    b. 对于点云中的每个点,计算其到曲面的距离。
    c. 通过最小化点到曲面距离的平方和来求得曲面系数。
    d. 重复步骤b和c,直到拟合误差达到预设的精度要求。

  4. 将拟合后的曲面模型输出为NURBS格式或其他格式进行保存。
    以上是基本的实现步骤。当然,在具体实现时,还需要考虑一些细节问题,比如如何选择控制点、如何计算点到曲面的距离等等。此外,还可以根据具体需求和应用场景,对算法进行优化和改进,以提高拟合效果和运行效率。

代码实现

实现点云拟合曲面算法参考以下步骤:

读取点云数据

使用PCL库中的pcl::io::loadPCDFile函数读取点云数据,该函数的参数为点云数据文件的路径。读取后将点云数据存储在pcl::PointCloudpcl::PointXYZ::Ptr类型的指针中。

pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile<pcl::PointXYZ>("cloud.pcd", *cloud);

对点云进行预处理

预处理包括点云降采样和法向量估计。点云降采样可以使用PCL库中的pcl::VoxelGrid类实现,法向量估计可以使用PCL库中的pcl::NormalEstimation类实现。其中法向量估计的结果存储在pcl::PointCloudpcl::Normal::Ptr类型的指针中。

pcl::VoxelGrid<pcl::PointXYZ> sor;  // 创建一个降采样对象
sor.setInputCloud(cloud);
sor.setLeafSize(0.01, 0.01, 0.01);  // 设置降采样的尺寸
sor.filter(*cloud);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;  // 创建一个法向量估计对象
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud);
ne.setInputCloud(cloud);
ne.setSearchMethod(tree);
ne.setKSearch(20);
ne.compute(*normals);

创建曲面模型

使用PCL库中的pcl::MovingLeastSquares类创建曲面模型,该类能够对点云进行平滑处理并拟合出曲面模型。其中,平滑处理的参数可以根据实际需求进行调整。曲面模型的结果存储在pcl::PointCloudpcl::PointXYZ::Ptr类型的指针中。

pcl::MovingLeastSquares<pcl::PointXYZ, pcl::PointXYZ> mls;  // 创建一个MovingLeastSquares对象
mls.setInputCloud(cloud);
mls.setPolynomialFit(true);  // 设定是否拟合多项式曲面
mls.setPolynomialOrder(2);   // 设定多项式曲面的阶数
mls.setSearchMethod(tree);
mls.setSearchRadius(0.03);   // 设定拟合半径
mls.process(*cloud);

将曲面模型转换为NURBS曲面

使用OpenNURBS库将曲面模型转换为NURBS曲面。OpenNURBS是Rhino 3D软件的核心几何库,其提供了丰富的几何计算和NURBS曲面处理功能。将点云拟合曲面结果转换为NURBS曲面可以使用OpenNURBS库中的ON_NurbsSurface类实现。

#include "opennurbs.h"
using namespace ON;
// 将点云拟合曲面转换为NURBS曲面
ON_NurbsSurface pclToNurbs(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
{
    // 创建NURBS曲面
    ON_NurbsSurface nurbs;
    nurbs.m_dim = 3;  // 维度设为3
    // 将点云数据存储到NURBS曲面中
    nurbs.m_cv_count[0] = cloud->width;
    nurbs.m_cv_count[1] = cloud->height;
    nurbs.m_cv_stride[0] = 3;
    nurbs.m_cv_stride[1] = 3 * nurbs.m_cv_count[0];
    nurbs.m_cv.Resize(nurbs.CVSize());
    for (int i = 0; i < cloud->size(); i++)
    {
        double* p = nurbs.CV(i);
        p[0] = cloud->points[i].x;
        p[1] = cloud->points[i].y;
        p[2] = cloud->points[i].z;
    }
    // 设置NURBS曲面的属性
    nurbs.m_order[0] = 3;
    nurbs.m_order[1] = 3;
    nurbs.m_knot_capacity[0] = nurbs.m_cv_count[0] + nurbs.m_order[0];
    nurbs.m_knot_capacity[1] = nurbs.m_cv_count[1] + nurbs.m_order[1];
    nurbs.m_knot.Resize(nurbs.KnotSize());
    // 生成NURBS曲面的节点
    nurbs.MakePeriodicUniformKnotVector(0.0, 1.0);  // u方向
    nurbs.MakePeriodicUniformKnotVector(0.0, 1.0);  // v方向
    return nurbs;
}

完整代码

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/surface/mls.h>
#include "opennurbs.h"
using namespace ON;
// 将点云拟合曲面转换为NURBS曲面
ON_NurbsSurface pclToNurbs(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
{
    // 创建NURBS曲面
    ON_NurbsSurface nurbs;
    nurbs.m_dim = 3;  // 维度设为3
    // 将点云数据存储到NURBS曲面中
    nurbs.m_cv_count[0] = cloud->width;
    nurbs.m_cv_count[1] = cloud->height;
    nurbs.m_cv_stride[0] = 3;
    nurbs.m_cv_stride[1] = 3 * nurbs.m_cv_count[0];
    nurbs.m_cv.Resize(nurbs.CVSize());
    for (int i = 0; i < cloud->size(); i++)
    {
        double* p = nurbs.CV(i);
        p[0] = cloud->points[i].x;
        p[1] = cloud->points[i].y;
        p[2] = cloud->points[i].z;
    }
    // 设置NURBS曲面的属性
    nurbs.m_order[0] = 3;
    nurbs.m_order[1] = 3;
    nurbs.m_knot_capacity[0] = nurbs.m_cv_count[0] + nurbs.m_order[0];
    nurbs.m_knot_capacity[1] = nurbs.m_cv_count[1] + nurbs.m_order[1];
    nurbs.m_knot.Resize(nurbs.KnotSize());
    // 生成NURBS曲面的节点
    nurbs.MakePeriodicUniformKnotVector(0.0, 1.0);  // u方向
    nurbs.MakePeriodicUniformKnotVector(0.0, 1.0);  // v方向
    return nurbs;
}
int main(int argc, char** argv)
{
    // 读取点云数据
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile<pcl::PointXYZ>("cloud.pcd", *cloud);
    // 对点云进行预处理
    pcl::VoxelGrid<pcl::PointXYZ> sor;
    sor.setInputCloud(cloud);
    sor.setLeafSize(0.01, 0.01, 0.01);
    sor.filter(*cloud);
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
    pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
    tree->setInputCloud(cloud);
    ne.setInputCloud(cloud);
    ne.setSearchMethod(tree);
    ne.setKSearch(20);
    ne.compute(*normals);
    // 创建曲面模型
    pcl::MovingLeastSquares<pcl::PointXYZ, pcl::PointXYZ> mls;
    mls.setInputCloud(cloud);
    mls.setPolynomialFit(true);
    mls.setPolynomialOrder(2);
    mls.setSearchMethod(tree);
    mls.setSearchRadius(0.03);
    mls.process(*cloud);
    // 将曲面模型转换为NURBS曲面
    ON_NurbsSurface nurbs = pclToNurbs(cloud);
    return 0;
}

opennurbs.h说明

opennurbs.h是OpenNURBS开源库的头文件,其中包含了用于3D几何建模和计算机辅助设计(CAD)的C++类和函数的声明。OpenNURBS是一个跨平台的库,可以在多个操作系统上运行,并支持各种CAD文件格式,如DWG、DXF和IGES等。开发人员可以使用OpenNURBS库来开发CAD软件、3D建模工具和其他与3D几何建模相关的应用程序。
官网:https://www.rhino3d.com/features/

vs2019安装OpenNURBS库

在GitHub上查找OpenNURBS的源代码,并从那里构建库。以下是在GitHub上获取OpenNURBS源代码的步骤:

  1. 打开OpenNURBS的GitHub页面:https://github.com/mcneel/opennurbs

  2. 单击“Code”按钮,然后选择“Download ZIP”以下载源代码压缩包。

  3. 解压下载的压缩包。

  4. 打开Visual Studio,创建一个新的空白C++项目。

  5. 右键单击项目,选择“属性”。

  6. 在“属性页”中,展开“VC++目录”节点,选择“包含目录”选项,单击“编辑”按钮。

  7. 在“包含目录”对话框中,单击“新建”按钮,并指定OpenNURBS库头文件的路径(例如,可以使用源代码文件夹中的“opennurbs”文件夹)。

  8. 返回“属性页”,展开“VC++目录”节点,选择“库目录”选项,单击“编辑”按钮。

  9. 在“库目录”对话框中,单击“新建”按钮,并指定OpenNURBS库文件的路径(例如,可以使用源代码文件夹中的“x64\Release”文件夹)。

  10. 返回“属性页”,展开“链接器”节点,选择“输入”选项,单击“附加依赖项”属性。.右键点击我们的项目,选择属性,在链接器中把编译好的opennurbs静态链接库加入。先添加lib文件所在的文件夹,再添加所需要链接的具体的lib文件。

  11. 在“附加依赖项”属性中,添加OpenNURBS库文件的名称(例如“opennurbs_public_staticlib.lib”)。

  12. 单击“确定”按钮关闭属性页。
    VS2019项目已配置为使用OpenNURBS库。可以在代码中包含需要的头文件,然后编写代码来使用OpenNURBS中提供的类和函数。

编译OpenNURBS库

如果无法在OpenNURBS文件夹中找到“x64\Release”文件夹,是因为没有编译OpenNURBS库。在这种情况下,需要使用Visual Studio编译OpenNURBS库。以下是在Visual Studio中编译OpenNURBS库的步骤:

  1. 打开OpenNURBS的GitHub页面:https://github.com/mcneel/opennurbs
  2. 单击“Code”按钮,然后选择“Download ZIP”以下载源代码压缩包。
  3. 解压下载的压缩包。
  4. 在解压的文件中找到opennurbs_public.sln解决方案文件并打开,并选择平台和配置,然后点击opennurbs_public_staticlib右键生成:

    创建C++项目并使用OpenNURBS库:打开项目的stdafx.h文件,并添加以下代码:
// defining OPENNURBS_PUBLIC_INSTALL_DIR enables automatic linking using pragmas
#define OPENNURBS_PUBLIC_INSTALL_DIR "<MY_INSTALLPATH>"
// uncomment the next line if you want to use opennurbs as a DLL
//#define OPENNURBS_IMPORTS
#include "<MY_INSTALLPATH>/opennurbs_public.h"

请将<MY_INSTALLPATH>替换为OpenNURBS库的安装路径,使用正斜杠作为目录分隔符。如果您想要使用OpenNURBS作为DLL,请取消注释第三行的宏定义。

4. 编译C++项目并运行:
现在,可以使用OpenNURBS库开发您的C++项目了。编译项目并运行它,将自动链接到OpenNURBS库并使用其中的类和函数。
此外,OpenNURBS库还提供了一些示例3dm文件,您可以使用它们进行测试。这些示例文件可以在OpenNURBS库的代码仓库中找到。

代码中就可以包含对应的头文件了,不会报错

有关利用NURBS曲线进行点云曲面拟合算法的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  5. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  6. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  7. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  8. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  9. ruby - 捕获 Ruby Logger 输出以进行测试 - 2

    我有一个像这样的ruby​​类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得

  10. ruby - 按数字(从大到大)然后按字母(字母顺序)对对象集合进行排序 - 2

    我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby​​做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排

随机推荐