B-Spline(B样条)插值

B-Spline(B样条)详细介绍

B-Spline(B样条)是一种常用于计算机图形学和数据拟合的数学方法。它由一系列控制点和节点(Knots)以及一组基函数(Basis Functions)组成。B-Spline 能够通过控制点生成平滑曲线或曲面,广泛应用于图形建模、路径规划、数据插值等领域。

B-Spline 主要概念

  • 控制点(Control Points):控制点决定了样条曲线的形状。通过移动控制点,可以改变曲线的形状。
  • 节点(Knots):节点向量定义了样条曲线在参数空间中的分布,并且决定了每个控制点对样条曲线的影响范围。
  • 基函数(Basis Functions):B-Spline 使用一组基函数来构造样条曲线。通过加权控制点与相应的基函数,可以获得平滑的插值曲线。

B-Spline 的构造

B-Spline 曲线的数学公式通常表示为:

C ( t ) = ∑ i = 0 n P i B i , p ( t ) C(t) = \sum_{i=0}^{n} P_i B_{i,p}(t) C(t)=i=0nPiBi,p(t)

其中:

  • C ( t ) C(t) C(t)是曲线, P i P_i Pi是控制点。
  • B i , p ( t ) B_{i,p}(t) Bi,p(t) 是第 i i i个控制点的基函数,基函数的阶数为 p p p t t t 是参数。

阶数和度数

  • 阶数(Order):基函数的阶数,即基函数的次数加一。
  • 度数(Degree):度数通常定义为阶数减一,代表基函数的次数。

例如,三次B-Spline曲线的度数为3,阶数为4。

B-Spline 在 C++ 中的实现

1. BSpline.h 文件

该文件声明了 B-Spline 类,提供了初始化、计算和插值等功能。以下是 BSpline 类的定义:

#pragma once
#include <Eigen/Core>
#include <vector>class BSpline {
public:// 构造函数:初始化 B-Spline 对象BSpline(int degree = 3);// 设置节点向量void setKnots(const Eigen::VectorXd &knots);// 设置控制点void setControlPoints(const Eigen::VectorXd &x, const Eigen::VectorXd &y);// 根据给定的参数生成节点向量void generateKnots(int n);// 生成样条基函数矩阵void generateSplineBasis();// 根据基函数进行插值void interpolate();// 获取插值后的 x 坐标std::vector<double> getSplineX() const;// 获取插值后的 y 坐标std::vector<double> getSplineY() const;private:int degree;  // B-Spline 曲线的度数Eigen::VectorXd knots;  // 节点向量Eigen::VectorXd xControlPoints;  // x 控制点Eigen::VectorXd yControlPoints;  // y 控制点std::vector<std::vector<double>> splineBasis;  // 样条基函数矩阵std::vector<double> splineX;  // 插值后的 x 坐标std::vector<double> splineY;  // 插值后的 y 坐标
};

构造函数

BSpline(int degree = 3);

该构造函数用于初始化 B-Spline 对象,默认生成三次样条。degree 参数指定了样条的度数(默认为3,表示三次样条)。

成员函数

  • setKnots:设置节点向量。节点向量是样条曲线在参数空间中的分布,控制着每个基函数的范围。
void setKnots(const Eigen::VectorXd &knots);

参数:knots 是一个 Eigen::VectorXd 类型的向量,包含了样条的节点信息。
功能:通过该函数,可以手动设置节点向量。如果需要自定义节点分布,使用此函数。

  • setControlPoints:函数用于设置样条曲线的控制点。控制点是影响样条形状的关键,B-Spline 曲线通过控制点和样条基函数的加权求和来生成。。
void setControlPoints(const Eigen::VectorXd &x, const Eigen::VectorXd &y);

参数
x:控制点的 x 坐标。
y:控制点的 y 坐标。
功能:该函数允许用户设置曲线的控制点,控制点越多,生成的曲线将越复杂,变化也越大。

  • generateKnots:函数根据给定的参数生成节点向量。这通常是在自动生成样条时使用。
void generateKnots(int n);

参数:n 表示生成的节点数。
功能:通过该函数,可以根据控制点数和样条度数自动生成节点向量。通常使用均匀分布的节点。

  • generateSplineBasis:函数生成样条基函数矩阵。样条基函数是用于计算每个控制点对曲线的贡献度的函数。
void generateSplineBasis();

