所用的学习链接:
【奥特学园】ROS机器人入门课程《ROS理论与实践》零基础教程P289-314
【以上视频笔记见http://www.autolabor.com.cn/book/ROSTutorials/】
官方链接:https://www.ros.org/reps/rep-0105.html
(全局概览图:定位+路径规划)
SLAM(实现地图构建和即时定位),也称为CML (Concurrent Mapping and Localization), 即时定位与地图构建,或并发建图与定位。SLAM问题可以描述为: 机器人在未知环境中从一个未知位置开始移动,在移动过程中根据位置估计和地图进行自身定位,同时在自身定位的基础上建造增量式地图,以绘制出外部环境的完全地图。(如红警起始一片黑,随着摸索出现地图)
ROS中保存地图的功能包是 map_server
传感器:如果要完成 SLAM ,机器人必须要具备感知外界环境的能力,尤其是要具备获取周围环境深度信息的能力。感知的实现需要依赖于传感器,比如: 激光雷达、摄像头、RGB-D摄像头
(确定在地图中的位置)
(全局+局部路径规划)
全局路径规划(gloable_planner)
根据给定的目标点和全局地图实现总体的路径规划,使用 Dijkstra 或 A* 算法进行全局路径规划,计算最优路线,作为全局路线
局部路径规划(local_planner)
在实际导航过程中,机器人可能无法按照给定的全局最优路线运行,比如:机器人在运行中,可能会随时出现一定的障碍物... 本地规划的作用就是使用一定算法(Dynamic Window Approaches) 来实现障碍物的规避,并选取当前最优路径以尽量符合全局最优路径
(控制速度和方向)
导航功能包集假定它可以通过话题"cmd_vel"发布geometry_msgs/Twist
类型的消息,这个消息基于机器人的基座坐标系,它传递的是运动命令。这意味着必须有一个节点订阅"cmd_vel"话题, 将该话题上的速度命令转换为电机命令并发送。
(感知周围环境)
感知周围环境信息,比如: 摄像头、激光雷达、编码器...,摄像头、激光雷达可以用于感知外界环境的深度信息,编码器可以感知电机的转速信息,进而可以获取速度信息并生成里程计信息。
在导航功能包集中,环境感知也是一重要模块实现,它为其他模块提供了支持。其他模块诸如: SLAM、amcl、move_base 都需要依赖于环境感知。
【根据自己向什么方向走了多少路判断位置】
【根据自己周围环境判断位置】
上述两种定位实现中,机器人坐标系一般使用机器人模型中的根坐标系(base_link 或 base_footprint)
里程计定位时,父级坐标系一般称之为 odom
传感器定位时,父级参考系一般称之为 map。
当二者结合使用时,map 和 odom 都是机器人模型根坐标系的父级,这是不符合坐标变换中"单继承"的原则的,所以,一般会将转换关系设置为: map -> doom -> base_link 或 base_footprint。
虽然导航功能包集被设计成尽可能的通用,在使用时仍然有三个主要的硬件限制:
它是为差速驱动的轮式机器人设计的。它假设底盘受到理想的运动命令的控制并可实现预期的结果,命令的格式为:x速度分量,y速度分量,角速度(theta)分量。
它需要在底盘上安装一个单线激光雷达。这个激光雷达用于构建地图和定位。
导航功能包集是为正方形的机器人开发的,所以方形或圆形的机器人将是性能最好的。 它也可以工作在任意形状和大小的机器人上,但是较大的机器人将很难通过狭窄的空间。
安装 ROS
当前导航基于仿真环境,先保证上一章的机器人系统仿真可以正常执行
在仿真环境下,机器人可以正常接收 /cmd_vel 消息,并发布里程计消息,传感器消息发布也正常,也即导航模块中的运动控制和环境感知实现完毕
# sudo apt install ros--gmapping
sudo apt install ros-noetic-gmapping
# sudo apt install ros--map-server
sudo apt install ros-noetic-map-server
# sudo apt install ros--navigation
sudo apt install ros-noetic-navigation
①右键 7.19_demo01/src ,新建package catkin ,命名【nav_demo】,导入dependences: gmapping map_server amcl move_base
②在nav_demo文件夹下新建文件夹launch、map、param、config
官方链接:http://wiki.ros.org/gmapping
SLAM算法有多种,当前我们选用gmapping。
gmapping 是ROS开源社区中较为常用且比较成熟的SLAM算法之一,gmapping可以根据移动机器人里程计数据和激光雷达数据来绘制二维的栅格地图。
gmapping 功能包中的核心节点是:slam_gmapping。为了方便调用,需要先了解该节点订阅的话题、发布的话题、服务以及相关参数。
tf (tf/tfMessage):用于雷达、底盘与里程计之间的坐标变换消息。
scan(sensor_msgs/LaserScan):SLAM所需的雷达信息。
map_metadata(nav_msgs/MapMetaData):地图元数据,包括地图的宽度、高度、分辨率等,该消息会固定更新。
map(nav_msgs/OccupancyGrid):地图栅格数据,一般会在rviz中以图形化的方式显示。
~entropy(std_msgs/Float64):机器人姿态分布熵估计(值越大,不确定性越大)。
dynamic_map(nav_msgs/GetMap):用于获取地图数据。
~base_frame(string, default:"base_link"):机器人基坐标系。
~map_frame(string, default:"map"):地图坐标系。
~odom_frame(string, default:"odom"):里程计坐标系。
~map_update_interval(float, default: 5.0):地图更新频率,根据指定的值设计更新间隔。
~maxUrange(float, default: 80.0):激光探测的最大可用范围(超出此阈值,被截断)。
激光探测的最大范围。
雷达坐标系→基坐标系:一般由 robot_state_publisher 或 static_transform_publisher 发布。
基坐标系→里程计坐标系:一般由里程计节点发布。
地图坐标系→里程计坐标系:地图到里程计坐标系之间的变换。
在nav_demo01/launch文件夹下新建 t1_slam.launch
①运行相关启动命令
ctrl+shift+b # 编译
# 终端1
roscore
# 终端2(启动之前的gazebo)
roslaunch urdf_gazebo t7_gazebo.launch
# 终端3(启动新建的地图绘制 launch )
roslaunch nav_demo t1_slam.launch
# 外部新终端1(启动键盘控制)
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
②在Rviz中 Add → Map → Topic :/map
可以看见rviz中出现了地图。控制小车移动,可以进一步绘制地图
③打开TF → Frames → 勾选 map、odom、base_footprint
勾选后会显示起始点的 map 和 odm 坐标,base_footprint可显示当前位置和原点的距离关系,其他选项根据需要自行调整
官方链接:http://wiki.ros.org/map_server
map_server功能包中提供了两个节点: map_saver 和 map_server,前者用于将栅格地图保存到磁盘,后者读取磁盘的栅格地图并以服务的方式提供出去。
map(nav_msgs/OccupancyGrid):订阅此话题用于生成地图文件。
map_metadata(nav_msgs / MapMetaData):发布地图元数据。
map(nav_msgs / OccupancyGrid):地图数据。
static_map(nav_msgs / GetMap):通过此服务获取地图。
〜frame_id(字符串,默认值:“map”):地图坐标系。
①新建用于地图保存的 t2_map_save.launch文件
②建完图后,运行 t2_map_save.launch
# 新终端
roslaunch nav_demo t2_map_save.launch
③查看
在nav_demo/map目录下,会看到生成了两个地图文件
直接使用图片查看程序即可打开。
用于描述图片,内容格式如下:
# 图片路径,可以是绝对路径也可以是相对路径
image: /home/netceor_ros/ROS/7.19_demo1/src/nav_demo/map/nav.pgm
# 分辨率(单位: m/像素)、地图刻度尺单位
resolution: 0.050000
# 地图位姿,相对于rviz中原点位姿。(x,y,偏航)偏航为逆时针旋转(偏航= 0表示无旋转)
origin: [-50.000000, -50.000000, 0.000000]
# 取反:是否应该颠倒白色/黑色自由/占用的语义
negate: 0
# 占用阈值:占用概率大于此阈值的像素被视为完全占用
occupied_thresh: 0.65
# 空闲阈值:占用率小于此阈值的像素被视为完全空闲
free_thresh: 0.196
a.地图中的每一个像素取值在 [0,255] 之间,白色为 255,黑色为 0,该值设为 x;
b.map_server 会将像素值作为判断是否是障碍物的依据,首先计算比例: p = (255 - x) / 255.0,白色为0,黑色为1(negate为true,则p = x / 255.0);
c.根据步骤2计算的比例判断是否是障碍物,如果 p > occupied_thresh 那么视为障碍物,如果 p < free_thresh 那么视为无物。
①新建用于地图读取的 t3_map_load.launch文件
②启动命令
ctrl+shift+b # 编译
# 集成终端1,运行完后地图信息就被发布
roslaunch nav_demo t3_map_load.launch
# 外部新终端2,开启一个rviz
rviz
# 外部新终端3,查看话题,如果有map相关就说明成功发布
# rostopic list
③ Rviz 中 → Add → Map → Topic:/map
【这里我第一次启动的时候没有map话题,把vscode重新启动后就有用了】
官方链接:http://wiki.ros.org/amcl
定位就是推算机器人自身在全局地图中的位置。SLAM中也包含定位算法实现,不过SLAM的定位是用于构建全局地图的,是属于导航开始之前的阶段,而当前定位是用于导航中。导航中,机器人需要按照设定的路线运动,通过定位可以判断机器人的实际轨迹是否符合预期。在ROS的导航功能包集navigation中提供了 amcl 功能包,用于实现导航中的机器人定位。
AMCL(adaptive Monte Carlo Localization) 是用于2D移动机器人的概率定位系统,它实现了自适应(或KLD采样)蒙特卡洛定位方法,可以根据已有地图使用粒子滤波器推算机器人位置。
scan(sensor_msgs/LaserScan):激光雷达数据。
tf(tf/tfMessage):坐标变换消息。
initialpose(geometry_msgs/PoseWithCovarianceStamped):用来初始化粒子滤波器的均值和协方差。
map(nav_msgs/OccupancyGrid):获取地图数据。
amcl_pose(geometry_msgs/PoseWithCovarianceStamped):机器人在地图中的位姿估计。
particlecloud(geometry_msgs/PoseArray):位姿估计集合,rviz中可以被 PoseArray 订阅然后图形化显示机器人的位姿估计集合。
tf(tf/tfMessage):发布从 odom 到 map 的转换。
global_localization(std_srvs/Empty):初始化全局定位的服务。
request_nomotion_update(std_srvs/Empty):手动执行更新和发布更新的粒子的服务。
set_map(nav_msgs/SetMap):手动设置新地图和姿态的服务。
static_map(nav_msgs/GetMap):调用此服务获取地图数据。
~odom_model_type(string, default:"diff"):里程计模型选择: "diff","omni","diff-corrected","omni-corrected" (diff 差速、omni 全向轮)
~odom_frame_id(string, default:"odom"):里程计坐标系。
~base_frame_id(string, default:"base_link"):机器人极坐标系。
~global_frame_id(string, default:"map"):地图坐标系。
里程计本身也是可以协助机器人定位的,不过里程计存在累计误差且一些特殊情况时(车轮打滑)会出现定位错误的情况,amcl 则可以通过估算机器人在地图坐标系下的姿态,再结合里程计提高定位准确度。
①amcl节点相关的 t4_amcl.launch文件
# 可查看模板
# roscd amcl
# ls examples
# 该目录下会列出两个文件: amcl_diff.launch 和 amcl_omni.launch 文件,前者适用于差分移动机器人,后者适用于全向移动机器人
# gedit examples/amcl_diff.launch
# 复制并修改
②编写测试 amcl 的 t5_amcl_test.launch 文件
amcl文件无法单独运行,需要新建一个启动文件
③运行
ctrl+shift+b # 编译
# 启动gazebo
roslaunch urdf_gazebo t7_gazebo.launch
# 启动键盘
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
# 启动 t5_amcl_test.launch
roslaunch nav_demo t5_amcl_test.launch
④Rviz 中 → Add → Map → Topic:/map
⑤Rviz 中 → Add → RobotModel
⑥Rviz 中 → Add → PoseArray → Topic:/particlecloud
控制小车运动起来,红色线越密集处,小车处于该位置的概率越大。
官方链接:http://wiki.ros.org/move_base
move_base 功能包提供了基于动作(action)的路径规划实现,move_base 可以根据给定的目标点,控制机器人底盘运动至目标位置,并且在运动过程中会连续反馈机器人自身的姿态与目标点的状态信息。move_base主要由全局路径规划与本地路径规划组成。
move_base已经被集成到了navigation包
【动作可以为机器人的行动提供实时反馈】
move_base/goal(move_base_msgs/MoveBaseActionGoal):move_base 的运动规划目标。
move_base/cancel(actionlib_msgs/GoalID):取消目标。
move_base/feedback(move_base_msgs/MoveBaseActionFeedback):连续反馈的信息,包含机器人底盘坐标。
move_base/status(actionlib_msgs/GoalStatusArray):发送到move_base的目标状态信息。
move_base/result(move_base_msgs/MoveBaseActionResult):操作结果(此处为空)。
move_base_simple/goal(geometry_msgs/PoseStamped):运动规划目标(与action相比,没有连续反馈,无法追踪机器人执行状态)。
cmd_vel(geometry_msgs/Twist):输出到机器人底盘的运动控制消息。
~make_plan(nav_msgs/GetPlan):请求该服务,可以获取给定目标的规划路径,但是并不执行该路径规划。
~clear_unknown_space(std_srvs/Empty):允许用户直接清除机器人周围的未知空间。
~clear_costmaps(std_srvs/Empty):允许清除代价地图中的障碍物,可能会导致机器人与障碍物碰撞,请慎用。
机器人导航(尤其是路径规划模块)是依赖于地图的。ROS中的地图其实就是一张图片,这张图片有宽度、高度、分辨率等元数据,在图片中使用灰度值来表示障碍物存在的概率。不过SLAM构建的地图在导航中是不可以直接使用的,因为:
所以,静态地图无法直接应用于导航,其基础之上需要添加一些辅助信息的地图,比如时时获取的障碍物数据,基于静态地图添加的膨胀区等数据。
代价地图有两张:global_costmap(全局代价地图) 和 local_costmap(本地代价地图),前者用于全局路径规划,后者用于本地路径规划。
两张代价地图都可以多层叠加,一般有以下层级:
Static Map Layer:静态地图层——SLAM构建的静态地图。
Obstacle Map Layer:障碍地图层——导航中传感器感知的障碍物信息。
Inflation Layer:膨胀层——在以上两层地图上进行膨胀(向外扩张),以避免机器人的外壳会撞上障碍物。
Other Layers:自定义costmap——根据业务设置的地图数据,临时模拟。
多个layer可以按需自由搭配。
在ROS中,计算代价值的方法如图
上图中,横轴是距离机器人中心的距离,纵轴是代价地图中栅格的灰度值。
膨胀空间的设置可以参考非自由空间。
路径规划算法在move_base功能包的move_base节点中已经封装完毕了,但是还不可以直接调用,因为算法虽然已经封装了,但是该功能包面向的是各种类型支持ROS的机器人,不同类型机器人可能大小尺寸不同,传感器不同,速度不同,应用场景不同....最后可能会导致不同的路径规划结果,那么在调用路径规划节点之前,我们还需要配置机器人参数。
①配置文件 costmap_common_params.yaml
在 param 文件夹下新建 costmap_common_params.yaml
#机器人几何参,如果机器人是圆形,设置 robot_radius,如果是其他形状设置 footprint
robot_radius: 0.12 #圆形
# footprint: [[-0.12, -0.12], [-0.12, 0.12], [0.12, 0.12], [0.12, -0.12]] #其他形状
obstacle_range: 3.0 # 用于障碍物探测,比如: 值为 3.0,意味着检测到距离小于 3 米的障碍物时,就会引入代价地图
raytrace_range: 3.5 # 用于清除障碍物,比如:值为 3.5,意味着清除代价地图中 3.5 米以外的障碍物
#膨胀半径,扩展在碰撞区域以外的代价区域,使得机器人规划路径避开障碍物
inflation_radius: 0.2
#代价比例系数,越大则代价值越小
cost_scaling_factor: 3.0
#地图类型
map_type: costmap
#导航包所需要的传感器
observation_sources: scan
#对传感器的坐标系和数据进行配置。这个也会用于代价地图添加和清除障碍物。例如,你可以用激光雷达传感器用于在代价地图添加障碍物,再添加kinect用于导航和清除障碍物。
scan: {sensor_frame: laser, data_type: LaserScan, topic: scan, marking: true, clearing: true}
②配置文件 global_costmap_params.yaml
在 param 文件夹下新建 global_costmap_params.yaml
global_costmap:
global_frame: map #地图坐标系
robot_base_frame: base_footprint #机器人坐标系
# 以此实现坐标变换
update_frequency: 1.0 #代价地图更新频率
publish_frequency: 1.0 #代价地图的发布频率
transform_tolerance: 0.5 #等待坐标变换发布信息的超时时间
static_map: true # 是否使用一个地图或者地图服务器来初始化全局代价地图,如果不使用静态地图,这个参数为false.
③配置文件 local_costmap_params.yaml
在 param 文件夹下新建 local_costmap_params.yaml
local_costmap:
global_frame: odom #里程计坐标系
robot_base_frame: base_footprint #机器人坐标系
update_frequency: 10.0 #代价地图更新频率
publish_frequency: 10.0 #代价地图的发布频率
transform_tolerance: 0.5 #等待坐标变换发布信息的超时时间
static_map: false #不需要静态地图,可以提升导航效果
rolling_window: true #是否使用动态窗口,默认为false,在静态的全局地图中,地图不会变化
width: 3 # 局部地图宽度 单位是 m
height: 3 # 局部地图高度 单位是 m
resolution: 0.05 # 局部地图分辨率 单位是 m,一般与静态地图分辨率保持一致
④配置文件 base_local_planner_params.yaml
在 param 文件夹下新建 base_local_planner_params.yaml
TrajectoryPlannerROS:
# Robot Configuration Parameters
max_vel_x: 0.5 # X 方向最大速度
min_vel_x: 0.1 # X 方向最小速速
max_vel_theta: 1.0 #
min_vel_theta: -1.0
min_in_place_vel_theta: 1.0
acc_lim_x: 1.0 # X 加速限制
acc_lim_y: 0.0 # Y 加速限制
acc_lim_theta: 0.6 # 角速度加速限制
# Goal Tolerance Parameters,目标公差
xy_goal_tolerance: 0.10
yaw_goal_tolerance: 0.05
# Differential-drive robot configuration
# 是否是全向移动机器人
holonomic_robot: false
# Forward Simulation Parameters,前进模拟参数
sim_time: 0.8
vx_samples: 18
vtheta_samples: 20
sim_granularity: 0.05
⑤参数配置技巧(如果跟着步骤,则无需操作)
以上配置在实操中,可能会出现机器人在本地路径规划时与全局路径规划不符而进入膨胀区域出现假死的情况。
全局路径规划与本地路径规划虽然设置的参数是一样的,但是二者路径规划和避障的职能不同,可以采用不同的参数设置策略:
- 全局代价地图可以将膨胀半径和障碍物系数设置的偏大一些;
- 本地代价地图可以将膨胀半径和障碍物系数设置的偏小一些。
这样,在全局路径规划时,规划的路径会尽量远离障碍物,而本地路径规划时,机器人即便偏离全局路径也会和障碍物之间保留更大的自由空间,从而避免了陷入“假死”的情形。
以及几个可能会调整的参数
a.robot_radius【如圆形机器人大小0.1,设置为0.12】
b.sensor_frame
a.global_frame
d.robot_base_frame
a.global_frame
d.robot_base_frame
⑥ 新建 t6_movebase.launch 文件
⑦ 新建 t7_346.launch 文件
①编译、运行gazebo、运行rviz
ctrl+shift+b # 编译
# 启动gazebo
roslaunch urdf_gazebo t7_gazebo.launch
# 启动 rviz
roslaunch nav_demo t7_346.launch
② Rviz 中 → Add 添加组件
③自动导航实现
选中菜单栏 " 2D Nav Goal ",任意选择一个位置,机器人开始自动导航
在前文的建图中,我们通过键盘控制小车运动,得到完整的地图模型,学习了自动导航之后,可以开始尝试使用自动导航来实现地图构建
由于SLAM建图过程中本身就会时时发布地图信息,所以无需再使用map_server,SLAM已经发布了话题为 /map 的地图消息了,且导航需要定位模块,SLAM本身也是可以实现定位的。
因此当前launch文件实现,无需调用map_server的相关节点,只需要启动SLAM节点与move_base节点。
新建一个 t8_slam_auto.launch 来集成slam和自动导航。
由于slam文件中已有打开rviz的语句,因此在t8中不需要这个语句。
但要修改 t1_slam.launch文件中关于启动rviz的相关参数,导入上一博客中保存好的 nav_test.rviz 环境
# 原句子
#
# 替换为
ctrl+shift+b # 编译
# 启动gazebo
roslaunch urdf_gazebo t7_gazebo.launch
# 启动 rviz
roslaunch nav_demo t8_slam_auto.launch
在rviz中通过2D Nav Goal设置目标点,机器人开始自主移动并建图
建图完成后,使用map_server保存地图,详情参考本文的二、3
机器人模型搭建参考链接:
ROS入门(五)——仿真机器人一(URDF+Rviz)
ROS入门(六)——仿真机器人二(Xacro+Rviz+Arbotix小车运动)
ROS入门(七)——仿真机器人三(Gazebo+Xacro)
ROS入门(八)——仿真机器人四(Gazebo+Rviz+雷达、摄像头、kinet仿真显示)