曲线生成 | 图解三次样条曲线生成原理(附ROS C++/Python/Matlab仿真)

news2024/10/1 1:26:10

目录

  • 0 专栏介绍
  • 1 什么是样条?
  • 2 三次样条曲线原理
    • 2.1 曲线插值
    • 2.2 边界条件
    • 2.3 系数反解
  • 3 算法仿真
    • 3.1 ROS C++仿真
    • 3.2 Python仿真
    • 3.3 Matlab仿真

0 专栏介绍

🔥附C++/Python/Matlab全套代码🔥课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。

🚀详情:图解自动驾驶中的运动规划(Motion Planning),附几十种规划算法


1 什么是样条?

样条(Spline)早期来源于工程制图,为了将一些固定点连成一条光滑曲线,采用具有弹性的木条固定在这些点上,通过样条作出的曲线经过各固定点且连续光滑,如图所示

在这里插入图片描述

后来,样条发展成一种平滑曲线的数学表示方法。它通过连接一系列给定的数据点(节点)来构建曲线,以便在这些节点上产生平滑的过渡。通常情况下,样条曲线是由多个连续的二次或三次函数组成,每个函数都在相邻节点之间定义。这些连续的函数被称为样条段,它们共同组成了整个曲线

样条是在各个领域中广泛应用的一种技术,例如计算机图形学、物理学模拟、金融和经济分析等。在计算机图形学中,样条通常用于创建平滑的曲线和曲面,以便在三维场景中呈现出更真实的效果。在物理学模拟中,样条可用于描述物体的运动轨迹和变形过程。在金融和经济分析中,样条可用于拟合和预测时间序列数据,例如股票价格和货币汇率

本节介绍常见的三次样条曲线(Cubic Splines)原理

2 三次样条曲线原理

2.1 曲线插值

给定一系列插值点

X = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ⋯   , ( x n − 1 , y n − 1 ) } X=\left\{ \left( x_0,y_0 \right) ,\left( x_1,y_1 \right) ,\cdots ,\left( x_{n-1},y_{n-1} \right) \right\} X={(x0,y0),(x1,y1),,(xn1,yn1)}

相邻两点间通过多项式曲线连接,因此共需要拼接 n − 1 n-1 n1段曲线。定义三次多项式曲线为

f i ( x ) = a i + b i ( x − x i ) + c i ( x − x i ) 2 + d i ( x − x i ) 3    i = 0 , 1 , ⋯   , n − 1 f_i\left( x \right) =a_i+b_i\left( x-x_i \right) +c_i\left( x-x_i \right) ^2+d_i\left( x-x_i \right) ^3\,\,i=0,1,\cdots ,n-1 fi(x)=ai+bi(xxi)+ci(xxi)2+di(xxi)3i=0,1,,n1

其中,当 i = n − 1 i=n-1 i=n1时的曲线是辅助曲线,用于计算前 n − 1 n-1 n1段曲线而不参与实际拼接。对于三次曲线,给出四个约束条件为

