从零开始实现移动机器人建图和定位

| |

在windows11 wsl2上进行ros2的安装和配置,环境:ubuntu22.04,ros2 humble, gazebo,rviz,使用slam_toolbox包进行建图。文件资源可从文末下载。

安装和配置环境

在windows11上安装wsl2,并安装ros2的相关依赖。

安装wsl2

开发人员可以在 Windows 计算机上同时访问 Windows 和 Linux 的强大功能。 通过适用于 Linux 的 Windows 子系统 (WSL),开发人员可以安装 Linux 发行版(例如 Ubuntu、OpenSUSE、Kali、Debian、Arch Linux 等),并直接在 Windows 上使用 Linux 应用程序、实用程序和 Bash 命令行工具,不用进行任何修改,也无需承担传统虚拟机或双启动设置的费用。

PowerShell

# 此命令将启用运行 WSL 并安装 Linux 的 Ubuntu 发行版所需的功能。
wsl --install

# 列出可安装的linux系统
wsl --list --online

# 安装一个指定的linux,安装完成之后需要重启电脑
wsl --install -d <DistroName>

# 列出已安装的wsl发行版
wsl -l

也可以在 Microsoft Store 中安装wsl2,直接搜索ubuntu,安装Ubuntu 22.04.3 LTS 即可。

20240716110635.png20240716110635.png

参考微软官方文档即可,非常简单易用。
https://learn.microsoft.com/zh-cn/windows/wsl/setup/environment

PowerShell

wsl #打开默认的Linux 发行版
wsl -d Ubuntu-22.04 #打开指定发行版

换源

bash

# 查询系统版本号
lsb_release -a
# 或者
cat /etc/issue

sudo vim /etc/apt/sources.list

参考清华源文档,若Ubuntu发行版为22.04,则将文档中的内容替换为

txt

# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse

# 以下安全更新软件源包含了官方源与镜像站配置,如有需要可自行修改注释切换
deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse
# deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse

# 预发布软件源,不建议启用
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse
# # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse

给WSL设置网络代理(选做)

设置代理主要是为了方便克隆Github仓库,如无此需求,可以不设置
默认情况下WSL的网络模式是NAT模式,在主机打开网络代理时,wsl无代理。参考微软官方文档可知设置 Windows 上的网络接口“镜像”到 Linux 中的方法为:
在C盘C:\Users\<UserName>\下新建.wslconfig文件,并添加以下内容:

txt

