Pure-Pursuit 跟踪五次多项式轨迹

Pure-Pursuit 跟踪五次多项式轨迹

考虑双移线轨迹 X 轴方向位移较大,机械楼停车场长度无法满足 100 ~ 120 m,因此采用五次多项式进行轨迹规划,在轨迹跟踪部分也能水一些内容

调整 double_lane.cpp 为 ref_lane.cpp,结合 FrenetPath 类将参考路径的构造抽离为单独的函数

#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/Quaternion.h>
#include <nav_msgs/Path.h>
#include <ros/ros.h>
#include <std_msgs/String.h>

#include <iostream>
#include <string>
#include <vector>
#include <math.h>
#include <fstream>
#include <Eigen/Geometry>

#include "cpprobotics_types_double.h"
#include "frenet_path_double.h"
#include "quintic_polynomial_double.h"

using namespace std;
using namespace cpprobotics;

// 双移线参考路径在 X 方向长度以及参考点的步长
const float length = 120.0;
const float step = 0.1;

// 五次多项式轨迹参数
#define DT 0.01
const double T = 50.0; // t-t0经历的时间
const double xend = 25.0;
const double yend = 3.0;

array<float, 4> calEulerToQuaternion(const float roll, const float pitch, const float yaw)
{
    array<float, 4> calQuaternion = {0.0f, 0.0f, 0.0f, 1.0f}; // 初始化四元数

    // 使用Eigen库来进行四元数计算
    Eigen::Quaternionf quat;
    quat = Eigen::AngleAxisf(roll, Eigen::Vector3f::UnitX()) *
           Eigen::AngleAxisf(pitch, Eigen::Vector3f::UnitY()) *
           Eigen::AngleAxisf(yaw, Eigen::Vector3f::UnitZ());

    calQuaternion[0] = quat.x();
    calQuaternion[1] = quat.y();
    calQuaternion[2] = quat.z();
    calQuaternion[3] = quat.w();

    return calQuaternion;
}

FrenetPath fp;
void calc_frenet_paths()
{
    // 起始状态
    std::array<double, 3> x_start{0.0, 0.0, 0.0};
    std::array<double, 3> x_end{xend, 0.0, 0.0};
    // 终点状态
    std::array<double, 3> y_start{0.0, 0.0, 0.0};
    std::array<double, 3> y_end{yend, 0.0, 0.0};

    // 纵向
    QuinticPolynomial lon_qp(x_start[0], x_start[1], x_start[2], x_end[0],
                             x_end[1], x_end[2], T);
    // 横向
    QuinticPolynomial lat_qp(y_start[0], y_start[1], y_start[2], y_end[0],
                             y_end[1], y_end[2], T, xend);

    for (double t = 0; t < T; t += DT)
    {
        double x = lon_qp.calc_point_x(t);
        double xd = lon_qp.calc_point_xd(t);
        double xdd = lon_qp.calc_point_xdd(t);

        fp.t.emplace_back(t);
        fp.x.emplace_back(x);
        fp.x_d.emplace_back(xd);
        fp.x_dd.emplace_back(xdd);

        double y_x_t = lat_qp.calc_point_y_x(x);
        double y_x_d = lat_qp.calc_point_y_x_d(x);
        double y_x_t_d = lat_qp.calc_point_y_t_d(y_x_d, xd);

        double y_x_dd = lat_qp.calc_point_y_x_dd(x);
        double y_x_t_dd = lat_qp.calc_point_y_t_dd(y_x_dd, xd, y_x_d, xdd);

        fp.y.emplace_back(y_x_t);
        fp.y_d.emplace_back(y_x_t_d);
        fp.y_dd.emplace_back(y_x_t_dd);
        // 压入航向角
        // fp.threat.emplace_back(lat_qp.calc_point_thetar(y_x_t_d, xd));

        // 压入曲率
        fp.k.emplace_back(lat_qp.calc_point_k(y_x_dd, y_x_d));
        // fp.k.emplace_back(lat_qp.calc_point_k(y_x_t_dd, y_x_t_d, xdd, xd));
    }
    int num = fp.x.size();
    for (int i = 0; i < num; i++)
    {
        double dy = fp.y[i + 1] - fp.y[i];
        double dx = fp.x[i + 1] - fp.x[i];
        fp.threat.emplace_back(lat_qp.calc_point_thetar(dy, dx));
    }
    // 最后一个道路航向角和前一个相同
    // fp.threat.push_back(fp.threat.back());
}

