基于 GPS 定位信息的 Pure-Pursuit 轨迹跟踪实车测试(1)

news2024/11/28 3:27:34

基于 GPS 定位信息的 Pure-Pursuit 轨迹跟踪实车测试(1)

进行了多组实验,包括顺逆时针转向,直线+圆弧轨迹行驶,以及Pure-Pursuit 轨迹跟踪测试

代码修改

需要修改的代码并不多,主要对 gps_sensor 功能包和 purepursuit 代码的修改

  • gps_sensor 发布机器人实际运动轨迹,而不是在 purepursuit 中发布
  • gps_sensor 同时实现 mrobot_states_update 功能包的功能,发布机器人状态信息
  • purepursuit 将最终的控制指令发送给实际机器人,cmd_to_robot 代替 cmd_to_mrobot

#include <ros/ros.h>
#include <sensor_msgs/NavSatFix.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/TwistStamped.h>
#include <nav_msgs/Path.h>

#include <math.h>
#include <iostream>
#include <Eigen/Geometry>
#include <chrono>

using namespace std;

struct my_pose
{
    double latitude;
    double longitude;
    double altitude;
};

struct my_location
{
    double x;
    double y;
};

// 角度制转弧度制
double rad(double d)
{
    return d * M_PI / 180.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;
}

// 全局变量
ros::Publisher state_pub_;
nav_msgs::Path ros_path_;
ros::Publisher vel_pub;
ros::Publisher pose_pub;

const double EARTH_RADIUS = 6371.393; // 6378.137;地球半径

bool init = false;
my_pose init_pose;

// 计算速度
my_location pre_location;
my_location curr_location;
chrono::_V2::system_clock::time_point pre_time;
chrono::_V2::system_clock::time_point curr_time;

void gpsCallback(const sensor_msgs::NavSatFixConstPtr &gps_msg_ptr)
{
    // 初始化
    if (!init)
    {
        init_pose.latitude = gps_msg_ptr->latitude;
        init_pose.longitude = gps_msg_ptr->longitude;
        init_pose.altitude = gps_msg_ptr->altitude;
        init = true;
    }
    else
    {
        // 计算相对位置
        double radLat1, radLat2, radLong1, radLong2, delta_lat, delta_long;
        radLat1 = rad(init_pose.latitude);
        radLong1 = rad(init_pose.longitude);
        radLat2 = rad(gps_msg_ptr->latitude);
        radLong2 = rad(gps_msg_ptr->longitude);
        // 计算x
        delta_lat = radLat2 - radLat1;
        delta_long = 0;
        double x = 2 * asin(sqrt(pow(sin(delta_lat / 2), 2) + cos(radLat1) * cos(radLat2) * pow(sin(delta_long / 2), 2)));
        x = x * EARTH_RADIUS * 1000;

        // 计算y
        delta_lat = 0;
        delta_long = radLong1 - radLong2;
        double y = 2 * asin(sqrt(pow(sin(delta_lat / 2), 2) + cos(radLat2) * cos(radLat2) * pow(sin(delta_long / 2), 2)));
        y = y * EARTH_RADIUS * 1000;

        // 计算yaw
        array<float, 4> calQuaternion = calEulerToQuaternion(0.0, 0.0, gps_msg_ptr->altitude);

        // 发布轨迹
        ros_path_.header.frame_id = "world";
        ros_path_.header.stamp = ros::Time::now();
        geometry_msgs::PoseStamped pose;
        pose.header = ros_path_.header;
        pose.pose.position.x = x;
        pose.pose.position.y = y;
        pose.pose.orientation.x = calQuaternion[0];
        pose.pose.orientation.y = calQuaternion[1];
        pose.pose.orientation.z = calQuaternion[2];
        pose.pose.orientation.w = calQuaternion[3];       
        ros_path_.poses.push_back(pose); 
        
        // 计算速度
        curr_location.x = x;
        curr_location.y = y;
        curr_time = chrono::system_clock::now();
        auto timeDifference = curr_time - pre_time;
        auto durationInMilliseconds = chrono::duration_cast<chrono::milliseconds>(timeDifference);
        double displacement = sqrt(pow(curr_location.x - pre_location.x, 2) + pow(curr_location.y - pre_location.y, 2));
        geometry_msgs::TwistStamped vel;
        // v = s / t
        vel.twist.linear.x = displacement / durationInMilliseconds.count() * 1000;
        // 更新信息
        pre_location = curr_location;
        pre_time = curr_time;
        
        cout << "位姿信息:" << endl;
        cout << x << "," << y << "," << gps_msg_ptr->altitude << endl;
        cout << "速度信息:" << endl;
        cout << vel.twist.linear.x << endl;
        cout << "---------" << endl;

        // 信息发布
        vel_pub.publish(vel);
        pose_pub.publish(pose);       
        state_pub_.publish(ros_path_);
    }
}

