自动驾驶轨迹生成-贝塞尔(Bézier)曲线

news2024/11/16 21:49:01

引言

最近刚看完贝塞尔曲线,工作就遇到了相应的需求,所以写一下过程。主要讲的是自动驾驶中,车换道时用到贝塞尔曲线,当然其他的很多领域也会有,例如图形学等。

在车遇到障碍物或者是前车速度较慢的时候,就会进入换道逻辑,那么如何从一个车道换到另外一个车道,同时要保证车里面的人的一个体感问题,就是如何平滑过度,这就是为什么要使用贝塞尔曲线来做换道时的轨迹生成了,OK那开始讲贝塞尔曲线了。

什么是贝塞尔曲线?

1. 一阶贝塞尔曲线:

在这里插入图片描述

对于一阶贝塞尔曲线,从上图我们可以看到,它是一条直线,通过几何知识,很容易根据t 的值,得出线段上某个点的坐标:
B 1 ( t ) = P 0 + ( P 1 − P 0 ) t B_{1}(t) = P_{0} + (P_{1}-P_{0})t B1(t)=P0+(P1P0)t

或者
B 1 ( t ) = ( 1 − t ) P 0 + P 1 t , 0 < t < 1 B_{1}(t) = (1-t)P_{0} + P_{1}t , 0<t<1 B1(t)=(1t)P0+P1t,0<t<1
一阶贝塞尔曲线, 就是根据t来的线性插值。P0和P1表示的是一个向量[ x , y ] , 其中x、y是分别按照这个公式来计算的。

2. 二阶贝塞尔曲线:

定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。

  • 由 P0 至 P1 的连续点 Q0,描述一条线段
  • 由 P1 至 P2 的连续点 Q1,描述一条线段
  • 由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线
    在这里插入图片描述

上面的红色曲线就是通过二阶公式生成的贝塞尔曲线(是不是很平滑🤗🤗🤗)。
下面是知乎大佬的原理讲解,比较好理解。

在这里插入图片描述
在这里插入图片描述
在平面内任选 3 个不共线的点,依次用线段连接。在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。
在这里插入图片描述
根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC。
在这里插入图片描述
这时候DE又是一条直线了, 就可以按照一阶的贝塞尔方程来进行线性插值了, t= AD:AE
这时候就可以推出公式了

在这里插入图片描述
下面的P都是向量,如[x,y]。

P 0 ′ ( t ) = ( 1 − t ) P 0 + P 1 t , 0 < t < 1 P_{0}'(t) = (1-t)P_{0} + P_{1}t , 0<t<1 P0(t)=(1t)P0+P1t,0<t<1
<==>对应着上图绿色线段的左端点。

P 1 ′ = ( 1 − t ) P 1 + P 2 t P_{1}' = (1-t)P_{1} + P_{2}t P1=(1t)P1+P2t
<==>对应着上图绿色线段的右端点。

B 2 ( t ) = ( 1 − t ) P 0 ′ + P 1 ′ t B_{2}(t) = (1-t)P_{0}' + P_{1}'t B2(t)=(1t)P0+P1t
= ( 1 − t ) ( ( 1 − t ) P 0 + t P 1 ) + t ( ( 1 − t ) P 1 + t P 2 ) = (1-t)((1-t)P_{0}+tP_{1})+t((1-t)P_{1}+tP_{2}) =(1t)((1t)P0+tP1)+t((1t)P1+tP2)
= ( 1 − t ) 2 P 0 + 2 t ( 1 − t ) P 1 + t 2 P 2 = (1-t)^2P_{0}+2t(1-t)P_{1}+t^2P_{2} =(1t)2P0+2t(1t)P1+t2P2
<==>对应着绿色线段的一阶贝塞尔曲线(线性插值)。

整理一下公式, 得到二阶贝塞尔公式:

B 2 ( t ) = ( 1 − t ) 2 P 0 + 2 t ( 1 − t ) P 1 + t 2 P 2 B_{2}(t) = (1-t)^2P_{0}+2t(1-t)P_{1}+t^2P_{2} B2(t)=(1t)2P0+2t(1t)P1+t2P2