{ 过插值点 : f i ( x i ) = y i 曲线连续 : f i ( x i + 1 ) = y i + 1 一阶连续 : f ˙ i ( x i + 1 ) = f ˙ i + 1 ( x i + 1 ) 二阶连续 : f ¨ i ( x i + 1 ) = f ¨ i + 1 ( x i + 1 ) ⇒ h i = x i + 1 − x i { a i = y i a i + b i h i + c i h i 2 + d i h i 3 = y i + 1 b i + 2 c i h i + 3 d i h i 2 = b i + 1 c i + 3 d i h i = c i + 1 \begin{cases} \text{过插值点}: f_i\left( x_i \right) =y_i\\ \text{曲线连续}: f_i\left( x_{i+1} \right) =y_{i+1}\\ \text{一阶连续}: \dot{f}_i\left( x_{i+1} \right) =\dot{f}_{i+1}\left( x_{i+1} \right)\\ \text{二阶连续}: \ddot{f}_i\left( x_{i+1} \right) =\ddot{f}_{i+1}\left( x_{i+1} \right)\\\end{cases}\xRightarrow{h_i=x_{i+1}-x_i}\begin{cases} a_i=y_i\\ a_i+b_ih_i+c_ih_{i}^{2}+d_ih_{i}^{3}=y_{i+1}\\ b_i+2c_ih_i+3d_ih_{i}^{2}=b_{i+1}\\ c_i+3d_ih_i=c_{i+1}\\\end{cases} 过插值点:fi(xi)=yi曲线连续:fi(xi+1)=yi+1一阶连续:f˙i(xi+1)=f˙i+1(xi+1)二阶连续:f¨i(xi+1)=f¨i+1(xi+1)hi=xi+1xi ai=yiai+bihi+cihi2+dihi3=yi+1bi+2cihi+3dihi2=bi+1ci+3dihi=ci+1

联立上式,用系数 统一表示其他参数可得

h i c i + 2 ( h i + h i + 1 ) c i + 1 + h i + 1 c i + 2 = 3 ( y i + 2 − y i + 1 h i + 1 − y i + 1 − y i h i ) h_ic_i+2\left( h_i+h_{i+1} \right) c_{i+1}+h_{i+1}c_{i+2}=3\left( \frac{y_{i+2}-y_{i+1}}{h_{i+1}}-\frac{y_{i+1}-y_i}{h_i} \right) hici+2(hi+hi+1)ci+1+hi+1ci+2=3(hi+1yi+2yi+1hiyi+1yi)

其他参数表示为

{ a i = y i b i = y i + 1 − y i h i − c i + 1 + 2 c i 3 h i d i = c i + 1 − c i 3 h i \begin{cases} a_i=y_i\\ b_i=\frac{y_{i+1}-y_i}{h_i}-\frac{c_{i+1}+2c_i}{3}h_i\\ d_i=\frac{c_{i+1}-c_i}{3h_i}\\\end{cases} ai=yibi=hiyi+1yi3ci+1+2cihidi=3hici+1ci

2.2 边界条件

注意到关于 c i c_i ci的线性方程仅有 n − 2 n-2 n2个,而未知向量

c = [ c 0 c 1 ⋯ c n − 2 c n − 1 ] T \boldsymbol{c}=\left[ \begin{matrix} c_0& c_1& \cdots& c_{n-2}& c_{n-1}\\\end{matrix} \right] ^T c=[c0c1cn2cn1]T

共有 n n n个元素,欠定方程组不足以进行求解。这是因为曲线首末处没有拼接约束,需要人为设定边界条件,常用的边界条件有

  • 自然边界(Natural Spline):令端点二阶导为零,即
    f 0 ′ ′ ( x 0 ) = f n − 1 ′ ′ ( x n − 1 ) = 0 f_{0}^{''}\left( x_0 \right) =f_{n-1}^{''}\left( x_{n-1} \right) =0 f0′′(x0)=fn1′′(xn1)=0
  • 固定边界(Clamped Spline):令端点一阶导为常数,即
    f 0 ′ ( x 0 ) = A , f n − 1 ′ ( x n − 1 ) = B f_{0}^{'}\left( x_0 \right) =A,f_{n-1}^{'}\left( x_{n-1} \right) =B f0(x0)=A,fn1(xn1)=B
  • 非扭结边界(Not-A-Knot Spline):令前两个点与最后两个点的三阶导值相等,即
    f 0 ′ ′ ′ ( x 0 ) = f 1 ′ ′ ′ ( x 1 ) , f n − 2 ′ ′ ′ ( x n − 2 ) = f n − 1 ′ ′ ′ ( x n − 1 ) f_{0}^{'''}\left( x_0 \right) =f_{1}^{'''}\left( x_1 \right) , f_{n-2}^{'''}\left( x_{n-2} \right) =f_{n-1}^{'''}\left( x_{n-1} \right) f0′′′(x0)=f1′′′(x1),fn2′′′(xn2)=fn1′′′(xn1)

2.3 系数反解

本节选择自然边界,则 c 0 = c n − 1 = 0 c_0=c_{n-1}=0 c0=cn1=0,将关于 c i c_i ci的线性方程改写为矩阵形式

[ 1 h 0 2 ( h 0 + h 1 ) h 1 h 1 2 ( h 1 + h 2 ) h 2 h 2 2 ( h 2 + h 3 ) h 3 ⋱ 1 ] [ c 0 c 1 c 2 c 3 ⋮ c n − 1 ] = 3 [ 0 y 2 − y 1 h 1 − y 1 − y 0 h 0 y 3 − y 2 h 2 − y 2 − y 1 h 1 y 4 − y 3 h 3 − y 3 − y 2 h 2 ⋮ 0 ] \left[ \begin{matrix} 1& & & & & \\ h_0& 2\left( h_0+h_1 \right)& h_1& & & \\ & h_1& 2\left( h_1+h_2 \right)& h_2& & \\ & & h_2& 2\left( h_2+h_3 \right)& h_3& \\ & & & & \ddots& \\ & & & & & 1\\\end{matrix} \right] \left[ \begin{array}{c} c_0\\ c_1\\ c_2\\ c_3\\ \vdots\\ c_{n-1}\\\end{array} \right] =3\left[ \begin{array}{c} 0\\ \frac{y_2-y_1}{h_1}-\frac{y_1-y_0}{h_0}\\ \frac{y_3-y_2}{h_2}-\frac{y_2-y_1}{h_1}\\ \frac{y_4-y_3}{h_3}-\frac{y_3-y_2}{h_2}\\ \vdots\\ 0\\\end{array} \right] 1h02(h0+h1)h1h12(h1+h2)h2h22(h2+h3)h31 c0c1c2c3cn1 =3 0h1y2y1h0y1y0h2y3y2h1y2y1h3y4y3h2y3y20

该方程组有唯一解

3 算法仿真

3.1 ROS C++仿真

核心代码如下所示

std::vector<double> CubicSpline::spline(std::vector<double> s_list, std::vector<double> dir_list, std::vector<double> t)
{
  // cubic polynomial functions
  std::vector<double> a = dir_list;
  std::vector<double> b, d;

  size_t num = s_list.size();

  std::vector<double> h;
  for (size_t i = 0; i < num - 1; i++)
    h.push_back(s_list[i + 1] - s_list[i]);

  // calculate coefficient matrix
  Eigen::MatrixXd A = Eigen::MatrixXd::Zero(num, num);
  for (size_t i = 1; i < num - 1; i++)
  {
    A(i, i - 1) = h[i - 1];
    A(i, i) = 2.0 * (h[i - 1] + h[i]);
    A(i, i + 1) = h[i];
  }
  A(0, 0) = 1.0;
  A(num - 1, num - 1) = 1.0;

  Eigen::MatrixXd B = Eigen::MatrixXd::Zero(num, 1);
  for (size_t i = 1; i < num - 1; i++)
    B(i, 0) = 3.0 * (a[i + 1] - a[i]) / h[i] - 3.0 * (a[i] - a[i - 1]) / h[i - 1];

  Eigen::MatrixXd c = A.lu().solve(B);
  for (size_t i = 0; i < num - 1; i++)
  {
    b.push_back((a[i + 1] - a[i]) / h[i] - h[i] * (c(i + 1) + 2.0 * c(i)) / 3.0);
    d.push_back((c(i + 1) - c(i)) / (3.0 * h[i]));
  }

  // calculate spline value and its derivative
  std::vector<double> p;
  for (const auto it : t)
  {
    auto iter = std::find_if(s_list.begin(), s_list.end(), [it](double val) { return val > it; });
    if (iter != s_list.end())
    {
      size_t idx = std::distance(s_list.begin(), iter) - 1;
      double ds = it - s_list[idx];
      p.push_back(a[idx] + b[idx] * ds + c(idx) * std::pow(ds, 2) + d[idx] * std::pow(ds, 3));
    }
  }
  return p;
}

3.2 Python仿真

核心代码如下所示

def spline(self, x_list: list, y_list: list, t: list):
	# cubic polynomial functions
	a, b, c, d = y_list, [], [], []
	h = np.diff(x_list)
	num = len(x_list)

	# calculate coefficient matrix
	A = np.zeros((num, num))
	for i in range(1, num - 1):
		A[i, i - 1] = h[i - 1]
		A[i, i] = 2.0 * (h[i - 1] + h[i])
		A[i, i + 1] = h[i]
	A[0, 0] = 1.0
	A[num - 1, num - 1] = 1.0

	B = np.zeros(num)
	for i in range(1, num - 1):
		B[i] = 3.0 * (a[i + 1] - a[i]) / h[i] - \
				3.0 * (a[i] - a[i - 1]) / h[i - 1]

	c = np.linalg.solve(A, B)
	for i in range(num - 1):
		d.append((c[i + 1] - c[i]) / (3.0 * h[i]))
		b.append((a[i + 1] - a[i]) / h[i] - h[i] * (c[i + 1] + 2.0 * c[i]) / 3.0)

	# calculate spline value and its derivative
	p, dp = [], []
	for it in t:
		if it < x_list[0] or it > x_list[-1]:
			continue
		i = bisect.bisect(x_list, it) - 1
		dx = it - x_list[i]
		p.append(a[i] + b[i] * dx + c[i] * dx**2 + d[i] * dx**3)
		dp.append(b[i] + 2.0 * c[i] * dx + 3.0 * d[i] * dx**2)

	return p, dp

在这里插入图片描述

3.3 Matlab仿真

核心代码如下所示

function p = spline(s_list, dir_list, t)
    % cubic polynomial functions
    a = dir_list;
    
    [num, ~] = size(s_list);
    
    h = diff(s_list);

    % calculate coefficient matrix
    A = zeros(num, num);
    for i=2:num - 1
        A(i, i - 1) = h(i - 1);
        A(i, i) = 2.0 * (h(i - 1) + h(i));
        A(i, i + 1) = h(i);
    end
  A(1, 1) = 1.0;
  A(num, num) = 1.0;

  B = zeros(num, 1);
  for i=2:num - 1
      B(i, 1) = 3.0 * (a(i + 1) - a(i)) / h(i) - 3.0 * (a(i) - a(i - 1)) / h(i - 1);
  end

  c = A \ B;
  
  b = zeros(num - 1, 1); d = zeros(num - 1, 1);
  for i=1:num - 1
    b(i) = (a(i + 1) - a(i)) / h(i) - h(i) * (c(i + 1) + 2.0 * c(i)) / 3.0;
    d(i) = (c(i + 1) - c(i)) / (3.0 * h(i));
  end

  % calculate spline value and its derivative
  p = [];
  for i =1:length(t)
      idx = find(s_list > t(i));
      if ~isempty(idx)
          id = idx(1) - 1;
          ds = t(i) - s_list(id);
          p = [p; a(id) + b(id) * ds + c(id) * power(ds, 2) + d(id) * power(ds, 3)];
      end
  end
end

在这里插入图片描述

完整工程代码请联系下方博主名片获取


🔥 更多精彩专栏

  • 《ROS从入门到精通》
  • 《Pytorch深度学习实战》
  • 《机器学习强基计划》
  • 《运动规划实战精讲》

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

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

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

相关文章

二.Winform使用Webview2在Demo1中实现地址简单校验

Winform使用Webview2在Demo1中实现地址简单校验 往期目录回顾添加对于的简单url验证提示通过上节和本节涉及到的函数有 往期目录 往期相关文章目录 专栏目录 回顾 通过一.Winform使用Webview2(Edge浏览器核心) 创建demo(Demo1)实现回车导航到指定地址 我们已经知道了解决资源…

C语言第五弹---分支语句(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 分支语句 1、if语句1.1、if1.2、 else1.3、 分支中包含多条语句1.4、嵌套if1.5、 悬空else问题 2、关系操作符3、 条件操作符总结 C语言是结构化的程序设计语言&…

爬虫案例—抓取找歌词网站的按歌词找歌名数据

爬虫案例—抓取找歌词网站的按歌词找歌名数据 找个词网址&#xff1a;https://www.91ge.cn/lxyyplay/find/ 目标&#xff1a;抓取页面里的所有要查的歌词及歌名等信息&#xff0c;并存为txt文件 一共46页数据 网站截图如下&#xff1a; 抓取完整歌词数据&#xff0c;如下图…

【网络奇遇记】揭秘计算机网络性能指标:全面指南

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 速率1.1 数据量1.2 速率 二. 带宽三. 吞吐量四. 时延4.1 发送时延4.2 传播时延…

华南理工大学数字信号处理实验实验二源码(薛y老师)

一、实验目的 ▪ 综合运用数字信号处理的理论知识进行信号分析并利用MATLAB作为编程工具进行计算机实现&#xff0c;从而加 深对所学知识的理解&#xff0c;建立概念。 ▪ 掌握数字信号处理的基本概念、基本理论和基本方法。 ▪ 学会用MATLAB对信号进行分析和处理。 ▪ 用F…

[小程序]基于token的权鉴测试

一、服务器配置 服务器基于flask&#xff0c;需要额外安装flask_jwt_extended包 from flask import Flask #导入Flask包 from flask import request from flask import jsonify #用来返回json消息 from flask_jwt_extended import create_access_token, jwt_requi…

实战项目(一)内容管理系统

一、实现技术 前端技术&#xff1a;html、javascript(jquery、ajax、json)、css 后端技术&#xff1a;java、mysql、servlet 开发工具&#xff1a;eclipse、vscode 二、项目描述 首页仿写某大学网页&#xff0c;上面有各种栏目及栏目内容&#xff0c;管理员能登录进去对首…

基于 OpenVINO, yolov5 推理

OpenVINO 是英特尔开发的一款功能强大的深度学习工具包&#xff0c;可实现跨多个硬件平台的优化神经网络推理。在本文中&#xff0c;我们讨论了 OpenVINO 的特性和优势&#xff0c;以及它如何与领先的计算机视觉平台 Viso Suite 集成&#xff0c;以构建和交付可扩展的应用程序。…

Linux配置主机名-使用主机名访问服务器

主要需要对Hosts文件进行操作&#xff0c; Hosts是一个没有扩展名的系统文件&#xff0c;可以用记事本等工具打开&#xff0c;其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”&#xff0c; 先将自己的主机名设置成有意义&#xff0c;别人好记的样子&a…

Win10升级Win11后卡顿了?

目录 关闭动画效果 任务栏居中改为居左 调整外观和性能 其他 当你看到最后&#xff0c;还知道哪些升级WIN11后必做的优化呢&#xff1f;欢迎在评论区分享出来&#xff01;❤️ win11上市目前也有一段时间了&#xff0c;想必很多大家都已经进行更新了。新的系统确实更加简洁…

线性表--链表--单链表(不带头单向不循环链表)

关于顺序表存在的问题&#xff1a; 1.中间/头部的插⼊删除&#xff0c;时间复杂度为O(N) 2.增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不小的消耗 3.增容⼀般是呈2倍的增长&#xff0c;势必会有一定的空间浪费 要如何解决这些问题&#xff1f;用线性…

HCIA vlan练习

目录 实验拓扑 实验要求 实验步骤 1、交换机创建vlan 2、交换机上的各个接口划分到对应vlan中 3、trunk干道 4、路由器单臂路由 5、路由器DHCP设置 实验测试 华为交换机更换端口连接模式报错处理 实验拓扑 实验要求 根据图划分vlan&#xff0c;并通过DHCP给主机下发…

Android学习之路(22) ARouter原理解析

1.ARouter认知 首先我们从命名来看:ARouter翻译过来就是一个路由器。 官方定义&#xff1a; 一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦 那么什么是路由呢&#xff1f; 简单理解就是&#xff1a;一个公共平台转发系统 工作方式&…

vue项目中使用XgPlay.js播放视频

官网&#xff1a;西瓜播放器 1、首先安装下载 XgPlay.js依赖 npm i xgplayer --savenpm i xgplayer-hls.js --save2、页面引用 import FlvPlayer from "xgplayer-flv.js"; import "xgplayer/dist/index.min.css"; 3、建立dom容器 // 提供一个容器 <…

【Linux驱动】休眠与唤醒 | POLL机制 | 异步通知 | 阻塞与非阻塞 | 软件定时器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3d3;休眠与唤醒&#x1f3f8;内核函数&#x1f3f8;驱动框架及编程 &#x1f3d3;…

VC++中使用OpenCV进行形状和轮廓检测

VC中使用OpenCV进行形状和轮廓检测 在VC中使用OpenCV进行形状和轮廓检测&#xff0c;轮廓是形状分析以及物体检测和识别的有用工具。如下面的图像中Shapes.png中有三角形、矩形、正方形、圆形等&#xff0c;我们如何去区分不同的形状&#xff0c;并且根据轮廓进行检测呢&#…

re:从0开始的HTML学习之路 2. HTML的标准结构说明

1. <DOCTYPE html> 文档声明&#xff0c;用于告诉浏览器&#xff0c;当前HTML文档采用的是什么版本。 必须写在当前HTML文档的首行&#xff08;可执行代码的首行&#xff09; HTML4的此标签与HTML5不同。 2. <html lang“en”> 根标签&#xff0c;整个HTML文档中…

基于SpringBoot的SSM整合案例

项目目录: 数据库表以及表结构 user表结构 user_info表结构 引入依赖 父模块依赖: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.12.RELEASE</version>…

LINUX文件fd(file descriptor)文件描述符

目录 1.文件接口 1.1open 1.2C语言为什么要对open进行封装 2.fd demo代码 第一个问题 第二个问题 打开文件流程 引言&#xff1a;在学习C语言的时候&#xff0c;我们见过很多的文件的接口&#xff0c;例如fopen&#xff0c;fwrite&#xff0c;fclose等等&#xff0c;但…

Mac NTFS 磁盘读写工具选哪个好?Tuxera 还是 Paragon?

在使用 Mac 电脑时&#xff0c;我们经常需要读写 NTFS 格式的硬盘或 U 盘。然而&#xff0c;由于 Mac 系统不支持 NTFS 格式的读写&#xff0c;因此我们需要借助第三方工具来实现这个功能。而在市场上&#xff0c;Tuxera 和 Paragon 是两款备受推崇的 Mac NTFS 磁盘读写工具。那…