int main(int argc, char **argv)
{
    init = false;
    ros::init(argc, argv, "gps_subscriber");
    ros::NodeHandle n;
    ros::Subscriber pose_sub = n.subscribe("/robot_gps", 10, gpsCallback);

    state_pub_ = n.advertise<nav_msgs::Path>("/gps_path", 10);
    vel_pub = n.advertise<geometry_msgs::TwistStamped>("/center_velocity", 10);
    pose_pub = n.advertise<geometry_msgs::PoseStamped>("/center_pose", 10);

    ros::spin();
    return 0;
}
#include <geometry_msgs/Twist.h>
#include <geometry_msgs/PoseStamped.h>
#include <nav_msgs/Path.h>
#include <ros/ros.h>
#include <tf/tf.h>
#include <tf/transform_broadcaster.h>

#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "cpprobotics_types.h"

#define PREVIEW_DIS 1.0 // 预瞄距离
#define Ld 0.55         // 轴距

using namespace std;
using namespace cpprobotics;

ros::Publisher purepersuit_;

const float target_vel = 0.2;
float carVelocity = 0;
float preview_dis = 0;
float k = 0.1;

// 计算四元数转换到欧拉角
std::array<float, 3> calQuaternionToEuler(const float x, const float y,
                                          const float z, const float w)
{
    std::array<float, 3> calRPY = {(0, 0, 0)};
    // roll = atan2(2(wx+yz),1-2(x*x+y*y))
    calRPY[0] = atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y));
    // pitch = arcsin(2(wy-zx))
    calRPY[1] = asin(2 * (w * y - z * x));
    // yaw = atan2(2(wx+yz),1-2(y*y+z*z))
    calRPY[2] = atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z));

    return calRPY;
}

cpprobotics::Vec_f r_x_;
cpprobotics::Vec_f r_y_;

int pointNum = 0; // 保存路径点的个数
int targetIndex = pointNum - 1;

// 计算发送给模型车的转角
void poseCallback(const geometry_msgs::PoseStamped &currentWaypoint)
{
    auto currentPositionX = currentWaypoint.pose.position.x;
    auto currentPositionY = currentWaypoint.pose.position.y;
    auto currentPositionZ = 0.0;

    auto currentQuaternionX = currentWaypoint.pose.orientation.x;
    auto currentQuaternionY = currentWaypoint.pose.orientation.y;
    auto currentQuaternionZ = currentWaypoint.pose.orientation.z;
    auto currentQuaternionW = currentWaypoint.pose.orientation.w;

    std::array<float, 3> calRPY =
        calQuaternionToEuler(currentQuaternionX, currentQuaternionY,
                             currentQuaternionZ, currentQuaternionW);

    cout << currentPositionX << "," << currentPositionY << "," << calRPY[2] << endl;

    for (int i = pointNum - 1; i >= 0; --i)
    {
        float distance = sqrt(pow((r_x_[i] - currentPositionX), 2) +
                              pow((r_y_[i] - currentPositionY), 2));
        if (distance < preview_dis)
        {
            targetIndex = i + 1;
            break;
        }
    }

    if (targetIndex >= pointNum)
    {
        targetIndex = pointNum - 1;
    }

    float alpha =
        atan2(r_y_[targetIndex] - currentPositionY, r_x_[targetIndex] - currentPositionX) -
        calRPY[2];

    // 当前点和目标点的距离Id
    float dl = sqrt(pow(r_y_[targetIndex] - currentPositionY, 2) +
                    pow(r_x_[targetIndex] - currentPositionX, 2));

    // 发布小车运动指令及运动轨迹
    geometry_msgs::Twist vel_msg;
    vel_msg.linear.z = 1.0;
    if (dl <= 0.2) // 离终点很近时停止运动
    {
        vel_msg.linear.x = 0;
        vel_msg.angular.z = 0;
        purepersuit_.publish(vel_msg);
    }
    else
    {
        float theta = atan(2 * Ld * sin(alpha) / dl);
        vel_msg.linear.x = target_vel;
        vel_msg.angular.z = theta;
        purepersuit_.publish(vel_msg);
    }
}