3. 三阶贝塞尔曲线:

在这里插入图片描述

4. 高阶贝塞尔公式:

可以通过递归的方式来理解贝塞尔曲线, 但是还是给出公式才方便计算的。划重点了: 系数是二项式的展开. 后面的很多的贝塞尔曲线的性质都可以用这个来解释
P ( t ) = ∑ i = 0 n P i B i , n ( t ) , 0 < = t < = 1 P_{}(t) = \sum_{i=0}^{n}P_{i}B_{i,n}(t),0<=t<=1 P(t)=i=0nPiBi,n(t),0<=t<=1
请添加图片描述
随着阶数的变化,图像中贝塞尔曲线也会跟着变化。
在这里插入图片描述

通过上面图,可以看出,最终的(红色)曲线,就是对这几个点进行拟合得到的贝塞尔曲线。

n个控制点对应着n-1 阶的贝塞尔曲线。

高阶的贝塞尔可以通过不停的递归直到一阶:

  • 4次贝塞尔曲线需要【递归】用到3次贝塞尔曲线;
  • 3次贝塞尔曲线需要【递归】用到2次贝塞尔曲线;
  • 2次贝塞尔曲线需要【递归】用到1次贝塞尔曲线;
  • 1次贝塞尔曲线就是线性插值,就是上面动图中的第一个图。

贝塞尔曲线的性质:

  • 阶次是控制点个数减1。它限定了,给你n个点,你如果要使用贝塞尔曲线,那么只能使用n-1次贝塞尔曲线来拟合,这个限制条件不太友好;
  • 牵一发动全身,移动一个控制点,整段曲线都会变化。

贝塞尔曲线的凸包性质

  • 贝塞尔曲线始终会在包含了所有控制点的最小凸多边形中, 不是按照控制点的顺序围成的最小多边形。这点大家一定注意. 这一点的是很关键的,也就是说可以通过控制点的凸包来限制规划曲线的范围,在路径规划是很需要的一个性质.

  • 凸包可以理解为,有一堆点集,使用一个橡皮筋来套住所有点,最后橡皮筋围成的形状,就是这些点集的凸包。上面最后一个图的5个点中,其实最后一个点P4不是在凸多边形上,而是在这些点组成的凸包内部。

  • 用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。

贝塞尔曲线的生成

通过选取几个控制点来生成贝塞尔曲线,该曲线为自动驾驶车的参考线,车沿着轨迹走。
QT代码的实现:

//递归
double factorial(int n){
    if(n<=1){
        return 1;
    }else {
        return factorial(n-1)*n;
    }
}

//贝塞尔公式
Vector2d bezierCommon(vector<Vector2d> Ps, double t){
    if(Ps.size()==1){
        return Ps[0];
    }
    Vector2d p_t(0.,0.);
    int n = Ps.size()-1;
    for (int  i= 0;  i< Ps.size(); i++) {
        double C_n_i = factorial(n)/(factorial(i)*factorial(n-i));
        p_t += C_n_i*pow((1-t),(n-i))*pow(t,i)*Ps[i];
    }
    return p_t;
}

