一、简介
机器人系统仿真:是通过计算机对实体机器人系统进行模拟的技术,在 ROS 中,仿真实现涉及的内容主要有三:对 机器人建模(URDF)、创建仿真环境(Gazebo) 以及 感知环境(Rviz) 等系统性实现。
—————————————————
搭建环境介绍:
1.URDF 是 Unified Robot Description Format 的首字母缩写,直译为统一(标准化)机器人描述格式,可以以一种 XML 的方式描述机器人的部分结构
比如底盘、摄像头、激光雷达、机械臂以及不同关节的自由度.....,该文件可以被 C++ 内置的解释器转换成可视化的机器人模型,是 ROS 中实现机器人仿真的重要组件
(在程序中,我们使用 xacro 优化 urdf 驱动轮实现)
————————————
2.RViz 是 ROS Visualization Tool 的首字母缩写,直译为ROS的三维可视化工具。
它的主要目的是以三维方式显示ROS消息,可以将 数据进行可视化表达。例如:可以显示机器人模型,可以无需编程就能表达激光测距仪(LRF)传感器中的传感 器到障碍物的距离
下载RViz :(ros-(ros的版本)-rviz)
sudo apt install ros-noetic-rviz
————————————
3.Gazebo 是一款3D动态模拟器,用于显示机器人模型并创建仿真环境,能够在复杂的室内和室外环境中准确有效地模拟机器人。与游戏引擎提供高保真度的视觉模拟类似,Gazebo提供高保真度的物理模拟,其提供一整套传感器模型,以及对用户和程序非常友好的交互方式。
下载 Gazebo:
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main"
>
/etc/apt/sources.list.d/gazebo-stable.list'
sudo apt install gazebo11
sudo apt install libgazebo11-dev
素材下载链接
https://github.com/zx595306686/sim_demo.git
——————
使用 xacro 优化 urdf 驱动轮实现
—————————————————
二、建立URDF文件(Xacro)- RVIZ
前置: 优化RVIZ启动
重复启动launch
文件时,Rviz 之前的组件配置信息不会自动保存,需要重复执行步骤4的操作,为了方便使用,可以使用如下方式优化:
(如下:文件命名为 urdf01_rviz 功能包包含 urdf xacro)
----------------------------
Xacro-语法解析
我们先来看看一个例子 - 建造小车底盘
<!-- 根标签,必须声明 xmlns:xacro -->
<robot name="my_base" xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 封装变量、常量 -->
<xacro:property name="PI" value="3.141"/>
<!-- 宏:黑色设置 -->
<material name="black">
<color rgba="0.0 0.0 0.0 1.0" />
</material>
<!-- 底盘属性 -->
<xacro:property name="base_footprint_radius" value="0.001" /> <!-- base_footprint 半径 -->
<xacro:property name="base_link_radius" value="0.1" /> <!-- base_link 半径 -->
<xacro:property name="base_link_length" value="0.08" /> <!-- base_link 长 -->
<xacro:property name="earth_space" value="0.015" /> <!-- 离地间距 -->
<!-- 底盘 -->
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="${base_footprint_radius}" />
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<cylinder radius="${base_link_radius}" length="${base_link_length}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow">
<color rgba="0.5 0.3 0.0 0.5" />
</material>
</visual>
</link>
<joint name="base_link2base_footprint" type="fixed">
<parent link="base_footprint" />
<child link="base_link" />
<origin xyz="0 0 ${earth_space + base_link_length / 2 }" />
</joint>
<!-- 驱动轮 -->
<!-- 驱动轮属性 -->
<xacro:property name="wheel_radius" value="0.0325" /><!-- 半径 -->
<xacro:property name="wheel_length" value="0.015" /><!-- 宽度 -->
<!-- 驱动轮宏实现 -->
<xacro:macro name="add_wheels" params="name flag">
<link name="${name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" />
<material name="black" />
</visual>
</link>
<joint name="${name}_wheel2base_link" type="continuous">
<parent link="base_link" />
<child link="${name}_wheel" />
<origin xyz="0 ${flag * base_link_radius} ${-(earth_space + base_link_length / 2 - wheel_radius) }" />
<axis xyz="0 1 0" />
</joint>
</xacro:macro>
<xacro:add_wheels name="left" flag="1" />
<xacro:add_wheels name="right" flag="-1" />
<!-- 支撑轮 -->
<!-- 支撑轮属性 -->
<xacro:property name="support_wheel_radius" value="0.0075" /> <!-- 支撑轮半径 -->
<!-- 支撑轮宏 -->
<xacro:macro name="add_support_wheel" params="name flag" >
<link name="${name}_wheel">
<visual>
<geometry>
<sphere radius="${support_wheel_radius}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="black" />
</visual>
</link>
<joint name="${name}_wheel2base_link" type="continuous">
<parent link="base_link" />
<child link="${name}_wheel" />
<origin xyz="${flag * (base_link_radius - support_wheel_radius)} 0 ${-(base_link_length / 2 + earth_space / 2)}" />
<axis xyz="1 1 1" />
</joint>
</xacro:macro>
<xacro:add_support_wheel name="front" flag="1" />
<xacro:add_support_wheel name="back" flag="-1" />
</robot>
————————
代码解析:
属性定义
<xacro:property name="xxxx" value="yyyy" />
属性调用
${属性名称}
算数运算
${数学表达式}
宏定义
<xacro:macro name="宏名称" params="参数列表(多参数之间使用空格分隔)">
.....
参数调用格式: ${参数名}
</xacro:macro>
宏调用
<xacro:宏名称 参数1=xxx 参数2=xxx/>
文件包含(包含其他编写组成的文件 car.)
<robot name="xxx" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="my_base.xacro" />
<xacro:include filename="my_camera.xacro" />
<xacro:include filename="my_laser.xacro" />
....
</robot>
2.launch文件
建立文件之后,需要有launch文件来启动这些节点,也就是一个个打开这些功能包
在下面,我们打开了集成的 car 文件,并且在rviz包里找到小车,并给他
那么其对应的launch文件就要找到这些文件
<launch>
<param name="robot_description" command="$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/car.urdf.xacro" />
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01_rviz)/config/show_mycar.rviz" />
<node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" output="screen" />
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" output="screen" />
<node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" output="screen" />
</launch>
而car.xacro又包括需要的所以xacro文件,这样就变成了一个嵌套
启动时,我们只需要把这个launch文件启动就好 ,以上面为例
初始化:source ./devel/setup.bash
roslaunch urdf01_rviz demo03....
————————————
控制小车运动
安装Arbotix
sudo apt-get install ros-<<VersionName()>>-arbotix
将 <<VsersionName()>> 替换成当前 ROS 版本名称
接着在config里加上这个文件即可
之后只需在launch文件中配置就可以使用
<node name="arbotix" pkg="arbotix_python" type="arbotix_driver" output="screen">
<rosparam file="$(find my_urdf05_rviz)/config/control.yaml" command="load" />
<param name="sim" value="true" />
</node>
_________________________
2. URDF集成Gazebo
包含矩阵的head文件
<robot name="base" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- Macro for inertia matrix -->
<xacro:macro name="sphere_inertial_matrix" params="m r">
<inertial>
<mass value="${m}" />
<inertia ixx="${2*m*r*r/5}" ixy="0" ixz="0"
iyy="${2*m*r*r/5}" iyz="0"
izz="${2*m*r*r/5}" />
</inertial>
</xacro:macro>
<xacro:macro name="cylinder_inertial_matrix" params="m r h">
<inertial>
<mass value="${m}" />
<inertia ixx="${m*(3*r*r+h*h)/12}" ixy = "0" ixz = "0"
iyy="${m*(3*r*r+h*h)/12}" iyz = "0"
izz="${m*r*r/2}" />
</inertial>
</xacro:macro>
<xacro:macro name="Box_inertial_matrix" params="m l w h">
<inertial>
<mass value="${m}" />
<inertia ixx="${m*(h*h + l*l)/12}" ixy = "0" ixz = "0"
iyy="${m*(w*w + l*l)/12}" iyz= "0"
izz="${m*(w*w + h*h)/12}" />
</inertial>
</xacro:macro>
</robot>
我们想把之前的 urdf 形成的小车模型在gazebo里面实现,我们就需要加入 改进后的xacro文件
开启 launch文件节点,启动gazebo
创建仿真环境:
与之前相比,差别就在多了个world文件(在之前的素材里,我们把它复制到我们的文件来)来构建环境,当然也可以自己在里面构建,然后保存,当然那样有大可能是会卡住的
雷达 摄像头 运动控制功能添加
1 新建 Xacro 文件,配置功能信息
2 xacro 文件集成
3 启动仿真环境
4 Rviz 显示数据
具体如下:
1. 在新的 gazebo环境下 建立新的xacro:
注: demo05/6/7是环境中 雷达 底盘 相机 的形状,而新建的gazebo文件里面添加的是需要使用的内容,有了这些配置,功能才能被使用
然后把这些使用的 xacro文件 集合到 car.xacro中
此外,我们还需要配置 gezebo里的 雷达 运动 摄像头的功能,我们把它放到urdf的gezebo下
1. 雷达功能
<robot name="my_sensors" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- 雷达 -->
<gazebo reference="laser">
<sensor type="ray" name="rplidar">
<pose>0 0 0 0 0 0</pose>
<visualize>true</visualize>
<update_rate>5.5</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1</resolution>
<min_angle>-3</min_angle>
<max_angle>3</max_angle>
</horizontal>
</scan>
<range>
<min>0.10</min>
<max>30.0</max>
<resolution>0.01</resolution>
</range>
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
<plugin name="gazebo_rplidar" filename="libgazebo_ros_laser.so">
<topicName>/scan</topicName>
<frameName>laser</frameName>
</plugin>
</sensor>
</gazebo>
</robot>
2. 运动控制
<robot name="my_car_move" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- 传动实现:用于连接控制器与关节 -->
<xacro:macro name="joint_trans" params="joint_name">
<!-- Transmission is important to link the joints and the controller -->
<transmission name="${joint_name}_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="${joint_name}">
<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
</joint>
<actuator name="${joint_name}_motor">
<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
</xacro:macro>
<!-- 每一个驱动轮都需要配置传动装置 -->
<xacro:joint_trans joint_name="left_wheel2base_link" />
<xacro:joint_trans joint_name="right_wheel2base_link" />
<!-- 控制器 -->
<gazebo>
<plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
<rosDebugLevel>Debug</rosDebugLevel>
<publishWheelTF>true</publishWheelTF>
<robotNamespace>/</robotNamespace>
<publishTf>1</publishTf>
<publishWheelJointState>true</publishWheelJointState>
<alwaysOn>true</alwaysOn>
<updateRate>100.0</updateRate>
<legacyMode>true</legacyMode>
<leftJoint>left_wheel2base_link</leftJoint> <!-- 左轮 -->
<rightJoint>right_wheel2base_link</rightJoint> <!-- 右轮 -->
<wheelSeparation>${base_link_radius * 2}</wheelSeparation> <!-- 车轮间距 -->
<wheelDiameter>${wheel_radius * 2}</wheelDiameter> <!-- 车轮直径 -->
<broadcastTF>1</broadcastTF>
<wheelTorque>30</wheelTorque>
<wheelAcceleration>1.8</wheelAcceleration>
<commandTopic>cmd_vel</commandTopic> <!-- 运动控制话题 -->
<odometryFrame>odom</odometryFrame>
<odometryTopic>odom</odometryTopic> <!-- 里程计话题 -->
<robotBaseFrame>base_footprint</robotBaseFrame> <!-- 根坐标系 -->
</plugin>
</gazebo>
</robot>
3.摄像头
<robot name="my_sensors" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- 被引用的link -->
<gazebo reference="camera">
<!-- 类型设置为 camara -->
<sensor type="camera" name="camera_node">
<update_rate>30.0</update_rate> <!-- 更新频率 -->
<!-- 摄像头基本信息设置 -->
<camera name="head">
<horizontal_fov>1.3962634</horizontal_fov>
<image>
<width>1280</width>
<height>720</height>
<format>R8G8B8</format>
</image>
<clip>
<near>0.02</near>
<far>300</far>
</clip>
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.007</stddev>
</noise>
</camera>
<!-- 核心插件 -->
<plugin name="gazebo_camera" filename="libgazebo_ros_camera.so">
<alwaysOn>true</alwaysOn>
<updateRate>0.0</updateRate>
<cameraName>/camera</cameraName>
<imageTopicName>image_raw</imageTopicName>
<cameraInfoTopicName>camera_info</cameraInfoTopicName>
<frameName>camera</frameName>
<hackBaseline>0.07</hackBaseline>
<distortionK1>0.0</distortionK1>
<distortionK2>0.0</distortionK2>
<distortionK3>0.0</distortionK3>
<distortionT1>0.0</distortionT1>
<distortionT2>0.0</distortionT2>
</plugin>
</sensor>
</gazebo>
</robot>
接着在总和 car.urdf.xacro文件把他们集合,这样就可以有图像又有功能了
接着,在总和集成后,再启动我们的launch文件即可
机器人运动控制以及里程计信息显示
1 为 joint 添加传动装置以及控制器(move)
2 xacro文件集成(car.xacro)
3 启动 gazebo并控制机器人运动(启动 car.xacro)
启动 launch 文件,使用 topic list 查看话题列表,会发现多了 /cmd_vel 然后发布 vmd_vel 消息控制即可
使用命令控制(或者可以编写单独的节点控制)(telekey 键盘控制节点)
————————————————————————————————————
导航
导航实现01_SLAM建图
gmapping 是ROS开源社区中较为常用且比较成熟的SLAM算法之一,gmapping可以根据移动机器人里程计数据和激光雷达数据来绘制二维的栅格地图
下载:
sudo apt install ros-noetic-gmapping
gmapping 功能包中的核心节点是:slam_gmapping。为了方便调用,需要先了解该节点订阅的话题、发布的话题、服务以及相关参数。
————————
2.1订阅的Topic
tf (tf/tfMessage)
- 用于雷达、底盘与里程计之间的坐标变换消息。
scan(sensor_msgs/LaserScan)
- SLAM所需的雷达信息。
————————————————
2.2发布的Topic
map_metadata(nav_msgs/MapMetaData)
- 地图元数据,包括地图的宽度、高度、分辨率等,该消息会固定更新。
map(nav_msgs/OccupancyGrid)
- 地图栅格数据,一般会在rviz中以图形化的方式显示。
~entropy(std_msgs/Float64)
- 机器人姿态分布熵估计(值越大,不确定性越大)。
————————————
2.3服务
dynamic_map(nav_msgs/GetMap)
- 用于获取地图数据。
————————————
2.4参数
~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)
- 激光探测的最大可用范围(超出此阈值,被截断)。
~maxRange(float)
- 激光探测的最大范围。
.... 参数较多,上述是几个较为常用的参数,其他参数介绍可参考官网。
————————————
2.5所需的坐标变换
雷达坐标系→基坐标系
- 一般由 robot_state_publisher 或 static_transform_publisher 发布。
基坐标系→里程计坐标系
- 一般由里程计节点发布。
————————————
2.6发布的坐标变换
地图坐标系→里程计坐标系
- 地图到里程计坐标系之间的变换
1 编写gmapping节点相关launch文件
2 执行
1.先启动 Gazebo 仿真环境(此过程略)
2.然后再启动地图绘制的 launch 文件:
roslaunch 包名(nav_...) launch文件名(nav01_...)
3.启动键盘键盘控制节点,用于控制机器人运动建图
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
4.在 rviz 中添加组件,显示栅格地图
导航实现02_地图服务
1.map_server简介
map_server功能包中提供了两个节点: map_saver 和 map_server,前者用于将栅格地图保存到磁盘,后者读取磁盘的栅格地图并以服务的方式提供出去
sudo apt install ros-noetic-map-server
2.1map_saver节点说明
2.2地图保存launch文件
首先,参考上一节,依次启动仿真环境,键盘控制节点与SLAM节点;
然后,通过键盘控制机器人运动并绘图;
最后,通过上述地图保存方式保存地图。
结果:在指定路径下会生成两个文件,xxx.pgm 与 xxx.yaml
其中 mymap 是指地图的保存路径以及保存的文件名称。
<launch>
<arg name="filename" value="$(find mycar_nav)/map/nav" />
<node name="map_save" pkg="map_server" type="map_saver" args="-f $(arg filename)" />
</launch>
xxx.pgm 本质是一张图片,直接使用图片查看程序即可打开。
xxx.yaml 保存的是地图的元数据信息,用于描述图片
3.2地图读取
通过 map_server 的 map_server 节点可以读取栅格地图数据,编写 launch 文件如下:
<launch>
<!-- 设置地图的配置文件 -->
<arg name="map" default="nav.yaml" />
<!-- 运行地图服务器,并且加载设置的地图-->
<node name="map_server" pkg="map_server" type="map_server" args="$(find mycar_nav)/map/$(arg map)"/>
</launch>
3.3地图显示
具体如下:
source roslaunch 运行没有报错就算成功
接着在我们map文件里面就会多两个文件,这就是保存成功了
而地图服务,想要显示,就要打开一个rviz
source roslaunch 打开nav03
然后在终端打开一个rviz ,add添加地图(map),选取话题即可
——————————————————————————
AMCL(adaptive Monte Carlo Localization) 是用于2D移动机器人的概率定位系统,它实现了自适应(或KLD采样)蒙特卡洛定位方法,可以根据已有地图使用粒子滤波器推算机器人位置。
sudo apt install ros-noetic-navigation
3.1编写amcl节点相关的launch文件
3.2编写测试launch文件
test.launch
3.3执行
1.先启动 Gazebo 仿真环境(此过程略);
2.启动键盘控制节点:
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
3.启动上一步中集成地图服务、amcl 与 rviz 的 launch 文件;
4.在启动的 rviz 中,添加RobotModel、Map组件,分别显示机器人模型与地图,添加 posearray 插件,设置topic为particlecloud来显示 amcl 预估的当前机器人的位姿,箭头越是密集,说明当前机器人处于此位置的概率越高;
5.通过键盘控制机器人运动,会发现 posearray 也随之而改变。
——————————————
4.1launch文件
关于move_base节点的调用,模板如下:
4.3launch文件集成
如果要实现导航,需要集成地图服务、amcl 、move_base 与 Rviz 等,集成示例如下:
4.4测试
1.先启动 Gazebo 仿真环境(此过程略);
2.启动导航相关的 launch 文件;
3.添加Rviz组件(参考演示结果),可以将配置数据保存,后期直接调用;
4.通过Rviz工具栏的 2D Nav Goal设置目的地实现导航
即 右上角粉红色箭头
——————————
导航与SLAM建图
1.首先运行gazebo仿真环境;
2.然后执行launch文件;
3.在rviz中通过2D Nav Goal设置目标点,机器人开始自主移动并建图了;
4.最后可以使用 map_server 保存地图。
——————————————————————————————-
小结
本章主要介绍了ROS中仿真实现涉及的三大知识点:
-
URDF(Xacro)
-
Rviz
-
Gazebo
rviz 是三维可视化工具,强调把已有的数据可视化显示;
gazebo是三维物理仿真平台,强调的是创建一个虚拟的仿真环境。
rviz需要已有数据。
rviz提供了很多插件,这些插件可以显示图像、模型、路径等信息,但是前提都是这些数据已经以话题、参数的形式发布,rviz做的事情就是订阅这些数据,并完成可视化的渲染,让开发者更容易理解数据的意义。
gazebo不是显示工具,强调的是仿真,它不需要数据,而是创造数据。
我们可以在gazebo中免费创建一个机器人世界,不仅可以仿真机器人的运动功能,还可以仿真机器人的传感器数据。而这些数据就可以放到rviz中显示,所以使用gazebo的时候,经常也会和rviz配合使用。当我们手上没有机器人硬件或实验环境难以搭建时,仿真往往是非常有用的利器。
综上,如果你手上已经有机器人硬件平台,并且在上边可以完成需要的功能,用rviz应该就可以满足开发需求。
如果你手上没有机器人硬件,或者想在仿真环境中做一些算法、应用的测试,gazebo+rviz应该是你需要的。