void velocityCall(const geometry_msgs::TwistStamped &carWaypoint)
{
    carVelocity = carWaypoint.twist.linear.x;
    // 预瞄距离计算
    preview_dis = k * carVelocity + PREVIEW_DIS;
}

void pointCallback(const nav_msgs::Path &msg)
{
    // 避免参考点重复赋值
    if (pointNum != 0)
    {
        return;
    }

    // geometry_msgs/PoseStamped[] poses
    pointNum = msg.poses.size();

    // 提前开辟内存
    r_x_.resize(pointNum);
    r_y_.resize(pointNum);
    for (int i = 0; i < pointNum; i++)
    {
        r_x_[i] = msg.poses[i].pose.position.x;
        r_y_[i] = msg.poses[i].pose.position.y;
    }
}

int main(int argc, char **argv)
{
    // 创建节点
    ros::init(argc, argv, "pure_pursuit");

    // 创建节点句柄
    ros::NodeHandle n;
    // 创建Publisher,发送经过pure_pursuit计算后的转角及速度
    purepersuit_ = n.advertise<geometry_msgs::Twist>("/cmd_vel", 20);

    ros::Subscriber splinePath = n.subscribe("/ref_path", 20, pointCallback);
    ros::Subscriber carVel = n.subscribe("/center_velocity", 20, velocityCall);
    ros::Subscriber carPose = n.subscribe("/center_pose", 20, poseCallback);
    ros::spin();
    return 0;
}

数据处理

原始的数据便于查阅,但不便于 MATLAB 进行读取分析,主要包括 ***_pub.txt 和 ***_path.txt 两种待处理数据,分别如下

$GPRMC,085750.20,A,3150.93719306,N,11717.59499143,E,0.071,252.6,161123,5.7,W,D*26

数据长度:83
latitude: 31.848953217667
longitude: 117.293249857167
heading_angle_deg: 252.600000000000
heading_angle_rad: 4.408701690538
heading_angle_rad: -1.874483616642
---------
$GPRMC,085750.30,A,3150.93719219,N,11717.59498178,E,0.297,264.0,161123,5.7,W,D*28

数据长度:83
latitude: 31.848953203167
longitude: 117.293249696333
heading_angle_deg: 264.000000000000
heading_angle_rad: 4.607669225265
heading_angle_rad: -1.675516081915
---------
位姿信息:
0.0169953,0.0210645,-2.24798
速度信息:
1.59198e-11
---------
位姿信息:
0.0183483,0.0200412,2.49058
速度信息:
0.0180464
---------

下面的命令首先使用**grep过滤包含"heading_angle_deg: "的行,然后使用awk**提取每行的第二个字段(即数字部分),最后将结果写入output.txt文件


grep "heading_angle_deg: " input.txt | awk '{print $2}' > output.txt

下面的命令使用**awk匹配包含"位姿信息:"的行,然后使用getline**读取下一行数据并打印出来,最后将结果写入output.txt文件

awk '/位姿信息:/ {getline; print}' input.txt > output.txt

处理前和处理后的文件目录如下

redwall@redwall-G3-3500:~/log/2023--11-16/raw$ tree
.
├── actual_path.txt
├── anticlock_path.txt
├── anticlock_pub.txt
├── clock_path.txt
├── clock_pub.txt
├── gps_path.txt
├── gps_pub.txt
├── lane_path.txt
└── lane_pub.txt
redwall@redwall-G3-3500:~/log/2023--11-16/processed$ tree
.
├── anticlock_path_pose.txt
├── anticlock_path_vel.txt
├── anticlock_pub_sift.txt
├── clock_path_pose.txt
├── clock_path_vel.txt
├── clock_pub_sift.txt
├── gps_path_pose.txt
├── gps_path_vel.txt
├── gps_pub_sift.txt
├── lane_path_pose.txt
├── lane_path_vel.txt
└── lane_pub_sift.txt