void double_lane_path()
{
  // 双移线构造的参数
  const float shape = 2.4;
  const float dx1 = 25.0, dx2 = 21.95;
  const float dy1 = 4.05, dy2 = 5.7;
  const float Xs1 = 27.19, Xs2 = 56.46;

  int points_size = length / step;
  fp.x.resize(points_size);
  fp.y.resize(points_size);
  fp.threat.resize(points_size);
  fp.k.resize(points_size);
  for (int i = 0; i <= points_size; ++i)
  {
    // 计算参考路径点信息
    float ref_x = i * step;
    float z1 = shape / dx1 * (ref_x - Xs1) - shape / 2.0;
    float z2 = shape / dx2 * (ref_x - Xs2) - shape / 2.0;
    float ref_y = dy1 / 2.0 * (1 + tanh(z1)) - dy2 / 2.0 * (1 + tanh(z2));
    float ref_phi = atan(pow(dy1 * (1 / cosh(z1)), 2) * (1.2 / dx1) - pow(dy2 * (1 / cosh(z2)), 2) * (1.2 / dx2));

    float y_dot = dy1 / 2 * pow(1 / cosh(z1), 2) * (shape / dx1) - dy2 / 2 * pow(1 / cosh(z2), 2) * (shape / dx2);
    float y_ddot = -2 * dy1 * ((cosh(z1) - 1) / pow(cosh(z1), 3)) * pow(shape / dx1, 2) + 2 * dy2 * ((cosh(z2) - 1) / pow(cosh(z2), 3)) * pow(shape / dx2, 2);
    float ref_k = abs(y_ddot) / pow(1 + y_dot * y_dot, 1.5);
    
    fp.x[i] = ref_x;
    fp.y[i] = ref_y;
    fp.threat[i] = ref_phi;
    fp.k[i] = ref_k;
  }
}

int main(int argc, char *argv[])
{
    ros::init(argc, argv, "ref_lane");

    ros::NodeHandle nh;
    ros::Publisher path_pub = nh.advertise<nav_msgs::Path>("/ref_path", 1000, true);

    nav_msgs::Path reference_path;
    reference_path.header.frame_id = "world";
    reference_path.header.stamp = ros::Time::now();

    geometry_msgs::PoseStamped pose;
    pose.header.stamp = ros::Time::now();
    pose.header.frame_id = "world";

    // 五次多项式轨迹
    calc_frenet_paths();

    // 双移线轨迹
    // double_lane_path();

    int points_size = fp.x.size();
    reference_path.poses.resize(points_size);
    for (int i = 0; i < points_size; ++i)
    {        
        cout << fp.x[i] << "," << fp.y[i] << "," << fp.threat[i] << endl;
        // 计算四元数位姿
        array<float, 4> calQuaternion = calEulerToQuaternion(0.0, 0.0, fp.threat[i]);

        pose.pose.position.x = fp.x[i];
        pose.pose.position.y = fp.y[i];
        pose.pose.position.z = 0.0;
        pose.pose.orientation.x = calQuaternion[0];
        pose.pose.orientation.y = calQuaternion[1];
        pose.pose.orientation.z = calQuaternion[2];
        pose.pose.orientation.w = calQuaternion[3];
        reference_path.poses[i] = pose;
    }

    ros::Rate loop(10);
    while (ros::ok())
    {
        path_pub.publish(reference_path);
        ros::spinOnce();
        loop.sleep();
    }

    return 0;
}

跟踪过程如下

在这里插入图片描述

跟踪效果如下

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里只分析出横向跟踪误差,因为 y 与 x 的关系可以直接获得,yaw 与 x 的关系没有直接的表达式