[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true

同时需要将WSL升级到预览版

PowerShell

wsl --update --pre-release

修改完成后,重启WSL即可。

PowerShell

wsl --shutdown

安装ros2

首先打开指定的wsl

PowerShell

wsl -d Ubuntu-22.04

完成各项配置

bash

sudo apt update && sudo apt upgrade

# 使用fishros安装ros2 humble
wget http://fishros.com/install -O fishros && . fishros

参阅:ROS2学习笔记1-安装、编译和基本概念

鱼香ros的安装包已经预装了海龟例程

shell

sudo apt install ros-humble-turtlesim # 安装海龟功能包

尝试一下海龟例程,在终端(Ctrl+Shift+T)中运行下面的命令

shell

ros2 run turtlesim turtlesim_node

打开一个新的终端(Ctrl+Shift+T),注意终端不要最大化,运行下面的命令

shell

ros2 run turtlesim turtle_teleop_key

即可在第二个终端中使用方向键控制小海龟的移动了。

海龟例程海龟例程

安装仿真需要的其他工具

安装gazebo

bash

# 安装gazebo仿真软件
sudo apt install gazebo
# 安装gazebo-ros功能包(所有)
sudo apt install ros-humble-gazebo-*
# 安装gazebo_ros插件
sudo apt install ros-humble-gazebo-ros
# 测试是否安装成功
gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so 

效果如下:

打开gazebo打开gazebo

“shift+鼠标左键”转换视角,“鼠标左键”平移视角,“滚轮”缩放大小。

设计一个机器人小车

初步设计

设计一个四轮的机器小车,车上安装有激光雷达模块,小车用差速模块驱动。

考虑小车有一个主体body,长方体结构,长300mm,宽200mm,高60毫米,车轮外径100mm,车轮宽度30mm。前后车轮轴轴距为240mm,车轮轮心连接在主体的两个侧面上的垂直中点上,这样body的中心距离地面的距离就是50mm,body下底面(车的底盘)距离地面距离就是20mm。我们这里最终目标是SLAM任务,因此为简便起见,我们不考虑机械结构上的细节。
机器人命名为four_wheel_robot,基坐标系设置在body中心下方30mm处。(基座标是为了在rviz中能让小车车轮着地,没有其他作用。)
简单地设计urdf模型内容如下:

xml

<?xml version="1.0"?>
<robot name="four_wheel_robot">
  <link name="base_link"/>
  <joint name="base_joint" type="fixed">
    <parent link="base_link"/>
    <child link="body"/>
    <origin xyz="0.0 0.0 0.05" rpy="0 0 0"/>
  </joint>
  <link name="body">
    <visual>
      <geometry>
        <box size="0.3 0.2 0.06"/>
      </geometry>
    </visual>
  </link>
  <joint name="joint_front_right" type="continuous">
    <parent link="body"/>
    <child link="front_right_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="0.12 0.115 0" rpy="0 0 0"/>
  </joint>
  <joint name="joint_front_left" type="continuous">
    <parent link="body"/>
    <child link="front_left_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="0.12 -0.115 0" rpy="0 0 0"/>
  </joint>
  <joint name="joint_back_right" type="continuous">
    <parent link="body"/>
    <child link="back_right_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="-0.12 0.115 0" rpy="0 0 0"/>
  </joint>
  <joint name="joint_back_left" type="continuous">
    <parent link="body"/>
    <child link="back_left_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="-0.12 -0.115 0" rpy="0 0 0"/>
  </joint>
  <link name="front_right_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
      <material name="black">
    <color rgba="0.0 0.0 0.0 0.5"/>
  </material>
    </visual>
  </link>
  <link name="front_left_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
  </link>
  <link name="back_right_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
  </link>
  <link name="back_left_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
  </link>
</robot>

为了检验这个模型的正确性,我们使用可视化工具来看看这个模型。
安装工具包,

shell

sudo apt-get install ros-humble-urdf-tutorial

假设上面的urdf文件命名为 four_wheel_robot.urdf ,保存路径为 /home/ubuntu/bot.urdf ,则执行命令:

shell

ros2 launch urdf_tutorial display.launch.py model:=/home/ubuntu/four_wheel_robot.urdf

即可看到小车模型正确显示了。
默认“base_link”为基坐标系。

小车模型小车模型

上面的urdf文件太啰嗦,我们简化它一下。

xml

<?xml version="1.0"?>

<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="fourbot">
  <xacro:property name="body_length" value="0.3" />
  <xacro:property name="body_width" value="0.2" />
  <xacro:property name="body_height" value="0.06" />

  <xacro:property name="wheel_radius" value="0.1" />
  <xacro:property name="wheel_width" value="0.03" />
  <xacro:property name="wheel_distance" value="0.24" />

  <link name="base_link"/>
  <joint name="base_joint" type="fixed">
    <parent link="base_link"/>
    <child link="body"/>
    <origin xyz="0.0 0.0 ${wheel_radius/2-body_height/2}" rpy="0 0 0"/>
  </joint>
  <link name="body">
    <visual>
      <geometry>
        <box size="${body&#95;length} ${body_width} ${body_height}"/>
      </geometry>
    </visual>
  </link>

  <xacro:macro name="wheels" params="fb frontk rightk">
    <link name="${fb}_wheel">
        <visual>
          <origin rpy="1.57079 0 0" xyz="0 0 0"/>
          <geometry>
            <cylinder length="${wheel&#95;width}" radius="${wheel_radius}"/>
          </geometry>
          <material name="black">
            <color rgba="0.0 0.0 0.0 0.5"/>
          </material>
        </visual>
    </link>

    <joint name="${fb}_joint" type="continuous">
        <parent link="body"/>
        <child link="${fb}_wheel"/>
        <axis xyz="0 1 0"/>
        <origin xyz="${frontk&#42;wheel&#95;distance/2} ${rightk*(body_width/2 + wheel_width/2)} 0" rpy="0 0 0"/>
    </joint>
  </xacro:macro>

  <xacro:wheels fb="front_right" frontk ="1" rightk = "1" />
  <xacro:wheels fb="front_left" frontk ="1" rightk = "-1" />
  <xacro:wheels fb="back_right" frontk ="-1" rightk = "1" />
  <xacro:wheels fb="back_left" frontk ="-1" rightk = "-1" />
</robot>

执行与原先同样的显示命令,得到效果为:

简化后代码运行效果简化后代码运行效果

校验URDF模型,奇怪的是这种办法不认xacro的简化代码,只认原始代码。

sh

check_urdf bot.urdf

文件结构文件结构

也可以使用软件包查看urdf文件结构

sh

sudo apt install liburdfdom-tools

在urdf文件所在的文件夹下,执行:

sh

urdf_to_graphviz bot.urdf

得到urdf的结构图,是一个PDF文件:

结构图结构图

上面的小车模型仅仅是一个图形模型,没有物理属性,我们来添加物理属性。
连杆的碰撞属性容易设置,这里只需与visual标签一致即可,没有特殊要求。为了赋予合理的质量、惯性属性,考虑车轮与车身的材料均为6061铝查询相关资料得6061铝合金密度 $2.75g/cm^3$ ,由上面设计的模型体积,可以计算出车身质量9900g,即9.9kg,每个车轮质量647.625g,即0.65kg

对宽度为$w$,高度为$h$,深度为$d$,质量为$m$ 的实心长方体:

$$ \begin{gather} I = \begin{bmatrix}\frac{1}{12}m (h^2 + d^2) & 0 & 0 \\ 0 & \frac{1}{12}m (w^2 + d^2) & 0\\ 0 & 0 & \frac{1}{12}m (w^2 + h^2) \end{bmatrix} \end{gather} $$

对于半径为$r$,高度为$h$,质量为$m$的实心圆柱体。
有:

$$ \begin{gather} I = \begin{bmatrix}\frac{1}{12}m (3r^2 + h^2) & 0 & 0 \\ 0 & \frac{1}{12}m (3r^2 + h^2) & 0\\ 0 & 0 & \frac{1}{2}m r^2 \end{bmatrix} \end{gather} $$

gazebo要求urdf文件中必须包含collision 标签、inertial标签,并且颜色设置需要使用指定的颜色标签。这与在rviz中显示的需求是不一样的,修改代码如下:

xml

<gazebo reference="link节点名称">
    <material>Gazebo/Blue</material>
</gazebo>

现在的urdf模型为:

xml

<?xml version="1.0"?>
<robot name="fourbot">
  <link name="base_link"/>
  <joint name="base_joint" type="fixed">
    <parent link="base_link"/>
    <child link="body"/>
    <origin xyz="0.0 0.0 0.05" rpy="0 0 0"/>
  </joint>
  <link name="body">
    <visual>
      <origin xyz="0 0 0.0" rpy="0 0 0"/>
      <geometry>
        <box size="0.3 0.2 0.06"/>
      </geometry>
    </visual>
    <collision>
      <origin xyz="0 0 0.0" rpy="0 0 0"/>
      <geometry>
        <box size="0.3 0.2 0.06"/>
      </geometry> 
    </collision>
    <inertial>
      <mass value="9.9"/>
      <inertia ixx="0.03597" ixy="0" ixz="0" iyy="0.10725" iyz="0" izz="0.07722"/>
    </inertial>
  </link>

  <joint name="joint_front_right" type="continuous">
    <parent link="body"/>
    <child link="front_right_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="0.12 0.115 0" rpy="0 0 0"/>
  </joint>
  <joint name="joint_front_left" type="continuous">
    <parent link="body"/>
    <child link="front_left_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="0.12 -0.115 0" rpy="0 0 0"/>
  </joint>
  <joint name="joint_back_right" type="continuous">
    <parent link="body"/>
    <child link="back_right_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="-0.12 0.115 0" rpy="0 0 0"/>
  </joint>
  <joint name="joint_back_left" type="continuous">
    <parent link="body"/>
    <child link="back_left_wheel"/>
    <axis xyz="0 1 0"/>
    <origin xyz="-0.12 -0.115 0" rpy="0 0 0"/>
  </joint>
  <link name="front_right_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
      <collision>
        <origin rpy="1.57079 0 0" xyz="0 0 0"/>
        <geometry>
          <cylinder length="0.03" radius="0.1"/>
        </geometry>
      </collision>
      <inertial>
        <mass value="0.65"/>
        <inertia ixx="0.000455" ixy="0" ixz="0" iyy="0.000455" iyz="0" izz="0.000135"/>
      </inertial>
  </link>
  <link name="front_left_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
      <collision>
        <origin rpy="1.57079 0 0" xyz="0 0 0"/>
        <geometry>
          <cylinder length="0.03" radius="0.1"/>
        </geometry>
      </collision>
      <inertial>
        <mass value="0.65"/>
        <inertia ixx="0.000455" ixy="0" ixz="0" iyy="0.000455" iyz="0" izz="0.000135"/>
      </inertial>
  </link>
  <link name="back_right_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
      <collision>
        <origin rpy="1.57079 0 0" xyz="0 0 0"/>
        <geometry>
          <cylinder length="0.03" radius="0.1"/>
        </geometry>
      </collision>
      <inertial>
        <mass value="0.65"/>
        <inertia ixx="0.000455" ixy="0" ixz="0" iyy="0.000455" iyz="0" izz="0.000135"/>
      </inertial>
  </link>
  <link name="back_left_wheel">
    <visual>
    <origin rpy="1.57079 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.03" radius="0.1"/>
      </geometry>
    </visual>
      <collision>
        <origin rpy="1.57079 0 0" xyz="0 0 0"/>
        <geometry>
          <cylinder length="0.03" radius="0.1"/>
        </geometry>
      </collision>
      <inertial>
        <mass value="0.65"/>
        <inertia ixx="0.000455" ixy="0" ixz="0" iyy="0.000455" iyz="0" izz="0.000135"/>
      </inertial>
  </link>

  <gazebo reference="front_right_wheel">
    <material>Gazebo/Black</material>
  </gazebo>
  <gazebo reference="front_left_wheel">
    <material>Gazebo/Black</material>
  </gazebo>
    <gazebo reference="back_right_wheel">
    <material>Gazebo/Black</material>
  </gazebo>
  <gazebo reference="back_left_wheel">
    <material>Gazebo/Black</material>
  </gazebo>
  <gazebo reference="body">
    <material>Gazebo/Red</material>
  </gazebo>
</robot>

在gazebo中打开urdf模型

使用命令行打开gazebo

bash

gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so

成功打开gazebo

命令行打开gazebo命令行打开gazebo

解释一下这句命令的含义:
这条命令行用于在 Gazebo 中加载特定的 ROS 插件,并启动 Gazebo 的详细日志模式。 1. gazebo
这是启动 Gazebo 仿真器的命令。

  1. --verbose
    这个选项启动 Gazebo 的详细日志模式。启用此选项后,Gazebo 会在终端中输出更多的调试和状态信息,这对排查问题和了解 Gazebo 内部工作原理非常有帮助。

  2. -s libgazebo_ros_init.so
    这个选项加载 Gazebo 的 ROS 初始化插件。libgazebo_ros_init.so 插件的作用是初始化 ROS 环境,确保 Gazebo 可以与 ROS 进行通信。这是加载 ROS 功能到 Gazebo 中的关键一步。

  3. -s libgazebo_ros_factory.so
    这个选项加载 Gazebo 的 ROS 工厂插件。libgazebo_ros_factory.so 插件的作用是提供将模型(如 URDF 模型)通过 ROS 加载到 Gazebo 仿真环境中的功能。这使得用户可以使用 ROS 话题和服务来在 Gazebo 中生成和控制模型。

这条命令行启动了 Gazebo 仿真器,并加载了两个重要的 ROS 插件(libgazebo_ros_init.solibgazebo_ros_factory.so),同时启用了详细日志输出模式。这使得 Gazebo 能够与 ROS 系统集成,并支持通过 ROS 加载和管理仿真模型。

下面在gazebo中加载机器人urdf模型,打开新的终端,执行:

bash

ros2 run gazebo_ros spawn_entity.py -file /home/user/four_wheel_robot.urdf -entity fourbot

20240720221902.png20240720221902.png
可以在gazebo中详细查看模型。
解释一下命令的含义
spawn_entity.py 是一个用于在 Gazebo 仿真环境中生成实体(如机器人模型)的 Python 脚本,通常与 ROS 2 一起使用。这个脚本是 Gazebo ROS 包的一部分,用于通过 ROS 2 接口在 Gazebo 中添加实体。
spawn_entity.py 脚本的主要功能是从 ROS 参数服务器或直接从文件中获取 URDF 或 SDF 描述,并将其生成到 Gazebo 仿真环境中。它允许用户在运行中的 Gazebo 仿真环境中动态地添加机器人模型或其他实体。可以通过命令行运行,并接受多种参数。以下是常用的参数和示例用法:

bash

ros2 run gazebo_ros spawn_entity.py [参数]

常用参数

bash

  -entity my_robot
bash

  -topic /robot_description
bash

  -file /path/to/your/urdf_file.urdf
bash

  -x 1.0
bash

  -y 2.0
bash

  -z 0.5
bash

  -R 0.0
bash

  -P 0.0
bash

  -Y 1.57

示例用法

  1. 从话题加载 URDF 并生成实体

假设 URDF 已经通过 ROS 参数服务器发布在 /robot_description 话题上,可以使用以下命令将其生成到 Gazebo 中:

bash

   ros2 run gazebo_ros spawn_entity.py -topic /robot_description -entity my_robot
  1. 从文件加载 URDF 并生成实体

假设 URDF 文件位于 /home/user/my_robot.urdf,可以使用以下命令将其生成到 Gazebo 中:

bash

   ros2 run gazebo_ros spawn_entity.py -file /home/user/my_robot.urdf -entity my_robot
  1. 指定生成位置和姿态

可以通过附加参数来指定实体在 Gazebo 世界中的位置和姿态:

bash

   ros2 run gazebo_ros spawn_entity.py -topic /robot_description -entity my_robot -x 1.0 -y 2.0 -z 0.5 -R 0.0 -P 0.0 -Y 1.57

添加仿真模块

要使小车能运动,除了具备物理属性之外,还需要有控制模块,这里引入差速驱动模块来驱动小车,再添加激光雷达模块备用。

先添加一个差速驱动模块,在urdf文件中添加:

xml

<gazebo>
  <!--差速驱动模块-->
  <gazebo>
    <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
      <!-- Plugin update rate in Hz -->
      <update_rate>20</update_rate>
      <left_joint>joint_front_left</left_joint>
      <right_joint>joint_front_right</right_joint>
      <wheel_separation>0.230</wheel_separation>
      <wheel_diameter>0.100</wheel_diameter>
      <!-- limits -->
      <max_wheel_torque>20</max_wheel_torque>
      <!-- output -->
      <publish_odom>true</publish_odom>
      <publish_odom_tf>true</publish_odom_tf>
      <publish_wheel_tf>false</publish_wheel_tf>
      <odometry_frame>odom</odometry_frame>
      <robot_base_frame>base_link</robot_base_frame>
    </plugin>
  </gazebo> 

注意这里的输出配置。

在添加激光lidar模块之前,我们先看看gazebo-ros包中有哪些插件。

bash

ls /opt/ros/humble/lib | grep libgazebo_ros

我看到了一系列的插件,我想有必要了解一下这些插件的含义。但是我始终没有找到相关的完整文档,可能是由于这些插件经常更新导致的。求助chatgpt,得到的回复如下:

  1. libgazebo_ros_ackermann_drive.so:提供对使用Ackermann转向机构的车辆的控制和仿真。支持前轮转向和后轮驱动的汽车模型。
  2. libgazebo_ros_bumper.so:提供对碰撞传感器(bumper)的仿真。发布碰撞事件到 ROS 话题。
  3. libgazebo_ros_camera.so:提供对摄像头传感器的仿真。发布图像数据到 ROS 话题。
  4. libgazebo_ros_diff_drive.so:提供对差速驱动机器人的控制和仿真。支持左右轮差速驱动的机器人模型。
  5. libgazebo_ros_elevator.so:提供对电梯(升降机)的控制和仿真。控制升降平台的运动。
  6. libgazebo_ros_factory.so:支持在仿真中动态添加和移除模型。通过ROS服务加载和删除模型。
  7. libgazebo_ros_force.so:提供对力传感器的仿真。发布施加在机器人某部分上的力的数据。
  8. libgazebo_ros_force_system.so:提供对力系统的仿真。管理和应用力系统。
  9. libgazebo_ros_ft_sensor.so:提供对力/扭矩传感器的仿真。发布力和扭矩数据到 ROS 话题。
  10. libgazebo_ros_gps_sensor.so:提供对 GPS 传感器的仿真。发布 GPS 数据到 ROS 话题。
  11. libgazebo_ros_hand_of_god.so:提供对手动移动模型的控制。允许用户通过 GUI 界面手动移动模型。
  12. libgazebo_ros_harness.so:提供对悬挂系统的仿真。控制和模拟悬挂装置的行为。
  13. libgazebo_ros_imu_sensor.so:提供对 IMU 传感器的仿真。发布惯性测量单元(IMU)数据到 ROS 话题。
  14. libgazebo_ros_init.so:初始化 Gazebo ROS 插件。配置和初始化所有 Gazebo ROS 插件。
  15. libgazebo_ros_joint_pose_trajectory.so:提供对关节轨迹控制的仿真。控制机器人关节的轨迹运动。
  16. libgazebo_ros_joint_state_publisher.so:提供对关节状态发布的仿真。发布关节位置、速度和加速度数据到 ROS 话题。
  17. libgazebo_ros_node.so:提供 Gazebo 和 ROS 之间的节点集成。创建并管理 ROS 节点。
  18. libgazebo_ros_p3d.so:提供对三维位置(P3D)传感器的仿真。发布机器人位置和姿态数据到 ROS 话题。
  19. libgazebo_ros_planar_move.so:提供对平面运动的控制和仿真。控制机器人在平面上的运动。
  20. libgazebo_ros_projector.so:提供对投影仪的仿真。模拟投影仪投影图像。
  21. libgazebo_ros_properties.so:提供对模型属性的管理和仿真。设置和获取模型属性。
  22. libgazebo_ros_ray_sensor.so:提供对射线传感器的仿真(例如激光雷达)。发布射线传感器数据到 ROS 话题。
  23. libgazebo_ros_state.so:提供对仿真状态的管理和控制。发布仿真状态数据到 ROS 话题。
  24. libgazebo_ros_template.so:提供一个 Gazebo ROS 插件模板。作为开发新插件的基础。
  25. libgazebo_ros_tricycle_drive.so:提供对三轮车驱动的控制和仿真。支持前轮转向和后轮驱动的三轮车模型。
  26. libgazebo_ros_utils.so:提供各种实用工具函数。为其他 Gazebo ROS 插件提供支持功能。
  27. libgazebo_ros_vacuum_gripper.so: 提供对真空吸盘的仿真模拟真空吸盘的抓取和释放动作
  28. libgazebo_ros_video.so:提供对视频传感器的仿真。发布视频数据到 ROS 话题。
  29. libgazebo_ros_wheel_slip.so:提供对轮胎打滑的仿真。模拟轮胎在不同表面上的打滑行为。

在前面的工作中已经使用到了libgazebo_ros_factory插件、 libgazebo_ros_diff_drive插件。
下面我们要使用libgazebo_ros_ray_sensor插件来仿真激光雷达。
参考官方文档
在urdf文件中添加:

xml

  <link name="laser_link">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="0.1 0.1 0.2"/>
      </geometry>
    </collision>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <box size="0.1 0.1 0.2"/>
      </geometry>
    </visual>
    <inertial>
      <mass value="0.05" />
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="2e-4" ixy="0" ixz="0" iyy="8e-5" iyz="0" izz="2e-4" />
    </inertial>
  </link>

  <joint name="laser_joint" type="fixed">
    <parent link="body"/>
    <child link="laser_link"/>
    <origin xyz="0.0 0.0 0.13" rpy="0 0 0"/>
  </joint>
  <gazebo reference="laser_link">
    <material>Gazebo/Blue</material>
  </gazebo>

  <!--激光雷达模块-->
  <gazebo reference="laser_link">
    <sensor name="laser_sensor" type="ray">
    <always_on>true</always_on>
    <pose>0 0 0 0 0 0</pose>
      <!-- 当为true时,在gpu激光器的扫描区域内可见半透明激光射线。这可能是一个有用的信息可视化,也可能是一种麻烦。 -->
      <visualize>true</visualize>
      <!-- 发布频率,不宜太高,否则可能导致卡顿 -->
      <update_rate>5</update_rate>
      <ray>
        <scan>
          <horizontal>
            <samples>360</samples>
            <resolution>1.0</resolution>
            <min_angle>-1.570796</min_angle>
            <max_angle>1.570796</max_angle>
          </horizontal>
        </scan>
        <range>
          <min>0.25</min>
          <max>5.0</max>
          <resolution>0.01</resolution>
        </range>
        <noise>
          <type>gaussian</type>
          <mean>0.0</mean>
          <stddev>0.01</stddev>
        </noise>
      </ray>
      <plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">

      <!-- 输出配置 -->
        <ros>
          <remapping>~/out:=scan</remapping>
        </ros>
        <output_type>sensor_msgs/LaserScan</output_type>
        <frameName>laser_link</frameName>
      </plugin>
    </sensor>
  </gazebo>

好了,一个四轮小车设计得差不多了,接下来我们将它导入到gazebo仿真环境中查看。

bash

gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so
# 打开新的终端
ros2 run gazebo_ros spawn_entity.py -file /home/user/four_wheel_robot.urdf -entity fourbot

设计完毕的小车模型设计完毕的小车模型

创建功能包

参考前面的博客创建功能包
首先我们需要创建一个节点,实现调用gazebo、rviz2、机器人模型。

新建文件夹名为rob1_ws,在文件夹中新建文件夹src,在src文件夹中新建功能包名为robbody,

shell

mkdir -p rob1_ws/src
cd rob1_ws/src
ros2 pkg create robbody --dependencies rclcpp geometry_msgs sensor_msgs --build-type ament_cmake

解释一下创建功能包命令的含义:“create robbody”是指创建名为robbody的功能包,“build-type ament_cmake”指功能包类型为cmake,“dependencies”指的是这个功能包的依赖,功能包需要依赖于“rclcpp”包内容,可以类比于C程序中库函数的概念。
打开生成的robbody文件夹,先看看CMakeList.txt文件,其中会有:

cmake

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)