数据分析

本次有“卫星”标签的连接车后的蘑菇头,以 10 Hz 的频率获取定位数据

  • 分析 lane_pub 和 lane_path(直线+圆弧轨迹运动)

由实际轨迹可知,纬度差应对应 X,经度差应对应 Y,应该修改代码

在这里插入图片描述

由实际航向角信息,结合实际轨迹,本次天线的基线向量应该是与正北方向重合,顺时针转向时航向角增大,说明天线安装的方式应该是正确的

在这里插入图片描述

  • 分析 clock_pub 和 clock_path (顺时针原地转向)

主要是分析航向角信息,由于提高采样频率,因此存在很大的噪声,可以对信号进行去噪处理

本次已经尽量控制两个蘑菇头的安装,安装前用卷尺进行了安装测量,但仍存在误差

结合之前的分析记录,顺时针转向时先增大至 360,再从 0 开始继续增大,下面符合该规律

在这里插入图片描述

💡 人为判断的正北朝向并不和实际正北朝向重合,因此起始和终止位置并不为 0 度

  • 分析 gps_pub、gps_path 和 actual_path

首先机器人是由北向南开,即初始朝向是向南,由 lane 数据可知,初始朝向是有问题的

其实 Pure-Pursuit 算法并不需要航向角信息,初始位姿是一个比较大的影响

实际速度会影响预瞄距离,实际速度较小会导致预瞄距离较小,导致控制的不稳定

在这里插入图片描述

存在问题

1、和 GPS 串口通信存在问题,读取配置以及信息长度方面存在问题,需要修改

2、GPS 航向角信息的获取仍存在较大问题,准确性比较低

3、GPS 对于高速移动的车辆,厘米级别的定位精度还可以,目前对于低速机器人可能不太适用

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

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

相关文章

蓝桥杯每日一题2023.11.26

题目描述 奖券数目 - 蓝桥云课 (lanqiao.cn) 将每一个数字进行一一枚举&#xff0c;如果检查时不带有数字4则答案可以加1 #include<bits/stdc.h> using namespace std; int ans; bool check(int n) {while(n){if(n % 10 4)return false;n / 10; }return true; } int m…

基于Haclon的标签旋转项目案例

项目要求&#xff1a; 图为HALCON附图“25interleaved_exposure_04”&#xff0c;里面为旋转的二维码标签&#xff0c;请将其旋转到水平位置。 项目知识&#xff1a; 在HALCON中进行图像平移和旋转通常有以下步骤&#xff1a; &#xff08;1&#xff09;通过hom_mat2d_ident…

<JavaEE> Thread线程类 和 Thread的常用方法

目录 一、Thread概述 二、构造方法 三、常用方法 1.1 getId()、getName()、getState()、getPririty() 1.2 start() 1.3 isDaemon()、setDaemon() 1.4 isAlive() 1.5 currentThread() 1.6 Interrupt()、interrupted()、isInterrupted() 1.6.1 方法一&#xff1a;添加共…

基于Haclon的图形镜像案例

项目要求&#xff1a; 图为HALCON的例图“green-dot”&#xff0c;请将其中的圆形图案按水平和垂直两个方向分别进行镜像。 项目知识&#xff1a; 首先要用BLOB分析的方法&#xff0c;得到圆形图案的目标区域&#xff0c;再对其进行镜像。 在HALCON中与镜像相关的算子为mirr…

跟着chatgpt学习|1.spark入门

首先先让chatgpt帮我规划学习路径&#xff0c;使用Markdown格式返回&#xff0c;并转成思维导图的形式 目录 目录 1. 了解spark 1.1 Spark的概念 1.2 Spark的架构 1.3 Spark的基本功能 2.spark中的数据抽象和操作方式 2.1.RDD&#xff08;弹性分布式数据集&#xff09; 2…

实战oj题——括号匹配问题

前言&#xff1a;前面我们已经做了一些关于顺序表和链表的oj题&#xff0c;今天我们就来解决一些有关于栈和队列的oj题。 我们对这个题看起来毫无头绪&#xff0c;但是我们刚学习了栈&#xff0c;就可以用栈来解决这一类问题&#xff0c;如果我们读取到左括号就入栈&#xff0c…

