系列文章目录
第一章 ROS空间创建、helloworld实现并打开多个节点 第二章 话题通信 第三章 服务通信 第四章 参数服务器 第五章 常用指令 第六章 通信机制的实际操作 第七章 ROS先进的通信机制(常用)API、Python导入模块) 第八章 元功能包,节点运行管理launch文件(teleop_twist安装方法) 第九章 重名问题,分布式通信 第十章-第一节 TF坐标变换(包含PyKDL 和PyInit__tf2解决功能缺失等问题) 第十章-第二节 TF实际操作的坐标变换 第十章-第三节 rosbag、rqt工具箱 第十一章-第一节 机器人系统仿真(URDF相关) 第十一章-第二节 仿真机器人系统(Gazebo相关) 第十二章 机器人导航(仿真)
前言
现在大二,大一之前有幸参加了2021年全国比赛,并获得了江苏赛区二等奖。但是发现无人机这个问题,真的是在堆钱。不上ROS不,现在记录一个纯小白学习ROS过程和问题。防止弟弟妹妹走我走过的弯路。板子是学长给的Jetson Nano(4GB),版本是Ubuntu18.04(已配置基础ROS所需配置)
导航是机器人系统中最重要的模块之一。例如,流行的服务室内机器人依靠机器人导航来实现室内独立移动。本章主要介绍模拟环境下的导航实现,主要包括: 导航相关概念 导航:机器人建图(SLAM)、地图服务、定位、路径规划…以可视化操作为主。 导航信息:了解地图、里程计、雷达、摄像头等相关信息格式。
预期的学习目标:
- 了解导航模块的组成部分及相关概念
- 独立完成机器人导航
一、概述
概念 在ROS中机器人导航(Navigation)多功能包组合实现,ROS 又称导航功能包集,官方介绍如下: 二维导航堆栈接收里程计、传感器流和目标姿态的信息,并输出发送到移动底盘的安全速度命令。
ROS 导航功能包的优点如下:
- 安全: 由专业团队开发和维护
- 功能: 功能更加稳定全面
- 高效: 解放开发者,让开发者更加关注上层功能的实现
1.导航模块简介
综上所述,涉及的关键技术有以下五点:
- 全局地图
- 自身定位
- 路径规划
- 运动控制
- 环境感知
机器人导航类似于无人驾驶。关键技术也由以上五点组成,但无人驾驶是基于室外的,我们目前介绍的机器人导航更多的是基于室内的。
- 全局地图-全局概览图:定位 路径规划 地图是机器人导航中的一个重要组成部分。当然,如果你想使用地图,你需要先画地图。关于地图建模技术的不断出现,其中一个被称为 SLAM 理论脱颖而出: SLAM可描述为: 机器人在未知环境中从一个未知位置开始移动,在移动过程中根据位置估计和地图进行自身定位,同时在自身定位的基础上建造增量式地图,以绘制出外部环境的完全地图。
- 自我定位-在地图上确定位置 SLAM 除此之外,还可以实现自己的定位,ROS 还提供了定位功能包:
amcl
amcl——2用于自适应蒙特卡洛定位D移动机器人的概率定位系统。它实现了自适应(或KLD采样)蒙特卡洛定位法,利用粒子过滤器根据已知地图跟踪机器人的姿态。-使用传感器实时感知到的信息来匹配地图。 - 路径规划-全局路径规划- 局部路径规划 导航是机器人从A点移动到B点的过程。在这个过程中,机器人需要根据目标位置计算整个运动路线,在运动过程中,运动路线需要根据一些动态障碍进行调整,直到达到目标点,这个过程称为路径规划。 ROS 中提供了 move_base 该功能包主要由两个规划器组成: (1).全局路径规划(gloable_planner) 根据给定的目标点和全局地图实现总体路径规划,使用 Dijkstra 或 A* 算法规划全局路径,计算最优路线 (2).当地时间规划(local_planner) 在实际导航过程中,机器人可能无法按照给定的全球最佳路线运行。例如,机器人在运行过程中随时可能出现某些障碍物… 本地规划的作用是使用某种算法(Dynamic Window Approaches) 为了避免障碍物,选择当前路径,尽可能符合全局最优路径
- 运动控制-控制速度和方向 假设导航功能包可以通过主题"
cmd_vel
"发布geometry_msgs/Twist
基于机器人基座坐标系的类型新闻传递运动命令。这意味着必须有一个节点来订阅"cmd_vel
"话题, 将主题上的速度命令转换为电机命令并发送。 - 环境感知-感知周围环境 例如: 相机、激光雷达、编码器…,摄像头和激光雷达可以用来感知外部环境的深度信息,编码器可以感知电机的速度信息,然后获取速度信息并生成里程计信息。 环境感知也是实现导航功能包集中的重要模块,它为其他模块提供了支持。其他模块如: SLAM、amcl、move_base 都依赖于环境感知。
2.导航坐标系
- 简介 实现定位取决于机器人本身。机器人需要逆向推导参考系原点,计算坐标系的相对关系。实现这一过程有两种常用方法: 里程定位:始终收集机器人的速度信息,并发布机器人坐标系与父级参考系的相对关系。 通过传感器定位:通过传感器收集外界环境信息通过匹配计算并发布机器人坐标系与父级参考系的相对关系。 导航中经常使用两种方法。
- 特点 里程计定位: 优点:里程计定位信息连续,无离散跳跃。 缺点:里程计有累积误差,不利于长距离或长期定位。 传感器定位: 优点:比里程计定位更准确; 缺点:传感器定位会出现跳变的情况,且传感器定位在标志物较少的环境下,其定位精度会大打折扣。
- 坐标系变换 在上述两种定位实现中,机器人坐标系一般采用机器人模型中的根坐标系(
base_link
或base_footprint
),里程计定位时,父级坐标系一般称为odom
,如果通过传感器定位,父级参考系一般称为map
。两者结合使用时,map 和 odom 都是机器人模型根坐标系的父级,不符合坐标变换"单继承"因此,转换关系一般设置为: 或。
3.导航条件说明
硬件和软件对导航的实现有一定的要求,需要提前准备。
- 硬件 导航功能包虽然设计成尽可能通用,但使用时仍有三大硬件限制: 它是为差速驱动的轮式机器人设计的。假设底盘由理想的运动命令控制,并且可以达到预期的结果,命令的格式是:
x速度分量,y速度分量,角速(theta)分量
。 它需要在底盘上安装一个单线激光雷达。该激光雷达用于构建地图和定位。 导航功能包是为方形机器人开发的,所以方形或圆形机器人将是最好的性能。 它也可以在任何形状和大小的机器人上工作,但更大的机器人将很难通过狭窄的空间。 - 软件 在实现导航功能之前,需要建立一些软件环境: 毫无疑问,必须先安装 ROS 基于模拟环境,当前导航首先确保上一章的机器人系统模拟能够正常执行 机器人可以在模拟环境中正常接收
/cmd_vel
传感器消息发布正常,即导航模块中的运动控制和环境感知
在后续导航注后续导航的实现: 使用 SLAM 绘制地图、地图服务、自身定位和路径规划。
二、实现导航
本节主要介绍导航的完整性,旨在掌握机器人导航的基本过程。本章的主要内容如下:
- SLAM建图(选择常见建图)gmapping)
- 地图服务(地图可以保存和重现)
- 机器人定位
- 路径规划
介绍完以上流程后,功能将进一步集成,实现探索性SLAM建图。
请先安装相关的ROS功能包: 安装 gmapping 包(用于构建地图):sudo apt install ros-<ROS版本>-gmapping
安装地图服务包(用于保存和读取地图):
sudo apt install ros-<ROS版本>-navigation
新建功能包,并导入依赖: gmapping map_server amcl move_base
mkdir -p daohang/src
cd daohang/
catkin_make
cd src
catkin_create_pkg nav_demo gmapping map_server amcl move_base
cd ..
catkin_make
1.导航实现01_SLAM建图
- gmapping简介
gmapping 是较为常用且比较成熟的SLAM算法之一,gmapping可以根据移动机器人里程计数据和激光雷达数据来绘制二维的栅格地图,对应的,gmapping对硬件也有一定的要求: 1.该移动机器人可以发布里程计消息 2.机器人需要发布雷达消息(该消息可以通过水平固定安装的雷达发布,或者也可以将深度相机消息转换成雷达消息)
- gmapping节点说明 gmapping 功能包中的核心节点是:
slam_gmapping
。为了方便调用,需要先了解该节点订阅的话题、发布的话题、服务以及相关参数。 - gmapping使用 3.1编写gmapping节点相关launch文件 launch文件编写可以参考 github 的演示 launch文件:https://github.com/ros-perception/slam_gmapping/blob/melodic-devel/gmapping/launch/slam_gmapping_pr2.launch
复制并修改如下:
<!-- 设置gmapping相关节点 -->
<launch>
<!-- 是否使用仿真,是的话就是true -->
<param name="use_sim_time" value="true"/>
<!-- gmapping的节点 -->
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen">
<!-- 设置雷达话题 -->
<!-- to指向的是你的scan名字 -->
<remap from="scan" to="scan"/>
<!-- 关键参数:坐标系 -->
<!-- ~base_frame(string, default:"base_link")机器人基坐标系。 ~map_frame(string, default:"map")地图坐标系。 ~odom_frame(string, default:"odom")里程计坐标系。 -->
<param name="base_frame" value="base_footprint" />
<param name="map_frame" value="map" />
<param name="odom" value="odom" />
<!-- 地图更新间隔 -->
<param name="map_update_interval" value="5.0"/>
<!-- 雷达长度阈值,根据实际的进行修改 -->
<param name="maxUrange" value="16.0"/>
<param name="sigma" value="0.05"/>
<param name="kernelSize" value="1"/>
<param name="lstep" value="0.05"/>
<param name="astep" value="0.05"/>
<param name="iterations" value="5"/>
<param name="lsigma" value="0.075"/>
<param name="ogain" value="3.0"/>
<param name="lskip" value="0"/>
<param name="srr" value="0.1"/>
<param name="srt" value="0.2"/>
<param name="str" value="0.1"/>
<param name="stt" value="0.2"/>
<param name="linearUpdate" value="1.0"/>
<param name="angularUpdate" value="0.5"/>
<param name="temporalUpdate" value="3.0"/>
<param name="resampleThreshold" value="0.5"/>
<param name="particles" value="30"/>
<param name="xmin" value="-50.0"/>
<param name="ymin" value="-50.0"/>
<param name="xmax" value="50.0"/>
<param name="ymax" value="50.0"/>
<param name="delta" value="0.05"/>
<param name="llsamplerange" value="0.01"/>
<param name="llsamplestep" value="0.01"/>
<param name="lasamplerange" value="0.005"/>
<param name="lasamplestep" value="0.005"/>
</node>
<!-- 所需的坐标变换 雷达坐标系→基坐标系 一般由 robot_state_publisher 或 static_transform_publisher 发布。 基坐标系→里程计坐标系 一般由里程计节点发布。 -->
<!-- rviz中显示urdf时,必须发布不同部件之间的 坐标系 关系 解决策略:ROS中提供了关于机器人模型显示的坐标发布相关节点(两个) rosrun joint_state_publisher joint_state_publisher #发布关节相关节点 rosrun robot_state_publisher robot_state_publisher #发布机器人相关节点 -->
<node pkg="joint_state_publisher" name="joint_state_publisher" type="joint_state_publisher" />
<node pkg="robot_state_publisher" name="robot_state_publisher" type="robot_state_publisher" />
<!--启动rviz-->
<node pkg="rviz" type="rviz" name="rviz" />
</launch>
3.2执行 1.先启动 Gazebo 仿真环境
cd workspace
source ./devel/setup.bash
roslaunch urdf02_gazebo demo03_env.launch
2.然后再启动地图绘制的 launch 文件:
cd workspace
source ./devel/setup.bash
roslaunch nav_demo nav01_slam.launch
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
4.在 rviz 中添加组件,显示栅格地图
2.导航实现02_地图服务
我们需要将栅格地图序列化到的磁盘以持久化存储,后期还要通过反序列化读取磁盘的地图数据再执行后续操作。在ROS中,地图数据的序列化与反序列化可以通过 map_server
功能包实现。
- map_server简介 map_server功能包中提供了两个节点:
map_saver
和map_server
,前者用于将栅格地图保存到磁盘,后者读取磁盘的栅格地图并以服务的方式提供出去。 - map_server使用之地图保存节点(
map_saver
)
2.1map_saver节点说明 订阅的topic:map(nav_msgs/OccupancyGrid)
订阅此话题用于生成地图文件。
2.2地图保存launch文件 地图保存的语法比较简单,编写一个launch文件,内容如下:
<launch>
<arg name="filename" value="$(find nav_demo)/map/nav" />
<node name="map_save" pkg="map_server" type="map_saver" args="-f $(arg filename)" />
</launch>
1.先启动 Gazebo 仿真环境
cd workspace
source ./devel/setup.bash
roslaunch urdf02_gazebo demo03_env.launch
2.然后再启动地图绘制的 launch 文件:
cd workspace
source ./devel/setup.bash
roslaunch nav_demo nav01_slam.launch
3.启动键盘键盘控制节点,用于控制机器人运动建图
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
4.在 rviz 中添加组件,显示栅格地图
5.保存地图
cd workspace
source ./devel/setup.bash
roslaunch nav_demo nav02_map_save.launch
结果:在指定路径下会生成两个文件,xxx.pgm 与 xxx.yaml
2.3 保存结果解释 xxx.pgm 本质是一张图片,直接使用图片查看程序即可打开。
xxx.yaml 保存的是地图的元数据信息,用于描述图片,内容格式如下:
# 1.声明地图图片资源的路径
image: /home/lzl/daohang/src/nav_demo/map/nav.pgm
# 2.地图刻度尺 单位:m/像素
resolution: 0.050000
# 3.地图的位姿信息(按照右手坐标系,地图右下角相对于rviz中的原点的位姿)[x,y,偏航角度]
origin: [-50.000000, -50.000000, 0.000000]
# 4.去反,黑色部分变白色,白变黑
negate: 0
# 地图中的障碍物判断:
# 最终地图结果:白色是可通行区域,黑色是障碍物,蓝灰是未知区域
# 判断规则:
# 1.地图中的每个像素都有取值[0,255] 白色:255 黑色0 像素值设为x
# 2.根据像素值计算一个比例:p=(255-像素值x)/255
# 即白色0,黑色1,灰色是介于0到1的值
# 3.判断是否是障碍物
# p>occupied_thresh 即为障碍物
# p<free_thresh 视为无物体,可以自由通行
# 下面两个相结合,判断地图上的东西是否是障碍物
# 5.占用阈值
occupied_thresh: 0.65
# 6.空闲阈值
free_thresh: 0.196
- map_server使用之地图服务(
map_server
) 3.1map_server节点说明 3.2地图读取 通过 map_server 的map_server
节点可以读取栅格地图数据,编写 launch 文件如下:
<launch>
<!-- 运行地图服务器,并且加载设置的地图-->
<node name="map_server" pkg="map_server" type="map_server" args="$(find nav_demo)/map/nav.yaml"/>
</launch>
其中参数是地图描述文件的资源路径,执行该launch文件,该节点会发布话题:map(nav_msgs/OccupancyGrid)
3.3地图显示 打开一个命令行:
cd workspace
source ./devel/setup.bash
roslaunch nav_demo nav03_map_server.launch
打开一个命令行:rviz
在 rviz 中使用 map 组件可以显示栅格地图: 若没配置过快捷键的话,以下为步骤: 快捷键 ctrl + shift + B 调用编译,选择:catkin_make:build(小齿轮) 可以点击配置设置为默认,修改.vscode/tasks.json 文件
{
// 有关 tasks.json 格式的文档,请参见
// https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"label": "catkin_make:debug", //代表提示的描述性信息
"type": "shell", //可以选择shell或者process,如果是shell代码是在shell里面运行一个命令,如果是process代表作为一个进程来运行
"command": "catkin_make",//这个是我们需要运行的命令
"args": [],//如果需要在命令后面加一些后缀,可以写在这里,比如-DCATKIN_WHITELIST_PACKAGES=“pac1;pac2”
"group": {
"kind":"build","isDefault":true},
"presentation": {
"reveal": "always"//可选always或者silence,代表是否输出信息
},
"problemMatcher": "$msCompile"
}
]
}
3.导航实现03_定位
所谓定位就是推算机器人自身在全局地图中的位置,当然,SLAM中也包含定位算法实现,不过SLAM的定位是用于构建全局地图的,是属于导航开始之前的阶段,而当前定位是用于导航中,导航中,机器人需要按照设定的路线运动,通过定位可以判断机器人的实际轨迹是否符合预期。在ROS的导航功能包集navigation
中提供了 amcl
功能包,用于实现导航中的机器人定位。
- amcl简介 AMCL(adaptive Monte Carlo Localization) 是用于2D移动机器人的概率定位系统,它实现了自适应(或KLD采样)蒙特卡洛定位方法,可以根据已有地图使用粒子滤波器推算机器人位置。
- amcl节点说明 amcl 功能包中的核心节点是:
amcl
。为了方便调用,需要先了解该节点订阅的话题、发布的话题、服务以及相关参数。 3.6坐标变换 里程计本身也是可以协助机器人定位的,不过里程计存在累计误差且一些特殊情况时(车轮打滑)会出现定位错误的情况,amcl 则可以通过估算机器人在地图坐标系下的姿态,再结合里程计提高定位准确度。 - amcl使用
3.1编写amcl节点相关的launch文件 关于launch文件的实现,在amcl功能包下的example目录已经给出了示例,可以作为参考,具体实现: 打开一个命令行:
roscd amcl
ls examples
gedit examples/amcl_diff.launch
该目录下会列出两个文件: amcl_diff.launch 和 amcl_omni.launch 文件,前者适用于差分移动机器人,后者适用于全向移动机器人,可以按需选择,此处参考前者,新建 launch 文件,复制 amcl_diff.launch 文件内容并修改如下:
<launch>
<node pkg="amcl" type="amcl" name="amcl" output="screen">
<!-- Publish scans from best pose at a max of 10 Hz -->
<param name="odom_model_type" value="diff"/>
<param name="odom_alpha5" value="0.1"/>
<param name="gui_publish_rate" value="10.0"/>
<param name="laser_max_beams" value="30"/>
<param name="min_particles" value="500"/>
<param name="max_particles" value="5000"/>
<param name="kld_err" value="0.05"/>
<param name="kld_z" value="0.99"/>
<param name="odom_alpha1" value="0.2"/>
<param name="odom_alpha2" value="0.2"/>
<!-- translation std dev, m -->
<param name="odom_alpha3" value="0.8"/>
<param name="odom_alpha4" value="0.2"/>
<param name="laser_z_hit" value="0.5"/>
<param name="laser_z_short" value="0.05"/>
<param name="laser_z_max" value="0.05"/>
<param name="laser_z_rand" value="0.5"/>
<param name="laser_sigma_hit" value="0.2<