这就对应了创建功能包时指令中指定的内容,如果后续需要其他依赖包,也需要在这里添加。

下面尝试创建一个简单的节点,在robbody/src/文件夹下的

sh

cd ~/rob1_ws/src/robbody/src
touch node_simple.cpp
# 先放在这里,暂时不用

在robbody文件夹下新建文件夹“urdf_models”用于专门存放urdf模型文件。

sh

cd ~/rob1_ws/src/robbody
mkdir urdf_models
mkdir launch

将此前的fourbot模型文件拷贝至urdf_models文件夹下,假设其名称为“four_wheel_robot.urdf”。

在launch文件夹下创建launch文件,文件命名为node_simple.launch.py,这个文件需要的功能为:

  1. 启动gazebo环境
  2. 加载机器人模型到gazebo环境中
  3. 启动robot_state_publisher节点,发布机器人模型的tf变换
  4. 启动joint_state_publisher节点,发布机器人关节的状态
  5. 启动rviz2,可视化机器人模型和传感器数据
python

import os
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():
    robot_name_in_model = 'fourbot'
    package_name = 'robbody'
    urdf_name = "four_wheel_robot.urdf"

    # 获取包路径
    pkg_share = FindPackageShare(package=package_name).find(package_name) 
    urdf_model_path = os.path.join(pkg_share, f'urdf_models/{urdf_name}')

    # 读取URDF文件内容
    try:
        with open(urdf_model_path, 'r') as infp:
            robot_description = infp.read()
    except Exception as e:
        raise RuntimeError(f"Failed to read URDF file: {urdf_model_path}, error: {e}")

    # 启动Gazebo服务器
    start_gazebo_cmd = ExecuteProcess(
        cmd=['gazebo', '--verbose', '-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so'],
        output='screen')

    # 启动robot_state_publisher节点
    start_robot_state_publisher_cmd = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        name='robot_state_publisher',
        output='screen',
        parameters=[{'robot_description': robot_description, 'use_sim_time': True}]
    )

    # 启动joint_state_publisher节点
    start_joint_state_publisher_cmd = Node(
        package='joint_state_publisher',
        executable='joint_state_publisher',
        name='joint_state_publisher',
        output='screen',
        parameters=[{'use_sim_time': True}]
    )

    # 启动RViz2
    start_rviz_cmd = Node(
        package='rviz2',
        executable='rviz2',
        name='rviz2',
        output='screen',
        parameters=[{'use_sim_time': True}]
    )

    # 加载机器人模型到Gazebo中
    spawn_robot_cmd = Node(
        package='gazebo_ros',
        executable='spawn_entity.py',
        arguments=['-entity', robot_name_in_model, '-file', urdf_model_path],
        output='screen',
        parameters=[{'use_sim_time': True}]
    )

    ld = LaunchDescription()

    # 添加启动命令到LaunchDescription
    ld.add_action(start_gazebo_cmd)
    ld.add_action(start_robot_state_publisher_cmd)
    ld.add_action(start_joint_state_publisher_cmd)
    ld.add_action(start_rviz_cmd)
    ld.add_action(spawn_robot_cmd)

    return ld