Cache学习(3):Cache地址映射(直接映射缓存组相连缓存全相连缓存)

1 Cache的与存储地址的映射 以一个Cache Size 为 128 Bytes 并且Cache Line是 16 Bytes的Cache为例。首先把这个Cache想象成一个数组&#xff0c;数组总共8个元素&#xff0c;每个元素大小是 16 Bytes&#xff0c;如下图&#xff1a; 现在考虑一个问题&#xff0c;CPU从0x0654…

再见 Pandas,再见算法

大家好,《再见pandas》 系列已有200多位朋友加入学习了,这段时间亲眼见证了很多朋友的飞跃进步,从无到有,从一个问问题的小白到开始慢慢回答别人的问题,在讨论和练习中不断成长。虽说pandas已经很普及了,但普及内容的深度却远远不够。 下面这套原创图文是我和几位小伙伴…

深入了解 Pinia:现代 Vue 应用的状态管理利器

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

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

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

python树长子兄弟链存储结构(孩子兄弟链存储结构)

长子兄弟链存储结构&#xff08;孩子兄弟链存储结构&#xff09;解释&#xff1a; 长子兄弟链存储结构是一种树的存储结构&#xff0c;它使用孩子兄弟表示法&#xff08;也称作左孩子右兄弟表示法&#xff09;来表示树的结构。这种表示方法主要用于存储一般的树&#xff0c;而不…

Linux服务器SSH客户端断开后保持程序继续运行的方法

目录 1. nohup 命令&#xff1a; 2. tmux 或 screen&#xff1a; 3 final shell 断开后服务器如何继续执行令&#xff1f; 方法一&#xff1a;使用 nohup 命令 方法二&#xff1a;将命令放在后台执行 4 你可以使用 jobs 命令查看当前终端中正在后台运行的任务 &#xff…

飞翔的鸟小游戏

第一步是创建项目 项目名自拟 第二步创建个包名 来规范class 再创建一个包 来存储照片 如下&#xff1a; package game; import java.awt.*; import javax.swing.*; import javax.imageio.ImageIO;public class Bird {Image image;int x,y;int width,height;int size;doub…

Qt4用子类化ProxyModel和子类化MainWindow实现全表筛选,中文排序和复制粘贴

目录 1 需求 2 子类化ProxyModel实现全表筛选 3 字符串列表实现中文排序 3.1 Qt5中文排序 3.2 Qt4排序 4 表格的复制粘贴 5 应用 1 需求 模型视图编程是Qt开发的基本功&#xff0c;其中有几个关键问题需要解决&#xff1a; 全表筛选&#xff0c;或者说多列搜索中文排序…

【精选必看】MyBatis映射文件及动态SQL,一级,二级缓存介绍

文章目录 MyBatis映射文件 < r e s u l t M a p > <resultMap> <resultMap>resultMap < sql>&< include>特殊字符处理 动态SQL < i f > < if> <if> < w h e r e > <where> <where> < s e t > <…

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

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

SAP Smartform小结

SAP系统做打印单据用的, 感觉很不好用, 特别是要嵌入韩文时必须使用嵌入的word编辑器,运行速度简直不可忍受. 见过一些Adobe interactive form的示例, 看着相当不错, 不过据说需要花money额外买licence, 哪有smartform这种免费东西来得实惠. 一般打印需求,会要求有标题抬头,打…

【开源】基于Vue+SpringBoot的农家乐订餐系统

项目编号&#xff1a; S 043 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S043&#xff0c;文末获取源码。} 项目编号&#xff1a;S043&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户2.2 管理员 三、系统展示四、核…

CSDN助手:一键下载CSDN博客:高效保存,随时阅读

文章目录 &#x1f4d6; 介绍 &#x1f4d6;&#x1f3e1; 环境 &#x1f3e1;&#x1f4d2; 使用方法 &#x1f4d2;⚓️ 相关链接 ⚓️ &#x1f4d6; 介绍 &#x1f4d6; 这是我自己无聊的时候写的一个应用&#xff0c;以前UI有点丑&#xff0c;这次重写了一下UI 功能如下 …

球面的表面积

此推导需要用到重积分的知识&#xff0c;另外关于曲面的面积公式可以看我之前的博客