y = 0.00000000 + 0.00000000 * x + 0.00000000 * x^2 + 0.00192000 * x^3 + -0.00011520 * x^4 + 0.00000184 * x^5

💡 需要完善对于五次多项式的学习

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenFeign入门

OpenFeign是Spring Cloud OpenFeign&#xff0c;是Spring Cloud团队开发的基于Feign的框架 1、OpenFeign功能升级 OpenFeign在Feign的基础上提供了以下增强和扩展功能 &#xff08;1&#xff09;便于集成Spring Cloud组件&#xff1a;OpenFeign与Spring Cloud其他组件&#…

float和double(浮点型数据)在内存中的储存方法

作者&#xff1a;元清加油 主页&#xff1a;主页 编译环境&#xff1a;visual studio 2022 (x86) 相信大家都知道数据在内存中是以二进制储存的 整数的储存方法是首位是符号位&#xff0c;后面便是数值位 那么浮点数在内存中是怎么储存的呢&#xff1f;我们先来看一个例子&am…

C语言-内存函数详解

文章目录 1. memcpy使用和模拟实现2. memmove使用和模拟实现3. memset函数的使用4. memcmp函数的使用 1. memcpy使用和模拟实现 返回类型和参数&#xff1a; void * memcpy ( void * destination, const void * source, size_t num );1.函数memcpy从source的位置开始向后复制…

叠加原理(superposition principle)、线性系统

叠加原理&#xff08;superposition principle&#xff09;&#xff1a;指对一个系统而言&#xff0c;两个或多个输入产生的输出&#xff0c;等于这几个输入单独引起的输出的和&#xff0c;即输入的叠加等于各输入单独引起的输出的叠加。 线性系统&#xff1a;一个系统&#x…

Java毕业设计 SpringBoot 车辆充电桩系统

Java毕业设计 SpringBoot 车辆充电桩系统 SpringBoot 车辆充电桩系统 功能介绍 首页 图片轮播 登录注册 充电桩展示 搜索充电桩 充电桩报修 充电常识 个人中心 我的收藏 后台管理 登录 首页 个人中心 维修员管理 用户管理 电桩类别管理 充电桩管理 充电桩报修管理 维修回复管…

B树与B+树的对比

B树&#xff1a; m阶B树的核心特性&#xff1a; 树中每个节点至多有m棵子树&#xff0c;即至多含有m-1个关键字根节点的子树数属于[2, m]&#xff0c;关键字数属于[1, m-1]&#xff0c;其他节点的子树数属于 [ ⌈ m 2 ⌉ , m ] [\lceil \frac{m}{2}\rceil, m] [⌈2m​⌉,m]&am…

机器学习:攻击方法FGSM系列

任务 FGSM I-FGSM MI-FGSM Ensemble Attack 攻击评价指标 准确率越低表明攻击越好 数据 预训练模型 BaseLine 实践

Java、PHP、C语言经典项目源码合集推荐(一)

&#xff08;一&#xff09;.Java智慧校园系统源码、 智慧学校源码、 智慧校园平台源码、智慧校园电子班牌系统源码、中小学智慧校园系统源码、 原生微信小程序端源码、电子班牌系统源码 项目技术栈 1、使用springboot框架Javavue2 2、数据库MySQL5.7 3、移动端小程序使用小程…

UWB实时定位系统源码,历史活动轨迹显示,视频联动,电子围栏

UWB实时定位系统源码&#xff0c;工厂企业人员安全定位&#xff0c;UWB源码 行业背景 工业企业多存在很多有毒有害、高危高压等生产环境&#xff0c;带电设备众多&#xff0c;容易发生安全事故&#xff1b;人员只能凭记忆遵守各项生产安全规范&#xff0c;如某些危险区域范围、…

运维 | 浅谈云计算的相关概念和分类

关注&#xff1a;CodingTechWork 云计算 云计算的出现 云计算是采用的按需付费的方式&#xff0c;通过互联网访问云服务器上的服务器、数据库等服务。云计算为何会出现&#xff1f;  如果现在一个企业想要进行软件管理部署&#xff0c;首先需要服务器主机和网络规划&#…

手把手教你如何在Linux下写进度条小程序(附源码)