在CMakeList.txt文件中添加内容:

cmake

install(DIRECTORY
  urdf_models
  launch
  DESTINATION share/${PROJECT_NAME}/
)

除了launch中的5条任务外,还需要添加一个节点,用于控制小车运动。这里我们使用rqt_robot_steering包,它是一个基于ROS的可视化界面,可以发布geometry_msgs/msg/Twist消息,默认话题为/cmd_vel,这是差速控制插件订阅的话题,通过它可以控制机器人前进、后退、左转、右转。

bash

# 安装rqt_robot_steering包
sudo apt install ros-humble-rqt-robot-steering
ros2 run rqt_robot_steering rqt_robot_steering

# 或者,控制小海龟的软件包也可以有相同的功能,选用一个即可
sudo apt install ros-humble-teleop-twist-keyboard
ros2 run teleop_twist_keyboard teleop_twist_keyboard

安装完rqt_robot_steering包之后,编译并执行,注意是在工作空间的文件夹中

sh

cd ~/rob1_ws
colcon build
source install/setup.bash
ros2 launch robbody node_simple.launch.py

可以看到成功打开了gazebo环境、rviz2、机器人模型、joint_state_publisher、robot_state_publisher节点。
此时我们修改rviz的配置,设置 Fixed Frame 为 odom,添加'RobotModel',选择topic为'/robot_description',添加'LaserScan',选择topic为'/scan',保存配置。
(配置可参阅fishros教程

在新的终端中执行

bash

ros2 run rqt_robot_steering rqt_robot_steering

打开rviz2,可以看到机器人模型和激光雷达数据。

运行结果运行结果

我们也可以自己写一个节点,发布/cmd_vel话题,控制小车运动,这里就不再赘述,本篇笔记的重点是定位与建图。

SLAM建图

绘制测试地图

首先简单地随便画一个测试使用的地图,参照fishros教程

具体步骤为:
打开gazebo,选择左上角Edit--Building Editor,选择create Walls,然后在界面的上半部分白色网格中自定义墙体,注意比例尺,建图的范围要与小车的大小相适应,不能太小叶不宜太大。
画完之后点击右上角File--Exit,此时弹出保存界面,自定义文件名称并保存即可。然后就进入了gazebo界面,再添加一些方形物体和圆柱形物体作为障碍物,添加完毕后保存为.world文件。

在前面的工作空间中,与launch、urdf_models文件夹同级新建一个maps文件夹,将刚才保存的.world文件放入其中。

bash

cd ~/rob1_ws/src/robbody/
mkdir maps
cp ~/gazebo_worlds/my_map.world maps/test_map.world

修改launch文件,使得gazebo启动时自动打开刚才保存的.world文件。

python

gazebo_world_path = os.path.join(pkg_share, 'maps/test_map.world')
start_gazebo_cmd = ExecuteProcess(
    cmd=['gazebo', '--verbose', '-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so', gazebo_world_path],
    output='screen')

修改CmakeList.txt文件,添加maps文件夹的编译:

cmake

install(DIRECTORY
  urdf_models
  launch
  maps
  DESTINATION share/${PROJECT_NAME}/
)

然后编译,运行,可以在rviz中看到雷达识别到了障碍物的位置。

添加测试地图环境之后的效果添加测试地图环境之后的效果

安装SLAM建图软件包

有很多开源的SLAM建图软件包,典型代表有: Gmapping、Hector SLAM、Cartographer、RTAB-Map、ORB-SLAM2/ORB-SLAM3等。

Gmapping是一种基于粒子滤波的2D激光SLAM算法,适用于ROS(Robot Operating System),不适用于ROS2。简单易用,适合初学者;在ROS社区中广泛使用。Gmapping GitHub
Hector SLAM是一种基于快速响应和高分辨率的2D激光SLAM算法,不需要轮速计数据。适用于无轮速计的小车,性能较好,实时性强。
Cartographer是Google推出的SLAM库,支持2D和3D建图,适用于复杂环境下的高精度建图。高精度,支持多种传感器输入,活跃的社区支持。它已被广泛应用于工业实践。
RTAB-Map(Real-Time Appearance-Based Mapping)是一种RGB-D、激光和立体相机支持的图像SLAM算法,适用于2D和3D建图。支持多种传感器,适用于室内和室外环境,支持大规模建图。
ORB-SLAM2/ORB-SLAM3是基于特征点的视觉SLAM算法,支持单目、立体和RGB-D相机。精度高,支持回环检测,适用于复杂的视觉环境。

由于Cartographer比较复杂,这里先从一个简单点的slam_toolbox开始。

安装slam_toolbox软件包:

bash

sudo apt-get install ros-humble-slam-toolbox

安装完成后打开配置文件

bash

# 这个配置文件夹下的四个文件对应了不同情形下的建图参数
cd /opt/ros/humble/share/slam_toolbox/config/
# 这里使用在线、异步建图
sudo vim mapper_params_online_async.yaml

阅读并核对配置文件,要求配置文件中的参数要和前面的urdf模型设置相匹配,例如base_frame需要由默认的base_footprint改为base_link,如果不修改配置则会导致建图失败!
修改完成后保存并退出。

运行SLAM建图

打开前面的ros包工作空间,编译运行

bash

cd ~/rob1_ws
colcon build
source install/setup.bash
ros2 launch robbody node_simple.launch.py

在新的终端中打开控制程序包

bash

ros2 run rqt_robot_steering rqt_robot_steering

打开新的终端,启动SLAM建图

bash

ros2 launch slam_toolbox online_async_launch.py

在rviz中,添加map,选择topic为/map,操纵小车开始移动,可以看到SLAM建图的过程。

界面效果界面效果

建图完成后还要保存地图,这里使用nav2_map_server软件包保存地图。

bash

# 安装nav2_map_server软件包
sudo apt install ros-humble-nav2-map-server
# 可以通过help查看帮助信息
ros2 run nav2_map_server map_saver_cli --help

把地图保存到maps文件夹中,并命名为get_fourbot_map.yaml

bash

# -t 是话题参数,-f 是保存的文件名
cd ~/ros2_ws/src/robbody/maps
ros2 run nav2_map_server map_saver_cli -t map -f get_fourbot_map

获得了两个文件,一个是.pgm格式的地图文件,一个是.yaml格式的地图描述文件。

程序汇总

上面的方案需要单独在新的终端中运行slam_toolbox节点和rqt_robot_steering节点,有点麻烦,可以将它们一起塞进launch文件中。

python

    #启动rqt_robot_steering节点
    start_rqt_robot_steering_cmd = Node(
        package='rqt_robot_steering',
        executable='rqt_robot_steering',
        name='rqt_robot_steering',
        output='screen'
    )
    # 启动slam_toolbox节点
    slam_toolbox_cmd = Node(
        package='slam_toolbox',
        executable='async_slam_toolbox_node',
        name='slam_toolbox',
        output='screen',
        parameters=[{'use_sim_time': True}, {'base_frame': 'base_link'}]
    )

    ld.add_action(slam_toolbox_cmd)
    ld.add_action(start_rqt_robot_steering_cmd)

编译后运行,就方便很多了。
手动操纵小车在地图里跑一跑,可以看到SLAM建图的过程,建图完成后保存地图即可。

实验总结

这篇笔记的重点有二,一是WSL的使用,二是一个简单的建图任务。我没有从源码编译建图软件包,也没有使用更常用的Cartographer,而是选择了slam_toolbox,只求简便。
存在的不足是,小车模型的设计不太好看,车轮大小与车身大小比例失调。绘制测试地图时,也没有考虑好尺寸问题,导致地图特别大。手动操纵小车时很不方便,比较难操纵。

资源下载,压缩文件内包含:

参阅