描述:本教程提供了一个发布导航堆栈的里程表信息的示例。它涵盖了通过ROS发布nav_msgs / Odometry消息,以及通过 tf 从“ odom”坐标系到“ base_link”坐标系的转换。
教程级别:初学者
导航堆栈使用tf确定机器人在世界上的位置,并将传感器数据与静态地图相关联。但是,tf没有提供有关机器人速度的任何信息。因此,导航堆栈要求所有测距源都通过包含速度信息的ROS发布转换和nav_msgs / Odometry消息。本教程介绍了nav_msgs / Odometry消息,并提供了示例代码,用于发布消息并分别通过ROS和tf进行转换。
目录
通过 ROS 发布里程表信息
nav_msgs / Odometry 消息
使用 tf 发布 Odometry 转换
编写代码
所述nav_msgs /里程计信息存储在自由空间中的机器人的位置和速度的估计值:
#表示对自由空间中的位置和速度的估计。
#此消息中的姿势应在header.frame_id给定的坐标系中指定。
#此消息中的扭曲应在child_frame_id给定的坐标系中指定
Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
此消息中的姿势对应于机器人在里程表中的估计位置,以及用于确定该姿势估计值的可选协方差。该消息中的扭曲对应于机器人在子框架(通常是移动基座的坐标框架)中的速度,以及用于确定该速度估计值的可选协方差。
正如“转换配置”教程中讨论的那样,“ tf ”软件库负责管理与转换树中与机器人相关的坐标系之间的关系。因此,任何里程表源都必须发布有关其管理的坐标系的信息。下面的代码假定您具有tf的基本知识,阅读Transform Configuration教程就足够了。
在本节中,我们将编写一些示例代码,这些代码用于通过ROS发布nav_msgs / Odometry消息,以及使用tf对仅绕圈行驶的假机器人进行转换。我们将首先完整地显示代码,并在下面逐条进行说明。
将依赖项添加到包的manifest.xml中
<depend package =“ tf” />
<depend package =“ nav_msgs” />
切换行号显示
#include
#include
#include
int main(int argc, char** argv){
ros::init(argc, argv, "odometry_publisher");
ros::NodeHandle n;
ros::Publisher odom_pub = n.advertise<nav_msgs::Odometry>("odom", 50);
tf::TransformBroadcaster odom_broadcaster;
double x = 0.0;
double y = 0.0;
double th = 0.0;
double vx = 0.1;
double vy = -0.1;
double vth = 0.1;
ros::Time current_time, last_time;
current_time = ros::Time::now();
last_time = ros::Time::now();
ros::Rate r(1.0);
while(n.ok()){
ros::spinOnce(); // check for incoming messages
current_time = ros::Time::now();
//compute odometry in a typical way given the velocities of the robot
double dt = (current_time - last_time).toSec();
double delta_x = (vx * cos(th) - vy * sin(th)) * dt;
double delta_y = (vx * sin(th) + vy * cos(th)) * dt;
double delta_th = vth * dt;
x += delta_x;
y += delta_y;
th += delta_th;
//since all odometry is 6DOF we'll need a quaternion created from yaw
geometry_msgs::Quaternion odom_quat = tf::createQuaternionMsgFromYaw(th);
//first, we'll publish the transform over tf
geometry_msgs::TransformStamped odom_trans;
odom_trans.header.stamp = current_time;
odom_trans.header.frame_id = "odom";
odom_trans.child_frame_id = "base_link";
odom_trans.transform.translation.x = x;
odom_trans.transform.translation.y = y;
odom_trans.transform.translation.z = 0.0;
odom_trans.transform.rotation = odom_quat;
//send the transform
odom_broadcaster.sendTransform(odom_trans);
//next, we'll publish the odometry message over ROS
nav_msgs::Odometry odom;
odom.header.stamp = current_time;
odom.header.frame_id = "odom";
//set the position
odom.pose.pose.position.x = x;
odom.pose.pose.position.y = y;
odom.pose.pose.position.z = 0.0;
odom.pose.pose.orientation = odom_quat;
//set the velocity
odom.child_frame_id = "base_link";
odom.twist.twist.linear.x = vx;
odom.twist.twist.linear.y = vy;
odom.twist.twist.angular.z = vth;
//publish the message
odom_pub.publish(odom);
last_time = current_time;
r.sleep();
}
}
好的,既然您已经有机会查看所有内容,那么让我们详细分解代码的重要部分。
2 #include <tf/transform_broadcaster.h>
3 #include <nav_msgs/Odometry.h>
4
由于我们将同时发布从“ odom”坐标系到“ base_link”坐标系的转换以及nav_msgs / Odometry消息,因此我们需要包括相关的头文件。
9 ros::Publisher odom_pub = n.advertise<nav_msgs::Odometry>("odom", 50);
10 tf::TransformBroadcaster odom_broadcaster;
我们需要同时创建一个ros :: Publisher和一个tf :: TransformBroadcaster,以便能够分别使用ROS和tf发送消息。
12 double x = 0.0;
13 double y = 0.0;
14 double th = 0.0;
我们假设机器人最初从“ odom”坐标系的原点开始。
16 double vx = 0.1;
17 double vy = -0.1;
18 double vth = 0.1;
在这里,我们将设置一些速度,以使“ base_link”框架在“ odom”框架中以x方向0.1m / s,y方向-0.1m / s和0.1rad /沿th方向。这或多或少会导致我们的假机器人绕圈行驶。
24 ros::Rate r(1.0);
在本示例中,我们将以1Hz的速率发布里程表信息,以使自省变得容易,大多数系统将希望以更高的速率发布里程表。
30 //根据机器人的速度以典型方式计算里程
30 //compute odometry in a typical way given the velocities of the robot
31 double dt = (current_time - last_time).toSec();
32 double delta_x = (vx * cos(th) - vy * sin(th)) * dt;
33 double delta_y = (vx * sin(th) + vy * cos(th)) * dt;
34 double delta_th = vth * dt;
35
36 x += delta_x;
37 y += delta_y;
38 th += delta_th;
在这里,我们将基于设置的恒定速度更新里程计信息。真正的里程表系统当然会集成计算出的速度。
40 //由于所有里程表均为6DOF,因此我们需要使用偏航
40 //since all odometry is 6DOF we'll need a quaternion created from yaw
41 geometry_msgs::Quaternion odom_quat = tf::createQuaternionMsgFromYaw(th);
我们通常会尝试使用系统中所有消息的3D版本,以允许2D和3D组件在适当的时候一起工作,并使我们必须创建的消息数量保持最少。因此,有必要将用于测距的偏航值转换为四元数以通过导线发送。幸运的是,tf提供了允许从偏航值轻松创建四元数并轻松访问来自四元数的偏航值的功能。
43 //首先,我们将在tf 上发布转换
43 //first, we'll publish the transform over tf
44 geometry_msgs::TransformStamped odom_trans;
45 odom_trans.header.stamp = current_time;
46 odom_trans.header.frame_id = "odom";
47 odom_trans.child_frame_id = "base_link";
在这里,我们将创建一个TransformStamped消息,该消息将通过tf发送出去。我们要在current_time发布从“ odom”框架到“ base_link”框架的转换。因此,我们将相应地设置消息的标题和child_frame_id,确保使用“ odom”作为父坐标框架,并使用“ base_link”作为子坐标框架。
49 odom_trans.transform.translation.x = x;
50 odom_trans.transform.translation.y = y;
51 odom_trans.transform.translation.z = 0.0;
52 odom_trans.transform.rotation = odom_quat;
53
54 //send the transform
55 odom_broadcaster.sendTransform(odom_trans);
在这里,我们从里程数数据中填写转换消息,然后使用TransformBroadcaster发送转换。
57 //接下来,我们将通过ROS 发布测距消息
57 //next, we'll publish the odometry message over ROS
58 nav_msgs::Odometry odom;
59 odom.header.stamp = current_time;
60 odom.header.frame_id = "odom";
我们还需要发布nav_msgs / Odometry消息,以便导航堆栈可以从中获取速度信息。我们将消息的标题设置为current_time和“ odom”坐标框架。
62 //设置位置
62 //set the position
63 odom.pose.pose.position.x = x;
64 odom.pose.pose.position.y = y;
65 odom.pose.pose.position.z = 0.0;
66 odom.pose.pose.orientation = odom_quat;
67
68 //设置速度
68 //set the velocity
69 odom.child_frame_id = "base_link";
70 odom.twist.twist.linear.x = vx;
71 odom.twist.twist.linear.y = vy;
72 odom.twist.twist.angular.z = vth;
这将使用测距数据填充消息,并将其通过电线发送出去。我们将消息的child_frame_id设置为“ base_link”框架,因为这是我们要在其中发送速度信息的坐标框架。