//主函数调用
int MainWindow::bezier(){
    vector<Vector2d>Ps{Vector2d (0,1),Vector2d(1,3),Vector2d(3,1),Vector2d(4,6),Vector2d(5,9)};
    vector<double>x_ref,y_ref;
    for(int i=0;i<Ps.size();i++){
        x_ref.push_back(Ps[i][0]);
        y_ref.push_back(Ps[i][1]);
    }

    for(int t=0;t<100;t++){

        Vector2d pos = bezierCommon(Ps,(double)t/100);
        x_.push_back(pos[0]);
        y_.push_back(pos[1]);
    return 0;
}


 /****************************************************
     *贝塞尔曲线画图
     ****************************************************/
    int result = bezier();
    QSplineSeries* series = new QSplineSeries();   // 创建一个样条曲线对象
    QScatterSeries* seriesPoint = new QScatterSeries(); //散点
    series->setName("曲线");
    #if 1
        // 添加生成的贝塞尔曲线的点
        for (int var = 0; var < x_.size(); ++var) {
            series->append(x_[var],y_[var]);
        }
        // 添加控制点
        *seriesPoint << QPointF(0, 1)<< QPointF(1, 3)<< QPointF(3, 1)<< QPointF(4, 6)<<QPointF(5,9);
    #else  // 添加数据方式3,一次性更新所有数据
        QList<QPointF> points;
        for(int i = 0; i < 20; i++)
        {
            points.append(QPointF(i, i %7));
        }
        series->replace(points);
    #endif

参考:
https://www.zhihu.com/question/29565629

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

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

相关文章

【编译原理核心知识点总结】第三章、正则文法、NFA、DFA

阅读规范&#xff1a; 本文以重点为主&#xff0c;零碎知识点/相对不够重要的为辅助阅读内容&#xff0c;以脚注形式给出&#xff0c;点击脚注即可快速跳转。助解题目通常是为了帮助理解给出的题目&#xff0c;考试不考&#xff0c;若已理解可直接跳过。文中提到的课本是陈火旺…

SpringBoot 并发编程学习历程(绝对的干货)

如果一个项目总用单线程来跑&#xff0c;难免会遇到一些性能问题&#xff0c;所以再开发中&#xff0c;我们应该尽量适量的使用多线程&#xff08;在保证线程安全的情况下&#xff09;。 本教程大概目录&#xff1a; 1.模拟单线程情节 2.用Callable实现 并发编程 3.用DeferedR…

高并发系统设计 --基于bitmap的用户签到

业务需求分析 一般像微博&#xff0c;各种社交软件&#xff0c;游戏等APP&#xff0c;都会有一个签到功能&#xff0c;连续签到多少天&#xff0c;送什么东西&#xff0c;比如&#xff1a; 签到1天送10积分&#xff0c;连续签到2天送20积分&#xff0c;3天送30积分&#xff0…

Qt之QDrag的使用(含源码+注释)

一、效果示例图 提示&#xff1a;主控件&#xff08;CDragTest界面&#xff0c;UI中中包含CWidget界面&#xff09;&#xff1b;子控件&#xff08;CWidget界面&#xff0c;在CDragTest界面添加&#xff09; 提示&#xff1a;源码中拖拽数据设置的文本不同&#xff0c;是博主准…

【ONE·C || 分支循环】

总言 C语言&#xff1a;分支循环。 文章目录总言1、分支语句1.1、if语句1.1.1、基本格式1.1.2、逻辑真假与悬空else1.1.3、练习1.2、switch语句1.2.1、基本格式&#xff1a;break、case、default1.2.2、练习&#xff1a;switch语句嵌套2、循环语句2.1、while循环2.1.1、基本格式…

uniapp实现界面可任意拖动小图标

uniapp实现界面可任意拖动小图标一、问题&#xff1a;二、解决步骤2.1 根据uni-app官方提供的案例&#xff0c;创建组件2.2 在需要的界面引入组件使用额外注意一、问题&#xff1a; 例如购物车小图标可任意拖动 二、解决步骤 2.1 根据uni-app官方提供的案例&#xff0c;创建…

Kubernetes教程(二)---集群网络之 Flannel 核心原理

来自&#xff1a;指月 https://www.lixueduan.com 原文&#xff1a;https://www.lixueduan.com/posts/kubernetes/02-cluster-network/ 本文主要记录了 Kubernetes 集群网络方案之 Flannel 核心原理详解&#xff0c;包括其隧道方案中的两种&#xff1a;UDP 实现和 VXLAN 实现…

Mysql之增删改查

这里的增删改查主要是对应表中的数据&#xff0c;不像前一篇那个列类型&#xff0c;耳机具体的哪一条数据 Insert 其实我们前面都用过好多次了 比如下面那个 可以 关于那个表名后面加不加&#xff08;列类型&#xff09;&#xff0c;下面有解释 INSERT INTO shanpin VALUES…

关于yolov8一些训练的情况

U神出品了最新的yolov8&#xff0c;从公开的参数量来看确实很优秀&#xff01;&#xff01;&#xff01;&#xff01;比如下图得一些指标&#xff1a; 可以看到s模型640得map已经达到了44.9&#xff0c;v8n得map也已经达到了37.3&#xff0c;很强了&#xff0c;但是实际上是怎么…

Python爬虫之Scrapy框架系列(3)——项目实战【某瓣top250电影信息获取】

目录&#xff1a;1. 某瓣电影top250首页电影信息的获取&#xff01;1.创建项目&#xff1a;2.创建爬虫文件&#xff1a;3.运行爬虫文件&#xff1a;4.设置请求头&#xff1a;5.获取到电影名字&#xff1a;5.1 使用shell交互式平台&#xff1a;5.1.1 首先&#xff1a;打开我们的…

239页10万字“联、管、用”三位一体雪亮工程整体建设方案

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目录 1、 项目概述 1.1 项目背…

用R语言绘制泰勒级数的逼近过程

文章目录泰勒级数是如何被发现的用图像理解Taylor级数的逼近过程前情提要 R语言微积分极限π,e,γ\pi, e, \gammaπ,e,γ洛必达法则连续性和导数数值导数差商与牛顿插值方向导数 泰勒级数是如何被发现的 如果我是泰勒&#xff0c;我会把思考的起点建立在这样的一个等式上 f(n…

Windows10电脑重装系统详细步骤(纯净版)

目录 前言&#xff1a; 一、准备工作 二、下载pe工具 三、下载系统镜像ISO文件 获取方式一 获取方式二 获取方式三 四、进入pe系统 1.检查以上的准备工作是否完成 2.然后拔出来u盘插入要重装的电脑上面 3.然后按电源键开机&#xff08;不能点击重启&#xff01;&…

【Git 从入门到精通】使用Git将本地代码推送到Github

文章目录一、创建远程库二、Git操作远程库1.推送代码2.克隆代码3.拉取代码4.Pull request5.常用命令总结一、创建远程库 打开github.com&#xff0c;点击右上角加号&#xff0c;点击第一个选项。 填写库的基本信息&#xff0c;如果你想代码开源就选择public&#xff0c;否则就…

开发模型和测试模型

开发模型瀑布模型特点&#xff1a;线性结构&#xff0c;每个阶段只执行一次&#xff0c;必须完成上一个才能执行下一个。是其他模型的基础框架缺点&#xff1a;测试后置&#xff0c;1&#xff09;前面各个阶段的遗留的风险推迟到测试阶段才被发现&#xff0c;导致项目大面积返工…

【7】SCI易中期刊推荐——图像处理领域(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

【LGR-(-17)】洛谷入门赛 #8个人思考

T306713 Hello, 2023 题目背景 Goodbye, 2022 Hello, 2023 题目描述 某 E 在 2022 年的幸运数字是 xxx&#xff0c;这个数可能是正的&#xff0c;也可能是负的。 某 E 想要知道 xmod2023x \bmod 2023xmod2023 的值。其中&#xff0c;mod\bmodmod 是取模操作。也就是说&am…

数据结构:线性表的顺序表示和实现

在实际应用程序中涉及的线性表的基本操作都需要针对线性表的具体存储结构加以实现。线性表可以有两种存储表示方法:顺序存储表示和链式存储表示。下面我们先说说顺序存储表示。 1、顺序表——线性表的顺序存储表示 在计算机中表示线性表的最简单的方法是用一组地址连续的存储…

Linux:自动化构建工具make/Makefile

文章目录一.前言二.Makefile如何写入/make命令使用2.1清楚依赖关系和依赖方法2.2删除文件2.3Makefile中的关键字.PHONY2.4一个小补充一.前言 在此之前我们已经可以用vim编写代码和用gcc编译代码。但是如果现在要写一个大型项目&#xff0c;一下子写了很多源文件&#xff0c;在…

C. Zero Path(DP)

Problem - 1695C - Codeforces 给你一个有n行和m列的网格。我们用(i,j)表示第i(1≤i≤n)行和第j(1≤j≤m)列的方格&#xff0c;用aij表示那里的数字。所有的数字都等于1或等于-1。 你从方格&#xff08;1,1&#xff09;开始&#xff0c;每次可以向下或向右移动一个方格。最后&…