效果展示 录屏2023 一、建立文件 mkdir ProgressBar //在当前目录下&#xff0c;建立新的目录 cd ProgressBar //进入这个目录 touch main.c makefile progressbar.c progressbar.h //在ProgressBar这个目录建立这几个文件进入ProgressBar这个目录之后&#xff0c;使…

【开源】基于Vue和SpringBoot的食品生产管理系统

项目编号&#xff1a; S 044 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S044&#xff0c;文末获取源码。} 项目编号&#xff1a;S044&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3…

过滤器的应用

javaWeb三剑客: 1. Servlet:接收请求,处理请求(单例,也就是说&#xff0c;多个用户请求的的servlet是同一个对象) 2. Filter:拦截请求(单例->也就是说&#xff0c;多个用户请求的的filter是同一个对象) 3. Listem: 监听用户/服务器行为,javaWeb三剑客: 过滤器的实现 1&…

Arrays.asList() 与 Collections.singletonList()的恩怨情仇

1. 概述 列表是我们使用 Java 时常用的集合类型。 众所周知&#xff0c;我们可以轻松地用一行初始化一个List。例如&#xff0c;当我们想要初始化一个只有一个元素的List时&#xff0c;我们可以使用Arrays.asList()方法或Collections.singletonList()方法。 在本文中&#x…

HCIP-十、BGP基础

十、BGP基础 实验拓扑实验需求及解法1.R1 属于 AS100&#xff0c;R2/3/4 属于 AS200&#xff0c;R5 属于 AS3002.AS200 内运行 OSPF3.建立 IBGP 邻居4.建立 EBGP 邻居5.BGP 发布路由6.路由黑洞 实验拓扑 实验需求及解法 本实验模拟 ISP 网络拓扑&#xff0c;运行 BGP。如图所示…

【DevOps】基于 KubeSphere 的 Kubernetes 生产实践之旅(万字长文)

基于 KubeSphere 的 Kubernetes 生产实践 1.KubeSphere 简介1.1 全栈的 Kubernetes 容器云 PaaS 解决方案1.2 选型理由&#xff08;从运维的角度考虑&#xff09; 2.部署架构图3.节点规划3.1 软件版本3.2 规划说明3.2.1 K8s 集群规划3.2.2 存储集群3.2.3 中间件集群3.2.4 网络规…

kafka,RabbitMQ,RocketMQ,他们之间的区别,架构,如何保证消息的不丢失,保证不重复消费,保证消息的有序性

文章目录 Kafka、RabbitMQ、RocketMQ 之间的区别是什么&#xff1f;性能数据可靠性服务可用性功能 RabbitMQ如何保证消息不丢失&#xff1f;Kafka 的架构说一下&#xff1f;Kafka 怎么保证消息是有序的&#xff1f;Kafka 怎么解决重复消费&#xff1f;Kafka 怎么保证消息不丢失…

TCP/IP协议、三次握手、四次挥手

TCP/IP TCP/IP协议分层TCP头部三次握手TCP四次挥手常见问题1、什么是TCP网络分层2、TCP为什么是三次握手&#xff0c;不是两次或者四次&#xff1f;3、TCP为什么是四次挥手&#xff0c;为什么不能是三次挥手将第二次挥手和第三次挥手合并&#xff1f;4、四次挥手时为什么TIME_W…

spring boot整合Jasypt实现配置加密

文章目录 目录 文章目录 前言 一、Jasypt是什么&#xff1f; 二、使用步骤 1.引入 2.测试使用 3.结果 总结 前言 一、Jasypt是什么&#xff1f; Jasypt&#xff08;Java Simplified Encryption&#xff09;是一个Java库&#xff0c;提供了一种简单的加密解密方式&#xff0c…

web:[ZJCTF 2019]NiZhuanSiWei1

题目 点进题目&#xff0c;网页显示如下&#xff0c;需要代码审计 $_GET["text"]和$_GET["file"]来获取传入的两个参数text和file。使用isset()函数来检查$text变量是否已设置并且不为null。如果设置了并且不为null&#xff0c;则执行下面的逻辑。在下面的…