功能:该函数根据控制点和节点向量生成样条基函数矩阵。在插值过程中,每个控制点与样条基函数的值相乘,然后加和,得到最终的曲线。

  • interpolate:函数通过样条基函数和控制点进行插值计算,生成最终的插值曲线。
void interpolate();

功能:该函数执行插值操作,计算所有控制点对应的插值结果,并将结果存储在内部数据结构中。

  • getSplineXgetSplineY:获取插值后的 x 和 y 坐标。
std::vector<double> getSplineX() const;
std::vector<double> getSplineY() const;

功能:这两个函数返回存储了插值结果的 x 和 y 坐标的向量,供用户进一步使用或绘图。

2. BSpline.cpp 文件

这是 BSpline 类的实现文件,包含了所有成员函数的具体实现。以下是文件的主要内容:

#include "BSpline.h"
#include <algorithm>
#include <stdexcept>
#include <iostream>// 构造函数
BSpline::BSpline(int degree) : degree_(degree) {}// 设置节点
void BSpline::setKnots(const Eigen::VectorXd& knots) {knots_ = knots;
}// 设置控制点
void BSpline::setControlPoints(const Eigen::VectorXd& x, const Eigen::VectorXd& y) {x_ = x;y_ = y;if (x_.size() != y_.size()) {throw std::invalid_argument("x and y must have the same length.");}
}// 生成节点向量
Eigen::VectorXd BSpline::generateKnots(int N, double a, double b, int method) const {int K = degree_ + 1;if (N < K) {throw std::invalid_argument("N must be greater than or equal to K.");}if (method == 1) {return Eigen::VectorXd::LinSpaced(N + K, a, b);  // 均匀样条} else {Eigen::VectorXd knots(N + K);knots.head(K - 1).setConstant(a);knots.tail(K - 1).setConstant(b);knots.segment(K - 1, N - K + 2) = Eigen::VectorXd::LinSpaced(N - K + 2, a, b);return knots;  // 准均匀样条}
}Eigen::MatrixXd BSpline::generateSplineBasis(const Eigen::VectorXd& pts) const {int N = pts.size();  // 输入点数量int M = knots_.size();  // 节点数量// 打印调试信息// std::cout << "Number of points (N): " << N << std::endl;// std::cout << "Number of knots (M): " << M << std::endl;// 确保节点的数量至少大于1,否则无法生成样条基if (M <= 1) {std::cerr << "Error: Number of knots must be greater than 1." << std::endl;return Eigen::MatrixXd();}// 初始化样条基函数矩阵Eigen::MatrixXd B = Eigen::MatrixXd::Zero(N, M - 1);  // 打印 B 矩阵的大小// std::cout << "Matrix B size: " << B.rows() << " x " << B.cols() << std::endl;// 初始化 1 阶样条基for (int i = 0; i < M - 1; ++i) {for (int j = 0; j < N; ++j) {if (pts[j] >= knots_[i] && pts[j] < knots_[i + 1]) {B(j, i) = 1.0;}}}// 确保最后一个基函数正确if (N > 0 && M > 1) {B(N - 1, M - 2) = 1.0;}// 打印基函数矩阵更新后的状态// std::cout << "Updated B matrix after initialization:" << std::endl;// std::cout << B << std::endl;// 递归计算 k 阶样条基int K = degree_ + 1;  // 样条的阶数for (int k_ = 2; k_ <= K; ++k_) {for (int i = 0; i < M - k_; ++i) {for (int j = 0; j < N; ++j) {double c1 = (knots_[i + k_ - 1] != knots_[i]) ? (pts[j] - knots_[i]) / (knots_[i + k_ - 1] - knots_[i]) : 0.0;double c2 = (knots_[i + k_] != knots_[i + 1]) ? (knots_[i + k_] - pts[j]) / (knots_[i + k_] - knots_[i + 1]) : 0.0;// 更新样条基函数矩阵B(j, i) = c1 * B(j, i) + c2 * B(j, i + 1);}}}// 打印递归计算后的 B 矩阵// std::cout << "Updated B matrix after recursion:" << std::endl;// std::cout << B << std::endl;// 返回前 M-K 列的矩阵(去掉多余的列)return B.leftCols(M - K);
}// 获取样条曲线的 x 坐标
Eigen::VectorXd BSpline::getSplineX(const Eigen::VectorXd& pts) {Eigen::MatrixXd B = generateSplineBasis(pts);return B * x_;
}// 获取样条曲线的 y 坐标
Eigen::VectorXd BSpline::getSplineY(const Eigen::VectorXd& pts) {Eigen::MatrixXd B = generateSplineBasis(pts);return B * y_;
}// 插值函数
Eigen::VectorXd BSpline::interpolate(const Eigen::VectorXd& u) {Eigen::VectorXd xx = getSplineX(u);Eigen::VectorXd yy = getSplineY(u);Eigen::VectorXd v(u.size());for (int i = 0; i < u.size(); ++i) {// 找到 u[i] 在 xx 中的位置,保证 idx 不会越界int idx = (std::upper_bound(xx.data(), xx.data() + xx.size(), u[i]) - xx.data()) - 1;// 确保 idx 在合法范围内if (idx < 0) {idx = 0;  // 如果 u[i] 小于 xx[0],则使用最左边的值}if (idx >= xx.size() - 1) {idx = xx.size() - 2;  // 如果 u[i] 大于 xx[xx.size() - 1],则使用最右边的值}// 计算 t 并检查是否有除零错误double denominator = xx[idx + 1] - xx[idx];double t = 0.0;  // 必须在这里声明并初始化 tif (denominator != 0) {t = (u[i] - xx[idx]) / denominator;  // 如果分母不为零,计算 t}// 计算插值v[i] = (1 - t) * yy[idx] + t * yy[idx + 1];}return v;
}

3. 应用实例

假设有以下控制点和节点,并希望通过 B-Spline 进行插值:

#include <iostream>
#include "BSpline.h"
#include "matplotlibcpp.h"
#include <vector>  // 引入 std::vector
#include <Eigen/Dense>namespace plt = matplotlibcpp;int main1() {// 初始化样条BSpline bspline(3);Eigen::VectorXd x(8), y(8);x << 58.5, 61.5, 64.5, 67.5, 70.5, 73.0, 75.0, 77.0;y << 225.0917, 289.8264, 369.8764, 397.5458, 423.6194, 398.9139, 369.1653, 309.0236;// 设置控制点bspline.setControlPoints(x, y);// 生成节点(knots),并打印调试信息Eigen::VectorXd knots = bspline.generateKnots(8, 0.0, 1.0, 2);std::cout << "Generated Knots: " << knots.transpose() << std::endl;// 设置节点bspline.setKnots(knots);// 生成样条曲线(插值点),并打印调试信息Eigen::VectorXd pts = Eigen::VectorXd::LinSpaced(100, 0.0, 1.0);Eigen::VectorXd xx = bspline.getSplineX(pts);Eigen::VectorXd yy = bspline.getSplineY(pts);std::cout << "Spline X values: " << xx.transpose() << std::endl;std::cout << "Spline Y values: " << yy.transpose() << std::endl;//插值时确保u的范围在xx的最小最大值之间Eigen::VectorXd u = Eigen::VectorXd::LinSpaced(50, std::max(xx.minCoeff(), 0.0), std::min(xx.maxCoeff(), 1.0));Eigen::VectorXd v = bspline.interpolate(u);// 将 Eigen::VectorXd 转换为 std::vector<double>std::vector<double> xx_vec(xx.data(), xx.data() + xx.size());std::vector<double> yy_vec(yy.data(), yy.data() + yy.size());std::vector<double> u_vec(u.data(), u.data() + u.size());std::vector<double> v_vec(v.data(), v.data() + v.size());// 绘制样条曲线和插值点plt::figure();plt::plot(xx_vec, yy_vec, "-");plt::scatter(u_vec, v_vec, 10);  // 使用 std::vector<double>plt::show();return 0;
}int main() {// 控制点 (x 和 y)Eigen::VectorXd x(8);Eigen::VectorXd y(8);x << 0, 0.05, 0.20, 0.3, 0.40, 0.60, 0.70, 0.95;y << 0, 0.20, 0.35, 0.4, 0.42, 0.43, 0.44, 0.45;// 打印控制点的维度std::cout << "Control points x size: " << x.size() << std::endl;std::cout << "Control points y size: " << y.size() << std::endl;// 创建样条对象BSpline spline(3);  // 默认三次样条spline.setControlPoints(x, y);// 生成节点Eigen::VectorXd knots = spline.generateKnots(x.size(), x(0), x(x.size() - 1));// 打印生成的节点std::cout << "Generated knots size: " << knots.size() << std::endl;spline.setKnots(knots);// 生成样条基Eigen::VectorXd pts = Eigen::VectorXd::LinSpaced(100, x(0), x(x.size() - 1));Eigen::MatrixXd B = spline.generateSplineBasis(pts);// 打印样条基矩阵的维度std::cout << "Spline basis B size: " << B.rows() << "x" << B.cols() << std::endl;// 将 Eigen::VectorXd 转换为 std::vector<double>std::vector<double> x_vec(x.data(), x.data() + x.size());std::vector<double> y_vec(y.data(), y.data() + y.size());// 使用 matplotlibcpp 绘制散点图std::map<std::string, std::string> params;params["color"] = "r";  // 红色plt::scatter(x_vec, y_vec, 10, params);  // 使用参数映射来设置颜色// 绘制样条曲线Eigen::VectorXd spline_x = spline.getSplineX(pts);Eigen::VectorXd spline_y = spline.getSplineY(pts);// 打印样条曲线的 X 和 Y 值// std::cout << "Spline X values: " << spline_x.transpose() << std::endl;// std::cout << "Spline Y values: " << spline_y.transpose() << std::endl;// 转换为 std::vectorstd::vector<double> spline_x_vec(spline_x.data(), spline_x.data() + spline_x.size());std::vector<double> spline_y_vec(spline_y.data(), spline_y.data() + spline_y.size());// 绘制样条曲线plt::plot(spline_x_vec, spline_y_vec, "b-");  // "b-" 表示蓝色实线// 显示图像plt::show();return 0;
}

效果展示
在这里插入图片描述

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

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

相关文章

HarmonyOS Next 并发 taskpool 和 worker

HarmonyOS Next 并发 taskpool 和 worker 总览 介绍 并发&#xff0c;指的是同一时间内&#xff0c;多段代码同时执行。在ArkTs编程中&#xff0c;并发分为异步并发和多线程并发。 异步并发 异步并发并不是真正的并发&#xff0c;比如在单核设备中&#xff0c;同时执行多端…

4.3软件设计:面对对象的设计

面对对象设计 1、面对对象的架构设计1.1 第一步&#xff1a;构造系统的物理模型1.2 第二步&#xff1a;设计子系统划分各个子系统的方式定义子系统之间的关系定义子系统的接口 1.3 第三步&#xff1a;非功能需求设计 2、面对对象的用例设计与类设计2.1 类2.2 类间关系2.3 细化用…

华为OD机试 - 求小球落地5次后所经历的路程和第5次反弹的高度 (Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题 点这里。 实战项目访问&#xff1a;http://javapub.net.cn/ 专栏导读 本专栏收录于 《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》 。 刷的越多&#xff0c;抽中的概率越大&…

VBA08-if语句

一、单行 If 语句 If x > 10 Then MsgBox "x is greater than 10"二、多行 If...Then...End If 语句 If x > 10 ThenMsgBox "x is greater than 10"y x 5 End If 三、If...Then...Else 语句 If condition Then 当条件为真时执行的代码块stateme…

JS 函数的基本知识

目录 1. 介绍函数 2. 使用函数 3. 函数传参 3.1 传递默认值 3.2 传递数组 3.3 传递变量 4. 函数返回值 5. 匿名函数 6. 立即执行函数 7. 注意 1. 介绍函数 在学习 CSS 样式过程中&#xff0c;经常有如下操作&#xff1a; 2. 使用函数 函数声明&#xff1a; 函数命名规…

澳鹏通过高质量数据支持 Onfido 优化AI反欺诈功能

“Appen 在 Onfido 的发展中发挥了至关重要的作用&#xff0c;并已成为我们运营的重要组成部分。我们很高兴在 Appen 找到了可靠的合作伙伴。” – Onfido 数据和分析总监 Francois Jehl 简介&#xff1a;利用人工智能和机器学习增强欺诈检测 在当今日益数字化的世界&#xff…

【大模型】Spring AI Alibaba 对接百炼平台大模型使用详解

目录 一、前言 二、Spring AI概述 2.1 spring ai是什么 2.2 Spring AI 核心能力 2.3 Spring AI 应用场景 三、Spring AI Alibaba 介绍 3.1 Spring AI Alibaba 是什么 3.2 Spring AI Alibaba 核心特点 3.3 Spring AI Alibaba 应用场景 四、SpringBoot 对接Spring AI Al…

小白学习之路:咖啡叶锈病分割

咖啡叶锈病分割系统源码&#xff06;数据集分享 [yolov8-seg-C2f-Faster-EMA&#xff06;yolov8-seg-SPPF-LSKA等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Globa…

RabbitMQ设置TTL(消息过期)时间(重要)

RabbitMQ设置消息过期时间 1、过期消息&#xff08;死信&#xff09;2、设置消息过期的两种方式2.1、设置单条消息的过期时间2.1.1、配置文件application.yml2.1.2、配置类RabbitConfig2.1.3、发送消息业务类service&#xff08;核心代码&#xff09;2.1.4、启动类2.1.5、依赖文…

让你的网站与众不同:6款独特播放器设计

文章目录 前言正文1.可拖动播放列表2.强调无障碍设计3.材质设计风格音频播放器4.旋转的黑胶唱片设计5.流畅且响应迅速6.带悬停标签的控制按钮 总结 前言 随着HTML5的普及&#xff0c;网站轻松添加音视频内容变得简单&#xff0c;但默认播放器功能有限&#xff0c;无法满足个性…

ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘ 的参考解决方法

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 ROS-Noetic 一、问题描述 自己在通过 pip install 安装module时 &#xff08;使用的是 pip install mmcv&#xff09;遇到如下问题&#xff1a; ImportError: cannot …

AI, Machine Learning, Deep Learning 和 Generative AI

人工智能的采用开始得相当缓慢&#xff0c;大多数人甚至不知道它的存在&#xff0c;即使知道&#xff0c;也似乎还需要 5 到 10 年的时间&#xff0c;但后来机器学习、深度学习等东西出现了&#xff0c;我们开始看到一些应用&#xff0c;然后基础模型出现了。 AI 人工智能&am…

C# 一个工具类让winform自动根据窗体大小自适应缩放所有控件

AutoControlSize.cs工具类&#xff0c;功能是使控件尺寸随着主对话框尺寸按比例调整。并且使用方式十分简单&#xff0c;只需要调用两个函数即可实现整个页面的控件根据窗体的大小改变而跟着缩放。 1、使用效果如下&#xff1a; 未缩放前的原始窗体页面 缩放后的窗体页面&…

用 Python 从零开始创建神经网络(二):第一个神经元的进阶

第一个神经元的进阶 引言1. Tensors, Arrays and Vectors&#xff1a;2. Dot Product and Vector Additiona. Dot Product &#xff08;点积&#xff09;b. Vector Addition &#xff08;向量加法&#xff09; 3. A Single Neuron with NumPy4. A Layer of Neurons with NumPy5…

VS2022项目配置笔记

文章目录 $(ProjectDir&#xff09;与 $(SolutionDir) 宏附加包含目录VC目录和C/C的区别 $(ProjectDir&#xff09;与 $(SolutionDir) 宏 假设有一个解决方案 MySolution&#xff0c;其中包含两个项目 ProjectA 和 ProjectB&#xff0c;目录结构如下&#xff1a; C:\Projects\…

十五、Linux线程(二)

4.线程的分离属性 通过属性设置线程的分离 1.线程属性类型&#xff1a; pthread_attr_t attr; 2.线程属性操作函数&#xff1a; &#xff08;1&#xff09;对线程属性变量的初始化 int pthread_attr_init(pthread_attr_t* attr); &#xff08;2&#xff09;设置线程分离属…

Unity学习笔记(1):素材导入

文章目录 前言学习目标开发环境资源文件下载Unity窗口设置修改导入像素素材设置可以直接拖动导入设置像素图片格式导入多合一素材设置切割 总结 前言 最近由于工作的事情&#xff0c;很糟心。最近非常的迷茫。 学习目标 根据我的加的几个QQ群了解到&#xff0c;国内游戏行业…

简历模板(艺术风)

每份简历模板都有四页 36款艺术风简历模板 一、水墨古风 二、唯美淡雅 三、时尚个性 四、艺术气质

数字乡村建设方案-5

1. 政策背景与乡村振兴战略 中国政府提出的乡村振兴战略&#xff0c;旨在全面建设小康社会和社会主义现代化国家&#xff0c;其中数字乡村建设是实现乡村全面振兴的关键途径。国家和江苏省相继出台政策&#xff0c;推动信息技术与农业生产生活的深度融合&#xff0c;加快农业农…