ROS 与 navigation 教程
说明:
介绍如何为机器人整合导航包,实现有效控制和自主导航等功能
目录:
ROS 与 navigation 教程-目录
ROS 与 navigation 教程-设置机器人使用 TF
ROS 与 navigation 教程-基本导航调试指南
ROS 与 navigation 教程-安装和配置导航包
ROS 与 navigation 教程-结合 RVIZ 与导航包
ROS 与 navigation 教程-发布里程计消息
ROS 与 navigation 教程-发布传感器数据
ROS 与 navigation 教程-编写自定义全局路径规划
ROS 与 navigation 教程-stage 仿真
ROS 与 navigation 教程-示例-激光发布(C++)
ROS 与 navigation 教程-示例-里程发布(C++)
ROS 与 navigation 教程-示例-点云发布(C++)
ROS 与 navigation 教程-示例-机器人 TF 设置(C++)
ROS 与 navigation 教程-示例-导航目标设置(C++)
ROS 与 navigation 教程-turtlebot-整合导航包简明指南
ROS 与 navigation 教程-turtlebot-SLAM 地图构建
ROS 与 navigation 教程-turtlebot-现有地图的自主导航
ROS 与 navigation 教程-map_server 介绍
ROS 与 navigation 教程-move_base 介绍
ROS 与 navigation 教程-move_base_msgs 介绍
ROS 与 navigation 教程-fake_localization 介绍
ROS 与 navigation 教程-voel_grid 介绍
ROS 与 navigation 教程-global_planner 介绍
ROS 与 navigation 教程-base_local_planner 介绍
1
ROS 与 navigation 教程-carrot_planner 介绍
ROS 与 navigation 教程-teb_local_planner 介绍
ROS 与 navigation 教程-dwa_local_planner(DWA)介绍
ROS 与 navigation 教程-nav_core 介绍
ROS 与 navigation 教程-robot_pose_ekf 介绍
ROS 与 navigation 教程-amcl 介绍
ROS 与 navigation 教程-move_slow_and_clear 介绍
ROS 与 navigation 教程-clear_costmap_recovery 介绍
ROS 与 navigation 教程-rotate_recovery 介绍
ROS 与 navigation 教程-costmap_2d 介绍
ROS 与 navigation 教程-costmap_2d-range_sensor_layer 介绍
ROS 与 navigation 教程-costmap_2d-social_navigation_layers 介绍
ROS 与 navigation 教程-costmap_2d-staticmap 介绍
ROS 与 navigation 教程-costmap_2d-inflation 介绍
ROS 与 navigation 教程-obstacle 层介绍
ROS 与 navigation 教程-Configuring Layered Costmaps
参考:
http://wiki.ros.org/navigation/Tutorials
ROS 与 navigation 教程-设置机器人使用 TF
说明:
介绍如何配置机器人让其使用 TF
注意:本教程假设您已完成 ROS 教程和 tf 基础教程。
本教程的代码在 robot_setup_tf_tutorial 包中可用
步骤:
(1)变换配置
2
许多 ROS 包需要使用 tf 软件库发布机器人的变换树。在抽象层,变换树根据不同坐标系之间的平移和旋
转来定义偏移量。为了使这更具体,考虑一个简单的机器人的例子,它具有安装在其顶部的单个激光器的
移动基座。在提及机器人时,我们定义两个坐标系:一个对应于机器人底座的中心点,一个坐标系安装在
基座顶部的激光中心点。我们还给他们名字,以供参考。我们将调用附加到移动基础“base_link”的坐标系
(对于导航,它重要的是将其放置在机器人的旋转中心),我们也会调用附加到激光“base_laser”的坐标系。
有关框架命名约定,请参见 REP 105
在这一点上,我们假设激光器的一些数据以与激光中心点的距离的形式来表达。换句话说,我们在
“base_laser”坐标系中有一些数据。现在假设我们想要获取这些数据并使用它来帮助移动基地避免活动空间
的障碍。要做到这一点,我们需要一种将我们从“base_laser”坐标系转换成“base_link”坐标系的激光扫描的
方法。实质上,我们需要定义“base_laser”和“base_link”坐标系之间的关系。
在定义这种关系时,假设我们知道激光器在移动基座的中心点之上 10 厘米和向前 20 厘米处安装。这给了
我们一个将“base_link”坐标系与“base_laser”坐标系相关联的平移值和偏移值。具体来说,我们知道要从
“base_link”坐标系到“base_laser”坐标系的数据,我们必须应用(x:0.1m,y:0.0m,z:0.2m)的转换,
并从“ base_laser“坐标系到”base_link“坐标系,我们必须应用相反的变换(x:-0.1m,y:0.0m,z:-0.20m)。
我们可以选择自己管理这种关系,这意味着在必要时存储和应用帧之间的适当变换,但随着坐标系的数量
增加,这成为一个真正的痛苦。幸运的是,我们不用自己做这个工作。相反,我们将使用 tf 定义“base_link”
和“base_laser”之间的关系,并让它管理两个坐标系之间的变换。
要使用 tf 定义和存储“base_link”和“base_laser”坐标系之间的关系,我们需要将它们添加到一个变换树中。
在概念上,变换树中的每个节点对应于坐标系,每个方向对应于需要应用从当前节点移动到其子节点的变
换。Tf 使用树结构来确保只有一个遍历将任何两个坐标系连接在一起,并假设树中的所有方向都是从父节
点引导到子节点。
3
要为我们的简单示例创建一个变换树,我们将创建两个节点,一个用于“base_link”坐标系,一个用于
“base_laser”坐标系。为了创建它们之间的方向,我们首先需要决定哪个节点是父节点,哪些节点是子节点。
记住,这种区别很重要,因为 tf 假定所有转换都从父对象移动到子对象。让我们选择“base_link”坐标系作
为父节点,因为随着其他部分/传感器被添加到机器人,通过遍历“base_link”框架,它们将最有意义地与
“base_laser”坐标系相关。这意味着连接“base_link”和“base_laser”的变换应为(x:0.1m,y:0.0m,z:
0.2m)。使用这个变换树设置,将在“base_laser”坐标系中接收到的激光扫描转换为“base_link”坐标系就
像调用 tf 库一样简单。我们的机器人可以使用这些信息来理解“base_link”框架中的激光扫描,并安全地计
划避开环境中的障碍物。
(2)编写代码
希望上面的例子有助于在概念层面了解 tf。现在,我们必须使用变换树并用代码创建它。在这个例子中,
假设你已熟悉 ROS,因此如果任何术语或概念不熟悉,请确保查看 ROS 文档。
假设我们有一个高级别的任务描述在“base_laser”坐标系中获取点并将其转换为“base_link”坐标系。我们需
要做的第一件事是创建一个负责在系统中发布转换的节点。接下来,我们必须创建一个节点来监听通过
ROS 发布的变换数据,并应用它来转换一个点。我们将首先为源代码创建一个包,然后我们给它一个简单
的名字,如“robot_setup_tf”我们将依赖于 roscpp,tf 和 geometry_msgs。
$ cd %TOP_DIR_YOUR_CATKIN_WS%/src
$ catkin_create_pkg robot_setup_tf roscpp tf geometry_msgs
请注意,您必须运行上面的命令(例如~/ros,您可能已为之前的教程创建)。
在 fuerte,groovy 和 hydro 中的替代方法:在 navigation_tutorials 包中有一个标准的 robot_setup_tf_tutorial
包。您可能希望通过以下方式安装 (%YOUR_ROS_DISTRO% can be { fuerte, groovy } .):
$ sudo apt‐get install ros‐%YOUR_ROS_DISTRO%‐navigation‐tutorials
(3)广播变换
4
现在我们有了我们的软件包,我们需要创建一个节点来完成在 ROS 上广播 base_laser → base_link 变换
的工作。在刚刚创建的 robot_setup_tf 包中,启动您喜欢的编辑器,并将以下代码粘贴到
src/tf_broadcaster.cpp 文件中。
#include
#include
int main(int argc, char** argv){
ros::init(argc, argv, "robot_tf_publisher");
ros::NodeHandle n;
ros::Rate r(100);
tf::TransformBroadcaster broadcaster;
while(n.ok()){
broadcaster.sendTransform(
tf::StampedTransform(
tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
ros::Time::now(),"base_link", "base_laser"));
r.sleep();
}
}
现在,我们来更详细看看发布 base_link → base_laser 变换相关的代码
#include
tf 包提供了一个 tf::TransformBroadcaster 的实现,以帮助使发布变换的任务更容易。要使用
TransformBroadcaster,我们需要包含 tf/transform_broadcaster.h 头文件。
tf::TransformBroadcaster broadcaster;
在这里,我们创建一个 TransformBroadcaster 对象,我们稍后将通过网络发送 base_link → base_laser
变换。
broadcaster.sendTransform(
tf::StampedTransform(
tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
ros::Time::now(),"base_link", "base_laser"));
5
这是实际工作完成的地方。使用 TransformBroadcaster 发送变换需要五个参数。首先,我们传递旋转变换,
它由 btQuaternion 指定,需要在两个坐标系之间进行任何旋转。在这种情况下,我们不要施加旋转,所以
我们发送一个由俯仰,滚动和偏航值等于零的 btQuaternion。第二,btVector3 可以应用到任何变换的,所
以我们创建一个 btVector3 对应的激光的 x 偏移 10 厘米和 Z 偏移距离机器人基座 20 厘米。第三,我们需
要给出发布的时间戳,我们将用 ros::Time::now()。第四,我们需要传递我们创建的链接的父节点的名称,
在这种情况下是“base_link”。第五,我们需要传递我们创建的链接的子节点的名称,在这种情况下是
“base_laser”。
(4)使用变换
以上,我们创建了一个通过 ROS 发布 base_laser → base_link 变换的节点。现在,我们要编写一个节点,
将使用该变换在“base_laser”坐标系中取一点,并将其转换为“base_link”坐标系中的一个点。我们将首先将
下面的代码粘贴到一个文件中,并进行更详细的解释。在 robot_setup_tf 包中,创建一个名为
src/tf_listener.cpp 的文件,并粘贴以下内容:
#include
#include
#include
void transformPoint(const tf::TransformListener& listener){
//we'll create a point in the base_laser frame that we'd like to transform to the base_l
ink frame
geometry_msgs::PointStamped laser_point;
laser_point.header.frame_id = "base_laser";
//we'll just use the most recent transform available for our simple example
laser_point.header.stamp = ros::Time();
//just an arbitrary point in space
laser_point.point.x = 1.0;
laser_point.point.y = 0.2;
laser_point.point.z = 0.0;
try{
geometry_msgs::PointStamped base_point;
listener.transformPoint("base_link", laser_point, base_point);
6
ROS_INFO("base_laser: (%.2f, %.2f. %.2f) ‐‐‐‐‐> base_link: (%.2f, %.2f, %.2f) at time
%.2f",
laser_point.point.x, laser_point.point.y, laser_point.point.z,
base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.sta
mp.toSec());
}
catch(tf::TransformException& ex){
ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"
base_link\": %s", ex.what());
}
}
int main(int argc, char** argv){
ros::init(argc, argv, "robot_tf_listener");
ros::NodeHandle n;
tf::TransformListener listener(ros::Duration(10));
//we'll transform a point once every second
ros::Timer timer = n.createTimer(ros::Duration(1.0), boost::bind(&transformPoint, boos
t::ref(listener)));
ros::spin();
}
现在我们将一步一步解释
#include
在这里,我们包含 tf/transform_listener.h 头文件,我们需要创建一个 tf::TransformListener。一个
TransformListener 对象自动订阅了 ROS 变换消息主题和管理所有将在网络中广播的变换数据。
void transformPoint(const tf::TransformListener& listener){
我们将创建一个函数,给定一个 TransformListener,在“base_laser”坐标系中取一点,并将其转换为
“base_link”坐标系。这个函数将作为在我们程序的 main()中创建的 ros::Timer 的回调,并且每秒都会触发。
//we'll create a point in the base_laser frame that we'd like to transform to the base_li
nk frame
geometry_msgs::PointStamped laser_point;
laser_point.header.frame_id = "base_laser";
7
//we'll just use the most recent transform available for our simple example
laser_point.header.stamp = ros::Time();
//just an arbitrary point in space
laser_point.point.x = 1.0;
laser_point.point.y = 0.2;
laser_point.point.z = 0.0;
在这里,我们将创建一个 geometry_msgs::PointStamped 的点。消息名称结尾处的“Stamped”只是意味着
它包含一个头,允许我们将时间戳和 frame_id 与消息相关联。我们将 laser_point 消息的 Stamp 字段设置
为 ros::Time(),它是一个特殊的时间值,允许我们向 TransformListener 询问最新的可用变换。对于标题
的 frame_id 字段,我们将把它设置为“base_laser”,因为我们在“base_laser”坐标系中创建一个点。最后,
我们将设置一些数据,例如 x:1.0,y:0.2 和 z:0.0 的点。
try{
geometry_msgs::PointStamped base_point;
listener.transformPoint("base_link", laser_point, base_point);
ROS_INFO("base_laser: (%.2f, %.2f. %.2f) ‐‐‐‐‐> base_link: (%.2f, %.2f, %.2f) at time
%.2f",
laser_point.point.x, laser_point.point.y, laser_point.point.z,
base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.sta
mp.toSec());
}
现在我们将“base_laser”坐标系中的点转换成“base_link”坐标系的点。为此,我们将使用 TransformListener
对象,调用 transformPoint(),并使用三个参数:我们要将点转换为(“base_link”在我们的例子中)坐标系
的名称,我们正在转换的点,以及存储变换点。所以在调用 transformPoint()之后,base_point 保存与
laser_point 相同的信息,包含变换后的信息。
catch(tf::TransformException& ex){
ROS_ERROR("Received an exception trying to transform a point from "base_laser" to "base_link": %s",
ex.what());
}
8