ROS-机器人编程实用指南-全-

ROS 机器人编程实用指南(全)

原文:zh.annas-archive.org/md5/edf77da8c3973e5e777cd87524a170a5

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

为什么会有关于使用 ROS 学习机器人的新书?嗯,编程只是与机器人一起工作的很小一部分。如果你想真正擅长机器人技术,你还需要在其他领域具备技能:机电一体化、机器人仿真、自主导航和机器学习/强化学习。这四个主题中的每一个都是你在获得完整机器人技能的道路上需要掌握的构建模块。本书分为四个部分,每个部分都致力于这些构建模块之一。

第一部分,物理机器人组装和测试,侧重于机电一体化,并描述机器人的每个硬件部分,提供如何测试其配备的每个传感器和执行器的实际演示。本书的这一部分应帮助你理解移动机器人是如何工作的。

第二部分,使用 Gazebo 进行机器人仿真,涉及机器人仿真。在这里,我们介绍了 ROS 并开发了一个两轮机器人仿真,它模拟了实际机器人的物理特性和行为。我们将探讨数字孪生的概念,这是一个虚拟机器人,它是物理机器人的孪生兄弟。这是开发机器人应用的基本部分,因为它减少了与测试真实硬件相关的成本。数字孪生使我们能够加快开发过程,并将物理机器人的测试留到开发的高级阶段。

第三部分,使用 SLAM 进行自主导航,专注于机器人导航,这是移动机器人最常见的任务。最先进的算法和技术以实用的方式解释,首先是模拟,然后是使用物理机器人。

第四部分,使用机器学习实现自适应机器人行为,专注于机器学习强化学习,这是机器人研究和实际机器人应用中最活跃的领域。通过使用这项技术,机器人能够从纯自动化的状态——即每个可能的行为或答案都被编码——过渡到灵活的行为机器,机器人能够通过学习数据以智能的方式对环境需求做出反应。这些数据可以来自机器人的先前经验,也可以从类似机器人的经验中收集。

要构建一个最先进的机器人应用,你首先需要掌握并组合这四个构建模块。结果将是我们通常所说的智能机器人。这就是你的任务——这就是你的挑战。

本书面向的对象

如果你是一名希望使用 ROS 构建 AI 机器人工程师,那么这本书就是为你准备的。希望开发自己的 ROS 机器人项目的技术人员和爱好者也会发现这本书是一个有用的资源。

本书涵盖的内容

第一章,组装机器人,提供了关于本书所有内容所基于的移动机器人的关键概念和实际组装指南。考虑到非常实用的方法,我们深入探讨了 GoPiGo3 的特性,使其成为学习机器人的理想且经济实惠的平台。通过完成 GoPiGo3 的组装,您将获得操作机器人典型组件所需的第一个手动技能。要购买 GoPiGo3 套件,您可以访问 www.dexterindustries.com/gopigo3/,并使用优惠券代码 BRJAPON@PACKT 获得折扣 10%。

第二章,GoPiGo3 的单元测试,为您提供了对 GoPiGo3 工作原理的实用见解。我们通过介绍 JupyterLab 环境,一个友好的界面,它采用由人类可读段落后跟 Python 代码片段组成的笔记本结构来实现这一点。您将为每个测试程序产生两个版本:JupyterLab 笔记本和纯 Python 脚本。使用这些编程工具,您将单独测试每个传感器/执行器,并检查其是否正常工作,同时了解背后的技术。

第三章,ROS 入门,解释了 ROS 的基本概念。它使用易于理解的语言介绍框架,避免使用非常技术性的描述。这是因为我们的主要目标是向您展示 ROS 在概念上的确切含义。在接下来的章节中,将提供深入的技术描述,以便您最终能够将 ROS 集成到您的项目中。

第四章,创建虚拟双轮 ROS 机器人,描述了如何构建一个简单的双轮机器人,它是 GoPiGo3 的数字孪生。该模型以统一机器人描述格式URDF)编写,并通过 RViz 进行检查,RViz 是一个 ROS 工具,它提供了一个可配置的图形用户界面GUI),允许用户显示他们所需的具体信息。RViz 可用于全局机器人可视化,以及在构建模型时调试特定功能。

第五章,使用 Gazebo 模拟机器人行为,教您如何将您的机器人数字定义(URDF 文件)插入到 Gazebo 的模拟环境中,Gazebo 是一个具有能够模拟真实行为的物理引擎。您还将了解如何检查和测试数字机器人,以确保其行为在现实中表现得很好。

第六章,ROS 命令和工具编程,向您介绍与 ROS 的命令行交互,并解释 ROS 命令的类型。我们将探讨 ROS 中最常用的通信模式,包括发布-订阅模型。为了处理您所有的 ROS 数据,您将了解 rqt,它简化了开发和调试应用程序的过程。此外,还介绍了 ROS 参数,以让您了解它们在高级别管理机器人配置方面的强大功能。

第七章,机器人控制和仿真,教您如何使用 GoPiGo3 设置真实机器人的 ROS 环境。我们将从使用笔记本电脑键盘的按键进行远程控制开始,然后过渡到更技术性的使用 ROS 主题的方法。本章将引导您从基于键盘和主题的手动控制到内部编程逻辑,以便您的机器人能够自主执行任务。

第八章,使用 Gazebo 进行虚拟 SLAM 和导航,通过实际方法和 GoPiGo3 的数字孪生探索同时定位与建图SLAM)技术。您将了解为什么在正确导航之前需要 SLAM。仿真将在 Gazebo 中运行,这是具有物理引擎的 ROS 原生仿真工具,可以提供逼真的结果。

第九章,机器人导航中的 SLAM,将重点转向物理 GoPiGo3 机器人。本章突出了在真实环境中面对机器人任务时出现的许多细节和实际问题。仿真是一个好的开始,但通过在实际场景中执行任务来验证您的机器人按预期工作,才是真正的证明。本章是您深入了解机器人导航的起点,如果您想在这个领域继续发展,这将对您的知识库至关重要。

第十章,在机器人学中应用机器学习,旨在对机器人学中的机器学习主题进行温和的介绍,更倾向于直观理解而非复杂的数学公式,并侧重于理解该领域常用的概念。本章中使用的实际示例将涉及 GoPiGo3 的 Pi 摄像头识别物体。

第十一章,使用 OpenAI Gym 进行机器学习,基于简单场景提供了强化学习的理论背景。这一章节让你更好地理解在经典强化训练任务中幕后发生了什么。我们将继续使用实际例子来探索所提出的概念,并使用开源环境 OpenAI Gym,它使我们能够轻松测试来自训练代理、甚至是在 ROS 中驾驶机器人的不同算法。

第十二章,通过强化学习实现目标,在计算机视觉对象识别方面更进一步,表明 GoPiGo3 不仅能感知事物,还能采取步骤实现目标。我们的机器人在模拟的每一步都需要决定执行什么动作以实现目标。执行每个动作后,机器人将获得关于其做出的决策好坏的反馈,形式为奖励。经过一些训练后,奖励的激励将加强并巩固良好的决策。

要充分利用本书

本书采用实践方法来处理事物,并鼓励你使用物理机器人来实践你所学的知识。我们选择 GoPiGo3 (www.dexterindustries.com/gopigo3/),因为它具有模块化、适中的成本,并且基于 Raspberry Pi。你可以在全球范围内的在线商店购买 Raspberry Pi 板。在购买套件中的任何组件之前,我们建议你首先阅读第一章,组装机器人,以获取所有你需要购买的组件的基本信息。要购买 GoPiGo3 套件,你可以访问www.dexterindustries.com/gopigo3/,并使用优惠券代码BRJAPON@PACKT获得 10%的折扣。

为了充分利用本书,需要具备一些 Python 和/或 C++编程知识,以及熟悉如 Raspberry Pi 这样的单板计算机。

最后,你需要一台装有 Ubuntu 16.04 Xenial Xerus 或 Ubuntu 18.04 Bionic Beaver 的笔记本电脑。本书的代码已在这两个操作系统上进行了测试。如果你必须从头开始,我们建议你使用 Ubuntu 18.04,因为它是由 Canonical 提供的最新长期支持LTS)版本,并将支持到 2023 年 4 月。

你需要的所有安装说明都包含在每个章节开头的技术要求部分。

下载示例代码文件

你可以从www.packt.com上的账户下载本书的示例代码文件。如果你在其他地方购买了本书,你可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给你。

您可以通过以下步骤下载代码文件:

  1. www.packt.com登录或注册。

  2. 选择“支持”标签。

  3. 点击“代码下载”。

  4. 在“搜索”框中输入书名,并遵循屏幕上的说明。

文件下载后,请确保使用最新版本解压缩或提取文件夹。

  • Windows 上的 WinRAR/7-Zip

  • Mac 上的 Zipeg/iZip/UnRarX

  • Linux 上的 7-Zip/PeaZip

书籍的代码包也托管在 GitHub 上,地址为github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/9781838551308_ColorImages.pdf

代码实战

访问以下链接查看代码运行的视频:

bit.ly/2PrRpXF

使用的约定

本书使用了多种文本约定。

CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“似乎只有最后一行被执行,即my_gopigo.left()。”

代码块设置如下:

msg_range.header.frame_id = "distance"
msg_range.radiation_type = Range.INFRARED
msg_range.min_range = 0.02
msg_range.max_range = 3.0

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

std_msgs/Header header
uint8 radiation_type
float32 field_of_view
float32 min_range

任何命令行输入或输出都如下所示:

$ cd./Chapter2_Unit_Tests/drivingAround
$ python <name_of_script.py>

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中如下所示。以下是一个示例:“通过点击“检查生命体征”来检查生命体征。”

警告或重要提示如下所示。

小贴士和技巧如下所示。

联系我们

欢迎读者反馈。

一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com给我们发邮件。

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现错误,我们将非常感激您向我们报告。请访问www.packtpub.com/support/errata,选择您的书,点击“勘误表提交表单”链接,并输入详细信息。

盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com与我们联系,并附上材料的链接。

如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com

评论

请留下评论。一旦您阅读并使用过这本书,为何不在购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

如需更多关于 Packt 的信息,请访问 packt.com

第一部分:物理机器人组装和测试

本节主要描述和设置将与本书一起使用的硬件。机械部件,包括传感器和执行器、微控制器和嵌入式计算机,是任何移动机器人的核心硬件特性。将包括运行 GoPiGo3 与 ROS 所需的软件的安装说明。

本节包含以下章节:

  • 第一章,组装机器人

  • 第二章,GoPiGo3 单元测试

  • 第三章,ROS 入门

第一章:组装机器人

本章将为您提供关于本书内容所基于的移动机器人的各种实用组装指南。考虑到非常实用的方法,我们将深入探讨 GoPiGo3 的特点以及它为何是一个理想的机器人学习平台。

首先,我们将关注硬件,并讨论每个机器人所组成的组件,包括机械部件和嵌入式系统、传感器和电机。

在完成 GoPiGo3 组装部分后,您将获得手动技能,以便您可以开始操作机器人中的典型组件。您还将被推动采用在组装机器人时应用部分验证测试的系统化方法,也称为单元测试

在本章的第一部分介绍了 GoPiGo3 机器人之后,我们将深入解释这些概念,包括嵌入式控制器、GoPiGo3 板和嵌入式计算机 Raspberry Pi。

接下来,我们将描述机器人将使用的传感器和执行器,我们将它们分为我们所说的机电

最后,我们将为您提供一些有用的指南,以便组装机器人变得简单直接。然后,我们将使用易于启动的软件 DexterOS 测试 GoPiGo3 机器人。尽管我们将在本书的后面部分采用 Ubuntu 作为运行 ROS 的操作系统,但我们建议您从 DexterOS 开始,这样您可以在避免特定软件编程任务的同时熟悉硬件,这些任务将在后面的章节中留出。

在本章中,我们将涵盖以下主题:

  • 理解 GoPiGo3 机器人

  • 熟悉嵌入式硬件——GoPiGo3 板和 Raspberry Pi

  • 深入了解机电——电机、传感器和 2D 摄像头

  • 整合所有内容

  • 在 DexterOS 下使用 Bloxter(可视化编程)进行硬件测试

理解 GoPiGo3 机器人

GoPiGo3 是由 Dexter Industries 制造的基于 Raspberry Pi 的机器人车。它旨在用作学习机器人技术和编程的教育套件,这两者都是互补的视角,清楚地表明了您应该获得的知识,以成为一名机器人工程师。我们将通过让 Modular Robotics 工程总监 Nicole Parrot 用自己的话来解释这一点来解释这意味着什么:

“GoPiGo 起源于 2014 年初的一次 Kickstarter 活动,当时 Raspberry Pi 还相对较新。最初的用户是爱好者,但很快教师和编程俱乐部志愿者开始将他们的 GoPiGo 分享给学生。这导致了对电路板的各种修改,以使其成为教室适用的机器人。它坚固耐用,功能齐全,并且仍然基于 Raspberry Pi!最新的版本自 2017 年以来一直存在,是一个稳定的平台。”

基于 Raspberry Pi 的机器人课堂中提供了许多优势。它可以使用多种编程语言进行编程,它可以在不使用蓝牙的情况下独立于学校 Wi-Fi,并且它可以直接在板上执行高级应用,如计算机视觉和数据收集。配备 DexterOS 的 GoPiGo 预装了所有科学库。配备 Raspbian for Robots 的 GoPiGo 允许用户安装项目所需的任何库和工具。它包含两个 Python 库:easygopigo3.py 和 gopigo3.py。这两个库都提供了对机器人的高级控制和低级控制,这取决于用户的技能水平。

GoPiGo 已经成为寻求简单、文档齐全的 Raspberry Pi 机器人的大学、研究人员和工程师的首选。

准备好深入机器人学了吗?让我们开始吧!

机器人学视角

从机器人学的角度来看,你将学习如何与基本部件协同工作:

  • 电机,它允许机器人从一个点移动到另一个点。在 GoPiGo3 中,我们有内置编码器的直流电机,它们提供精确的运动。这是从 GoPiGo2 升级的主要改进之一,在那里编码器位于电机外部,并且不太准确。

  • 传感器,它们从环境中获取信息,例如近物距离、亮度、加速度等。

  • 控制器——即 GoPiGo3 红色主板——负责与传感器和执行器的物理接口。这是 GoPiGo3 与物理世界交互的实时组件。

  • 一块单板计算机SBC)Raspberry Pi 3B+,它提供处理能力。因此,它运行在一个操作系统下,通常是基于 Linux 的发行版,从软件角度来看提供了广泛的灵活性。

大多数教育套件仅停留在 3 级控制器;它们不包括 4 级单板计算机。控制器中的软件是一个小的程序(只有一个),它嵌入在板上。每次你想修改机器人的代码时,你必须完全替换现有的程序,并使用 USB 端口上的串行连接从外部计算机中刷新新版本。

这里的一个经典例子是 Arduino 控制的机器人。在这里,Arduino 板扮演着我们的 GoPiGo3 板的角色,如果你曾经使用过它,你一定会记得你需要如何通过 USB 线将新程序从笔记本电脑上的 Arduino IDE 传输到机器人。

编程视角

从编程的角度来看,GoPiGo3 允许你通过学习一个视觉编程语言 Bloxter(Google Blockly 的开源分支,专门为 GoPiGo3 开发)来轻松入门。当涉及到学习编写软件程序的基本概念时,这是一个非常舒适的先决条件。

但如果您正在阅读这本书,我们确信您已经知道如何使用许多可用的语言之一进行编程,即 C、C++、Java、JavaScript 或 Python。Dexter Industries 提供了各种开源库(github.com/DexterInd/GoPiGo3/tree/master/Software),您可以使用这些库来编程 GoPiGo3。以下是一些例子:

  • C

  • C#

  • Go

  • Java

  • Node.js(JavaScript)

  • Python

  • Scratch

无论如何,在本章的第一部分,我们鼓励您只使用 Bloxter 来强调机器人视角,并熟悉您手中的硬件。之后,您可以使用您选择的任何语言,因为有许多可用的 GoPiGo3应用程序编程接口API)可供选择。

在这本书中,我们将重点关注 Python 作为 ROS(机器人操作系统)编程的主要语言。Python 语言易于学习,同时仍然非常强大,在机器人和计算机科学领域占主导地位。在阅读了第二章中的 Python 示例,“GoPiGo3 单元测试”之后,我们将开始学习机器人操作系统ROS),它不是一个实际的编程语言,而是为机器人提供开发应用框架的开发应用程序。因此,我们将向您展示如何通过包装器来适应您的 Python 程序,以便它们也可以在 ROS 中作为构建高级功能的部分运行。

当您发现 GoPiGo3 的 Python 代码基础被 ROS 包装后,它能做更多的事情时,您将欣赏这种跳转到 ROS 的附加价值。这个软件升级为 GoPiGo3 提供了一个工具包,使学生、创造者和工程师能够理解机器人是如何工作的。此外,您应该知道 ROS 在专业环境中被广泛使用。

机器人套件和资源

从高层次来看,我们可以将机器人的硬件分为两组:

  • 机电学:这指的是允许它与物理世界交互的传感器和执行器。

  • 嵌入式硬件:允许它从传感器获取信号,将其转换为数字信号,并提供处理逻辑并向执行器发送命令的电子板。在这里,我们通常有两种类型的电子板:

    • 控制器,它作为与传感器和执行器的物理接口——即 GoPiGo3 板。控制器处理来自机电设备的模拟和数字信号,将它们转换为 CPU 可以处理的数字信号。

    • 计算机,它为我们提供了实现智能逻辑的手段。在大多数机器人中,这是一个 SBC(单板计算机)。在 GoPiGo3 的情况下,这是一个运行 Linux 操作系统发行版的 Raspberry Pi,例如 Raspbian 或 Ubuntu。

尽管您可以直接通过树莓派的通用输入/输出GPIO)引脚将数字设备连接到树莓派,但从功能角度来看,最好通过控制器——即 GoPiGo3 板——将所有传感器和执行器进行接口——也就是说,保持与物理世界的接口在控制器层面,并在计算机层面进行处理和计算。

如果您是经常使用树莓派的用户并且拥有该板,您只需要购买 GoPiGo3 机器人基础套件(www.dexterindustries.com/product/gopigo3-robot-base-kit/)。该套件包括以下内容:

  • GoPiGo3 板(红色板)

  • 底盘(框架、轮子、硬件)

  • 电机

  • 编码器

  • 电源电池组和电缆

  • 组装用的螺丝刀

以下图片展示了所有包含的部件:

),该套件包括树莓派 3 及其配件,以及一个配备伺服电机的可转向距离传感器,使其能够覆盖 180°的视野。此传感器套件由以下组成:

以下图片展示了组装完成的初学者入门套件最终的外观。通过添加树莓派和可转向的距离传感器,使用机器人基础套件也能得到相同的结果:

)提供了控制器所期望的一般功能:

  • 与传感器和执行器进行实时通信。

  • 通过串行外设接口SPI)进行输入/输出I/O)接口,该接口将传感器的数据传输到 Raspberry Pi,并且也可能接收对执行器的命令(也来自 Raspberry Pi,在它的 CPU 中运行控制循环的每一步逻辑之后)。

  • 在板上加载的单个程序称为固件。由于该软件的目标是在计算机实现逻辑的同时实现通信协议,因此除非你决定在可用新版本时升级它,否则不需要对其进行更改。

让我们简要解释一下我们在前面的项目符号列表中提到的输入/输出接口协议。SPI 是一个用于在微控制器和外部设备之间发送数据的总线,在我们的例子中是传感器。它使用独立的时钟和数据线,以及一个选择线来选择要与之通信的设备。生成时钟的连接端称为主设备,在我们的例子中是 Raspberry Pi,而另一端称为从设备,即 GoPiGo3 板。这样,两个板就同步了,比异步串行通信更快,后者是通用板(如 Arduino)中的典型通信协议。

你可以在learn.sparkfun.com/tutorials/serial-peripheral-interface-spi的简单教程中了解更多关于 SPI 协议的信息。通过 SPI 与 Raspberry Pi 进行通信是通过引脚接口进行的,这可以在以下图像的顶部部分看到。只需要 40 个 GPIO 引脚中的 5 个用于此类接口:

图片

图片来源:Dexter Industries: https://32414320wji53mwwch1u68ce-wpengine.netdna-ssl.com/wp-content/uploads/2014/07/GoPiGo3-Bottom_annotated-600x441.jpg

为了与设备接口,该板提供了以下功能(板的俯视图可以在下一张图中看到):

  • 两个 I2C 端口——两个 Grove 端口通过电平转换芯片连接到 Raspberry Pi 的 I2C 总线

  • 一个串行端口——一个 Grove 端口通过电平转换芯片连接到 Raspberry Pi 的串行引脚

  • 两个模拟-数字端口——两个连接到 GoPiGo3 微控制器的 Grove 端口

  • 两个 PWM 类型伺服电机的伺服端口:

图片

图片来源:Dexter Industries: https://32414320wji53mwwch1u68ce-wpengine.netdna-ssl.com/wp-content/uploads/2014/07/GoPiGo3-Top-768x565.jpg

让我们解释这些新概念:

  • 串行端口:这是我们之前在讨论 SPI 时提到的互补通信协议。虽然后者是同步的(需要五个接口引脚),但串行端口是异步的——也就是说,没有时钟信号需要跟随,只需要两个引脚:Tx用于数据传输和Rx用于数据接收。在 GoPiGo3 中,此端口通过一个电平转换芯片直接连接到 Raspberry Pi 的串行引脚。

  • I2C 端口:正如其名称所示,它使用 I2C 通信协议。就像 SPI 一样,它是一个同步协议,比异步串行更快。I2C 使用两条线,SDA用于数据,SCL用于时钟信号。第三和第四条线是用于电源供应的:VIN为 5V 和GND接地——即 0V 参考。SDA是双向的,因此任何连接的设备都可以发送或接收数据。在这两个端口中,你将连接距离传感器和线跟随传感器。

  • 模拟-数字:这些端口可以连接到模拟、数字或 I2C Grove 设备。我们将连接到一个模拟-数字端口,即 IMU 传感器。我们将在稍后详细讨论这一点。

  • 伺服端口,连接 PWM 伺服电机:这些端口比配备编码器的电机便宜且易于控制,同时提供足够的精度来控制它们将支持的方位。在 GoPiGo3 中,我们可以将距离传感器或 Pi 摄像头连接到伺服电机。脉冲宽度调制PWM)技术指的是通过改变电压供应的占空比来在连续范围内进行控制,从而产生从 0V 到 5V 的等效输出:0V 是 0%占空比,而 100%对应于在整个周期内施加 5V。通过在周期中应用低于 100%的百分比 5V,你可以获得对位置的连续控制,范围从 0 到 180°的电机轴旋转。有关此内容的解释和一些有用的图表,请访问www.jameco.com/jameco/workshop/howitworks/how-servo-motors-work.html

Raspberry Pi 3B+

Raspberry Pi 在教育领域和工业领域都有最大的社区,这使得它成为开发机器人或物联网IoT)设备嵌入式软件的最佳单板计算机选择。以下图像显示了 Raspberry Pi 3B+,这是为 GoPiGo3 供电的最常见型号:

图片

图片来源:https://en.wikipedia.org/wiki/File:Raspberry_Pi_3_B%2B_(39906369025).png, 许可证 CC BY-SA 2.0

Raspberry Pi 3B+ 的主要特性如下:

  • 由四个 Cortex-A53 1.4 GHz 组成的 中央处理单元CPU)。

  • 图形处理单元GPU)是 250 MHz 的 Broadcom VideoCore IV。

  • 同步动态随机存取存储器SDRAM)为 1 GB,与 GPU 共享。

  • 板载存储通过 MicroSDHC 插槽提供。您可以选择任何适合您需求的 micro SD 卡大小。无论如何,一般建议使用 16 GB 容量的 10 级 micro SD 卡——10 表示它能够以 10 Mb/second 的速度写入。

让我们回顾一下这些组件的功能:

  • CPU 提供了运行各种算法的计算能力。这正是我们机器人智能所在之处。

  • GPU 的任务是处理计算机图形和图像处理。在我们的案例中,它将主要致力于处理 Pi 相机的图像并提供计算机视觉功能。

  • SDRAM 有 1 GB 易失性存储,与 GPU 共享,因此这是您分配给 GPU 的内存量(默认情况下,它最多占用 64 Mb)。RAM 是程序加载并执行的地方。

  • 板载 microSD 卡是包含操作系统以及所有已安装软件的持久存储。

Raspberry Pi 运行操作系统,通常是基于 Linux 的发行版,如 Debian 或 Ubuntu。

虽然基于 Debian 的 Raspbian 是 Raspberry Pi 基金会的官方发行版,但我们将使用由 Canonical 支持的 Ubuntu,因为这是 Open Robotics (www.openrobotics.org) 每年提供 ROS 版本的平台,与 Ubuntu 的年度版本同步。

为什么机器人需要 CPU?

除了这本书的目标是让您获得一些 ROS 的实际操作经验——为此,您需要 Linux 操作系统来安装软件——如果您真的想创建一个智能机器人,您需要运行计算密集型算法的处理能力,这正是 Raspberry Pi 等 CPU 所提供的。

为什么这个计算是必要的?因为一个智能机器人必须将环境信息与当前任务的逻辑相结合,才能成功完成它。让我们以将一个物体从当前位置移动到目标位置为例。为此,激光距离传感器、3D 相机和/或 GPS 等设备为机器人提供环境信息。这些数据源必须结合起来,以便机器人能够在环境中定位自己。通过提供目标位置,它还必须计算将物体携带到那里的最佳路径,这被称为路径规划。在执行这样的路径规划时,它必须检测路径上可能出现的障碍物并避开它们,同时不失目标。因此,任务的每一步都涉及到在机器人的 CPU 中执行一个算法。

这就是您将学会使用 ROS 解决的大量实际场景之一,ROS 目前是机器人应用开发的事实标准

深入了解机电一体化

如 GoPiGo 官方文档www.dexterindustries.com/GoPiGo/learning/technical-specifications-for-the-gopigo-raspberry-pi-robotics-kit/所述,GoPiGo3 机器人的规格如下:

  • 工作电压:7V-12V

  • 外部接口

    • I2C 端口:两个连接到 Raspberry Pi I2C 总线的 Grove 端口,通过一个电平转换芯片

    • 串行端口:一个连接到 Raspberry Pi 串行引脚的 Grove 端口,通过一个电平转换芯片

    • 模拟数字端口:两个连接到 GoPiGo3 微控制器的 Grove 端口

  • 编码器:两个每转六脉冲计数的磁性编码器(通过 120:1 的齿轮减速,每轮旋转总共 720 个脉冲)

  • 车轮直径:66.5 毫米

  • 车轮间距:117 毫米

  • 更多信息:设计信息可在官方 GitHub 仓库github.com/DexterInd/GoPiGo3找到

这只是对我们之前在标题为GoPiGo3 板的章节中解释的内容的总结。在本节中,我们将专注于描述连接到 GoPiGo3 板的设备。

最有用的传感器

我们将要安装到 GoPiGo3 上的传感器是我们完成机器人顶级任务(即低成本的运动规划导航)所需的传感器。以下是一些传感器:

  • 距离传感器

  • 跟踪线

  • 惯性测量单元IMU)传感器

  • 2D 相机

在使用循线传感器的情况下,由于机器人将跟随地板上标记的路径(通常是黑色),可以跳过运动规划部分,导航将变得容易得多。如果路径上有障碍物,你必须应用一个算法来绕过障碍物并返回路径——也就是说,将循线传感器再次放置在黑线上方。

现在,我们应该花时间了解每个传感器提供的信息。在这本书的后面部分,你将遇到这样的导航问题以及可以用来实现它的算法。

距离传感器

简单的距离传感器使我们能够测量其前方物体的距离。它有一个小激光器,用于测量到物体的距离。传感器使用飞行时间方法进行非常快速和精确的距离读取。产品页面可在www.dexterindustries.com/product/distance-sensor/查看:

图片来源:Dexter Industries:https://shop.dexterindustries.com/media/catalog/product/cache/4/image/1800x2400/9df78eab33525d08d6e5fb8d27136e95/d/e/dexter-industries-raspberry-pi-robot-distance-sensor-for-robots-front-of-sensor-1.jpg

你可以将距离传感器连接到任意的两个 I2C 端口之一。请注意,GoPiGo3 软件库不会要求你指定使用哪个端口。这将被自动检测。

你可以将传感器安装到伺服包上,以扫描大约 180°的宽角度。伺服电机可以连接到伺服端口 1 或伺服端口 2。产品页面可在www.dexterindustries.com/product/servo-package/查看:

图片来源:Modular Robotics:https://www.dexterindustries.com/wp-content/uploads/2019/09/GoPiGo3-Molded-Servo-Frontal-300x200.jpg

在第二章“GoPiGo3 单元测试”中,有一个你可以用你的机器人运行的特定测试,以检查这个单元是否正常工作。

循线传感器

GoPiGo3 的循线传感器由六对 LED 光电晶体管组成。当你将传感器放在面前读取字母时,LED 发射器是每对中最右边的一部分。这可以在以下照片中看到,这是一张有电源的传感器的图片,尽管你此时还看不到 LED 的光束:

为什么在图片中看不到它们?因为 LED 发出红外光,人眼无法检测到;然而,手机摄像头可以揭示它(默认情况下,这些摄像头的光学系统不包括红外滤光片)。所以,如果您后来发现线跟踪器工作不正常,您必须首先检查硬件。为此,只需用智能手机的相机应用程序拍照即可。

在下面的图像中,传感器视图被有意模糊处理,以便您可以看到光线并确认光线是从 LED 光敏晶体管的右侧发出的。接收部分,即光敏晶体管,是检测是否有从 LED 发射器反射的光线的组件。该组件的产品页面可在www.dexterindustries.com/product/line-follower-sensor/查看:

图片

现在,您已经可以理解线跟踪传感器的原理了:

  • 如果光线因为地板是白色的而反射,光敏晶体管就会接收到反射的光束,并在数据传感器流中提供这一信息。

  • 如果传感器位于黑色表面上,光敏晶体管不会接收到任何反射光线,并让机器人知道。

反射会使传感器电子报告的信号接近 1(白色表面),而吸收则提供接近 0 的值(黑色表面)。但是,如果传感器离地板很远或者没有面向它呢?好吧,从传感器的角度来看,吸收等同于没有反射。因此,报告的信号接近零。这一特性使得 GoPiGo3 不仅能跟随地面上的黑色路径,还能沿着边缘行走,避免可能损坏机器人的凹坑。

由于您有六对,您将拥有六个信号,每个信号报告 0 或 1。这六个数字将使我们能够推断出机器人在黑色线上的中心位置有多好。传感器的规格如下:

以下是从发射器-接收器底部的视图。这个面必须比地板高出几毫米,以确保 LED 发射的正确反射:

图片来源:Modular Robotics:https://shop.dexterindustries.com/media/catalog/product/cache/4/thumbnail/1800x2400/9df78eab33525d08d6e5fb8d27136e95/l/i/linefollower_bottom.jpg

以下图片显示了线跟踪传感器正确安装在 GoPiGo3 上——即在地板上方:

图片来源:Modular Robotics:http://www.dexterindustries.com/wp-content/uploads/2019/03/linefollowerinaction.jpg

有关如何组装和校准传感器的说明,请访问 www.dexterindustries.com/GoPiGo/line-follower-v2-black-board-getting-started/。对于连接,你可以使用线跟踪传感器上可用的两个 I2C 连接器中的任何一个。请记住,其中一个将被距离传感器使用。

如果你使用 Raspbian For Robots (www.dexterindustries.com/raspberry-pi-robot-software/),线跟踪器也可以连接到 AD 端口之一。它适用于更高级的使用,并且为此配置编写的代码略有不同。

为了介绍机器人技术,我们将通过将传感器连接到 I2C 端口并使用更友好的 DexterOS (www.dexterindustries.com/dexteros/) 来简化操作。在 第二章 GoPiGo3 的单元测试 中,我们将介绍你可以运行的具体测试,以检查该单元是否正常工作。

IMU 传感器

IMU 传感器使我们能够测量机器人的方向,以及在其移动过程中获得其位置的估计。Dexter Industries IMU 的产品页面可以在 www.dexterindustries.com/product/imu-sensor/ 查看。传感器的相应方面可以在以下图片中看到:

图片来源:Dexter Industries:https://shop.dexterindustries.com/media/catalog/product/cache/4/thumbnail/1800x2400/9df78eab33525d08d6e5fb8d27136e95/i/m/imu-sensor_mount2-800x800.jpg

在以下图片中,你可以看到它安装在 GoPiGo3 上。要连接到机器人,你只需将其插入 GoPiGo 板上的 AD1 或 AD2 即可:

图片来源:Dexter Industries:https://shop.dexterindustries.com/media/catalog/product/cache/4/thumbnail/1800x2400/9df78eab33525d08d6e5fb8d27136e95/i/m/imu-sensor_gpg3_3.jpg

此 IMU 有九个 自由度DOF),以及温度测量功能。让我们来谈谈 IMU 的每个传感器以及它们提供的数据类型:

  • 让我们从更简单的一个开始,即温度。这提供了环境室温,可以与其它传感器结合使用,例如,通过在 GoPiGo3 覆盖的表面多个位置进行测量,创建一个房间的温度图。

  • 加速度计是一个绝对传感器,因为它的值始终参照为零加速度(静止物体)。它为三个轴——X, Y,Z——提供值:

    • 它适合测量机器人的倾斜度(一个余弦值为垂直加速度除以重力值=9.81 m/s²的角度)和自由落体状态,这相当于一个 90°的斜坡,是一个垂直墙面(传感器持续检测重力,即 9.81 m/s²,如果物体保持在水平面上)。

    • 加速度计在测量速度方面并不准确,因为传感器没有直接提供这个值。我们可以通过在时间上对加速度信号进行积分来获得它,这会产生累积误差(漂移),主要来自传感器噪声(电子)和测量误差本身。这就是陀螺仪发挥作用以提供准确速度测量的地方。

    • 陀螺仪是一个差分传感器,它相对于任意参考提供了三个旋转(X, Y,Z 轴)。它们实际上提供的是旋转速度。这意味着它们在测量旋转速度方面很准确,但不适合测量角位置(你必须对速度信号进行时间积分,积累测量误差和传感器噪声,从而产生漂移)。

一个六自由度 IMU 将是一个结合了加速度计(三个自由度)和陀螺仪(三个自由度)的设备:

  • 加速度计可以准确地测量相对于垂直的倾斜度。它在中等/长期测量中没有漂移,但短期测量不准确。

  • 陀螺仪可以准确地测量旋转速度,但它们有漂移。这意味着它们不适合中等/长期测量。

通过结合加速度计和陀螺仪的六个值,可以获得一个改进的测量方向。这通过以下图中的欧拉角——α, β, γ——来表示:

图片来源:https://commons.wikimedia.org/wiki/File:Euler_angles_zxz_int%2Baxes.png,许可协议 CC BY-SA 4.0

比欧拉角更常用的方法是 Tait-Bryan 版本或导航角,即偏航-俯仰-横滚角,其定义如下:

图片来源:https://es.m.wikipedia.org/wiki/Archivo:Flight_dynamics_with_text.png,许可协议 CC BY-SA 3.0

这些角度是通过应用一个特殊的滤波器,称为互补滤波器,到传感器的信号中获得的。它的工作原理如下:

  • 对于加速度计的信号,它表现为低通滤波器,因为我们信任它的中/长期测量。

  • 对于陀螺仪的信号,它表现为高通滤波器,因为我们信任它的短期测量。

从数学上讲,互补滤波器表示如下:

在这里,AB 的和必须为 1。这些常数由传感器的校准确定,典型值包括 A = 0.98,B = 0.02。互补滤波器提供与卡尔曼滤波器非常相似的结果,卡尔曼滤波器是最佳线性(无偏)估计器BLE),但计算量更大。

现在,我们有三个旋转(关于 XYZ 轴),但它们还不是绝对角度:

  • 多亏了加速度计,相对于垂直方向的姿态是一个绝对参考,但对于水平平面上的姿态,我们缺少这样的参考,因为陀螺仪是一个差分传感器。

  • 这就是磁力计似乎给我们提供地球磁场(三个轴 XYZ)的方向。

因此,使用我们的 6 + 3 = 9 个自由度 IMU,我们有了机器人的绝对姿态,以重力场和磁场矢量作为参考。在第二章,GoPiGo3 的单元测试中,我们将介绍你可以用你的机器人运行的一个特定测试来检查这个单元是否正常工作。

Pi 相机

Pi 相机是一个定制的 2D 相机,具有相机串行接口CSI)。下面的图像显示了两个物理组件——即相机的电子板和带状电缆:

图片来源:https://commons.wikimedia.org/wiki/File:Raspberry_Pi_Camera_Module_v2_with_ribbon.jpg, 许可证 CC BY-SA 4.0

在下面的图像中,我们可以看到如何将带子连接到 Raspberry Pi 的 CSI 端口:

图片来源:https://www.flickr.com/photos/nez/9398354549/in/photostream by Andrew,许可证:CC BY-SA 2.0

Pi 相机能够提供高达 30 帧每秒FPS)的 HD 分辨率(1920 x 1080 像素)。你可以在picamera.readthedocs.io/en/release-1.12/fov.html的文档中找到可能的配置。在第二章,GoPiGo3 的单元测试中,我们将介绍你可以用你的机器人运行的一个特定测试来检查这个单元是否正常工作。

将所有这些放在一起

现在您已经熟悉了硬件,是时候将所有部件组装起来,连接它们,并进行快速测试以检查 GoPiGo3 是否正常工作。组装过程在官方文档中有详细的步骤说明,包括大量的图表和照片;您可以在www.dexterindustries.com/GoPiGo/get-started-with-the-gopigo3-raspberry-pi-robot/1-assemble-gopigo3/找到这份文档。

或者,您可以使用edu.workbencheducation.com/上的 Workbench 培训环境,并免费注册一个个人账户,在注册进度的同时完成相同的组装过程。如果您这样做,请按照制造商官方文档中的两个组装阶段进行操作:

请注意,每个电机的电缆必须插入同一侧的连接器。如果您反方向操作,那么当您使用 GoPiGo3 API 库命令前进时,机器人会向后移动,反之亦然。如果您遇到这种情况,您只需交换连接器,使其正常工作。

要安装 Pi 相机,请按照www.dexterindustries.com/GoPiGo/get-started-with-the-gopigo3-raspberry-pi-robot/4-attach-the-camera-and-distance-sensor-to-the-raspberry-pi-robot上的说明进行。这些说明扩展了 Raspberry Pi 部分。

组装好基础套件后,您可以继续安装传感器:

以下图片显示了连接了三个传感器的 GoPiGo3:

图片

注意,IMU 传感器的Z轴应指向前方,X轴应指向左轮,因此Y轴应沿着垂直轴向上。当正确校准并放置在水平表面上时,两个角度,俯仰横滚,将为零,如果Z指向磁南,偏航角度也将为零。

快速硬件测试

为了快速测试并专注于手头的硬件,我们将使用 DexterOS,这是 Dexter Industries 创建的基于 Raspbian 的发行版,允许用户快速开始。操作系统的详细信息可在www.dexterindustries.com/dexteros/找到。Dexter Industries 通过提供一个简单的网络环境,简化了界面,无需处理完整的 Linux 桌面。

你可以通过连接到名为 GoPiGo 的 Wi-Fi 接入点来访问它(不需要密码)。这样,你将直接通过你的笔记本电脑连接到机器人。在安装之前,让我们回顾一下我们可用的资源。

资源

在与机器人一起工作时,你将至少管理以下三个网站/仓库:

  • 在 GitHub 上托管由 Dexter Industries 提供的官方库。具体如下:

    • GoPiGo3 官方库github.com/DexterInd/GoPiGo3。这个仓库不仅包含多种语言的 API(Python、Scratch、C、JavaScript、Go 等),还包含示例和完整的项目,其中一些我们将在下一章中使用,以扩展到 ROS。

    • DI 传感器库github.com/DexterInd/DI_Sensors。这个仓库涵盖了 Dexter Industries 提供的所有传感器,不仅限于 GoPiGo3 中使用的传感器。它为 Python、Scratch、C#和 Node.js 提供了 API。

    • 基于 Web 的学习平台edu.workbencheducation.com/。如果你是从零开始,这是一个针对 GoPiGo3 的指导培训网站。

开始使用 DexterOS

在完成“整合一切”部分的第 1 步和第 2 步后,你应该通过www.dexterindustries.com/dexteros/get-dexteros-operating-system-for-raspberry-pi-robotics/中的步骤,在那里你可以下载操作系统的映像文件,并按照说明使用 Etcher 应用程序([www.balena.io/etcher/](https://www.balena.io/etcher/))烧录微 SD 卡。按照以下步骤开始使用 DexterOS:

  1. 一旦你将卡插入 Raspberry Pi 的插槽中,打开 GoPiGo 板并连接到它创建的 Wi-Fi 网络(其 SSID 为 GoPiGo,无需密码)。

  2. 之后,前往http://mygopigo.comhttp://10.10.10.10以访问机器人的环境,首页看起来如下。你可以在edu.workbencheducation.com/cwists/preview/26657x找到逐步操作流程:

图片

请注意,如果你保持笔记本电脑的互联网连接(有线),那么你应该连接到机器人的 IP 地址http://10.10.10.10。如果你需要帮助,你可以访问 DexterOS 论坛forum.dexterindustries.com/c/DexterOS

从这一点开始,正如你在首页上看到的那样,你可以做以下事情:

  • DRIVE:通过基本控制面板在所有方向上移动机器人。

  • LEARN:通过在 Bloxter 中遵循引导教程——我们选择的语言之一——或使用 Jupyter Lab 笔记本的 Python。

  • 使用 Bloxter 编写代码:基于 Google 的开源 Blockly 的可视化编程语言(github.com/google/blockly)。

  • 使用 Python 编写代码:我们将用它来开发我们的机器人训练。

接下来,我们将开始使用 Bloxter 进行编码。

使用 Bloxter 进行编码

在可用的编程语言中,Bloxter为你提供了在不涉及编写代码的复杂性下学习机器人的机会。使用可视化界面,你可以排列和连接模块,并快速开发基本的程序来控制 GoPiGo3。让我们开始吧:

  1. 通过在首页上点击“LEARN”,然后点击“Bloxter 中的课程”,你可以访问可用的课程,如下面的截图所示:

图片

  1. 选择你喜欢的,考虑到它们是按难度递增排序的:

图片

在开始第二章,GoPiGo3 单元测试之前,建议你完成 Bloxter 部分的 LEARN 部分。课程易于跟随,并且它们会教你比仅仅阅读文档更多的关于 GoPiGo3 的知识。

校准机器人

按照以下步骤校准机器人:

  1. 返回主页面,点击mygopigo.com/,然后在首页右上角的图标上点击。会出现一个帮助屏幕,包含两个按钮,一个用于校准,另一个用于检查电池状态,如下面的截图所示:

图片

  1. 点击“检查生命体征”来检查生命体征:

图片

  1. 现在,点击前面的按钮,测试你机器人的精度并进行校准。你会看到以下屏幕:

图片

  1. 调整尺寸,使其与你的机器人尺寸相匹配:

    • 轮径: 在地面上标记 2 米的距离,然后点击驱动 2m按钮。如果 GoPiGo3 刚好到达终点线,66.5 毫米是合适的。如果没有到达,你应该稍微增加直径;如果超过了终点线,你应该减少它。再次测试。通过试错,你会找到最适合你自己的机器人的直径。

    • 车轮之间的距离: 这个过程非常相似,唯一的区别是,在这种情况下,当你按下旋转一周时,机器人将围绕自身旋转。如果 GoPiGo3 完成 360°的完整旋转,117 毫米是合适的。如果没有完成旋转,你应该减少距离;如果旋转超过 360°,你应该增加它。再次测试。通过试错,你将能够调整这个距离,就像调整轮径一样。

驾驶机器人

要驾驶机器人,请按照以下步骤操作:

  1. 关闭帮助窗口,并在主页上选择DRIVE项。

  2. 通过点击此按钮,你可以访问一个面板,其中包含用于移动机器人前后和旋转左右的控制按钮。继续检查 GoPiGo3 是否按预期移动。

  3. 每当你需要停止运动时,请按键盘上的空格键:

图片

接下来,我们将检查传感器。

检查传感器

按照以下步骤检查传感器:

  1. 返回主页并点击Code in Bloxter

  2. 在屏幕的右侧,你会看到一个滑动窗口,你可以在这里指定连接到每个传感器的端口。在我们的示例中,我们设置了以下配置:

    • 距离传感器插入 I2C-1,GoPiGo3 左侧的 I2C 接口

    • 循线传感器连接到 I2C-2,GoPiGo3 右侧的 I2C 接口

    • IMU传感器连接到 AD1(左侧)

  3. 一旦你在 DexterOS 中选择了一个端口,你将能够为出现的每个下拉菜单进行选择,这些都是关于来自传感器的实时数据,如下面的截图所示:

图片

  1. 检查所有三个——即距离传感器、循线传感器和 IMU——以提供读数。在距离传感器中,你可能会得到一个错误未知的消息。不要担心,传感器并没有损坏,只是软件的一个错误。在我们下一章使用 Python 时,你肯定会获得良好的读数。

  2. 最后,让我们看看更复杂的传感器——IMU 的数据。在将其连接到 AD1 后,当您选择“惯性测量单元”或“惯性测量单元(数据)”时,窗口会提示您——将机器人空转 3 秒钟以校准其方向。这样,我们通过结合地球的重力和磁场来获得绝对方向参考。然后,如果我们从下拉列表中选择“惯性测量单元”,我们将看到实时报告的欧拉角度。如果它们已经正确校准,我们应该找到以下内容:

    • 当 GoPiGo3 在水平表面上并且面向东方时,欧拉航向(偏航、俯仰和滚转)的三个角度都是零。在这种情况下,Z轴(在传感器上涂有颜色)指向南方。

    • 在这个位置,如果你用手围绕Z轴旋转 GoPiGo3 超过 90°,那么滚转角度将是 90°,X轴将指向向上(指向天顶)。

    • 回到原始位置,如果你用手围绕X轴旋转 GoPiGo3 +90°,俯仰角度将是 90°,Y轴将指向南方。

GoPiGo3 中 IMU 的物理位置可以在以下图片中看到:

图片

在下一章中,我们将学习如何使用 Pi Camera,当我们使用 Python 编程机器人时。

关闭机器人

要完成您与 GoPiGo3 的第一段旅程,只需长时间按住 GoPiGo 红色板上的黑色按钮。几秒钟后,红色 LED 将停止闪烁,这意味着关闭过程已完成。

摘要

在本章中,我们通过了解套件中传感器和电机的物理原理,熟悉了 GoPiGo3 硬件。我们通过运行一些快速测试来检查它们是否正常工作,以便我们可以开始编程任务。

在下一章中,我们将学习如何使用 Python 编程 GoPiGo3,同时为它的每个主要组件执行一些单元测试:伺服电机、距离传感器、线跟踪器、IMU 和 Pi Camera。

问题

  1. 机器人是否必须配备像 Raspberry Pi 这样的计算机?

A) 是的,因为计算机为控制板供电。

B) 不,因为计算机只需要在屏幕上可视化软件代码。如果已经将程序烧录到控制器中,它可能可以独立工作。

C) 并非真的;你可以编写一个小程序来控制机器人,并将其烧录到控制器的芯片上。每次你给机器人供电时,它都会在无限循环中执行程序。

  1. GoPiGo3 的距离传感器发射什么类型的辐射?

A) 激光

B) 红外线

C) 超声波

  1. 为什么你看不到线跟踪传感器发出的光?

A) 因为传感器必须先通过软件命令预先激活

B) 它不发射光,而是一个磁场

C) 它在可见光范围内不发射任何东西

  1. GoPiGo 红板上的串行端口有什么用途?

A) 为了让我们能够以与 Arduino 板相同的方式编程它,在那里您使用串行端口将程序烧录到微控制器的芯片上。

B) 为了从传感器同步传输数据到板。

C) 为了从 Raspberry Pi 访问 GPIO 的串行引脚。

  1. IMU 传感器是否提供机器人的绝对方向?

A) 是的,因为这是将加速度计和陀螺仪放在一起的目标。

B) 只有当 IMU 包含磁力计。

C) 只有在具有六个自由度的 IMU 传感器的情况下。

进一步阅读

您可以通过阅读制造商提供的详尽官方文档来了解更多关于 GoPiGo3 的功能:

第二章:GoPiGo3 的单元测试

在完成硬件组装后,在本章中,您将通过 JupyterLab 环境熟悉 GoPiGo3 的工作方式,这是一个友好的界面,它采用由人类可读段落和 Python 代码片段组成的笔记本结构。您将为每个测试程序产生两个版本:JupyterLab 笔记本和纯 Python 脚本。

使用这些编程工具,您将单独测试每个传感器/执行器,并检查它们是否正常工作,同时了解每个技术背后的知识。

我们将涵盖以下主题:

  • 在 Jupyterlab 友好的环境中开始 Python 编程

  • 测试机器人感知:距离传感器、循线传感器和 2D

  • 测试机器人动作:电机和编码器

如果您遵循实际练习,您将了解不同的包(传感器、电机等)如何构建整个机器人,传感器测量了什么,以及如何系统地测试传感器和执行器。

技术要求

第一部分的代码,Python 和 Jupyterlab 入门,包含在 GoPiGo3 制造商提供的自定义操作系统 DexterOS 的图像中。在第一章组装机器人中,我们解释了如何获取此图像并将其烧录到 SD 卡中。这已在快速硬件测试部分和DexterOS 入门子部分的入门部分中解释。

本章的代码位于本书的 GitHub 仓库中,位于github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter2_Unit_Tests.单元测试传感器和驱动器部分的开始,我们解释了如何在 Raspberry Pi 上本地克隆代码。

在 Jupyterlab 友好的环境中开始 Python 编程

JupyterLab 是 Python 社区中的一个非常全面的工具,因为它允许您编写程序,就像在学校课堂上解决数学问题一样。也就是说,您编写标题,然后是问题陈述和初始数据。在此声明之后,您编写一个段落来解释您将要执行的操作,然后编写执行该操作的 Python 行(代码单元格)。对于每个操作,您重复相同的步骤:

  1. 一个人类可读的段落解释下一个操作,该段落使用众所周知的 markdown 语法 格式化。commonmark.org/help/

  2. 包含执行操作的 Python 代码行的代码单元格。

  3. 对每个执行单个操作的代码片段重复步骤 1 和 2。最后一个将提供问题的解决方案。

这里有一个自解释的示例,用于读取 GoPiGo3 的距离传感器:

接下来,我们将解释如何为 GoPiGo3 启动 JupyterLab。

启动 GoPiGo3 的 JupyterLab

在 DexterOS 中,你有两个课程解释了 Jupyter 笔记本和用 Python 控制机器人:

  1. 你可以通过访问 http://mygopigo.comhttp://10.10.10.10 来访问它们,然后点击 LEARN,然后点击 Python 中的课程:

然后启动 JupyterLab 环境,在窗口的左侧你会看到两个文件:

    • 1_Moving_Around.ipynb 是一个 Jupyter 笔记本,逐步解释了如何让机器人前进和旋转。按照笔记本中的说明执行命令。

    • 2_The_Environment.ipynb 是另一个例子,解释了如何处理 JupyterLab:运行一个单元、停止执行等等。如果你在前一个笔记本中遇到了困难,请按照这个笔记本操作,然后返回到第一个笔记本。

  1. 现在我们尝试 1_Moving_Around.ipynb。以下命令使机器人前进 10 厘米:
my_gopigo.drive_cm(10)
  1. 如果你更喜欢用英寸工作,使用这个命令:
my_gopigo.drive_inches(10)
  1. 如果你同时在同一个代码单元中执行这两个命令,你会在执行第二个命令之前注意到一个小间隙:
my_gopigo.drive_cm(10)
my_gopigo.drive_inches(10)

现在我们用机器人进行一次物理测试:

  1. 在起点在地面上做一个标记,向前开 10 厘米,向后开 10 厘米,看看运动有多精确:
# Forward
my_gopigo.drive_cm(10)
my_gopigo.drive_inches(10)

# Backward
my_gopigo.drive_cm(-10)
my_gopigo.drive_inches(-10)

你应该会发现它准确地回到了起点。我们用千分尺测量的误差(测量了三次)分别是 +1.29 毫米、-0.76 毫米和 +2.16 毫米。正数表示在倒车时它通过了起点,而负数表示它没有到达起点。

  1. 如果你将距离相加(10 厘米 + 10 英寸 = 35.4 厘米)并放入一个向前命令中,然后发出另一个命令以相同的距离后退,我们测量的误差是(我们再次进行了三次测量)-0.01 毫米、-1.40 毫米和 -0.72 毫米:
# Forward
my_gopigo.drive_cm(+35.4)

# Backward
my_gopigo.drive_cm(-35.4)

你可以看到,使用两个命令引入的暂停引入了一个大约 1 毫米的错误。仅使用一个命令向前和一个命令向后可以显著减少错误。

  1. 我们可以对转向进行类似的测试:
# Turn clockwise (right)
my_gopigo.turn_degrees(90)

# Turn counterclockwise (left)
my_gopigo.turn_degrees(-90)
  1. 其他有用的命令如下:
my_gopigo.forward()
my_gopigo.backward()
my_gopigo.right()
my_gopigo.left()
  1. 要停止机器人,使用这个命令:
my_gopigo.stop()
  1. 非常重要的是要注意,软件流程可能与机器人物理不兼容。尝试这个序列并观察 GoPiGo3 的表现:
my_gopigo.forward()
my_gopigo.backward()
my_gopigo.right()
my_gopigo.left()

似乎只有最后一行被执行,即 my_gopigo.left()。实际上发生的情况是,命令执行得非常快(每次几毫秒),机器人的惯性不允许 GoPiGo 有足够的时间向前、向后或向右移动。移除最后一行来检查它:现在你看到的唯一实际执行的命令是 my_gopigo.right()

在为机器人编程时,最具挑战性的问题之一是理解其动力学,因为可能看起来像是软件错误的东西,可能是机器人意外物理反应。因此,在开发软件之前,您必须确保您理解问题的物理原理,包括其质量(惯性)、摩擦力、电机上的最大负载、电源限制和电池水平。来自物理世界的变量列表可能是无限的,您应该应用您的力学和电学知识来成功开发功能软件。

对于这个简单案例的一个可能解决方案是在序列中指定您想要它移动的距离或角度:

my_gopigo.drive_cm(-10) # Forward
my_gopigo.drive_cm(10) # Backward
my_gopigo.turn_degrees(90) # Right (clockwise)
my_gopigo.turn_degrees(-90) # Left (counterclockwise)

要在 DexterOS 中保存您的作品,您必须从 ~/.lessons_python 文件夹中进行。这个文件夹属于 pi:users,而 DexterOS 用户是 jupyter(在终端中输入 $ whoami 命令或在提示符中查看)。在树中向上移动一级,并创建一个您想要的任何名称的文件夹。然后使用文件 | 另存为... 在该位置保存您的更改。

硬件测试

要在 LEARN 环境之外访问 DexterOS 中的这些笔记本,请导航到 mygopigo.comhttp://10.10.10.10,然后点击 Python 中的代码。JupyterLab 将启动:

在屏幕左侧的文件管理器视图中,您将找到这两个笔记本:

  • 其中一个是 First Ride Around.ipynb,它提供了一个用于通过视觉面板控制机器人的小部件:

  • 另一个笔记本是 Hardware Testing.ipynb,它为电池、LED 灯和编码器运行特定的测试。以下小节中讨论了我们对 GoPiGo3 行为的预期。

测试电池、LED 灯和电机/编码器

打开笔记本并逐个检查每个单元,以了解其功能。

电池水平

以下命令读取并显示当前电池电压. 对于 GoPiGo3 正常工作,这个值应该高于 9 V。当电池电量不足时,您将失去与机器人的 Wi-Fi 连接,您必须充电或更换新电池:

print("Battery voltage : ", GPG.get_voltage_battery() )

OUTPUT ===> Battery voltage : 9.114

接下来,我们将查看硬件信息和电压水平。

硬件信息和当前电压水平

下一个命令块将打印制造商信息、硬件和固件版本以及电池水平。以下与输出相关:

  • 电池电压 与之前相同。测量给出略有不同的值,9.294V,这是正常的。

  • 5V 电压 是通过 GPIO 向 Raspberry Pi 提供电源的电压。GoPiGo 红色电路板有一个电源调节器,将 9V 的原始输入转换为稳定的 5V。如果电池充电良好,这个值应该非常接近 5V,如所示:

print("Manufacturer : ", GPG.get_manufacturer() )
print("Board : ", GPG.get_board() )
print("Serial Number : ", GPG.get_id() )
print("Hardware version: ", GPG.get_version_hardware())
print("Firmware version: ", GPG.get_version_firmware())
print("Battery voltage : ", GPG.get_voltage_battery() )
print("5v voltage : ", GPG.get_voltage_5v() )

OUTPUT ===> 
Manufacturer    :  Dexter Industries
Board           :  GoPiGo3
Serial Number   :  F92DD433514E343732202020FF112535
Hardware version:  3.x.x
Firmware version:  1.0.0
Battery voltage :  9.294
5v voltage      :  4.893

接下来,我们将检查 LED 灯和闪烁灯。

LED 灯和闪烁灯

板上顶部有两个 LED 灯,模拟 GoPiGo3 的眼睛。通过运行以下代码块,它们将改变颜色:

colors = [ (255,0,0), (255,255,0), (255,255,255), (0,255,0), (0,255,255), (0,0,255), (0,0,0)]
for color in colors:
    GPG.set_eye_color(color)
    GPG.open_eyes()
    time.sleep(0.5)

闪烁灯是位于前面红色板下方的两个小红灯。以下代码使它们闪烁 5 次:

for i in range(5):
 GPG.led_on("left")
 GPG.led_on("right")
 time.sleep(0.5)
 GPG.led_off("left")
 GPG.led_off("right")
 time.sleep(0.5)

视觉检查,并注意控制台不会显示任何消息。

电机和编码器测试

下一个单元格将运行大约 5 秒,并将报告编码器读数:

GPG.set_motor_dps(GPG.MOTOR_LEFT | GPG.MOTOR_RIGHT, 100)
start = time.time()
lapse = 0

while lapse < 5:
   lapse = time.time() - start
   time.sleep(0.5)
   print("LEFT: {} RIGHT:{}".format(GPG.get_motor_status(GPG.MOTOR_LEFT),GPG.get_motor_status(GPG.MOTOR_RIGHT)))

passed_test = GPG.get_motor_status(GPG.MOTOR_LEFT)[0]==0 and GPG.get_motor_status(GPG.MOTOR_RIGHT)[0]==0
GPG.set_motor_dps(GPG.MOTOR_LEFT | GPG.MOTOR_RIGHT, 0)

if passed_test:
   print("Test passed.")
else:
   print("Test failed.")

这些是结果:

LEFT: [0, 26, 3095, 101]  RIGHT:[0, 26, 4806, 101]
LEFT: [0, 26, 3146, 101]  RIGHT:[0, 28, 4856, 101]
LEFT: [0, 26, 3196, 101]  RIGHT:[0, 28, 4906, 101]
LEFT: [0, 26, 3246, 101]  RIGHT:[0, 26, 4957, 96]
LEFT: [0, 26, 3296, 101]  RIGHT:[0, 26, 5007, 101]
LEFT: [0, 26, 3347, 101]  RIGHT:[0, 28, 5057, 101]
LEFT: [0, 24, 3397, 105]  RIGHT:[0, 26, 5107, 96]
LEFT: [0, 21, 3447, 96]  RIGHT:[0, 26, 5158, 101]
LEFT: [0, 26, 3497, 101]  RIGHT:[0, 21, 5208, 101]
LEFT: [0, 28, 3547, 96]  RIGHT:[0, 28, 5258, 96]
LEFT: [0, 33, 3598, 101]  RIGHT:[0, 33, 5308, 101]
Test passed.

我们应该看到最后的消息,告诉我们测试是否通过。目前不必担心理解这些数字:这是一个 GoPiGo3 自我检查并报告是否通过或失败的内测。

如果成功,你可以继续进行以下测试。GoPiGo3 将向前行驶 10 厘米,最后的输出值应该大约是 10。如果这个测试失败,你可以通过点击顶部红色的停止按钮来停止机器人:

GPG.reset_encoders()
#GPG.set_speed(GPG.DEFAULT_SPEED)
GPG.drive_cm(10)
encoders_read = round(GPG.read_encoders_average())
print("Drove {:.2f} cm".format(encoders_read))
if encoders_read == 10:
 print("Test passed.")
else:
 print("Test failed.")

如果一切顺利,你将获得以下消息:

Drove 10.00 cm Test passed.

再次,如果这个测试失败,你可以通过按顶部红色的停止按钮来停止机器人。在这些基本测试之后,我们对 GoPiGo3 的硬件和软件有了足够的了解,可以执行单元测试,这是本章的主要目标。

传感器和驱动单元测试

在本节中,我们将通过使用 Jupyter Notebooks 在 Python 中运行一些简单的脚本。从你的笔记本电脑中的终端,克隆书籍仓库并进入Chapter2_Unit_Tests文件夹以访问本章的文件:

$ git clone https://github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming
$ cd Hands-On-ROS-for-Robotics-Programming/Chapter2_Unit_Tests

为了完整性,我们在lessons_GettingStarted文件夹中包含了上一节使用的笔记本。

使用传感器和电机快速入门

要进入机器人的 Python 环境,打开红色板,并从你的笔记本电脑连接到GoPiGo Wi-Fi 网络。然后,在浏览器中访问以下 URL:

http://10.10.10.10/python

在 JupyterLab 中,关注左侧,那里显示了文件存储。要运行任何示例,你必须手动上传到你想在机器人存储中放置的位置。创建一个名为Chapter2_Unit_Tests的文件夹并上传所需的文件。我们将在每个练习的开始处指出我们将使用哪个文件。

驱动绕行

本小节中的文件位于仓库的./Chapter2_Unit_Tests/drivingAround文件夹中。你可以逐个上传它们。之后,在 JupyterLab 中通过打开启动器标签页启动一个 shell 会话:

  1. 从标签页中选择终端图标,如图所示:

  1. 通过更改到它们的目录来从终端执行脚本:
$ cd./Chapter2_Unit_Tests/drivingAround
$ python <name_of_script.py>

第一个脚本是1-easyMotors.py,它执行一个非常简单的序列:

  1. 电机向前移动 1 秒。

  2. 电机停止 1 秒。

  3. 驱动机器人 50 厘米然后停止。

  4. 向右转 1 秒。

  5. 向左转 1 秒。

  6. 停止。

您可以在以下代码列表中看到脚本的第一个部分,它显示了前进 1 秒然后停止 1 秒的命令:

# import the time library for the sleep function
import time
# import the GoPiGo3 drivers
from easygopigo3 import EasyGoPiGo3

# Create an instance of the GoPiGo3 class.
# GPG will be the GoPiGo3 object.
gpg = EasyGoPiGo3()

print("Move the motors forward freely for 1 second.")
gpg.forward()
time.sleep(1)
gpg.stop()
print("Stop the motors for 1 second.")
time.sleep(1)

第二部分如下所示,它包括前进 50 厘米,然后向右转 1 秒,然后向左转 1 秒的命令:

print("Drive the motors 50 cm and then stop.")
gpg.drive_cm(50, True)
time.sleep(1)

print("Turn right 1 second.")
gpg.right()
time.sleep(1)
print("Turn left 1 second.")
gpg.left()
time.sleep(1)

print("Stop!")
gpg.stop()
print("Done!")

下一个脚本是 2-driveSquare.py,它做了它所说的:它在地板上画一个正方形。如果我们使用一个for循环,在每次驱动 30 厘米后进行 90 度旋转,我们得到一个行数非常少的程序:

from easygopigo3 import EasyGoPiGo3

gpg = EasyGoPiGo3()
length = 30

for i in range(4):
  gpg.drive_cm(length) # drive forward for length cm
  gpg.turn_degrees(90) # rotate 90 degrees to the right

3-circularMoves.py Python 脚本使 GoPiGo3 在一个方向上走半圆,然后在相反方向上走,返回起点:

from easygopigo3 import EasyGoPiGo3

gpg = EasyGoPiGo3()

gpg.orbit(180, 50) # draw half a circle
gpg.turn_degrees(180) # rotate the GoPiGo3 around
gpg.orbit(-180, 50) # return on the initial path
gpg.turn_degrees(180) # and put it in the initial position

程序 4-drawEight.py 将弧线和直线路径结合起来,在地板上绘制一个 8 形状:

from easygopigo3 import EasyGoPiGo3

gpg = EasyGoPiGo3()
radius = 30

gpg.orbit(-270, radius) # to rotate to the left
gpg.drive_cm(radius * 2) # move forward
gpg.orbit(270, radius) # to rotate to the right
gpg.drive_cm(radius * 2) # move forward

最后,程序 5-accelerateForward.py 展示了如何加速机器人。这个序列相当简单:

  1. 设置初始速度和结束速度。

  2. 通过将它们之间的间隔除以 20 来计算步长。

  3. 运行一个循环,每次迭代增加步进值。

  4. 每 0.1 秒执行一次迭代。

  5. 2 秒后,GoPiGo3 将达到最大速度并停止:

from easygopigo3 import EasyGoPiGo3
from time import time, sleep

gpg = EasyGoPiGo3()

# setting speed to lowest value and calculating the step increase in speed
current_speed = 50
end_speed = 400 step = (end_speed - current_speed) / 20
gpg.set_speed(current_speed) # start moving the robot at an ever increasing speed
gpg.forward()
while current_speed <= end_speed:
  sleep(0.1)
 gpg.set_speed(current_speed)
  current_speed += step # and then stop it
gpg.stop() 

现在我们来测试我们为机器人配备的所有传感器。

距离传感器

为了与传感器通信,我们将使用 DI-sensors Python 库 github.com/DexterInd/DI_Sensors

首先,我们需要修改端口连接。这就是我们现在要回顾的内容。

检查端口连接

以下图表在连接传感器到 GoPiGo3 端口时应非常有帮助,以确保硬件正确布线:

图片来源:Dexter Industries: https://gopigo3.readthedocs.io/en/master/_images/gpg3_ports.jpg

从上一章,你应该记得每个端口的用途:

  • AD1AD2是通用输入/输出端口。

  • SERVO1SERVO2是伺服控制器端口。

  • I2C端口是您连接I2C 启用设备的地方。

  • 串行端口是您可以连接UART 启用设备的地方。

根据这个描述,检查传感器是否按照我们在第一章中描述的方式连接:

  • 距离传感器应该插入到I2C-1,GoPiGo3 左侧的 I2C 端口。

  • 伺服包应该连接到SERVO1

  • 线跟踪器应该插入到I2C-2,GoPiGo3 右侧的 I2C 端口。

  • IMU传感器应该连接到AD1(在左侧)。

距离传感器单元测试

尽管您已经将距离传感器连接到I2C-1端口,但请注意,GoPiGo3 软件库不会要求您在脚本中指定您使用的是两个端口中的哪一个。它将自动检测。

测试文件位于存储库的 ./Chapter2_Unit_Tests/DI-distance 文件夹中。您可以逐个上传到 DexterOS:

  • di-distance_cm.py

  • di-distance-easygopigo_mm.py

然后,通过打开启动器窗口并从中选择终端图标,在 JupyterLab 中打开一个 shell 会话。通过移动到它们的位置来在终端中执行脚本:

$ cd./Chapter2_Unit_Tests/DI-distance
$ python <script.py>

第一个脚本是di-distance_cm.py。它以固定的时间速率读取数据:

# import the modules
from di_sensors.easy_distance_sensor import EasyDistanceSensor
from time import sleep

# instantiate the distance object
my_sensor = EasyDistanceSensor()

# and read the sensor iteratively
while True:
  read_distance = my_sensor.read()
  print("distance from object: {} cm".format(read_distance))

  sleep(0.1)

发布间隔为 0.1 秒,如sleep(0.1)行中指定。距离传感器 API 的详细说明可在di-sensors.readthedocs.io/en/master/api-basic.html#easydistancesensor找到。my_sensor.read()方法提供厘米距离,但如果你更喜欢使用其他单位,还有另外两种方法:

  • my_sensor.read_mm()用于毫米。

  • my_sensor.read_inch()用于英寸。

第二个脚本di-distance-easygopigo_mm.py按照github.com/DexterInd/GoPiGo3导入 GoPiGo3 库,这隐式地包含了传感器库github.com/DexterInd/DI_Sensors。你可以看到它使用相同的类方法来读取数据。在这种情况下,使用的是读取毫米距离的函数:

# import the GoPiGo3 drivers
import time
import easygopigo3 as easy

# This example shows how to read values from the Distance Sensor

# Create an instance of the GoPiGo3 class.
# GPG will be the GoPiGo3 object.
gpg = easy.EasyGoPiGo3()

# Create an instance of the Distance Sensor class.
# I2C1 and I2C2 are just labels used for identifyng the port on the GoPiGo3 board.
# But technically, I2C1 and I2C2 are the same thing, so we don't have to pass any port to the constructor.
my_distance_sensor = gpg.init_distance_sensor()

while True:
    # Directly print the values of the sensor.
 print("Distance Sensor Reading (mm): " + str(my_distance_sensor.read_mm()))

为了完整性,我们已将这两个库包含在本章的文件夹中:

  • easysensors.py用于传感器

  • easygopigo3.py用于机器人

如果你检查后者,你将在文件开头看到这条import行:

import easysensors
...
try:
 from di_sensors import easy_distance_sensor

这样,我们可以将所需的 DI 传感器集成到你的机器人中,以增加其感知能力。让我们通过一个例子来更好地理解 Dexter Industries 提供的库。

GoPiGo3 API 库

机器人的主要类是GoPiGo3,你可以在以下图中看到类结构。easysensors库被EasyGoPiGo3类(继承)导入,因此可以访问所有传感器方法。类结构在以下图中展示:

图片

图片来源:Dexter Industries:https://gopigo3.readthedocs.io/en/master/_images/inheritance-e4cb3d2ae1367b2d98aab1a112a1c8e1b7cd9e47.png

一个包含该类所有功能的总结表格可在gopigo3.readthedocs.io/en/master/api-basic/structure.html#functions-short-list找到。API 库的详细内容可在gopigo3.readthedocs.io/en/master/api-basic/easygopigo3.html找到。

因此,使用easygopigo3.py库,你可以创建你的机器人实例并初始化所需的传感器。例如,距离传感器通过以下类方法进行初始化:

easygopigo3.EasyGoPiGo3.init_distance_sensor([port])

在我们的脚本中,这是通过三行实现的:

import easygopigo3 as easy
gpg = easy.EasyGoPiGo3()
my_distance_sensor = gpg.init_distance_sensor()

在第一行,您导入easygopigo3库。在第二行,您实例化一个机器人对象,在第三行,您初始化距离传感器。然后,您就可以从传感器获取数据了:

my_distance_sensor.read_mm()

简而言之,如果您使用easygopigo3.py库,顶层对象是机器人本身。另一方面,如果您在一个不涉及 GoPiGo 的自定义项目中使用传感器,顶层对象尚未创建,除非您已经有了相应的库。如果没有,您需要定义一个表示该实体(例如气象站)的类,并导入 DI 传感器库。这是下一小节的主题。

DI 传感器 API 库

每种传感器类型都有自己的类和方法。在./Chapter2_Unit_Tests/DI-distance/di-distance_cm.py脚本中,我们使用了 DI-sensors 库。类结构在以下屏幕截图中有显示,并在di-sensors.readthedocs.io/en/master/structure.html#library-structure中进行了说明:

图片

图片由 Dexter Industries 提供:https://di-sensors.readthedocs.io/en/master/_images/inheritance-a8243413ad98ddae26cdf121c775ad137c7f2e30.png

所有 DI 传感器的总结表格方法显示在di-sensors.readthedocs.io/en/master/structure.html#functions-short-list。您将找到每个传感器的两种使用方法:

  • 简单方法——以easy前缀开头——是为了快速使用,并提供顶层功能。您可以在前一个图例的右侧看到语法。

  • 没有带easy前缀的高级方法是供开发者使用的,并提供低级控制。

距离传感器由以下类管理:

di_sensors.easy_distance_sensor.EasyDistanceSensor([…])

在我们的脚本中,传感器初始化如下::

from di_sensors.easy_distance_sensor import EasyDistanceSensor
my_sensor = EasyDistanceSensor()

前面的行描述如下:

  • 在第一行,您导入距离传感器的类。请注意,您只需要从di_sensors.easy_distance_sensor导入EasyDistanceSensor类。

  • 在第二行,您使用Easy类实例化距离传感器对象。

然后,您就可以从传感器获取数据了:

read_distance = my_sensor.read()

与不使用easy选项的情况进行比较。语法类似,唯一的区别是去除了_easyEasy前缀:

from di_sensors.distance_sensor import DistanceSensor my_sensor = DistanceSensor()
read_distance = my_sensor.read()

同样的方案将适用于我们稍后要介绍的其他传感器,特别是线跟踪器和 IMU。

伺服包

伺服包由一个脉冲宽度调制PWM)伺服电机组成。它通过施加产生电机轴在 180°幅度范围内的成比例旋转的电压进行开环控制。在下面的图像中有一个小杆,使我们能够可视化旋转。在我们的 GoPiGo3 组装中,我们将看到旋转距离传感器:

图片来源:Dexter Industries:https://www.dexterindustries.com/wp-content/uploads/2017/06/GoPiGo3-Servo-Assembly-11-600x338.jpg

伺服包是描述在 距离传感器单元测试 部分的 GoPiGo3 API 库的一部分。它的 API 方法在 gopigo3.readthedocs.io/en/master/api-basic/sensors.html#servo 中详细说明。

接下来,我们将执行一些单元测试来检查它是否能够正确旋转。由于我们已经将距离传感器与伺服包一起安装,我们可以通过旋转伺服来测量 180º 视场内的障碍物距离。在测试中,我们还将校准机器人能够覆盖的实际视场。

伺服包单元测试

测试包括将距离传感器相对于伺服电机轴放置在一个角度位置,这个位置覆盖了 GoPiGo3 的整个前方视图,从左到右。所有的解释和代码都在位于 ./Chapter2_Unit_Tests/DI-servo_package/servoCalibration.ipynb 的 Jupyter 笔记本中:

  1. 首先,我们从 EasyGoPiGo3 类创建伺服对象。为此,我们需要导入库并实例化 GoPiGo3
import easygopigo3 as easy
my_gpg3 = easy.EasyGoPiGo3()
  1. 然后,我们初始化伺服:
servo = my_gpg3.init_servo()
  1. 我们检查我们是否能够完全旋转伺服。你应该已经卸下传感器以防止与机器人底盘碰撞:
servo.rotate_servo(0)   # This is 0º position
servo.rotate_servo(180) # This is 180º position
  1. 然后,将伺服移动到区间的中间,90º,并将传感器安装好,使其朝向前方:
servo.rotate_servo(90)
  1. 调整角度,使传感器正好朝向前方。在我们的例子中,这个角度是 95°。你应该找出你自己的角度:
servo.rotate_servo(95)
  1. 一旦我们检查了参考位置,让我们设置实际限制。为此,找到防止机器人底盘干扰传感器的角度。在我们的例子中,这些角度是 30º 和 160º。同样,你应该找出你自己的角度:
servo.rotate_servo(30)
servo.rotate_servo(160)

这样,我们就完成了带有距离传感器的伺服包的设置。

对于高级用户,还有一个 API 库,gopigo3,它提供了对硬件的低级访问,以便您能够完全控制它。尽管本书的范围不包括低级编程,但您提供了一个 Python 脚本,Servo.py,简要说明了其方法的使用。此脚本以 计数 而不是 旋转角度 来设置旋转。我们执行一个循环,当计数从 1000 到 2001 时。

这样,您就可以访问伺服电机的全部分辨率,并且应该对它能够提供的旋转步长的大小有一个概念。

跟踪线

如第一章所述,跟踪线由六个发射器-接收器对组成,用于感应地板上的六个对齐点,以确定机器人相对于它将跟随的黑线的偏离程度。

线路跟随器也是 GoPiGo3 API 库的一部分。其方法在di-sensors.readthedocs.io/en/master/api-basic.html#easylinefollower中有详细说明。我们将进行的单元测试包括验证传感器能否告知机器人位于黑色线哪一侧。

线路跟随器单元测试

测试用的 Jupyter 笔记本是./CH2-uniTests/di-lineFollower/lineFollower_libraries.ipynb。这个笔记本还展示了简单的库,di_sensors.easy_line_follower,以及高级版本,di_sensors.line_follower,的实际应用。

当传感器报告中心时,这是因为机器人在黑色线路上很好地居中,如下一张图片所示。这意味着两个外部发射器-接收器对报告白色,而它们之间的对报告黑色

图片

当传感器报告左侧时,这意味着线路稍微在机器人左侧,如下一张照片所示。这意味着最左侧的对报告黑色,中间的对黑色,其余的对白色

图片

如果机器人完全偏离了线路,所有对都会报告白色,线路跟随器总体上也会报告白色。反之亦然:如果所有对都报告黑色,可能是因为黑色线条太宽或者机器人被放置在了一个暗色的表面上。

不惯性测量单元(IMU)

使用提供的惯性测量单元(IMU),我们有以下传感器:

  • 磁力计,三轴

  • 惯性测量单元(IMU),三轴

  • 加速度计,三轴

如前一章所述,有了这三个传感器——一旦完成校准设置——就可以获得机器人在 3D 空间中的绝对方向,用欧拉角来量化。此外,我们还有温度,因为 DI IMU 配备了温度传感器。

IMU 单元测试

测试用的 Jupyter 笔记本是./Chapter2_Unit_Tests/DI-IMU/IMU_reading.ipynb。相应的 DI 传感器 API 库在di-sensors.readthedocs.io/en/master/examples/imu.html上有文档说明。

在运行笔记本时,你可以检查报告的欧拉角是否正确。

Raspberry Pi

Pi 是任何机器人不可或缺的感知设备。考虑到大约 80%的人类大脑处理的感觉数据来自视觉。在本节中,我们只将测试 Pi 是否能够拍照以检查其是否正常工作。在第十章,在机器人学中应用机器学习,我们将使用相机捕获的图像上的对象识别算法。因此,机器人将表现出更智能的行为,能够识别颜色、形状、面孔等等。

Pi 单元测试

测试的 Jupyter 笔记本位于 ./Chapter2_Unit_Tests/PiCamera/Taking_Photos.ipynb。这个简单的例子拍摄照片并创建直方图,即显示图像中每种颜色的数量和每种颜色的数量的图表。

因此,让我们拍摄一张颜色较少的照片,以便更容易理解直方图的信息。以下是用 GoPiGo3 拍摄的照片,具有我们需要的特征:

图片

这可以通过以下代码片段实现:

  1. 首先,我们拍照。然后将其转换为 JPG 图像格式并保存,最后在屏幕上显示结果:
with picamera.PiCamera() as camera:
 camera.resolution = (640, 480)
 camera.capture(output, format = 'rgb', use_video_port = True)

img = Image.fromarray(output)
img.save("../photo.jpg")

plt.imshow(output)
  1. 最后,我们用这个简单的命令绘制直方图:
img = Image.open("../photo.jpg")
histogram = img.histogram()
plt.plot(histogram)

这是结果:

图片

你可能会看到颜色集中在三个点上。x 轴的刻度从 0(黑色)到 768(白色)。这个区间是通过考虑 24 位 RGB 像素值可以高达 768 来解释的。让我们看看每根垂直线代表什么:

  • x = 0 的线上表示的是黑色区域。

  • x = 525 的线上表示的是更闪亮的区域,即灯泡。

  • 最后,很容易推断出 x = 250 对应的是显示器周围的较亮区域,看起来有灰色调。

通过这次最后的测试,我们验证了我们的机器人所有的传感器和驱动器都工作正常,我们准备进入下一步,构建功能行为。

GoPiGo3 项目

在 Github 上托管的官方 GoPiGo3 软件提供了几个有趣的项目。github.com/DexterInd/GoPiGo3。在这个阶段,我们建议你克隆仓库并将其中的一些项目上传到运行 DexterOS 的机器人。项目位于仓库的这个文件夹中:github.com/DexterInd/GoPiGo3/tree/master/Projects

你可以尝试,例如,位于 github.com/DexterInd/GoPiGo3/tree/master/Projects/BasicRobotControl基本机器人控制 项目。你拥有使用连接到机器人的无线键盘的按键来完全控制驱动器、LED 和闪烁灯的方法:

[key w ] : Move the GoPiGo3 forward
[key s ] : Move the GoPiGo3 backward
[key a ] : Turn the GoPiGo3 to the left
[key d ] : Turn the GoPiGo3 to the right
[key <SPACE> ] : Stop the GoPiGo3 from moving
[key <F1> ] : Drive forward for 10 centimeters
[key <F2> ] : Drive forward for 10 inches
[key <F3> ] : Drive forward for 360 degrees (aka 1 wheel rotation)
[key 1 ] : Turn ON/OFF left blinker of the GoPiGo3
[key 2 ] : Turn ON/OFF right blinker of the GoPiGo3
[key 3 ] : Turn ON/OFF both blinkers of the GoPiGo3
[key 8 ] : Turn ON/OFF left eye of the GoPiGo3
[key 9 ] : Turn ON/OFF right eye of the GoPiGo3
[key 0 ] : Turn ON/OFF both eyes of the GoPiGo3
[key <INSERT>] : Change the eyes' color on the go
[key <ESC> ] : Exit

到目前为止,你可能认为我们用来将新文件放入机器人的方法很繁琐。我们使用了 DexterOS,这样你就可以快速开始,无需处理 Linux 问题。

从下一章开始,我们将在你的笔记本电脑上使用 Ubuntu 16.04 和 GoPiGo3 内部的完整 Linux 桌面,Ubuntu 18.04。这将使机器人能够连接到互联网,并允许你直接将仓库克隆到机器人中。

摘要

在本章中,我们通过运行允许我们测试机器人传感器和执行器的简单程序,熟悉了 GoPiGo3 的 Python 环境。我们按照正式的单元测试方法逐一检查了它们。这实现了两个目标:开始 Python 编程和功能验证您的机器人硬件。

接下来,我们将把机器人放到下一章,我们将讨论 ROS 的核心软件概念,这是我们稍后用来编程 GoPiGo3 的。

问题

  1. 如果你有这个 Python 命令序列,GoPiGo3 会做什么?
my_gopigo.drive_cm(10)
my_gopigo.turn_degrees(90)

A) 它将向前行驶 10 厘米,然后向右转 90 度

B) 它将向前行驶 10 厘米,然后向左转 90 度

C) 它将转 90 度

  1. GoPiGo3 正常工作所需的电池电量是多少?

A) 略高于 9V 就足够了。

B) 5V,与 Raspberry Pi 所需的电压相同。

C) 没有最小值。如果电压低,机器人将缓慢行驶。

  1. 哪组命令将使 GoPiGo3 绘制直径为 60 厘米的直角圆弧?

A) gpg.orbit(90, 60) B) gpg.orbit(90, 30) C) gpg.orbit(180, 30)

  1. 线跟踪器的六个传感器信号中哪一种组合不对应于 GoPiGo3 位于黑色线右侧?(w: 白色,b: 黑色)

A) b-b-b-b-w

B) w-b-b-b-w

C) b-b-b-w-w

  1. 如果有一个图像直方图,它由在x = 0 和x = 768 处的两条等高垂直线组成,且x的颜色范围是从 0 到 768,那么图像中存在哪些颜色?

A) 灰色,因为它是以相等比例混合黑色和白色所得的结果。

B) 图像的一半是黑色,另一半是白色。

C) 不可能得到这样的直方图。

进一步阅读

要深入了解 GoPiGo3 的技术细节,您可以在以下官方指南中找到非常详细的信息:

第三章:ROS 入门

机器人操作系统ROS)是一个开源软件。其开发始于 Willow Garage,一个技术孵化器和机器人研究实验室。其起源可以追溯到 2000 年中叶斯坦福大学的几个项目,当时研究人员每次都必须为每个项目构建软件时,都会发现自己是在重新发明轮子。

2007 年,Willow Garage 率先推出了 ROS。主要目标是重用现有代码,并使快速原型化新机器人设计成为可能,专注于高级功能并最大限度地减少编辑代码的需求。如果您对 ROS 如何成为机器人应用程序开发的事实上的标准感兴趣,可以查看一个交互式页面www.ros.org/history

ROS 旨在开发需要不同设备相互通信以创建灵活和可扩展环境的应用程序。本章将解释 ROS 的基本概念。它将使用通俗易懂的语言介绍框架,同时避免过于技术性的描述。这是因为我们的首要目标是干净利落地向你展示 ROS 在概念上的含义。在接下来的章节中,我们将有机会涵盖技术描述;这些描述在任何情况下你都需要,以便能够在项目中使用 ROS。

在本章的第一部分,我们将介绍 ROS 在底层是如何工作的,以及为什么它不应仅仅被视为一种特定用途的编程语言,而应被视为开发机器人应用程序的框架。您将深入了解 ROS 图的核心概念和节点之间的异步通信。

在第二部分,您将逐步引导配置笔记本电脑上的 ROS 环境。

在第三部分,通过一个实际练习的引导,你将学习如何使用命令行在节点之间交换简单的消息。

最后,我们将为您概述许多由开源社区贡献的可用 ROS 包。

在本章中,我们将涵盖以下主题:

  • ROS 基本概念和 ROS 图

  • 配置您的 ROS 开发环境

  • ROS 节点之间的通信:消息和主题

  • 使用 ROS 的公开可用包

技术要求

本章的实践方面要求您能够访问带有以下两种 Ubuntu 版本之一的台式计算机或笔记本电脑:

  • Ubuntu 16.04 Xenial

  • Ubuntu 18.04 Bionic

长期支持LTS)Ubuntu 版本由 Canonical 维护长达 5 年。此外,这类版本在偶数年发布。因此,2016 年发布的 Ubuntu 16.04 将会维护到 2021 年,而 2018 年发布的 Ubuntu 18.04 将会维护到 2023 年。我们不会考虑奇数年版本,即 Ubuntu 17 或 Ubuntu 19,因为它们是开发版本,不是 LTS 版本。

Open Robotics 每年都会发布一个新的 ROS 版本,与每个 Ubuntu 版本同步。对应关系如下:

  • 在 Ubuntu 16.04 Xenial 下运行的 ROS Kinetic

  • 在 Ubuntu 18.04 Bionic 下运行的 ROS Melodic

到本书写作时,最常用的版本是 ROS Kinetic。以下章节中提供的实际例子适用于 Ubuntu 16.04 和 Ubuntu 18.04。因此,它们也适用于 ROS Kinetic 和 ROS Melodic。

当我们不区分 Ubuntu 或 ROS 版本时,你应该假设它们适用于这两个版本。如果根据版本的不同,命令或要运行的脚本有所不同,我们将明确指出。

本章的代码可以在本书仓库的 Chapter3_ROS_basics 中找到,该仓库托管在 GitHub 上,网址为 github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter3_ROS_basics。在 设置 ROS 软件包 部分中,你将学习如何下载代码并将其与你的 ROS 安装一起使用。

ROS 基本概念

在早期,斯坦福大学机器人学的研究人员发现,为机器人原型设计软件是一项繁重的编程任务,因为他们必须为每个项目从头开始编码。曾经有一段时间,C++ 和 Python 等编程语言被用作机器人的一般编程语言,而这一事实要求人们付出巨大的努力来构建每一块软件,以提供机器人级别的功能,例如导航或操作。

这不仅是一个代码可重用性的问题,而且也是一个机器人工作方式的问题。在过程式编程中,程序的典型流程是按顺序执行一个步骤接着另一个步骤,如下面的图所示:

这个程序执行的任务是将多个图像合并成一个,这一点很容易推断。从我们的机器人角度来看,这种工作流程的主要缺点是,如果某些步骤未能执行,那么流程就无法继续,因此无法提供其结果,即合并后的图像。如果我们用机器人的类比,这意味着一个类人机器人如果其一只手臂(例如,肘关节的驱动电机损坏)不能正常工作,那么它可能无法完成其任务。类人机器人当然应该能够用另一只手臂完成任务。这正是 ROS 所做的!

让我们看看以下 ROS 图的例子。

ROS 图

这个图对于 ROS 来说,就像工作流程图对于过程式编程来说一样:

在前面的图中,每个圆圈——称为节点——代表一个独立的程序(这可以用 Python、Java、C++、Ruby、Node.js 或 ROS 已实现的任何其他编程语言编写)。它们之间的连接——称为边缘——代表信息的交换。箭头方向的意义是显而易见的:数据发射器是箭头开始的节点,接收节点是箭头指向的节点。出现在边缘上方的单词是 ROS 所说的主题,它们构成了节点之间交换信息的通道。当一个节点需要使用主题的信息时,它会通过订阅它来这样做,这个操作会在图中从数据提供者(此类主题的数据)到订阅节点添加一个新的箭头。

那么,从机器人的角度来看,这个图做了什么?记住,GoPiGo3 是一个经典的差速驱动机器人,其中每个轮子都可以独立转动,传感器——例如,距离传感器——在移动时为机器人提供有关障碍物距离的信息:

图片来源:Dexter Industries https://32414320wji53mwwch1u68ce-wpengine.netdna-ssl.com/wp-content/uploads/2017/05/GPG3_FullServoDistanceGoogly_1-800x800.jpg

因此,你可能已经猜到了,在前面的 ROS 图中,L_servo节点是控制左伺服电机的程序(通过旋转该轮子),而R_servo节点对右轮做同样的操作。

sensor节点是那个从距离传感器读取数据并通过连接到它的边缘(即使用/sharp_data主题)使它们对control节点可用的节点。

control节点中运行着决定如果 GoPiGo3 遇到障碍物时应该做什么的程序。例如,它可以旋转机器人直到找到一个没有障碍物的方向。旋转意味着control节点向L_servoR_Servo节点发送相反的命令。

这些信号是通过图中的相应边缘流动的:/speed_left主题或左舵机,以及/speed_right用于另一个。在 ROS 语言中,我们说control节点在/speed_left主题中发布左舵机的命令。

roscore

roscore是必须启动的第一个节点,以便 ROS 环境可以运行。这允许每个节点通过订阅它发布的主题来找到任何其他节点。roscore 还管理 ROS 参数的数据库——定义机器人配置的值。因此,如果 roscore 进程崩溃,主节点失败,机器人将停止工作。您可以猜测,这个单点故障对于像 ROS 这样的分布式计算框架来说是一个明显的限制。因此,ROS2 解决了这个问题,运行中的软件不再需要主节点。节点之间的通信依赖于DDS(数据分发服务)架构,适用于实时系统。

您能看出编写机器人程序与编写软件应用程序的不同之处吗?在机器人领域,您关注应用程序的顶层功能,并集成其他人编写的预制软件组件。

请注意,并非所有机器人领域的软件都遵循这种方法。实际上,在其众多用途中,我们选择 ROS 是因为其哲学与本书所采用的实践学习方法论非常契合。

这些软件组件,由代码块组成的节点群组,构成了我们所说的ROS 软件包。几个相关的节点构成了一个 ROS 软件包,提供特定的功能,例如,使用摄像头进行物体识别。

工作空间和 catkin

在 ROS 中,工作空间是一个用于构建和运行您软件的独立环境。您可以使用不同的工作空间来管理不同的项目。工作空间将包含您项目所需的全部 ROS 软件包。

在物理上,它是在您的家目录中的一个文件夹,其中包含您应用程序的所有特定文件,这样部署这个工作空间到另一个预装了 ROS 的机器上时可以正常工作,并且与原始计算机上的表现相同。

catkin 与工作空间的概念紧密相连,它是 ROS 的构建系统,结合了 CMake 宏和 Python 脚本,在 CMake 的正常工作流程之上提供功能。在此阶段,您只需知道这是一个工具,每次您在工作空间中包含新的软件包时都会用来构建软件。您可以在wiki.ros.org/catkin/conceptual_overview找到 catkin 的深入概念解释。

配置您的 ROS 开发环境

在本节中,我们将指导您如何安装和配置您在笔记本电脑上舒适地使用 ROS 所需的工具。简而言之,在开始本章的实践练习之前,您需要完成以下步骤:

  1. 请确保您的计算机运行Ubuntu 16.04Ubuntu 18.04。这两个都是 LTS 版本,在撰写本书时拥有最广泛的 ROS 软件包集合。

  2. 在您的笔记本电脑上安装和设置 ROS。您还将获得在 Raspberry Pi、GoPiGO3 机器人的 CPU 上安装 ROS 的指南。然而,在本书的第二部分,您只需要笔记本电脑,因为我们将会处理一个机器人的虚拟模型。物理机器人的包含留待本书的第三部分。

  3. 对于 集成开发环境IDE),我们将使用 RoboWare Studio,它基于微软的通用 IDE Visual Studio Code。我们将在下面的 安装 RoboWare Studio 部分提供设置说明。

因此,假设您的笔记本电脑具备所需的操作系统,我们现在继续进行配置的第二步。

安装 ROS

如果您使用的是 Ubuntu 16.04,您需要安装 ROS 的 Kinetic 发行版。另一方面,如果您有 Ubuntu 18.04,要安装的 ROS 版本称为 Melodic。请记住,这些选择在 技术要求 部分中已有详细解释。

在安装软件之前,请确保您有这两个基本的 curlgit 工具。如果不是这种情况,请运行以下命令来安装缺少的工具:

$ sudo apt update
$ sudo apt install curl git

ROS Kinetic 安装页面上的说明(wiki.ros.org/kinetic/Installation/Ubuntu)非常清晰且直接。它们适用于您的笔记本电脑(amd64/i386)和 Raspberry Pi(armhf 架构)。它们包含在此处以完成描述。

ROS Melodic 也有一个安装页面 wiki.ros.org/melodic/Installation/Ubuntu。在那里,您会发现命令与 Kinetic 相同,因为它们是以一种不依赖于所选 Ubuntu 版本的方式编写的。

按照以下步骤,您将在笔记本电脑上安装并运行 ROS:

  1. 首先,添加 ROS 的源仓库:
$ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

此命令将 ROS 仓库源添加到您的系统中。由于此类源依赖于 Ubuntu 版本,上面片段中的内部命令 $(lsb_release -sc) 输出的是版本,即 Ubuntu 16.04 的 xenial 和 Ubuntu 18.04 的 bionic

  1. 然后,设置您的密钥:
$ sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

或者,您可以使用 curl 而不是 apt-key 命令。如果您在代理服务器后面,这将很有用:

$ curl -sSL 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xC1CF6E31E6BADE8868B172B4F42ED6FBAB17C654' | sudo apt-key add -

如果您没有得到验证密钥,那么它可能因安全原因而更改(截至 2020 年 2 月,密钥仍然有效)。如果此刻不是这种情况,请访问官方安装页面 wiki.ros.org/melodic/Installation/Ubuntu,并找到需要替换密钥值 0xC1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 的部分。

  1. 更新您的源:
$ sudo apt update
  1. 如果你的笔记本电脑上安装了 Ubuntu 16.04,请安装完整的 ROS Kinetic 堆栈,包括名为 Gazebo 的模拟器、导航和机器人感知包(推荐):
$ sudo apt install ros-kinetic-desktop-full

如果你正在使用 Ubuntu 18.04,请执行 ROS Melodic 的安装:

$ sudo apt install ros-melodic-desktop-full

或者,你可以安装桌面版,它只包括 ROS GUI 工具(rqtrviz)。稍后,当需要时,你可以添加模拟(Gazebo)、导航和感知的包(记住,这些包在上述第 4 步中概述的完整版本中安装):

$ sudo apt install ros-kinetic-desktop

或者,如果你使用的是 Ubuntu 18.04,你可以使用以下方法:

$ sudo apt install ros-melodic-desktop
  1. 初始化 rosdep。这是使你能够轻松安装源代码编译所需系统依赖的组件。它也是运行 ROS 中一些核心组件所必需的:
$ sudo rosdep init
$ rosdep update
  1. 为你的交互式 shell 会话设置 ROS 环境:
$ source /opt/ros/kinetic/setup.bash

或者,如果你使用的是 Ubuntu 18.04,请使用以下方法:

$ source /opt/ros/melodic/setup.bash

为了避免每次打开新终端时都需要运行此命令,请使用以下命令将其包含在 .bashrc 文件的末尾:

$ echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
$ source ~/.bashrc

上面的代码片段中的第二个命令执行了 .bashrc 文件,刷新你的自定义设置。如果在 Ubuntu 18.04 中,只需将 kinetic 替换为 melodic

$ echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc $ source ~/.bashrc
  1. 最后,安装 rosinstall,这是一个命令行工具,它使你能够轻松下载许多 ROS 包的源代码树:
$ sudo apt install python-rosinstall python-rosinstall-generator python-wstool build-essential

请记住,随着你对机器人中的 Raspberry Pi 和你的笔记本电脑之间的通信越来越熟悉,你可以从你的电脑上执行所有桌面交互,让 Raspberry Pi 只执行机器人特定的任务。这种方法将使 GoPiGo3 更加响应,因为你将在 Raspberry Pi 上有一个最小的 Ubuntu 服务器和 ROS 基础版本,它不包括 GUI 工具,只包含核心包以及构建和通信库。

你可以参考下一节,了解如何为机器人准备 ROS 环境的具体细节。

Ubuntu 和 Raspberry Pi 上的 ROS

由于你将在 Raspberry Pi 上仅使用核心 ROS 包,因此建议你安装最新的 Ubuntu LTS 版本,即 Ubuntu Mate 18.04 Bionic (ubuntu-mate.org/download/)。这是因为,尽管几乎所有贡献的 ROS 包都适用于 ROS Kinetic,但同样真实的是,核心包已经在 ROS Melodic 2018 版本中可用。因此,在 Ubuntu 18.04 下在 Raspberry Pi 上安装此版本是安全的。

如前所述,ROS Melodic 安装页面 (wiki.ros.org/melodic/Installation/Ubuntu) 上的说明非常清晰和直接。

按照以下指南决定安装哪个版本:

  • 如果安装 ROS 桌面版(对于初学者和涵盖本书内容来说推荐使用),请使用以下命令:
$ sudo apt-get install ros-melodic-desktop
  • 如果您希望安装基础版本,即 ROS Base,从而从您的 Raspberry Pi 获得更好的性能,那么请使用此命令(这仅推荐给高级用户;目前,没有桌面 GUI):
$ sudo apt install ros-melodic-ros-base

如本节开头所述,对于本章,您只需要一台笔记本电脑。物理机器人留到本书的第三部分,从 第六章 开始,即 ROS 编程 – 命令和工具。在该章中,我们将提供正确设置 Raspberry Pi 上软件的必要细节。

集成开发环境 (IDE)

在 ROS wiki 专门介绍 集成开发环境 的部分 (wiki.ros.org/IDEs) 中,描述了目前可用的 ROS IDE——在撰写本书时,共有 15 个 IDE。在所有这些选项中,我们选择使用 RoboWare Studio,原因如下:

  • 它是微软的通用和可定制的 IDE Visual Studio CodeVSC)的一个分支,在开发者社区中被广泛使用。它是开源的、轻量级的、易于使用,并提供大量贡献的插件,使得您可以定制 IDE 环境以满足自己的特定需求。RoboWare Studio 是基于 VSC 代码构建的,以提供 ROS 开发功能。此外,IDE 的插件面板已定制,以便您可以轻松按需安装 ROS 软件包。当前版本是 1.2.0,于 2018 年 6 月发布。其代码是开源的,并在 GitHub 上公开提供,地址为 TonyRobotics/RoboWare-Studio (github.com/TonyRobotics/RoboWare-Studio)。

  • RoboWare Studio 的即开即用功能让您可以开始使用 ROS 的所有主要特性:工作空间、软件包、节点、消息/服务/动作等。

在本书的所有解释中,我们都会为您提供在 bash 中执行的命令,因为这是指挥 ROS 的原生方式。

RoboWare Studio 等集成开发环境在至少两种场景下可能会有所帮助:

  • 当您刚开始使用 ROS 时,如果您不太熟悉命令行,可以避免处理其复杂性

  • 在开发项目时,IDE 可以帮助管理与您的应用程序组成的 ROS 软件包一起散布的大量文件。这些软件包提供诸如坐标变换、计算机视觉、机器人导航等功能。

在任何情况下,我们的建议是使用 RoboWare Studio 完成这本书的第一轮学习,然后在第二轮学习中直接进入 bash。如果你想跳过其中一个,那么就放弃 IDE 选项,并继续使用命令行学习。练习 bash 命令是真正理解 ROS 的工作方式的最佳方法,你当然知道这不仅仅适用于 ROS,也适用于任何在 Linux OS 下运行的软件工具。

因此,从现在开始,使用你选择的 IDE 编辑文件,并且始终使用 ROS 命令行(或者如果你更喜欢使用 GUI 界面,则使用 RoboWare)。

安装 RoboWare Studio

假设你已经在你的计算机上安装了 ROS,请在终端中执行以下命令块以安装所需的依赖项,如 RoboWare Studio 可用手册中所示,手册链接为 github.com/TonyRobotics/RoboWare/blob/master/Studio/RoboWare_Studio_Manual_1.2.0_EN.pdf

$ sudo apt-get update
$ sudo apt-get install build-essential python-pip pylint

然后,安装 clang-format-3.8 包:

$ sudo apt-get install clang-format-3.8

二进制文件托管在 GitHub 上,链接为 github.com/TonyRobotics/RoboWare/blob/master/Studio。你可以从以下链接下载适用于 Ubuntu OS AMD64 的 RoboWare Studio 最新版本:github.com/TonyRobotics/RoboWare/raw/master/Studio/roboware-studio_1.2.0-1524709819_amd64.deb。你还可以在 GitHub 上找到源代码,链接为 github.com/tonyrobotics/roboware-studio

安装相当简单;从你获取 .deb 文件的位置执行以下命令:

$ sudo dpkg -i roboware-studio_1.2.0-1524709819_amd64.deb

完成后,你可以通过点击 RoboWare Studio 图标来启动 IDE。

ROS 节点之间的通信 – 消息和主题

我们将通过逐步进行的方式实现让两个节点相互通信的目标。首先,你需要创建一个个人工作空间,然后从 github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming 拉取书籍仓库,并进入 Chapter3_ROS_basics 文件夹,自行完成练习。

创建工作空间

按照以下步骤从命令行创建工作空间:

  1. 首先,创建你稍后需要放置 ROS 包的文件夹:
$ mkdir -p ~/catkin_ws/src

以下是对文件夹的描述:

  • catkin_ws 将成为你工作空间的根位置。

  • src 是你放置代码的地方,即在 ROS 包内部。

注意,**~** 等同于主目录,即 /home/bronquillo

  1. 移动到最后一个文件夹,并执行以下命令以初始化工作空间:
$ cd ~/catkin_ws/src
$ catkin_init_workspace

最后一条命令将生成 ~/catkin_ws/src/CMakeLists.txt 文件,该文件包含工作空间的定义和配置。该文件实际上是一个指向 ROS 安装文件夹中定义工作空间配置位置的符号链接:

/opt/ros/melodic/share/catkin/cmake/toplevel.cmake
  1. 首次构建工作空间;此时文件夹为空无关紧要:
$ cd ~/catkin_ws
$ catkin_make

通过列出内容,你会看到两个新的文件夹:

    • build 包含我们工作空间的编译结果,并在创建用于执行 ROS 命令的包时提供所有代码。

    • devel 包含工作空间的配置,每次你打开终端时都会源生它(参考以下步骤)。

注意,编译必须在根文件夹 ~/catkin_ws 中完成,而工作空间初始化是在应用程序代码文件夹 ~/catkin_ws/src 中完成的。

  1. 要将工作空间添加到你的 ROS 环境中,你需要源生生成的 setup 文件:
$ source ~/catkin_ws/devel/setup.bash
  1. 为了避免每次打开新终端时都要运行此命令,将其包含在你的 .bashrc 文件中:
$ echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
$ source ~/.bashrc
  1. 然后,你应该在你的 .bashrc 文件中添加以下两行:
source /opt/ros/kinetic/setup.bash
source ~/catkin_ws/devel/setup.bash

记住,第一行负责 ROS 系统配置,第二行负责你的工作空间设置。

创建工作空间并使用 RoboWare 构建

以下说明允许你使用 RoboWare IDE 创建工作空间,避免使用命令行:

  1. 启动 RoboWare 并点击“新建工作空间...”项:

截图

  1. 在弹出的窗口中,指定工作空间名称并选择你想要放置它的文件夹:

截图

设置文件夹名称后,RoboWare 会为用户透明地执行 catkin_init_workspace,你将在 IDE 窗口的左侧看到一个新的文件和一个新的文件夹。它们是 src 文件夹和其中的文件 CMakeLists.txt,该文件包含你的工作空间定义和配置:

截图

现在,你只需要注意到该文件有一个指向 ROS 安装文件夹 /opt/ros/kinetic 中的 ROS 系统文件的符号链接。此文件夹包含所有可能创建的工作空间的通用配置。

你可以在 RoboWare 中打开一个终端,并使用 ls -la 命令从特定的 ~/catkin/src 位置列出所有文件:

截图

这样的终端可以通过顶部菜单栏访问,通过点击“视图”项,然后从下拉菜单中选择“集成终端 (Ctrl + `)”:

  1. 选择构建模式并将其设置为调试,如下面的截图所示:

截图

  1. 然后,从顶部菜单栏中选择 ROS | 构建:

截图

你将在屏幕底部的输出窗口中看到以下日志:

图片

如果成功,最后几行应该看起来像这样:

图片

接下来,让我们设置 ROS 软件包。

设置 ROS 软件包

按照以下步骤设置 ROS 软件包:

  1. 从终端克隆书籍仓库 github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming 到您的家目录:
$ cd ~
$ git clone https://github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming 
  1. 我们将把本章的代码复制到 ROS 工作区。这样,您将有一个更干净的 ROS 环境:
$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter3_ROS_basics ~/catkin_ws/src

不要在 src 文件夹名称后添加反斜杠,\n。如果您这样做,文件将直接复制到 src 文件夹,而不是在 src/Chapter3_ROS_basics 之下。

  1. Chapter3_ROS_basics 文件夹中,您可以找到属于您将要使用的第一个 ROS 软件包的本章文件。其配置包含在 package.xml 文件中。请注意,软件包名称定义在 <name>ros_basics</name> 标签内。您可以在以下代码文件中找到它:
<?xml version="1.0"?>
<package format="2">
    <name>ros_basics</name>
    <version>0.0.0</version>
    <description>Code samples for "Chapter 3: Getting started with ROS"</description>
    <maintainer email="brjapon@therobotacademy.com">Bernardo R. Japon</maintainer
    <license>GNU v3.0</license>

    <buildtool_depend>catkin</buildtool_depend>
    <build_depend>rospy</build_depend>
    <build_export_depend>rospy</build_export_depend>
    <exec_depend>rospy</exec_depend>
</package>
  1. 然后,前往工作区根目录并再次构建:
$ cd ~/catkin_ws
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

通常,您至少需要在以下两种情况下重新构建工作区:

  • 每次您包含一个新的软件包

  • 如果您的代码包含用可编译语言(如 C++)编写的部分

在这本书中,我们将主要使用 Python,这是一种广泛使用的开源语言,它使得开始使用 ROS 更加容易。由于 Python 是一种解释型语言,因此您不需要每次修改软件包的代码库时都重新构建工作区。因此,只有在添加或删除 ROS 软件包时才需要重新构建。检查新添加的软件包 ros_basics 是否为 ROS 所知的方法是执行此简单命令:

$ rospack list | grep ros_basics

输出应该看起来像这样:

ros_basics /home/bronquillo/catkin_ws/src/book/Chapter3_ROS_basics

虽然在这里,我们将从一个预制的 ROS 软件包开始工作,但了解在此阶段如何从头开始创建自己的软件包是相关的。从您工作区 src 位置运行此命令($ cd ~/catkin_ws/src/):

$ catkin_create_pkg <YOUR-PACKAGE-NAME> <DEPENDENCIES>

<YOUR-PACKAGE-NAME> 代表您想要分配给软件包的名称。<DEPENDENCIES> 指的是您代码运行所需的 ROS 软件包列表。例如,如果您的软件包将包含 Python 和 C++ 代码,您将需要 rospy 用于前者,roscpp 用于后者。然后,命令将如下所示:

$ catkin_create_pkg <YOUR-PACKAGE-NAME> rospy roscpp

这将创建一个以软件包名称命名的文件夹和两个文件:

  • package.xml:如前所述的软件包配置

  • CMakelists.txt:CMake 构建系统的输入,用于构建软件包

CMakelists.txt 也包含对 <YOUR-PACKAGE-NAME> 的引用。在我们的例子中,此文件如下所示:

cmake_minimum_required(VERSION 2.8.3)
project(ros_basics)

find_package(catkin REQUIRED COMPONENTS rospy)

###################################
## catkin specific configuration ##
###################################
catkin_package()

###########
## Build ##
###########
include_directories()

使用 RoboWare 访问软件包文件并构建工作区

这里有一个替代方法。以下演示了您可以使用 RoboWare 克隆软件包仓库并构建工作区。

在按照设置 ROS 包部分中解释的方法克隆并放置章节代码后,你可以在 IDE 窗口左侧的文件树视图中探索内容。点击任何文件都会让你在主窗口中看到内容:

图片

最后,构建工作空间;请注意,每次你创建自己的新包或克隆外部包时,你都需要做这件事。为此,转到顶部菜单栏,选择 ROS,然后像以前一样点击构建。

发布主题的节点

对于接下来的步骤,由于我们需要处理多个同时终端,我们将使用一个非常方便的工具,即 Terminator,它允许你同时处理多个终端。运行以下命令在你的系统上安装它:

$ sudo apt-get update
$ sudo apt-get install terminator

启动 Terminator 并将屏幕分为四个终端(你可以通过鼠标右键连续分割窗口)。我们将它们分别称为T1T2T3T4,如下面的截图所示:

图片

在终端 1(左上角的窗口)中启动 roscore 节点:

T1 $ roscore

你应该能在T1中看到这个输出:

图片

这是 ROS 的根进程。roscore 启动一个节点并启动主服务,因为 ROS 是一个集中式系统。主节点始终是必需的,以便其他节点可以执行,并且必须在任何其他节点之前启动。

在下一个终端T2中,运行以下命令来启动发布者节点:

T2 $ rosrun ros_basics topic_publisher.py

这将启动topic_publisher节点,但没有任何事情发生!没错。发布者就是那样,一个发布者。我们需要一个监听器来了解正在发送什么数据。

前往终端 3 并列出当前声明的主题:

T3 $ rostopic list
/counter
/rosout
/rosout_agg

在列表中,/counter似乎是一个每 0.5 秒更新增量计数器的主题。其他两个主题/rosout/rosout_agg是 ROS 中的控制台日志报告机制。

topic_publisher.py文件中的这一行设置了/counter主题发布者:

pub = rospy.Publisher('counter', Int32, queue_size=10)

要查看发布的消息,请在终端中运行此命令:

T3 $ rostopic echo counter -n 5

这将输出将在/counter主题中发布的下五条消息:

data: 530
---
data: 531
---
data: 532
---
data: 533
---
data: 534
---

最后,我们将展示一个实时视图,它会打印发送消息的实时频率:

T3 $ rostopic hz counter

average rate: 2.000
min: 0.500s max: 0.501s std dev: 0.00020s window: 146
average rate: 2.000
min: 0.500s max: 0.501s std dev: 0.00020s window: 148
average rate: 2.000
min: 0.500s max: 0.501s std dev: 0.00020s window: 150
average rate: 2.000
min: 0.500s max: 0.501s std dev: 0.00020s window: 152
average rate: 2.000
min: 0.500s max: 0.501s std dev: 0.00020s window: 154
average rate: 2.000
min: 0.500s max: 0.501s std dev: 0.00020s window: 156
average rate: 2.000

Ctrl + C停止T3中的日志。请注意,如果你在前两个终端中的任何一个终端这样做,每个终端控制的进程都将终止,并产生以下后果:

  • 终端 T2:发布者进程将结束,并且不再通过/counter主题发送消息。

  • 终端 T1:在此终端中按下Ctrl + C将终止 roscore,这使人们明显看出,这个过程是 ROS 的单点故障,也就是说,所有相关进程(包括节点和消息)都将终止。

监听主题的节点

由于有一个节点在/counter主题上发布增量计数,我们现在将启动一个订阅此主题的节点。

首先,让我们通过在T3中发出以下命令来可视化当前的 ROS 图:

T3 $ rqt_graph

一个窗口会弹出并显示以下内容:

只有一个节点,即我们在T2中启动的那个。/counter主题没有出现,因为没有其他节点在监听它。为了使主题显示,请在T4中启动监听器:

T4 $ rosrun ros_basics topic_subscriber.py

你将在右下角的终端窗口中看到这个日志,除非你终止(按Ctrl + CT1T2中的任何一个,它们分别对应于roscoretopic_publisher

如果你回到 ROS 图窗口并点击更新图标,你会看到/topic_publisher节点,路由消息的/counter主题,以及监听节点/topic_subscriber

这个简单的例子说明了 ROS 中通信的发布/订阅架构。你已经可以欣赏到它是如何工作的,以及底层通信原理是多么简单,这是 ROS 框架的基础。我们现在将做一些实际的操作。

让我们的计数器表现得像一个计时器。我们该如何做呢?嗯,你只需要修改脚本topic_publisher.py,通过更改指定每秒向主题发送消息次数的参数,这个概念被称为发布的频率,其单位是赫兹(=次数/秒)。在脚本中,负责这种行为的 Python 对象是:

rate = rospy.Rate(2)

在脚本下面的循环中,通过将rospysleep方法应用于定义的rate对象来实现配置的行为:

rate.sleep()

记住,rospy是允许我们运行用 Python 编写的节点的 ROS 库。rospy定义了一组方法来在 ROS 中实现有用的功能,例如以这种方式设置发布频率。

上面的行表示每秒发布两条消息,即每 0.5 秒一条消息。因此,如果rate设置为 1 Hz,频率将是每秒一条消息,因此模拟了一个计时器。你可以在不需要停止 ROS 的情况下修改这样的脚本topic_publisher.py,并且一旦再次执行它,节点就会回到图中。让我们一步一步地展示如何做:

  1. 在终端T2中按Ctrl + C停止节点的执行。

  2. 修改文件以以 1 Hz 的速率发布,如之前所示。

  3. 然后,重新启动节点:

T2 $ rosrun ros_basics topic_publisher.py

你将在启动topic_subscriber.py的终端T4中看到效果。因此,新行出现的速度(即计数)将是之前的两倍,当速率是 2 Hz 时。这将产生每秒一次的计数更新(+1),对应于 1 Hz 的速率。

在同一节点中结合发布者和订阅者

一个节点能否像真实人类一样同时说话和监听?嗯,如果 ROS 是一个机器人框架,那么这是可能的。让我们来探索这个问题:

  1. 在前两个终端中启动这两个命令,每个命令都在一个独立的终端中:
T1 $ roscore
T2 $ rosrun ros_basics doubler.py

/doubler 是一个订阅 /number 主题的节点,如 doubler.py 脚本中的这两行所述:

rospy.init_node('doubler')
...
sub = rospy.Subscriber('number', Int32, callback)
...

此外,/doubler 还在 /doubled 主题中发布其结果:

...
pub = rospy.Publisher('doubled', Int32, queue_size=10)

你会注意到没有任何事情发生,因为它需要被喂以可以乘以 2 的数字,如 topic_subscriber.py 的回调中所示:

def callback(msg):
  doubled = Int32()
  doubled.data = msg.data * 2
  1. 启动一个用于监听 /doubled 主题的终端:
T3 $ rostopic echo doubled

然后,让我们手动发布一个 /number 主题:

T4 $ rostopic pub number std_msgs/Int32 2
 publishing and latching message. Press ctrl-C to terminate

T3 终端的输出是 4,正如预期的那样:

data: 4
---
  1. T4 中尝试发送其他数字,并检查双倍值是否立即在 T3 中显示。

如果你在一个第五个终端中发出 rqt_graph,你会看到类似这样的内容:

这里,你可以看到 /number/doubled 主题,/doubler 节点,以及两个具有大机器名称的其他节点,它们的对应关系如下:

    • 前一图中左侧的节点 /rostopic_5469_ 是由 T4 中的命令创建的:
T4 $ rostopic pub number std_msgs/Int32 2
    • 右侧的节点 /rostopic_5631_ 是由 T3 中的命令创建的:
T3 $ rostopic echo doubled
  1. 为了完成这个练习,我们将把数字喂给 /counter 主题,不是从命令行,而是从上一节中看到的发布节点脚本 topic_publisher.py 中:将 doubler.py 中的主题名称修改为 counter
sub = rospy.Subscriber('counter', Int32, callback)

然后,在停止所有终端后,在每个独立的终端中执行以下每一行:

T1 $ roscore
T2 $ rosrun ros_basics doubler.py
T3 $ rosrun ros_basics topic_publisher.py
T4 $ rostopic echo doubled

在这个终端中,每次发布 /counter 时,你将看到 counter * 2 的结果。看看 ROS 图(记得点击刷新按钮),你会发现它反映了正在发生的事情:

请记住,rqt_graph 在调试代码时将提供非常有用的信息,例如,检测主题名称中的任何错误。看看下面的图,其中乘以 2 不起作用,因为 /topic_publisher 订阅了 count 主题(注意末尾缺少 r 字符)。节点彼此断开连接,错误输入的主题(没有人监听)没有出现:

在下一节中,我们将概述 ROS 包系统的扩展。

使用 ROS 的公共包

ROS 贡献的包在官方网站 www.ros.org/browse/list.php 上索引。

截至 2018 年 7 月,ROS Indigo(为 Ubuntu 14.04 LTS 发布的发行版)有超过 2900 个包,而 ROS Kinetic(2016 年,Ubuntu 16.04 LTS)有超过 1600 个。其中一些下载量较大的包括以下内容:

  • rviz (wiki.ros.org/rviz): 这是 ROS 的 3D 可视化工具。您将在第四章,创建虚拟双轮 ROS 机器人中开始使用它。

  • gazebo_ros_pkgs (wiki.ros.org/gazebo_ros_pkgs): 这允许您在 ROS 内部使用 Gazebo 3D 模拟器。我们将在第五章,使用 Gazebo 模拟机器人行为中介绍 Gazebo。

  • sensor-msgs (wiki.ros.org/sensor_msgs): 这是一个定义常用传感器(如摄像头和扫描激光测距仪)消息的包。

  • tf2 (wiki.ros.org/tf2): 这是一个处理环境中所用到的多个参考系之间坐标变换的包。

  • laser-geometry (wiki.ros.org/laser_geometry): 这使得将 2D 激光扫描转换为点云以用于导航成为可能。

这应该能给您一个清晰的了解,ROS 编程对您意味着什么。换句话说,集成其他开发者(即包)开发的软件,并通过设计一个连贯的 ROS 图来使它们协同工作,以构建您希望机器人拥有的功能。

摘要

在本章中,我们介绍了 ROS,并通过简单的示例进行实践,以便帮助您理解 ROS 的架构。顶级实体是工作空间,它是一个用于构建和运行软件的独立环境。

工作空间由 ROS 包组成,即提供特定功能以集成到您的机器人中的预制的软件块。随后,catkin 是每次您在工作空间中包含新包时用于构建软件的工具。

节点是 ROS 中的基本实体,它包含使机器人工作的功能代码块。提供特定功能的节点集合构成了一个 ROS 包。roscore,运行主节点的进程,允许每个节点通过订阅发布的主题来找到其他节点。roscore还管理 ROS 参数的数据库。

节点之间的通信是通过主题实现的,主题是 ROS 图中的通道——信息流过的边缘。当一个节点需要使用主题的信息时,它通过订阅它来实现。

在下一章中,你将学习如何构建一个差速驱动机器人的虚拟模型,该模型模拟 GoPiGo3 的特性。本章还将为你提供方法,首先使用虚拟机器人测试你的代码,然后安装并在物理机器人上执行它。

问题

  1. ROS 环境中的顶级组件是什么?

A) 包

B) 工作空间

C) ROS 图

  1. roscore 进程的目的是什么?

A) 允许节点与其他节点通信并管理机器人参数

B) 为图中的所有其他节点提供主节点

C) 允许 ROS 节点从 LAN 外部被发现

  1. 标记正确的句子:一个节点可以 __ 。

A) 只发布主题或只订阅主题

B) 同时发布主题和订阅主题

C) 发布主题和/或订阅其他主题

  1. 如果一个节点未能执行其程序,会发生什么?

A) 机器人的一些功能将失败

B) 机器人将无法工作

C) 它发布的主题将被设置为未定义

  1. 标记错误的句子:在发布的主题中识别消息的方法是 __。

A) 启动一个发布此类主题数据的节点

B) 输入 $ rostopic echo </topic_name> 命令

C) 编写一个订阅该主题的节点

进一步阅读

要深入了解本章中解释的概念,你可以遵循以下链接和教程:

第二部分:使用 Gazebo 进行机器人仿真

本节是第一部分,物理机器人组装与测试的数字对应部分,在那里你处理了物理机器人。在本节中,你将构建 GoPiGo3 机器人的数字孪生,并在 Gazebo 模拟器中的虚拟环境中完成测试活动。

本节包括以下章节:

  • 第四章,创建虚拟两轮 ROS 机器人

  • 第五章,使用 Gazebo 模拟机器人的行为

第四章:创建虚拟两轮 ROS 机器人

RViz 是一个 3D 可视化工具,可以显示机器人模型。它提供了一个可配置的 图形用户界面GUI),允许用户显示他们可能请求的任何信息,以便执行当前任务。RViz 可用于机器人可视化和在构建 统一机器人描述格式URDF)模型时调试特定功能。此格式使用 XML 来模拟机器人。

为了说明如何使用 RViz 和 URDF,在本章中,你将构建一个简单的两轮机器人,它是 GoPiGo3 的数字孪生。你将创建描述机器人主要组件的 URDF 文件。此文件作为多个 ROS 工具可视化的输入 – 不仅包括 RViz,还包括 Gazebo 模拟工具,它还包含物理引擎。Gazebo 将在下一章中介绍,而本章中,你将集中精力熟悉 RViz。

本章将教你如何理解 URDF 文件的语法,并掌握在构建机器人模型时使用 RViz 系统性地测试/检查功能的技能。

在本章中,我们将介绍以下主题:

  • 开始使用 RViz 进行机器人可视化

  • 使用 URDF 构建 differential drive 机器人

  • 使用 RViz 检查 GoPiGo3 模型在 ROS 中的情况

  • URDF 模型中的机器人参考系

  • 使用 RViz 在构建过程中检查结果

技术要求

在上一章中,你被告知如何克隆本书的代码仓库,以便它在你的笔记本电脑的主文件夹中。如果你没有这样做,从你的笔记本电脑上的终端,像这样将仓库克隆到你的主文件夹中:

$ cd ~
$ git clone https://github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming 

接下来,只需将本章的代码复制到 ROS 工作空间中。这样,你将拥有一个更干净的 ROS 环境:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter4_RViz_basics ~/catkin_ws/src/

移动到新文件的路径并检查文件是否存在:

$ cd ~/catkin_ws/src/Chapter4_RViz_basics
$ ls -la

本章包含一个名为 rviz_basics 的新 ROS 软件包,因此请重新构建工作空间,以便它被你的 ROS 环境所识别。

$ cd ~/catkin_ws
$ catkin_make

通过选择软件包并列出其文件来检查软件包是否正确安装:

$ roscd rviz_basics
$ ls -la

现在,你已经准备好完成这一章了。

开始使用 RViz 进行机器人可视化

RViz 提供了一个可配置的 GUI,以便你可以显示有关机器人的特定信息。

为了确保 RViz 加载默认配置,请将 default.rviz 文件放置在 ~/.rviz/ 文件夹中。你将在该文件夹中找到 Chapter4_RViz_basics

你可以使用以下命令打开 RViz GUI:

T1 $ roscore
T2 $ rviz

T2 命令,rviz 是官方 $ rosrun rviz rviz 声明的缩写,其中第一个 rviz 指的是软件包,第二个 rviz 指的是同名节点。

目前,RViz 窗口将是空的,所以它只会显示地板级别的网格。在下一节中,我们将教你如何构建机器人模型并准备可视化它。一旦启动,你将看到一个类似于以下截图的窗口:

图片

如果你仔细查看前面的截图,你会在左侧面板看到一个错误消息。这是因为还没有加载机器人模型。我们将在下一节中开发这个模型。

使用 URDF 构建差速驱动机器人

GoPiGo3 套件由四个子组件组成:

  • 底盘,这是所有部件都连接到的主要结构。这包括以下内容:

    • Raspberry Pi 和 GoPiGo3 板

    • 电机

    • 电池包

    • 轮子

    • 万向轮

  • 两个轮子——左轮和右轮,每个轮子由一个电机驱动。

  • Caster(万向轮),它是一个连接在底盘后部的小型自由轮,使机器人在三个点上保持支撑:左右轮子和万向轮本身。请注意,一个自由轮是使机器人能够在地面上滚动所需的最小条件:

    • 如果没有万向轮,系统将会欠约束。那么,你将得到一个需要由其电机连续驱动以保持平衡的自平衡机器人。这是一个闭环控制问题,需要从其加速度计和陀螺仪获取的惯性测量单元IMU)数据来驱动电机并保持机器人平衡:

图片

图片来源:Dexter Industries https://shop.dexterindustries.com/media/catalog/product/cache/4/thumbnail/1800x2400/9df78eab33525d08d6e5fb8d27136e95/b/a/balancebot_remote2-150x150_1_1.jpg

    • 如果有两个万向轮,系统将会过约束。机器人将在四个点上得到支撑——两个轮子和两个万向轮——第二个万向轮的位置将由另一个万向轮和两个轮子确定。如果四个轮子/万向轮中的任何一个没有与地面接触,你将得到一个跛脚的机器人。

从模拟模型的角度来看,像 GoPiGo3 这样的差速驱动机器人由三个部分组成,每个部分都是一个刚体。因此,我们将机器人分为移动部分:

  • 机器人本体,包括底盘和所有连接到它上的固定部件(Raspberry Pi、GoPigo3 板、电机和电池包)

  • 左轮和右轮

  • Caster

回到 ROS,你将使用 URDF 构建一个模拟的 GoPiGo3。这是一个表示组件级别的机器人模型的 XML 格式描述。ROS 包含一个 URDF 包(wiki.ros.org/urdf),以便在模拟目的下接受这种机器人描述格式。

在接下来的章节中,我们将展示如何使用 URDF 描述 GoPiGo3 的四个移动部分。

GoPiGo3 的 URDF 概述

首先,我们将为您提供一个构建模型的概述,然后我们将一步一步地引导您通过这个过程。我们的 URDF 模型在 RViz 中的渲染方式如下:

图片

此模型对应以下 URDF 描述:

图片

XML 标签的内容已经折叠——这可以从行号右侧的加号推断出来——以显示每个块对应于机器人的一个部分:

  • 最高级标签是<robot>,它标识了整个机器人。

  • <link>标签指的是每个移动部分,通过name属性进行标识。所有内容都指定了该部分的特征:

    • name="base_link"指的是机器人身体,在这种情况下,底盘及其附件:树莓派、GoPiGo3 板、电机和电池包。

    • name="caster"指的是万向节自由轮,但它位于<visual>子标签内,这意味着它是机器人身体的一部分,而不是一个独立的移动部分。尽管它是一个滚动元件,但请记住,模拟模型试图用简单的描述来捕捉其现实世界的特性。由于万向节仅仅是一个支撑,它不需要由电机驱动。因此,我们可以将其固定在机器人身体上,并且只处理三个移动部分(机器人身体、右轮和左轮)而不是四个。如果你在担心它可能产生的摩擦,那么请不要担心——稍后,我们将学习如何设置零值以确保它像自由轮一样运行。《`标签指的是机器人一部分的刚体表示,而无需将其定义为单独的连接。

    • name="right_wheel"指的是右轮。

    • name="left_wheel"指的是左轮。

  • <joint>标签代表两个部分之间的连接。从机械角度来看,这些关节标签对应于车轮安装的轴承。每个车轮连接都有一个与之相关联。

接下来,我们将详细解释在这个模型中使用的每个<link><joint>标签的内容。

URDF 机器人身体

<link>元素,如 URDF XML 规范中定义的(wiki.ros.org/urdf/XML/link),定义了一个具有惯性、视觉特征和碰撞属性的刚体。在本章中,我们将介绍<visual>。我们将把<inertia><collision>留到本书的后面部分,因为这些属性仅在执行 Gazebo(见第五章,使用 Gazebo 模拟机器人行为)的物理模拟时才需要。

<visual>标签描述了部件的视觉外观。不要将其与<collision>标签混淆,因为后者定义了用于干涉或碰撞计算的体积。通常,两者可能定义不同的体积,尽管它们在大多数情况下是一致的。

为什么它们应该不同?对于复杂形状,干涉计算在 CPU 负载和时间长度上可能会很重。因此,在<collision>标签中使用简单形状会更好,将它们定义为实际形状的包络。例如,对于以下屏幕截图中的机械臂,你可以将手臂的<visual>元素定义为实际形状——即显示的形状——并将<collision>元素简化为手臂的包络圆柱,以方便干涉计算:

<origin>标签指定了<visual>元素相对于链接参考框架的参考框架。

<geometry>标签描述了视觉形状(盒子、圆柱、球体或网格)及其尺寸。

<material>标签使用<color><texture>设置<visual>元素的外观。

给定这些标签描述,我们可以在以下代码片段中轻松地了解base_link元素,即机器人身体:

<?xml version='1.0'?>
<robot name="gopigo3">

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0" />
      <geometry>
          <box size="0.5 0.5 0.25"/>
      </geometry>
      <material name="blue">
        <color rgba="0 0.5 1 1"/>
      </material>
    </visual>

    <!-- Caster -->
    <visual name="caster">
      <origin xyz="0.2 0 -0.125" rpy="0 0 0" />
      <geometry>
        <sphere radius="0.05" />
      </geometry>
    </visual>

  </link>
...

因此,base_link元素由以下内容组成:

  • 一个长度为 0.5 米、高度为 0.25 米的盒子。

  • 它的参考框架位于盒子的几何中心,与链接的参考框架相同(所有三个线性轴和三个旋转轴的值均为零)。

  • <material>标签指定蓝色颜色为 RGBA 索引:红色=0,绿色=0.5,蓝色=1。第四个,A=1,是 alpha 通道,它表示不透明度。值为 1 表示不透明物体,而值为 0 表示它是透明的。

在 RViz 中渲染链接提供了以下简单的盒子方面:

但等等——那个附着在底部面的半球形形状是什么?那是驱动器,是我们可以将它建模为机器人身体的一部分的自由轮,如前所述。

驱动器

驱动器描述嵌套在<link name="base_link">元素中。这意味着它是一个固定在机器人身体上的刚性部分。让我们来看看它是什么:

  • 它是一个半径为 0.05 米的球体,位于 x=0.2 米,z=-0.125 米。请注意,Z坐标是盒子高度的一半(=0.25 米)且为负值。这意味着上半球体嵌入在盒子内,而下半球体仅保持在盒子的底部部分可见。

  • 默认情况下,选择的颜色与为盒子定义的颜色相同。

仔细检查以下代码,以确保你理解它:

    <!-- Caster -->
 <visual name="caster">
 <origin xyz="0.2 0 -0.125" rpy="0 0 0" />
 <geometry>
 <sphere radius="0.05" />
 </geometry>
 </visual>

这就是驱动器在 RViz 中的外观,使盒子半透明:

注意 XYZ 轴的位置和方向,并注意它们的方向。这个事实在匹配 IMU 的轴时尤其重要。下面的照片显示了如何在物理 GoPiGo3 上放置这样的传感器,以确保 IMU 轴与 base_link 框架平行,并且具有相同的方向(请参见传感器表面打印的标记轴组):

最后,在下面的照片中,你可以看到整个机器人。这将帮助你确保你知道前一张照片中细节的位置:

接下来,让我们看看 URDF 模型的左右轮子。

URDF 模型的左右轮子

既然你已经了解了基本的 URDF 标签,那么阅读右轮的定义就很简单了,如下代码片段所示:

  <!-- Right Wheel -->
  <link name="right_wheel">
    <visual>
      <origin xyz="0 0 0" rpy="1.570795 0 0" />
      <geometry>
          <cylinder length="0.1" radius="0.2" />
      </geometry>
      <material name="black">
        <color rgba="0.05 0.05 0.05 1"/>
      </material>
    </visual>
  </link>

  <joint name="joint_right_wheel" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel"/>
    <origin xyz="0 -0.30 0" rpy="0 0 0" /> 
    <axis xyz="0 1 0" />
  </joint>

<origin> 标签内部,rpy 属性的第一个分量(绕 X 轴的旋转),1.570795 = π/2,是设置轮子为垂直位置的关键。圆柱形轮子的半径为 0.2 m,长度为 0.1 m。

这里新增的元素是 <joint> 标签 (wiki.ros.org/urdf/XML/joint),它用于指定关节的动力学和运动学以及其安全限制:

  • type="continuous" 表示一个铰链关节,它围绕轴旋转且没有上下限。

  • 父链接和子链接标识了哪些链接通过这个关节连接。

  • 原点指定了相对于父链接的子链接在 XYZ 方向上的偏移以及三个旋转。然后,<origin xyz="0 -0.30 0" rpy="0 0 0" /> 将关节放置在 Y = -0.30 m 的位置。这些坐标是相对于父链接的框架:

    • axis 定义了相对于父框架的关节旋转轴。这里,<axis xyz="0 1 0" /> 表示旋转轴是 Y(Y 坐标中的值 1)。

左轮的 XML 描述几乎与右轮相同。唯一的区别是关节在 Y = 0.30 m 的位置(<origin xyz="0 0.30 0" ... />),与右轮的符号相反,即 <origin xyz="0 -0.30 0" ... />

  <!-- Left Wheel -->
  <link name="left_wheel">
    <visual>
      <origin xyz="0 0 0" rpy="1.570795 0 0" />
      <geometry>
          <cylinder length="0.1" radius="0.2" />
      </geometry>
      <material name="black"/>
    </visual>
  </link>

  <joint name="joint_left_wheel" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 0.30 0" rpy="0 0 0" /> 
    <axis xyz="0 1 0" />
  </joint>

在下一节中,你将学习如何在 RViz 中可视化 URDF 描述,RViz 是 ROS 的可视化工具。

使用 RViz 检查 ROS 中的 GoPiGo3 模型

现在,是时候开始使用 ROS 进行工作了!你将发现 roslaunch,这是 ROS 命令,允许我们一次性启动多个节点,避免了像上一章那样需要打开单独的终端的需求。

由于您已经克隆了本书的代码仓库,我们将处理的文件位于仓库的 Chapter4_RViz_basics 文件夹中,并且它们都是 rviz_basics ROS 软件包的一部分,如 package.xml 中定义的那样。本章的文件结构可以在以下 RoboWare Studio IDE 截图中看到:

您可以在终端中使用 tree bash 命令获取此树状结构:

$ tree ~/catkin_ws/src/book/Chapter4_RViz_basics

请记住,它默认不包含在 Ubuntu 中,您可能需要安装它:

$ sudo apt-get update
$ sudo apt-get install tree

这将产生以下输出:

├── CMakeLists.txt
├── launch
│   ├── gopigoMinimal_rviz.launch
│   └── gopigoMinimal_rviz_simple.launch
├── package.xml
├── README.md
├── rviz
│   ├── default.rviz
│   └── gopigoMinimal.rviz
└── urdf
 └── gopigoMinimal.urdf

此树状结构根据文件类型将文件组织到各种文件夹中:

  • ./launch 将具有 *.launch 扩展名的文件分组,即将在运行时环境中使用的不同机器人配置和软件包。每个启动文件对应于特定运行的设置。

  • ./rviz 存储特定 RViz 配置的文件,每个可能的配置一个文件。

  • ./urdf 包含我们之前描述的机器人模型的 XML URDF 文件。

确保此文件夹位于您的工区内,并使用 catkin 构建它,以便 ROS 能够识别您的新软件包:

$ cd ~/catkin_ws
$ catkin_make

在 第三章 的 ROS 入门 部分的 使用 Roboware – 创建工作空间并构建它 小节中,我们解释了如何在 Roboware IDE 中执行这些操作。如果您更喜欢使用桌面应用程序,请查看该章节和该小节。

最后,您可以从终端使用以下片段执行 roslaunch

$ roslaunch rviz_basics gopigoMinimal_rviz.launch model:=gopigoMinimal

RViz 窗口将启动,您将看到一个简化的 GoPiGo3 模型,如下所示:

您可以通过旋转鼠标并单击左键来检查模型,并通过使用鼠标滚轮或单击右键并向前(放大)或向后(缩小)移动鼠标来放大/缩小。

RViz 的左侧面板包含两个对我们感兴趣的项目。用鼠标单击展开以检查它们:

  • RobotModel:您可以使用刻度来可视化所需的内容。在“链接”下的项目允许我们访问 GoPiGo URDF 模型的各个部分:base_link(机器人本体)、left_wheelright_wheel

  • TF:它提供了访问多个坐标系框架的权限。在我们的案例中,GoPiGo3 的每个部分都有一个框架,每个关节也有一个框架。

接下来,我们将查看 roslaunch 命令。

理解 roslaunch 命令

让我们退一步,使用一个最小的启动文件来理解这些脚本的语言法:

$ roslaunch rviz_basics gopigoMinimal_rviz_simple.launch

正如我们在 rosrun 中所看到的,ROS 命令的结构非常相似:

  • 第一个是命令名称本身,roslaunch

  • 第二个是软件包名称,rviz_basics

  • 第三个是我们将要执行的脚本,gopigoMinimal_rviz_simple.launch

gopigoMinimal_rviz_simple.launch 文件的内容如下:

<launch>
   <!-- set these parameters on Parameter Server -->
   <param name="robot_description" textfile="$(find rviz_basics)/urdf/gopigoMinimal.urdf" />

   <!-- Start 3 nodes: joint_state_publisher, robot_state_publisher and rviz -->

   <!-- Send joint values -->
   <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher"/>

   <!-- Combine joint values to TF-->
   <node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher"/>

   <node name="rviz" pkg="rviz" type="rviz" args="-d $(find rviz_basics)/rviz/gopigoMinimal.rviz" required="true" />
</launch>

语法,以 XML 格式,应该对你来说很熟悉。在这个文件中,有三种类型的标签:

  • <launch> </launch>: 定义了属于 roslaunch 描述的行块。

  • <node />: 这是用来执行 ROS 节点的句子。它等同于我们在上一章中解释的 rosrun 命令。因此,与 <node /> 标签行等效的命令如下:

<node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher"/>

is equivalent to...

$ rosrun robot_joint_state_publisher state_publisher

你可以很容易地推断出 <node> 标签的 pkg 属性是包名,而属性类型指的是包含此节点代码的脚本。

  • <param /> 代表参数,是 ROS 中的一个新概念。它包含一个存储在 ROS 参数服务器中的值,你可以将其视为存储机器人特征的地方。一组参数定义了特定的机器人配置。如 ROS 官方文档(wiki.ros.org/Parameter%20Server)中所述,ROS 参数服务器如下:

"这是一个可以通过网络 API 访问的共享、多变量字典。节点使用此服务器在运行时存储和检索参数。由于它不是为高性能设计的,因此最好用于静态、非二进制数据,如配置参数。"

在我们的特定情况下,我们在启动文件中有以下声明:

 <param name="robot_description" textfile="$(find rviz_basics)/urdf/gopigoMinimal.urdf" />

robot_description 参数是存储 URDF 文件的路径。你将看到这样的路径在 $(find rviz_basics) 文件属性中包含一个环境变量。这是 ROS 提供的一个非常好的功能,这样你就不必提供绝对或相对路径。find 命令应用于 rviz_basics 包,并返回包的绝对路径,即 ~/catkin_ws/src/book/Chapter4_RViz_basics$ 符号表示与系统环境变量相同的值。

使用 Roboware 执行启动文件

使用 roslaunch **rviz_basics** gopigoMinimal_rviz_simple.launch 命令所做的工作,可以在 Roboware IDE 中通过将其放置在文件顶部,右键单击鼠标以显示上下文菜单,并选择第一个项目,即运行启动文件来完成:

在接下来的部分,我们将执行另一个启动文件,gopigoMinimal_rviz_simple.launch,它引入了更多高级功能。在此之前,请关闭任何打开的 RViz 窗口或在终端中按 Ctrl + C 以关闭正在运行的 ROS 进程。

从 RViz 控制 GoPiGo3 机器人的轮子

可以使用以下命令启动机器人的完整版本:

$ roslaunch rviz_basics gopigoMinimal_rviz.launch model:=gopigoMinimal

我们在这里所做的是从命令行提供一个参数,gopigoMinimal。如果你注意查看启动文件的内容,即 gopigoMinimal_rviz.launch,你会在文件开头找到一个带有 <arg /> 标签的新部分:

<launch>
    <!-- values passed by command line input -->
    <arg name="model" default="gopigoMinimal" />
    <arg name="gui" default="False" />

    <!-- set these parameters on Parameter Server -->
    <param name="robot_description" textfile="$(find rviz_basics)/urdf/$(arg model).urdf" />

    <!-- Start 3 nodes: joint_state_publisher, robot_state_publisher and rviz -->

    <!-- Send joint values -->
    <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher">
      <param name="/use_gui" value="$(arg gui)"/>
    </node>
    <!-- Combine joint values to TF-->
    <node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher"/>

    <node name="rviz" pkg="rviz" type="rviz" args="-d $(find rviz_basics)/rviz/$(arg model).rviz" required="true" />
    <!-- (required = "true") if rviz dies, entire roslaunch will be killed -->
</launch>

被标记为参数的值可以通过在 <filename>.launch 后简单添加参数名称、:= 符号和其值从命令行传递:

<arg name="model" default="gopigoMinimal" />

is invoked with...

model:=gopigoMinimal

<arg /> 标签中,你可以使用默认属性提供一个默认值。在我们这个特定情况下,我们不需要将参数值添加到 roslaunch 命令中,因为值是默认的。因此,结果是与你写下以下内容完全相同:

$ roslaunch rviz_basics gopigoMinimal_rviz.launch

在启动文件中,有一个可选的第二个参数,gui

<arg name="gui" default="False" />

它是一个布尔值,默认值是 False,也就是说,不会发生任何不同的事情。现在,假设你执行命令时指定它为 True

$ roslaunch rviz_basics gopigoMinimal_rviz.launch model:=gopigoMinimal gui:=True

如果你这样做,你将在 RViz 旁边看到一个额外的窗口。暴露的 GUI 允许你使用滑块独立旋转每个轮子:

图片

这个交互式功能是 joint_state_publisher 包的一部分 (wiki.ros.org/joint_state_publisher),其 joint_state_publisher 节点通过启动文件使用 gui:=True 参数调用。我们将在下一节中解释这个功能。

使用 joint_state_publisher 包

启动文件中允许我们使用 GUI 交互式旋转轮子的部分如下:

 <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher">
   <param name="/use_gui" value="$(arg gui)"/>
 </node>

joint_state_publisher 节点公开了 /use_gui 参数来决定是否应该显示 joint_state_publisher 窗口。如果设置为 True,则窗口处于活动状态。如果是这样,我们通过在启动文件中定义的 gui 参数将期望的值作为参数传递给节点:

<arg name="gui" default="False" />

记住 roslaunch 命令:

$ roslaunch rviz_basics gopigoMinimal_rviz.launch model:=gopigoMinimal gui:=True

gui 参数设置为 True。然后,joint_state_publisher/use_gui 参数值被设置为 gui 参数的值,正如在 gopigoMinimal_rviz.launch 文件中的 <param name="/use_gui" value="$(arg gui)"/> 标签所表达的那样。

joint_state_publisher 节点启动了一个小部件,允许你交互式地旋转每个轮子。最后,从终端运行 rqt_graph 来查看 ROS 图:

图片

/joint_states 主题是我们移动窗口中滑块时修改的主题。

URDF 模型中的机器人参考框架

理解如何放置你将用于机器人的不同参考框架非常重要。首先,你必须决定 <joint> 元素在空间中的位置。在我们的例子中,我们有两个:一个用于右轮,一个用于左轮。让我们先看看右轮的配置:

  <joint name="joint_right_wheel" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel"/>
    <origin xyz="0 -0.30 0" rpy="0 0 0" /> 
    <axis xyz="0 1 0" />
  </joint>

现在,让我们看看左轮的配置:

 <joint name="joint_left_wheel" type="continuous">
   <parent link="base_link"/>
   <child link="left_wheel"/>
   <origin xyz="0 0.30 0" rpy="0 0 0" />
   <axis xyz="0 1 0" />
 </joint>

在这里,你可以看到 <origin> 标签指定了位置:

  • 右关节原点沿 y 地面轴为 -0.30 m(绿色轴)。

  • 左关节原点沿 y 地面轴为 +0.30 m。

在这两种情况下,关于 rpy="0 0 0" 属性,没有旋转,并且两个坐标框架都与地面的坐标框架平行。我们知道这两个 <joint> 标签相对于地面,因为它们都有 base_link 作为父链接,而且你知道我们模型的第一个链接,base_link,是机器人整体位置和朝向的绝对参考。

通常,<origin> 是从父链接到子链接的变换。关节位于子链接的原点。<axis xyz="0 1 0"> 标签指定旋转轴。在这种情况下,它是 y 轴,因为它有 1 的值,而 xz 的值是 0

在下面的截图中,您可以看到的是 base_linkright_wheelleft_wheel 的相应框架。它们位于每个链接的 <visual> 标签内。在这三个案例中,在 URDF 文件中,您将看到它们遵循以下模式:

<link name="base_link">
   <visual>
     <origin xyz="0 0 0" rpy=".. .. .." />

xyz="0 0 0" 表示它们与关节参考框架重合:

图片

在轮子的例子中,我们有以下情况:

<origin xyz="0 0 0" rpy="1.570795 0 0" />

1.570795 = π/2 = 90° 是绕 x 轴(红色)的旋转。这确保了形成轮子的圆柱体是垂直朝向的。

使用 RViz 在构建模型时进行检查

考虑到我们关于 URDF 所介绍的所有概念,我们可以使用 RViz 工具在构建机器人模型时提供帮助。它可以提供的检查如下:

  • 机器人的总体尺寸必须与实际机器人的尺寸相匹配。当您部分部分地构建模型时,如果有关部件尺寸的任何错误,当您测量总体尺寸(长度、宽度和高度)时将出现错误。您可以通过使用 RViz 工具栏中的测量工具来检查这一点。

  • 您还可以通过视觉检查机器人部件之间可能存在的干扰,特别是那些相邻且相对于彼此移动的部件(相对于彼此)。

  • 参考坐标系的朝向。

  • 您可以通过在 RViz 中取消选中相应的链接或更改 <color> 标签来应用透明效果,从而可视化被其他部分隐藏的部分。

在下面的图中,您可以看到我们已从 RViz 中提取了俯视图,并使用透明度检查所有部件是否对齐以及它们之间的相对位置:

图片

这种透明度使我们能够检查滑轮的位置是否正好位于 base_link 的中间。此外,轮子不会与 base_link 发生干扰,它们的旋转轴是同轴的。

在 RViz 窗口中更改模型的外观

在 RViz 中控制模型的可视化效果,您可以通过显示窗口中的一些参数进行修改,如下面的截图所示:

图片

我们用灰色标记了本章中我们所做的基本更改:

  • Alpha:此参数控制整个模型的透明度级别。值1对应不透明的外观,而0是完全透明的,即不可见。每个链接也有一个 Alpha 参数来控制单个部分的透明度(在“链接”子树下展开以访问它)。

  • 显示名称:如果勾选,链接的名称将在屏幕上显示。

  • 显示轴:如果勾选,它将显示每个链接的参考框架。

  • 显示箭头:如果勾选,它将显示每个关节的方向。

  • 标记比例:默认值为1。减小其值,使屏幕上的文字更小。

要保存此组参数,请转到顶部菜单中的“文件”项并选择“另存为配置”。在此,您指定具有.rviz扩展名的文件名,您的自定义设置将保存在那里。

注意,在同一个“文件”菜单中,您还有“保存图像”选项,这将生成当前 RViz 窗口的截图。

有用的 ROS 检查工具

最后,您应该知道,如果您想进行一些检查,有两个有用的 ROS 工具:

  • check_urdf尝试解析 URDF 文件以验证运动链:
$ roscd rviz_basics
$ check_urdf ./urdf/gopigoMinimal.urdf

roscd命令将提示更改为您作为参数指示的 ROS 包的路径,即rviz_basics。输出如下,其中当前文件夹是请求的rviz_basics路径,即$符号之前,~/catkin_ws/src/CH4_RVIZ_BASICS

~/catkin_ws/src/CH4_RVIZ_BASICS$ check_urdf ./urdf/gopigoMinimal.urdf
robot name is: gopigoMinimal
---------- Successfully Parsed XML ---------------
root Link: base_link has 2 child(ren)
 child(1): left_wheel
 child(2): right_wheel

一切正常!

  • rqt_tf_tree允许我们在 GUI 环境中可视化tf信息,如下所示:
$ roslaunch rviz_basics gopigoMinimal_rviz.launch model:=gopigoMinimal 
$ rosrun rqt_tf_tree rqt_tf_tree

将打开一个窗口,显示此方面图:

前面的图表告诉您,base_link(机器人身体部分)和车轮连接良好。箭头代表关节元素:joint_right_wheeljoint_left_wheel

或者,您可以将相同的输出生成为一个 PDF 文件:

$ rosrun tf view_frames

然后,您可以打开创建的 PDF 文件,如下所示:

$ evince frames.pdf

如果您想与同事分享输出,这种方式做事情更方便。

摘要

在本章中,我们介绍了 ROS 的两个基本元素。一个是 URDF 格式,这是描述机器人虚拟模型的标准方式。另一个是 RViz,ROS 可视化工具,它允许您在构建模型时检查模型并检查最终结果。

通过查看 GoPiGo3,你已经了解了这些基本元素,在那里你创建了一个包含底盘、电机和轮子的简化模型。我们向你展示了如何通过访问joint_state_publisher节点的joint_states主题与 GUI 交互式地旋转轮子,该节点属于同名包。这个包提供了一个工具,用于为给定的 URDF 模型设置和发布关节状态值。在 GoPiGo3 的情况下,我们有两个关节:左轮和右轮。万向轮是第三个关节,但由于它是一个自由轮(不是由电机驱动),所以我们不需要在虚拟 GoPiGo3 中将其定义为这样的关节。将其刚性连接到机器人本体就足够了。

在 RViz 中,我们可以模拟机器人的运动学。在下一章中,我们将更进一步,模拟动力学。这将需要我们完成 URDF 模型,包括质量和惯性属性,并在轮子中指定滚动阻力,以再现作用在机器人上的所有力。我们将使用 Gazebo 来完成这项工作,这是一个与 ROS 集成的动力学模拟器。使用 Gazebo,我们还可以再现机器人的物理环境(障碍物、斜坡、墙壁等)。

问题

  1. URDF 模型的格式是什么?

A) 它是一个文本文件。

B) JSON。

C) XML。

  1. GoPiGo3 的 URDF 模型有多少个链接和关节?

A) 四个链接(机器人本体、万向轮、左轮和右轮)和两个关节

B) 三个链接(机器人本体、左轮和右轮)和两个关节

C) 三个链接(机器人本体、左轮和右轮)和三个关节

  1. 在 URDF 模型中,你可以使用哪个标签来指定链接的颜色?

A) <visual>

B) <geometry>

C) <material>

  1. 在 ROS 包中,是否必须按文件夹(SRC、URDF、RViz、launch)分组文件类型?

A) 不,唯一强制条件是将包放在~/catkin_ws/src/下。

B) 只建议创建一个干净的包结构。

C) 不,但如果你这样做,你必须在该package.xml配置文件中声明位置。

  1. 你是否总是需要在终端中运行roscore来启动 ROS 进程?

A) 是的,因为由roscore启动的主节点是保持图中节点间通信的东西。

B) 当使用roslaunch时,你可以隐式启动主节点。

C) 如果你已安装roscore包,则必须运行roscore进程。

进一步阅读

第五章:使用 Gazebo 模拟机器人行为

本章处理机器人的动态模拟,从概念上讲,这是检查机器人实际行为的一种更好的方法,而不是仅仅使用软件。当在模拟器中复制时,与尝试使用物理硬件实现相比,刚体力学(包括质量、惯性)、摩擦、阻尼、电机控制器、传感器检测特性、噪声信号以及模型中可以以合理精度保留的机器人和环境的各个方面都要便宜得多。

通过阅读本章,你将学习如何将你的机器人(URDF 文件)的数字定义插入到由物理引擎驱动的 Gazebo 模拟环境中,该引擎能够模拟真实行为。你还将通过检查和测试数字机器人来扩展你的训练,使其行为代表物理世界应该发生的情况。

要实现 ROS 与 Gazebo 的集成,一组名为 gazebo_ros_pkgs 的 ROS 软件包(wiki.ros.org/gazebo_ros_pkgs)提供了所需的包装器。这些软件包提供了用于在 Gazebo 中使用 ROS 消息、服务和可重新配置的 ROS 参数来模拟机器人的接口。

通过遵循引导路径,你将熟悉 Gazebo 的 ROS 模拟环境。具体来说,你将学习如何准备机器人的模型,以便使用 Gazebo 物理引擎模拟真实行为。最后,你将模拟 GoPiGo3 可以携带的最大重量,并将其与现实世界进行比较。

在本章中,我们将涵盖以下主题:

  • 开始使用 Gazebo 模拟器

  • 对机器人 URDF 进行修改

  • 验证 Gazebo 模型并查看 URDF

  • 移动你的模型

技术要求

本章的代码文件可以在以下位置找到:github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter5_Gazebo_basics

通过完成上一章,你应该已经将本书的代码仓库克隆到你的笔记本电脑的主文件夹中。如果你没有这样做,我们现在会讲解这个过程。在你的笔记本电脑的终端中,将仓库克隆到你的主文件夹,如下所示:

$ cd ~
$ git clone https://github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming 

接下来,我们将本章的代码复制到 ROS 工作空间。这样,你将拥有一个更干净的 ROS 环境:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter5_Gazebo_basics ~/catkin_ws/src

本章包含一个名为 gazebo_basics 的新 ROS 软件包,因此请重新构建工作空间,以便 ROS 环境能够识别:

$ cd ~/catkin_ws
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

通过选择该软件包并列出其文件来检查软件包是否正确安装:

$ roscd gazebo_basics
$ pwd
 ~/catkin_ws/src/Chapter5_Gazebo_basics

pwd 命令的输出显示了预期的位置。现在,你已准备好完成本章。

开始使用 Gazebo 模拟器

让我们快速浏览一下,以便你对实现 GoPiGo3 动态模拟时可以期待的内容有一个清晰的理解。Gazebo 是一个开源的 3D 机器人仿真器,包括一个 ODE 物理引擎和 OpenGL 渲染,并支持在机器人驱动中的闭环控制代码集成——即传感器模拟和执行器控制。在这个定义中有两个新概念。让我们分别解释一下:

  • Open Dynamics EngineODE),一个用 C/C++编写的物理引擎,包括两个主要组件:刚体动力学模拟和碰撞检测(www.ode.org/)。

  • Open Graphics LibraryOpenGL),这是一个跨语言和跨平台的 API,用于渲染 2D 和 3D 矢量图形。这个 API 通常用于与 GPU 交互,以实现硬件加速渲染。这是一个规范,它规定了 PC 的 GPU 如何通过渲染 2D 和 3D 矢量图形来在屏幕上显示图形。作为一个规范,它本质上具有跨平台性,每个制造商都可以使用它(GPU 驱动程序)实现不同的实现。这个问题的关键在于它提供的功能必须符合标准中指定的功能,这样我们才能说该驱动程序是 OpenGL 兼容的。

按照以下步骤开始使用 Gazebo:

  1. 通过启动预制的环境来测试 Gazebo 的安装:
$ roslaunch gazebo_ros empty_world.launch

gazebo_ros包是一个位于/opt/ros/kinetic/share/系统文件夹中的 ROS 包。它随ros-kinetic-desktop-full(或如果你在 Ubuntu 18.04 上,则为ros-melodic-desktop-full)的安装而来,这在第三章,ROS 入门中已有详细说明。

如果你不想运行完整的 ROS 安装,你可以单独安装包。对于 Gazebo,如果你使用的是 Ubuntu 16.04,安装命令是$ sudo apt-get install ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-ros-control;如果你使用的是 Ubuntu 18.04,安装命令是$ sudo apt-get install ros-melodic-gazebo-ros-pkgs ros-melodic-gazebo-ros-control

除了empty_world.launch之外,你还有其他可用的世界启动文件,其名称可以通过以下命令找到,该命令列出了gazebo_ros包的启动文件夹内的文件:

$ roscd gazebo_ros/launch && ls -la

&&符号在 bash 中常用以在同一行运行两个命令。它们的执行顺序与它们书写的顺序相同。输出如下:

-rw-r--r-- 1 root root 2013 Jan 23 16:58 elevator_world.launch
-rw-r--r-- 1 root root 2300 Jan 23 16:58 empty_world.launch
-rw-r--r-- 1 root root 637 Jan 23 16:58 mud_world.launch
-rw-r--r-- 1 root root 850 Jan 23 16:58 range_world.launch
-rw-r--r-- 1 root root 640 Jan 23 16:58 rubble_world.launch
-rw-r--r-- 1 root root 640 Jan 23 16:58 shapes_world.launch
-rw-r--r-- 1 root root 646 Jan 23 16:58 willowgarage_world.launch
  1. 启动mud_world.launch并耐心等待;由于它包含移动部件,渲染可能需要几秒钟:
$ roslaunch gazebo_ros mud_world.launch

下面的截图显示了前面命令的输出:

图片

使用鼠标,你可以移动和旋转世界以改变视角:

  • 当 Gazebo 窗口处于活动状态时,按住鼠标左键。移动鼠标会导致世界在屏幕上移动。

  • 按住鼠标中间的滚轮按钮,并移动鼠标来旋转世界。旋转点将是您第一次按下滚轮时鼠标指针所在的位置。

  • 按住鼠标的右键。通过前后移动鼠标,您可以分别实现放大和缩小。

要停止 Gazebo,您必须在执行命令的终端中按Ctrl + C。这个过程可能需要几秒钟。请注意,关闭 Gazebo 窗口不足以结束模拟过程。

一个更复杂的版本是willowgarage_world

$ roslaunch gazebo_ros willowgarage_world.launch

它看起来如下所示:

作为对 Gazebo 的简要了解,我们将识别用户 GUI 中的面板——如以下截图所示——您可以使用此命令进行复制:

$ roslaunch gazebo_basics gopigo_gazebo.launch

为了使这生效,您需要复制本章仓库中的文件,如我们在技术要求部分中解释的那样:

让我们详细看看前一张截图中的面板上可用的不同字段:

  • 环境工具栏:有一些图标,您可以使用它们在以下不同模式之间切换:选择、对象/机器人的平移、对象的旋转和缩放(限于简单形状)。还有一些图标,您可以使用它们创建简单形状、提供照明特性以及改变视角。

  • 世界面板:这为我们提供了访问所有环境元素的方式:场景、物理、模型和灯光。

  • 关节面板:这为我们提供了访问可以拾取模型的地点。第一个是/home/<username>/.gazebo/models,这是用户从主 Gazebo 仓库中选择的 Gazebo 模型的仓库。这个仓库是第二个选项,可在models.gazebosim.org找到。

  • 主窗口菜单栏:这提供了在基本文件、编辑、查看、窗口和帮助标题下的选项。

  • 模拟面板:位于环境显示的底部,这是一个方便的工具,用于运行模拟脚本并提供在录制或回放模拟时的实时信息。

现在我们已经了解了 Gazebo 模拟器的工作原理,让我们对机器人 URDF 进行一些修改。

修改机器人 URDF

如我们在上一章中解释的,URDF 代表统一机器人描述格式,具有符合 XML 语法的语法,用于模拟机器人的视觉属性。这个格式,符合其构思的范围,不模拟一些动态模拟所需的特性。更确切地说,它不能指定以下内容:

  • 机器人在世界中的姿态。

  • 关节环(平行连杆)。

  • 摩擦和其他属性。

  • 非机器人事物,如灯光、高度图等。

由于这些原因,一种名为 Simulation Description FormatSDF)的进化 XML 格式开始作为 Gazebo 机器人模拟器的一部分进行开发。SDF 允许我们描述机器人模拟器、可视化和控制环境中的对象。多年来,SDF 已经成为稳定、健壮且可扩展的格式,能够描述机器人的所有方面。

扩展 URDF 以生成 SDF 机器人定义

如果您从上一章中看到的 URDF 定义开始,为您的机器人获得 SDF 规范的方式相当直接。

总是随身携带 SDF 格式规范(sdformat.org/spec),因为它提供了一个交互式树,您可以导航到所有标签,从而了解每个标签的目的以及它如何与其他标签相关。开源代码仓库位于 bitbucket.org/osrf/sdformat

为了说明 SDF 扩展而不破坏 URDF 规范,以下是一些简单的指南,允许您将您的 URDF 模型转换为 Gazebo 准备好的 SDF 描述:

  • 最基本的适应是每个 <link> 元素内包含一个 <inertia> 元素。这个新元素的目标是包含所有机器人连杆的质量和转动惯量属性,这对于进行动态模拟至关重要。我们在这里列出的其余适应都是可选的。

  • 为每个 <link> 元素添加 <gazebo> 元素,可以提供将视觉颜色转换为 Gazebo 格式以及将 STL 文件转换为 DAE 文件以获得更好的纹理的功能。传感器插件放置在此标签内。

  • 为每个 <joint> 元素添加 <gazebo> 元素,允许我们指定阻尼、摩擦和弹簧刚度,并允许我们添加执行器控制插件。

  • <robot> 元素添加 <gazebo> 元素。

  • 如果机器人应该刚性连接到 world/base_link,请添加 <link name="world"/> 链接。

您可以通过遵循教程 使用 URDF 在 Gazebo 中gazebosim.org/tutorials?tut=ros_urdf)以及应用示例来了解更多关于这种转换的信息。

<gazebo> 标签设置了一些默认值,这些值将自动包含在您的 SDF 描述中。此标签允许我们识别在 SDF 格式中找不到但在 URDF 格式中存在的任何元素。如果没有使用 reference=" " 属性的 <gazebo> 标签,则假定描述内部指的是整个机器人模型。参考参数通常指的是特定的机器人连杆——即它定义了其材料。

碰撞和物理属性

碰撞 标签指定了物理引擎需要考虑的体积,以检测物体之间的干涉/间隙。由于 URDF 中的视觉标签仅用于可视化目的,因此在此计算中忽略。这意味着,通常,你可以将机器人的可见部分(更详细)与用于计算干涉的包络形状(更简单的形状)解耦。

部分的 惯性 标签指定了其质量和惯性张量(3 x 3),以及所有其组成部分(由于矩阵是对称的,只需要六个组成部分)。

urdf 文件夹下的 gopigo.urdf 文件中,你可以找到 base_link 和 caster 的 XML 块(记住后者已被建模为 base_link 的一部分,作为模型的可接受简化)。这个第一个片段对应于 base_link 本身,并指定了碰撞和质量属性:

<link name="base_link">
...    
    <!-- Base collision, mass and inertia --> <collision>
        <origin xyz="0 0 0" rpy="0 0 0" />
        <geometry>
            <box size="0.5 0.5 0.25"/>
        </geometry>
    </collision>

    <inertial>
      <mass value="5"/>
      <inertia ixx="0.13" ixy="0.0" ixz="0.0" iyy="0.21" iyz="0.0" izz="0.13"/>
    </inertial>

这包括框架原点和方向(<origin> 标签)、元素的几何形状(<geometry>)、质量(<mass>)和惯性张量(<inertia>)。第二部分建模了 caster 并使用 </link> 标签关闭块:

    <!-- Caster collision, mass and inertia -->
    <collision>
      <origin xyz="0.2 0 -0.125" rpy="0 0 0" />
      <geometry>
        <sphere radius="0.05" />
      </geometry>
    </collision>
    <inertial>
      <mass value="0.5"/>
 <inertia ixx="0.0001" ixy="0.0" ixz="0.0" iyy="0.0001" iyz="0.0" izz="0.0001"/>
    </inertial>
</link>

这些属性使用我们之前解释的相同标签。对于右轮,以下是对应的代码片段:

<!-- Right Wheel -->
<link name="right_wheel">
...
    <!-- Right Wheel collision, mass and inertia -->
    <collision>
      <origin xyz="0 0 0" rpy="1.570795 0 0" />
      <geometry>
          <cylinder length="0.1" radius="0.2" />
      </geometry>
    </collision>
    <inertial>
 <mass value="0.5"/>
 <inertia ixx="0.01" ixy="0.0" ixz="0.0" iyy="0.005" iyz="0.0" izz="0.005"/>
 </inertial>
</link>

对于左轮,其规格完全相同。

Gazebo 标签

如我们之前提到的,<gazebo> 标签用于指定在 Gazebo 的本地格式 SDF 中需要的 URDF 的附加元素。在 URDF 文件夹下的 gopigo.gazebo 文件中,你可以找到以下代码块,指定了每个连接件的材质:

  • 第一个是机器人本体(base_link)。此块指定了部件的颜色以及其初始姿态:
  <gazebo reference="base_link">
    <material>Gazebo/Blue</material>
    <pose>0 0 3 0 0 0</pose>
  </gazebo>
  • 接下来是轮子。这些块只需要指定颜色,因为姿态已在 base_link 中定义:
  <gazebo reference="right_wheel">
    <material>Gazebo/Black</material>
  </gazebo>
...
  <gazebo reference="left_wheel">
    <material>Gazebo/Black</material>
  </gazebo>

如果你计划重用此代码或分享它,建议你将依赖项添加到 package.xml 文件中的 gazebo_basics 包。以下语句应添加到此类文件的依赖项部分:

<exec_depend>gazebo_ros</exec_depend>

这样,在构建 ROS 工作空间时,会考虑所需的依赖项,从而提供一个允许在运行时调用时执行包的输出。

验证 Gazebo 模型并查看 URDF

一旦在 gopigo.urdf 文件中完成 SDF 定义,你应该进行一些检查以确保文件可以被 Gazebo 读取。为此,导航到模型所在的文件夹:

$ roscd gazebo_basics/urdf

roscd 命令是一个非常有用的 ROS 命令,它与 Linux 的 cd 命令等效,但指定了相对于给定包的路径。它也更易于使用,因为你只需要提供你想要移动到终端的包的名称。第一部分 gazebo_basics 获取包的绝对路径,以及你想要显示的文件夹或子文件夹路径的第二部分。这个 ROS 命令,以及其他有用的命令,将在下一章的 Shell 命令 子节中详细说明。

使用以下两个命令分别打印和检查模型:

$ gz sdf --print gopigo.gazebo
$ gz sdf --check gopigo.gazebo

第一条命令在终端窗口中打印 XML 文件,以便你可以检查它。第二条检查此类文件的语法。或者,你可以用单个命令完成这些操作,分别(不需要初始的 roscd):

$ gz sdf --print $(rospack find gazebo_basics)/urdf/gopigo.gazebo
$ gz sdf --check $(rospack find gazebo_basics)/urdf/gopigo.gazebo

在这种情况下,我们使用另一个 ROS 命令 rospack find 来写入模型的路径。

bash 中开括号前的 $ 符号告诉我们这个:返回 gazebo_basics 包的路径

在关闭括号之后,是包内的路径——即 /urdf——这是 gopigo.gazebo 所在的位置。

如果检查过程中一切顺利,你将获得一条成功消息:

Check complete

如果你故意移除标签的结尾 > 或完整的 <tag>,检查命令将抛出以下错误:

Error [parser.cc:293] Error parsing XML in file [~/catkin_ws/src/CH5_GAZEBO_BASICS/urdf/gopigo.gazebo]: Error reading end tag.
Error: SDF parsing the xml failed

如果你移除了开头的 <link> 和结尾的 </link>,你会得到以下错误:

Error [parser_urdf.cc:3474] Unable to call parseURDF on robot model
Error [parser.cc:310] parse as old deprecated model file failed.
Error: SDF parsing the xml failed

从文件中删除任何错误的语法,并确保它通过检查程序。当你准备好后,继续下一节,我们将看到模型的实际应用。

在 Gazebo 中启动 GoPiGo 模型

本章的单个启动文件可以在包的启动文件夹中找到,并命名为 gopigo_gazebo.launch。为了解释目的,我们将它的代码分为以下两个片段:

<launch>
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="world_name" value="$(find gazebo_basics)/worlds/gopigo.world"/>
    <arg name="paused" default="false"/>
    <arg name="use_sim_time" default="true"/>
    <arg name="gui" default="true"/>
    <arg name="headless" default="false"/>
    <arg name="debug" default="false"/>
  </include>

在这里,你可以看到两个新的标签,<include><arg>。前者允许我们包含来自其他 ROS 包的启动文件,而后者允许我们使用本地参数使启动文件可配置。《arg》标签将在 使用 标签解释可配置启动文件 部分中解释。

<include> 块调用外部文件并定义参数的默认值。如果我们记住在终端使用其等效命令时,其语法可以清楚地理解:

$ roslaunch gazebo_ros empty_world.launch

如你所猜,<include> 标签指定了属于 gazebo_ros ROS 包(随 ROS 安装提供,因此是一个系统包)的 empty_world.launch 文件。

关于文件路径,值得提一下 ROS 使用 find 关键字来通过抽象任何包在磁盘上的物理位置来使用的机制:

$(find gazebo_basics)

类似于 bash 中的$符号的作用,即访问环境变量的值,前面的代码片段为我们提供了一个消息,声明“返回 gazebo_basics 包的路径”。在括号关闭后,我们可以看到包内的路径——即/launch——这是empty_world.launch所在的位置。

你可以通过列出文件来探索其内容,就像平常一样:

$ roscd gazebo_ros/launch
$ cat empty_world.launch 

这里的可用世界(即粗体字母所指的行)是从 Gazebo 安装目录加载的——如果你在 Ubuntu 16.04 上,则是/usr/share/gazebo-7/worlds;如果你在 Ubuntu 18.04 上,则是/usr/share/gazebo-9/worlds。在我们的启动文件中,我们使用worlds/empty.world。在下面的代码中,它被加粗标记:

<launch>
  <!-- these are the arguments you can pass this launch file, for example paused:=true -->
  ...
  <arg name="physics" default="ode"/>
  <arg name="verbose" default="false"/>
  <arg name="world_name" default="worlds/empty.world"/>
  ...

  <!-- start gazebo server-->
  ...

  <!-- start gazebo client -->
  ...
</launch>

<include>标签后面跟着第二个代码片段,描述了要启动的 Gazebo 节点:

 ...
  <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" output="screen"
     args="-file $(find gazebo_basics)/urdf/gopigo.gazebo -urdf -model gopigo" />
</launch>

该节点使用gazebo_ros包中的spawn_model脚本来将 GoPiGo3 模型放入 Gazebo。那么为什么我们不在这里使用<include>标签呢?因为我们正在包含一个外部单个节点。我们保留<include>用于包含更多节点和配置选项的启动文件。

最后,发出roslaunch命令以启动模拟:

$ roslaunch gazebo_basics gopigo_gazebo.launch

这将产生以下输出(请耐心等待;根据你的显卡,3D 场景可能需要几秒钟才能在 Gazebo 窗口中启动):

图片

我们将通过解释之前跳过的<arg>标签来结束本节。

使用标签解释可配置的启动文件

参数是在启动文件的作用域中使用变量的方式。让我们以include标签内的world_name参数为例:

<include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="world_name" value="$(find gazebo_basics)/worlds/gopigo.world"/>

这个标签告诉我们使用哪个 Gazebo 世界——通过name属性引用——并指定可以找到世界文件的路径,即value="$(find gazebo_basics)/worlds/gopigo.world"。请注意,指定文件路径的方式与我们告诉它包含启动文件的方式完全相同。

<arg>标签的语法在官方文档中有详细的解释,请参阅wiki.ros.org/roslaunch/XML/arg。记住,在前一章的“从 RViz 控制 GoPiGo3 车轮”部分,我们解释了在提供默认值的情况下,如何指定roslaunch命令的参数值。

到目前为止,你已经准备好理解模型是如何模拟你机器人动力学了。

移动你的模型

一旦启动了模拟,你就可以使用 Gazebo 窗口中的交互图标。例如,你可以通过环境工具栏的旋转工具来玩,看看当你将圆锥从平衡点移动时重力是如何影响它的:

图片

此外,你可以访问关节面板(如果不可见,从 Gazebo 窗口的右侧拖动),从左侧面板中选择 GoPiGo 模型,然后在关节面板的力选项卡下,对左轮施加 1 N.m 的扭矩。你会看到机器人开始围绕没有外部力作用的右轮旋转:

图片

这些交互相当简单,到目前为止,它们应该让你对模拟如何支持你作为机器人工程师的工作有一个很好的了解。

调整 Gazebo 模型的指南

模拟的一般目标是尽可能以最小的努力重现现实,这种近似应该成为你设计机器人或评估现有机器人性能的具体目标。

数字模型是现实的抽象。你不需要在 Gazebo 模型中重现物理机器人的每一个细节。如果你尝试这样做,所需的工作量会非常高,以至于模拟的好处无法弥补这种努力。相反,你所做的是重现这些特性来验证机器人。你应该首先定义具体目标,然后构建最简单的模型,使我们能够满足这些目标。

让我们通过一个例子来理解这些概念。在本章中,你已经看到 GoPiGo3 模型只是一个盒子(机器人本体)、一个半球(转向器)和两个圆柱(左右轮子)。真正的 GoPiGo3 包含许多更多部件,包括螺栓、垫圈和螺母,正如你在第一章“组装机器人”中组装硬件时的经验一样。如果你试图在 URDF 中重现所有这些元素,我们确信你会因为这项艰巨的任务而感到沮丧。与其沿着这条死胡同走下去,不如问问自己你想要这个模型做什么。从现在开始,我们将把模拟模型称为数字孪生,这是我们用来指代物理机器人数字副本的技术名称。

这里是两个可能的目标:

  • 测量伺服电机能施加的实际最大扭矩

  • 确定 GoPiGo3 可以运输的最重物体的重量,作为斜坡坡度的函数

现在,让我们思考数字孪生应该具备的特点:

  • 由于我们正在尝试测量动态特性,我们只需要重现 GoPiGo3 的总质量和作用在轮子上的扭矩。如果机器人要沿着平滑路径移动,惯性矩不是必需的。例如,在旋转运动的情况下,需要考虑惯性张量来模拟当机器人围绕位置不动的位置旋转时的最大转向速度。这可以通过以最大速度旋转左右轮子并使用相反的符号来实现。

  • 我们将需要 <visual> 标记的元素来在屏幕上看到数字模型,但不需要 <collision> 元素,因为我们不需要包括障碍物。

只需这些特性,你将拥有最小的数字孪生。实现这些目标的过程如下:

  1. 在现实世界中,取一个你可以手动改变坡度的斜坡。然后,让 GoPiGo3 爬上斜坡,并确定它能处理的最大的坡度。你会达到一个点,此时机器人几乎静止不动,既不爬坡也不后退。让我们假设这个角度是 a

  2. 两个电机施加的力由以下公式给出 ,其中 m 是机器人的质量,g 是重力加速度(9.8 m/s²)。

  3. 每个电机为了产生这种牵引力而施加的扭矩由以下公式给出 ,其中 r 是车轮的半径,系数 2 表示我们使用两个电机(每个车轮一个)来产生这种力量。

  4. 一旦你确定了最大扭矩 T,你就可以进入仿真环境,将其应用于每个电机,并观察机器人在斜率为α < a 的斜坡上滚动。通过逐渐增加机器人身体的质量(集中在base_link URDF 元素中),你会找到使机器人停止向前移动的总重量。

请记住,如果斜坡的坡度为 a,车轮必须施加的力以使机器人爬坡将对应于最大电机扭矩,因此它将没有运输更多重量的能力。因此,你应该始终考虑一个低于 a 的斜率,α。

通过这样做,你将获得 GoPiGo3 在给定最大斜率α时可以携带的最大重量的良好估计。你可以通过修改 URDF 文件中base_link的质量值,非常容易地在 Gazebo 中引入这种修改,如下所示:

<link name="base_link">
...
    <inertial>
         <mass value="m+dm"/>
    </inertial>
...

在这里,m + dm 表示两个项的和:

  • m 是无载机器人的质量。

  • dm 是要运输的物体的质量。确保机器人不爬坡的 dm(千克)值将是斜率α的最大载重。对于斜率等于 a 的斜坡,我们有 dm = dm= 0*。

我们将在第七章[0653ab6b-8710-41e7-9c01-5024865e3e27.xhtml]的“机器人控制和仿真”部分中介绍如何在 Gazebo 中指定最大电机扭矩。在这里,你会看到有一个插件元素可以用来模拟移动机器人(如 GoPiGo3)所拥有的电机控制器。

通过对 URDF 文件进行这些修改,以及我们在 Gazebo 中对斜坡坡度等于 adm= 0* 的检查,你已经调整了数字孪生,使其能够模拟货运运输,并且具有 dm 千克的最大斜率α的运输能力。

摘要

A) 它用于引用具有相同名称的其他 ROS 节点

进一步阅读

按照与第四章中“创建虚拟两轮 ROS 机器人”相同的并行过程,我们在./urdf/gopigo.gazebo文件中创建了一个机器人描述。然后通过运行./launch/gopigo_gazebo.launch来启动模拟。

在下一章中,我们将探讨物理机器人并解释如何与之接口。你到目前为止在虚拟机器人上所做的工作将帮助你预测当你在其 CPU 上运行 ROS 程序时,实际的 GoPiGo3 会如何表现。

到现在为止,你应该已经开始学会如何使用 Gazebo 的物理引擎来模拟机器人现实行为的感觉了。这将为你提供一个强大且经济的工具,你可以用它来排查物理机器人和其数字孪生之间的差异。

C) 它允许你轻松地在 ROS 环境中找到任何文件

SDF 的格式是什么?

  1. 模拟器 Gazebo 教程(ROS 特定):wiki.ros.org/simulator_gazebo/Tutorials

首先,你了解了 SDF,这是由 Gazebo 驱动的机器人模拟的标准 XML 格式。SDF 扩展了 URDF,使我们能够描述机器人模拟器、可视化和控制中的对象和环境。

B) JSON

在本章中,你被提供了 Gazebo 界面 GUI 组织的概述,并对 GoPiGo3 模型进行了一些简单的交互,以了解它如何受到重力或其一个车轮关节上扭矩应用的影响。

  1. 为什么 URDF 格式不能直接用于机器人的模拟?

C) 在制造机器人之前检查机器人的外观

B) 它返回作为其参数指定的 ROS 包的绝对路径

A) 用于定义机器人必须避免的物理障碍

  1. <collision>标签是用来做什么的?

C) XML

B) 它是执行机器人关节干扰检查的可选标签

C) 为了定义干扰检查中要考虑的机器人链接的体积

  1. ROS 的find命令是用来做什么的?

Gazebo 中的机器人模拟是用来做什么的?

A) 因为 URDF 不能指定机器人在世界中的姿态

  1. Gazebo 教程:gazebosim.org/tutorials

A) 在购买机器人之前了解更多关于机器人的信息

B) 在将其应用于真实机器人之前开发功能

C) 以上所有

  • B) 因为它不能指定关节的动态属性,如刚度、阻尼和/或摩擦

  • 本章中,我们介绍了 ROS 的仿真环境 Gazebo,它是一个独立的模拟器,同时也提供了与 ROS 的完全集成。

  • 《ROS 机器人编程:由 TurtleBot3 开发者编写的手册》,YoonSeok Pyo,HanCheol Cho,RyuWoon Jung,TaeHoon Lim(2017),ROBOTIS Co. Ltd,第一版:www.pishrobot.com/wp-content/uploads/2018/02/ROS-robot-programming-book-by-turtlebo3-developers-EN.pdf,章节:ROS 工具:RViz 和 rqt以及 10.9 使用 Gazebo 的 TurtleBot3 仿真

第三部分:使用 SLAM 进行自主导航

本节纯粹关于 ROS 和机器人工程。你将获得技能以强制执行 ROS 应用程序以执行导航任务。你将了解到路径规划对于成功至关重要,以及这一功能是如何建立在通过同时定位与地图构建SLAM)技术生成的覆盖区域地图之上的。

本节包含以下章节:

  • 第六章,在 ROS 中进行编程 – 命令和工具

  • 第七章,机器人控制和仿真

  • 第八章,使用 Gazebo 进行虚拟 SLAM 和导航

  • 第九章,机器人导航中的 SLAM

第六章:在 ROS 中进行编程 - 命令和工具

本章重点介绍在 GoPiGo3 上运行 ROS。在前一章中,我们在远程笔记本电脑上做了同样的事情,在下一章中,我们将教你如何使机器人和你的笔记本电脑作为一个单一的 ROS 环境协同工作。

在本章中,你将最终学习如何使用 ROS,这对于后面章节中涉及到的机器人导航和机器学习的高级内容是必需的。ROS 由于以下因素在开始时可能难以使用:

  • 它是基于命令行的。

  • 它处理异步事件。

  • 它是一个分布式计算环境。

矛盾的是,这三个特性使得它对于机器人编程非常强大。你所投入的努力将值得,因为你很快就会发现的。

通过学习本章,你将熟悉 ROS 的命令行交互,并了解几种类型 ROS 命令的范围。你将习惯于使用 ROS 中最常用的通信模式:发布-订阅。为了在 ROS 运行时访问实时机器人数据,你将介绍rqt GUI 工具,这些工具简化了应用程序的开发和调试工作。此外,还将介绍 ROS 参数,以给你一个关于它们在高级别管理机器人配置的强大功能的概述。

本章我们将涵盖以下主题:

  • 设置物理机器人

  • ROS 编程的快速入门

  • 如何编写 ROS 包(案例研究 1

  • ROS 命令概述

  • 创建和运行发布者和订阅者节点

  • 使用 roslaunch 自动化节点执行

  • ROS GUI 开发工具(案例研究 2

  • 如何使用 ROS 参数

我们将基于两个实际案例研究对这些概念进行解释:

  • 案例研究 1:发布和读取距离传感器

  • 案例研究 2:从 Pi 相机获取并可视化图像

有一个第三案例研究涉及机器人控制和仿真——案例研究 3:机器人驱动(电机和编码器)——作为一个实际例子,它将支持在第七章,机器人控制和仿真中将要介绍的概念。

因此,我们有三个案例研究,并且由于本章和下一章的内容,我们将拥有 GoPiGo3 的第一个完整版本的 ROS 包。这将是本书剩余章节中构建智能机器人行为的基础。

技术要求

本章的代码文件可在以下链接找到:github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter6_ROS_programming

当您完成 Raspberry Pi 的设置,如设置物理机器人部分所述,克隆书库到您的家目录中:

$ cd ~
$ git clone https://github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming 

记住这个位置,以便检查您的工作,因为在本章中,我们的目的是让您自己编写所有代码。或者,如果您决定使用提供的代码,您只需像往常一样将Chapter6_ROS_programming文件夹复制到 ROS 工作空间即可:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter6_ROS_programming ~/catkin_ws/src

下一节将初始化机器人的 ROS 工作空间。

设置物理机器人

如第三章中提到的,ROS 入门,我们现在将开始使用物理机器人。因此,首先要做的是准备在 Raspberry Pi 上运行的软件。本节将逐步引导您完成这个过程。

下载和设置 Ubuntu Mate 18.04

Mate在撰写本文时,是推荐在 Raspberry Pi 下运行的 Ubuntu 桌面。它是一个完整的 Ubuntu 发行版,拥有一个漂亮的桌面界面。按照以下步骤在 GoPiGo3 上运行它:

  1. ubuntu-mate.org/download/下载图片,选择推荐的 Raspberry Pi 版本(AArch32 (ARMv7)):将镜像烧录到微型 SD 卡上。之后,将其放入 Raspberry Pi 的插槽中,连接鼠标和键盘,连接到 HDMI 屏幕,然后打开板子电源。

  2. 第一次启动时,设置助手将引导您完成配置操作系统的过程。在本书中,我们假设 Ubuntu 用户名为pi。如果您使用的是其他用户名,请相应地更改代码。

  3. 确保您通过从屏幕右上角的无线图标点击时出现的列表中选择网络来连接到本地 WiFi。强烈建议您添加第二个移动 WiFi 网络,最好是智能手机提供的那个。这样,在将来将机器人移出家中时,您就不需要连接到 HDMI 屏幕、键盘和鼠标。

以下说明是可选的,并被认为是访问 Raspberry Pi 的一个更友好的方式。

访问定制

为了更方便地访问您的系统,建议您允许 Ubuntu 用户无需密码即可访问sudo

  1. 在终端中输入以下命令:
$ sudo visudo
  1. 然后,在文件末尾添加此行:
pi ALL=(ALL) NOPASSWD: ALL
  1. 保存您的更改并退出,以便它们生效。

更新系统并安装基本工具

通过运行以下命令,首先您将更新系统中的 Ubuntu 仓库;之后,系统包将被升级:

$ sudo apt update && sudo apt upgrade -y

你需要一个git软件包来克隆代码库。如果系统中没有这个软件包,你可以使用以下命令安装它:

$ sudo apt install git

我们还将添加另一个有用的软件包tree,以便从文件系统中获取信息:

$ sudo apt install tree

tree软件包是一个实用程序,它允许你同时查看文件夹及其子文件夹的内容,并通过树状结构可视地表示它。

启用 SSH 访问

在 Ubuntu 预装中遇到 SSH 服务器问题是很常见的:

  1. 为了避免任何问题,请移除OpenSSH软件包并重新安装:
$ sudo apt remove openssh-server
$ sudo apt install openssh-server
  1. 启用服务以便在启动时启动:
$ sudo systemctl enable ssh
$ sudo systemctl start ssh
  1. 确认 SSH 服务器正在运行:
$ sudo systemctl status ssh

在这个阶段,你应该能够从这个主机登录到任何启用了 SSH 的 Ubuntu 服务器,例如你的笔记本电脑。

设置 VNC 服务器(x11vnc)

OpenSSH 允许我们通过终端远程登录到我们的树莓派。我们还将为我们的机器人配备另一种远程连接方式,即通过访问其桌面。为此,我们将使用x11vnc,这是一个实现远程桌面使用虚拟网络计算VNC)的软件包:

  1. 更新 Ubuntu 软件仓库并安装:
$ sudo apt-get update
$ sudo apt-get install x11vnc
  1. 现在创建一个密码以连接到客户端:
$ x11vnc -storepasswd
  1. 输入一个密码并记住它。假设当前用户是pi,密码将存储在/home/pi/.vnc中。然后,启动x11vnc服务器:
$ sudo x11vnc -auth guess -forever -loop -noxdamage -repeat \
 -rfbauth /home/pi/.vnc/passwd -rfbport 5900 -shared
  1. 如果你想要在不需要密码的情况下启动服务器,只需从命令中移除该选项:
$ sudo x11vnc -auth guess -forever -loop -noxdamage -repeat \
 -rfbport 5900 -shared

现在,你准备好使用 VNC 客户端(如 RealVNC)连接到客户端了。www.realvnc.com/download/viewer/

在启动时设置自动启动

我们希望x11vnc在重启或开机时自动启动。因此,在/lib/systemd/system/位置创建一个名为x11vnc.service的脚本,执行以下操作:

$ sudo nano /lib/systemd/system/x11vnc.service

在编辑时,添加以下行:

[Unit]
Description=Start x11vnc at startup.
After=multi-user.target

[Service]
Type=simple
# If  using a password
ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/pi/.vnc/passwd -rfbport 5900 -shared

[Install]
WantedBy=multi-user.target

然后,启用并启动新创建的服务:

$ sudo systemctl daemon-reload
$ sudo systemctl enable x11vnc.service
$ sudo systemctl start x11vnc.service

要从远程 PC 使用 RealVNC 查看器连接,请输入树莓派的 IP 地址后跟显示编号,即<IP 地址>:<显示编号>。如果没有指定显示,则默认为0。因此,0值将被分配给你启动的第一个服务器(即你的唯一服务器)。

由于你正在使用默认端口5900运行 VNC,因此你不需要为连接指定它。如果不是这种情况,你应该在连接字符串中指定自定义端口。

强制 HDMI 输出和屏幕布局

为了调试目的,确保你可以始终从屏幕访问 Ubuntu Mate 桌面是有用的,即使系统没有启动它。为了实现这一点,你必须修改树莓派的配置选项:

$ sudo nano /boot/config.txt

文件内容将在屏幕上显示。取消以下行的注释,设置显示的值:

hdmi_force_hotplug=1

hdmi_group=2   
hdmi_mode=47

最后两行强制执行以下显示:在 1440 x 900 分辨率下,60 Hz。如果您想将其增加到全高清分辨率(1080 像素),则可以根据刷新率这样做:

  • 对于 50 Hz 的显示器,使用以下设置:
hdmi_group=1
hdmi_mode=31
  • 对于 60 Hz 的显示器,使用以下设置:
hdmi_group=2
hdmi_mode=82

这些命令使获取 1080 像素变得容易。

Geany IDE

Ubuntu Mate 自带一个名为Pluma的轻量级编辑器。虽然它适合编辑,但不包括终端窗口或其他在典型开发环境中常见的特性。因此,我们将安装Geany,这是一个轻量级 IDE,适合在 Raspberry Pi 上运行,并支持常见的编程语言:

$ sudo apt-get update
$ sudo apt-get install -y geany

我们将主要使用 Python,因此 Geany 对我们来说非常合适。

安装 GoPiGo3 和 DI 传感器的驱动程序

现在,我们将准备系统以与 GoPiGo3 一起工作。Dexter Industries 提供了自动化脚本来完成所有安装任务:

相关步骤在官方文档中提供,请参阅dexterindustries.com/update_gopigo3。简而言之,您只需打开一个终端并依次执行以下两个命令:

$ curl -kL dexterindustries.com/update_gopigo3 | bash
$ curl -kL dexterindustries.com/update_sensors | bash

安装过程需要几分钟,所以请耐心等待。完成后,您将在桌面上看到以下新图标:

图片

这些附加实用程序如下:

  • 高级通信选项:此图标用于启用蓝牙和/或红外接收器。

  • 线跟踪校准:此图标用于调整传感器对当前光照条件的灵敏度。

  • gopigo3_control_panel:此图标用于通过屏幕上显示的简单面板来驱动机器人。

  • 测试和故障排除:此实用程序为您的机器人生成日志文件,可以发送给制造商,以便它提供技术支持。

例如,双击测试和故障排除图标。它将生成您的机器人的日志文件,该文件应如下所示:

GoPiGo3 Troubleshooting Script log

Checking for hardware, and checking hardware and firmware version.
==================================================================
Manufacturer : Dexter Industries
Board : GoPiGo3
Serial Number : F92DD433514E343732202020FF112535
Hardware version: 3.x.x
Firmware version: 1.0.0
Battery voltage : 9.414
5v voltage : 4.889

到目前为止,您的操作系统已准备好运行任何 GoPiGo3 代码,即我们在第二章,GoPiGo3 单元测试中使用的 Python 脚本。

设置 Pi 摄像头

设置分为步骤 1-2。首先,我们将从 Raspberry Pi 启用对摄像头硬件的访问,其次,我们将安装 Python 模块来处理摄像头:

  1. 首先,我们需要编辑/boot/config.txt文件:
$ sudo nano /boot/config.txt
  1. 然后,我们在末尾添加这两行:
start_x=1
gpu_mem=128
  1. 或者,您也可以通过在命令行中添加以下这些行来达到相同的结果:
$ sudo bash -c "echo 'start_x=1' >> /boot/config.txt"
$ sudo bash -c "echo 'gpu_mem=128' >> /boot/config.txt"
  1. 接下来,按照以下方式安装 Python 模块:
$ sudo pip install picamera
  1. 为了检查相机是否正常工作,创建这个 Python 脚本,并将其命名为 captureFile.py (你可以在本章代码的 piCamera 文件夹中找到它):
from time import sleep
from picamera import PiCamera

camera = PiCamera()
camera.resolution = (1024, 768)

camera.start_preview()

# Camera warm-up time
sleep(2)

camera.capture('test.jpg')
  1. 给 Python 模块执行权限并运行:
$ chmod +x captureFile.py
$ ./captureFile.py

相机将激活 2 秒。检查相机板上的红色 LED 是否亮着。这是它在获取图像的视觉信号。在此期间过后,LED 将关闭,你应该在脚本相同的路径下找到一个名为 test.jpg 的新文件。如果你打开它,你应该能看到相机在最后那 2 秒钟看到的景象;这是通过:camera.capture(test.jpg) 实现的。

准备好 Raspberry Pi 的最后一步是安装 ROS。

安装 ROS Melodic

ROS Melodic 安装页面 (wiki.ros.org/melodic/Installation/Ubuntu) 上的说明非常清晰和直接。我们在这里包括它们是为了完整性:

  1. 首先,添加 ROS 源仓库:
$ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
  1. 然后,设置你的密钥:
$ sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

如果你没有得到一个验证过的密钥,那么它可能已经被更改(出于安全原因)。如果是这种情况,那么请访问官方安装页面 (wiki.ros.org/melodic/Installation/Ubuntu),搜索该行,然后用新的一行替换它。

  1. 接下来,更新你的源:
$ sudo apt-get update
  1. 安装 ROS 的桌面版本,以便你可以利用 Mate 桌面环境。这将允许你使用 ROS 图形界面工具(如 rqt 或 RViz):
$ sudo apt-get install ros-melodic-desktop
  1. 初始化 rosdep。这是使你能够轻松安装源代码编译的系统依赖项的组件。它还要求你运行 ROS 的一些核心组件:
$ sudo rosdep init
$ rosdep update
  1. 为你的交互式 shell 会话设置 ROS 环境:
$ source /opt/ros/melodic/setup.bash

为了避免每次都必须运行此命令,你可以打开一个新的终端,并将其包含在你的 .bashrc 文件中:

$ echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
$ source ~/.bashrc

现在剩下的就是配置 Pi 相机。让我们接下来这么做。

安装 Pi Camera ROS 软件包

作为 ROS 安装的一部分,我们应该包括允许你从 ROS 访问 Pi 相机的软件。最常用的软件包来自 Ubiquity Robotics,它托管在 GitHub 上,地址为 github.com/UbiquityRobotics/raspicam_node

为了安装软件包,我们首先需要一个 ROS 工作空间,并且需要掌握一些关于克隆和创建 ROS 软件包的实用概念。这个安装将在本章稍后完成;你将在 案例研究 2 – ROS GUI 开发工具 – Pi 相机 中找到它,全局

因此,我们现在继续创建一个工作空间并在其中添加第一个软件包。

ROS 编程的快速介绍

本节致力于解释一个简单的 GoPiGo3 ROS 示例。通过这样做,我们可以快速让我们的机器人开始工作,以便在后面的章节中,我们可以以实用的方式处理 ROS 命令和工具,应用这些命令并理解它们的作用。

这个非常简单的例子是基于 GoPiGo3 的距离传感器。它包括发布传感器读数和从其他 ROS 节点访问它们。

设置工作空间

要开始使用贡献的 ROS 软件包或创建自己的软件包,你需要有一个工作空间来放置代码。完成此类任务的逐步过程如下:

  1. 从 bash 终端创建一个文件夹并初始化工作空间:
$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/src
$ catkin_init_workspace

初始化就像创建一个指向 ROS 安装文件夹中文件定义的符号链接一样简单。如果您列出src文件夹中的文件,您将看到指向/opt/ros/melodic/share/catkin/cmake/toplevel.cmake的新CMakeLists.txt文件:

$ ls -la

... CMakeLists.txt -> /opt/ros/melodic/share/catkin/cmake/toplevel.cmake
  1. 接下来,构建工作空间:
$ cd ~/catkin_ws
$ catkin_make
  1. 然后,将其添加到你的 ROS 环境中:
$ source ~/catkin_ws/devel/setup.bash
  1. 或者,您可以通过在.bashrc文件末尾包含此命令来自动执行此命令。为此,执行以下命令,然后运行文件,以便其内容在系统中生效:
$ echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
$ source ~/.bashrc

到目前为止,文件末尾应该有以下两行(第一行是 ROS 全局环境,第二行是您的私有工作空间):

source /opt/ros/kinetic/setup.bash
source ~/catkin_ws/devel/setup.bash

现在一切准备就绪,可以包含我们需要的 ROS 软件包。在下一节中,我们将添加其中两个:一个克隆现有仓库,另一个从头创建软件包。

克隆 ROS 软件包

对于克隆选项,我们将使用一个基本的 GoPiGo3 ROS 软件包,该软件包在 GitHub 上公开可用,地址为github.com/ros-gopigo/gopigo3_node。切换到src文件夹,这是我们放置所有 ROS 软件包的位置,然后克隆源代码:

$ cd ~/catkin_ws/src
$ git clone https://github.com/ros-gopigo/gopigo3_node

每次添加新的软件包时,你必须重新构建工作空间,以便 ROS 知道其存在并将其添加到执行环境。因此,运行以下命令:

$ cd ~/catkin_ws
$ catkin_make

现在,你会看到一系列表示进度的百分比和当前构建步骤正在做什么的行。如果一切正常,最后一行显示 100%完成并返回到命令行。这意味着你刚刚成功安装了gopigo3_node软件包。

我们第一次执行 ROS 节点

由于 ROS 将在树莓派上运行,因此你需要远程连接到它。为此,打开 GoPiGo 的 VNC 连接。然后,在其桌面上打开一个终端并安装 Terminator(我们在第三章,ROS 入门)中使用的相同实用程序,以便在同一窗口中拥有尽可能多的终端:

$ sudo apt-get update
$ sudo apt-get install terminator

移动到新添加的 ROS 软件包的位置,并列出其中的文件:

$ cd ~/catkin_ws/src/gopigo3_node/src
$ ls

您将看到几个用于控制 GoPiGo3 传感器和驱动器的 Python 文件。假设您已经按照设置物理机器人部分中的说明安装了 GoPiGo3 和 DI 传感器库,打开 Terminator 并将窗口分成至少三个终端。我们将执行 ROS 下的distance_sensor.py文件。为此,我们需要发出以下三个命令:

T1 $ roscore
T2 $ rosrun gopigo3_node distance_sensor.py
T3 $ rostopic echo /distance_sensor/distance

以下是全球每个命令执行的内容:

  • T1启动 roscore 进程。这对于所有后续的 ROS 通信过程都是必要的。

  • T2执行distance_sensor节点,该节点读取数据并将其发布到/distance_sensor/distance主题。

  • T3实时监听发布的数据,并在每次获取读取时打印一条新消息。

在下面的屏幕截图中,您可以查看每个终端显示的内容。每次读取传感器都会传递一个包含多个字段的消息。在后面的案例研究 1 – 编写 ROS 包 – 距离传感器部分,我们将解释这个消息是如何创建的。现在,您只需要知道红色方框内的range字段是传感器以米为单位的测量值:

图片

如果只想获取最后的测量值,只需运行以下命令,其中-n后面的数字表示您想要打印的消息数量,在我们的例子中是 1:

T4 $ rostopic echo /distance_sensor/distance -n 1

下一步将是创建您自己的 ROS 包。我们将编写的代码将创建与distance_sensor.py脚本相同的包,但使用EasyDistanceSensor类(来自di_sensors.easy_distance_sensor库)而不是完整的DistanceSensor版本(来自di_sensors.distance_sensor库),后者是我们之前克隆的包中使用的脚本。

案例研究 1 – 编写 ROS 距离传感器包

在本节中,您将从零开始创建一个 ROS 包,并生成代码以提供 GoPiGo3 的最小 ROS 功能,即读取其距离传感器。请注意,您之前在此位置克隆的代码是您代码预期要执行的工作解决方案:

 ~/Hands-On-ROS-for-Robotics-Programming/Chapter6_ROS_programming/pkg_mygopigo

我们鼓励您尝试根据本章后面提供的解释自己构建 ROS 包。

创建新包

首先,让我们在工作空间中设置一个文件夹,我们将放置包文件:

  1. 将工作目录移动到catkin_ws工作空间文件夹中的src位置:
$ cd ~/catkin_ws/src
  1. 创建一个名为mygopigo的包:
$ catkin_create_pkg mygopigo

此命令创建了两个包定义文件CMakeLists.txtpackage.xml,这些文件在上一章中已经介绍过。由于有一个新的包,您应该重新构建工作空间:

$ cd ~/catkin_ws
$ catkin_make

如果在某个时候您想更改项目名称,那么您需要完成以下三个步骤:

  1. <name>mygopigo</name>标签中编辑 package.xml。

  2. 编辑CMakeLists.txt文件中的project (mygopigo)行,其中项目名称必须与package.xml`标签相同。

  3. 重新构建工作空间。

存储包文件的文件夹可以命名为您喜欢的任何名称 – 它不必与包名相同。

生成您的源代码

mygopigo文件夹已准备好,以便我们可以创建包结构并放置文件:

  1. 在包内创建src文件夹 – 注意这是我们用来标准化代码在存储库中位置的约定:
$ roscd mygopigo
$ mkdir src
$ cd src

roscd ROS 命令与 Linux bash 的cd命令等效。它的优点是您只需指定包名即可移动到包的文件夹,即~/catkin_ws/src/mygopigo/。然后,创建一个 Python 文件以从距离传感器获取数据:

$ nano distance-sensor.py
  1. 在文件中全局复制并粘贴以下行:
#!/usr/bin/env python

# import the modules
from di_sensors.easy_distance_sensor import EasyDistanceSensor
from time import sleep

# instantiate the distance object
my_sensor = EasyDistanceSensor()

# and read the sensor iteratively
while True:
  read_distance = my_sensor.read()
  print("distance from object: {} cm".format(read_distance))
  sleep(0.1)

这是我们在上一章中审查的距离传感器的单元测试文件。我们将解释如何将其转换为 ROS 集成脚本。

ROS 要求源代码存储在具有以下执行权限的文件中:$ chmod +x distance-sensor.py.

  1. 要运行,只需从命令行调用它:
$ ./distance-sensor.py

这将在每 0.1 秒打印一次测量的距离(以厘米为单位)。此时,代码仍然是纯 Python。

我们现在将解释需要进行哪些更改才能将其集成到 ROS 中:

  1. 首先,导入 ROS 所需的模块:
import rospy
from sensor_msgs.msg import Range

rospy库是 Python 客户端,sensor_msgs.msg定义了处理 ROS 中传感器数据的消息类型。在我们的特定情况下,我们只需要Range类型消息,这就是我们需要用于距离传感器的。

  1. 由于rospy提供了处理 ROS 中时间特性的方法,Python 的time库不再需要。因此,您可以删除该行:
from time import sleep
  1. 接下来,我们将代码放在main()函数定义下:
def main():
    my_sensor = EasyDistanceSensor()
    rospy.init_node("distance_sensor")
    pub_distance = rospy.Publisher("~distance", Range, queue_size=10)
    msg_range = Range()
    msg_range.header.frame_id = "distance"
    msg_range.radiation_type = Range.INFRARED
    msg_range.min_range = 0.02
    msg_range.max_range = 3.0
    rate = rospy.Rate(rospy.get_param('~hz', 1))

  1. 我们启动一个名为distance_sensor的 ROS 节点,定义一个名为pub_distance的发布者,在msg_range消息中设置传感器的特性,并指定我们想要读取传感器的速率,即 1 Hz。我们通过运行一个每次迭代读取传感器读数的不停循环来完成主函数的代码:
    while not rospy.is_shutdown():

        read_distance = my_sensor.read()/100.0
        msg_range.range = read_distance
        msg_range.header.stamp = rospy.Time.now()

        print msg_range.range*1000," mm"
        pub_distance.publish(msg_range)

        rate.sleep()

在每次迭代中,此代码将传感器数据保存到msg_range实例中,将消息发布到/distance_sensor/distance主题,并在下一个读取之前运行时延迟以尊重指定的速率。最后,我们告诉 Python 运行main()函数:

comment ...
if __name__ == '__main__':
    main()

在以下小节中,我们将详细介绍这些代码片段的更多信息。

包含所需的库 – rospy 和 msgs.msg

以下两行导入所需的 ROS 库:

import rospy
from sensor_msgs.msg import Range

这些库的解释如下:

  • rospy (wiki.ros.org/rospy):这是 ROS 的官方 Python 客户端。它实现了 API 方法,以便您可以将用 Python 编写的 ROS 节点集成。

  • sensor_msgs (wiki.ros.org/sensor_msgs):这是 ROS 包,允许您根据机器人的传感器和驱动程序处理不同类型的 ROS 消息;例如,BatteryStateFluidPressureLaserScan。在距离传感器的例子中,我们使用 Range 类型。

为脚本分配节点名称

此任务是通过使用 rospy 库的 init_node 方法完成的:

rospy.init_node("distance_sensor")

使用 distance_sensor 名称,我们可以在 ROS 的任何地方引用距离传感器节点。

定义发布者

发布者是函数——再次强调,这是一个 rospy 方法,允许您将测量结果赋值给 pub_distance 变量,在我们的例子中,它是 Range 类型:

pub_distance = rospy.Publisher("~distance", Range, queue_size=10)

引号之间的值是主题名称,~distance。前置符号 ~ 等同于 <name of node>/,在我们的例子中是 distance_sensor。随后,命名空间主题将如下所示:

/distance_sensor/distance

queue_size 参数指定 ROS 在内存中保留多少条消息以确保订阅节点可以读取它们。默认值 10 是一个很好的选择。

设置 msg_range 对象

距离传感器使用在 sensor_msgs.msg 库中定义的消息类型,该类型在 Range 类中,其结构如下:

uint8 ULTRASOUND=0
uint8 INFRARED=1

std_msgs/Header header
uint8 radiation_type
float32 field_of_view
float32 min_range
float32 max_range
float32 range

这些字段将构成任何涉及传感器数据流的消息的一部分,其语法在文档中有详细说明(docs.ros.org/api/sensor_msgs/html/msg/Range.html)。所有字段都是传感器的特定特征,除了测量值本身 range。因此,以下代码片段为我们的距离传感器提供了一个特定的定义:

msg_range = Range()

msg_range.header.frame_id = "distance"
msg_range.radiation_type = Range.INFRARED
msg_range.min_range = 0.02
msg_range.max_range = 3.0

在这里,第一行初始化 msg_range 变量为 Range() 类型。在 header.frame_id 字段中,我们指明将要测量的物理量,即 distance

radiation 类型设置为 INFRARED(没有选项将其设置为 LASER,但将指定为 INFRARED 比其他选项 ULTRASOUND 更为合适,后者会提供一个宽视野而不是直线)。LASERINFRARED 都是定向的,因此使用此类型更好。

最后两行指定了传感器可以测量的最大距离(3 米)和最小距离(2 厘米)。

将单位转换为国际单位制

采用国际单位制SI)是 ROS 规范中声明的规范(www.ros.org/reps/rep-0103.html)。由于 read() 方法提供的是厘米单位的测量值,我们只需将其除以 100 即可得到米单位的距离,并按照 ROS 标准将其馈送到系统中:

read_distance = my_sensor.read()/100.0

此值将在之后插入到 msg_range 对象中,我们将在下一部分进行介绍。

向 msg_range 对象添加测量距离和时间戳

msg_range.range字段中,我们分配测量的距离,在另一个字段msg_range.header.stamp中,我们分配当前的时间戳:

msg_range.range = read_distance
msg_range.header.stamp = rospy.Time.now()

时间戳是从rospy库的Time.now()方法获得的。这样,我们就有了完整的测量记录。

设置读取频率

使用Rate方法,我们可以将读取频率设置为 1 Hz(这等于每秒 1 个样本;在 SI 单位中),如下所示:

rate = rospy.Rate(rospy.get_param('~hz', 1))

我们通过定义一个名为以下内容的 ROS 参数同时完成此操作(记住~符号的含义):

distance_sensor/hz

使用此设置,传感器将每秒读取一次。

运行无限循环

我们使用rospy中的特定 ROS 方法来运行无限循环:

while not rospy.is_shutdown():

其语法是自我解释的,也就是说,除非关闭 ROS,否则它将运行。

发布每个新事件

我们通过使用之前定义的pub_distance发布者,每次测量可用时发布一个新的msg_range消息:

pub_distance.publish(msg_range)

等待下一次读取

为了确保我们尊重 1 Hz 的采集率,我们将sleep方法应用于我们上面定义的rate对象(我们为其设置了 1 Hz 的频率,即每秒一个周期):

rate.sleep()

请记住,这不会阻塞 ROS 执行(它只是阻塞了此节点的脚本),也就是说,只是这个distance_sensor节点的代码。如果有其他节点在环境中,那么它们将有自己的独立执行线程。如果你使用的是原生异步语言,如 JavaScript,那么你还可以在节点内运行异步代码,并避免脚本执行中的阻塞,也就是说,你的节点可以在等待下一次传感器读取时执行其他行。

如果你对此感兴趣,可以调查 ROS 客户端库rosnodejs(www.npmjs.com/package/rosnodejs),它允许你使用 JavaScript 语言编写节点。在此阶段,请记住,ROS 的一个酷特性是你可以混合使用 Python 编写的相同 ROS 图节点与使用 JavaScript 或其他 ROS 客户端库编写的节点(wiki.ros.org/Client%20Libraries)。

启动 ROS 执行环境

现在我们已经了解了 Python 脚本如何与 ROS 集成,我们将使用以下步骤在终端中作为 ROS 运行时环境的一部分执行它:

  1. 再次准备好你的分割 Terminator 窗口以提高可见性,然后在独立的终端中运行以下每个命令:
T1 $ roscore
T2 $ rosrun mygopigo distance-sensor.py
T3 $ rostopic echo /distance_sensor/distance

第一个启动 ROS 主节点。第二个是我们刚刚解释的脚本,而第三个允许我们实时查看/distance_sensor/distance主题下发布的信息。这是你应该看到的内容:

图片

  1. 然后,绘制 ROS 图以获得节点和主题如何连接的视觉洞察:
T4 $ rqt_graph

一个新窗口弹出,显示当前的 ROS 图:

在这里,您可以看到它重现了我们正在三个终端中做的事情:我们执行了/easyDistance_sensor节点,该节点在/easyDistance_sensor/distance主题上发布传感器数据,然后我们使用主题订阅节点/rostopic_2797_156571927410显示读取值。

在通过这个例子之后,我们将使用它来展示您可用的各种 ROS 命令和工具。

使用 ROS 命令

在本节的第一个部分,我们将涵盖三个类别:要在 bash(shell)内部使用的命令、ROS 执行命令和信息命令。

Shell 命令

Shell 命令被捆绑到 ROS 核心包rosbash(wiki.ros.org/rosbash)中。让我们继续看看每个提供的内容。

更改当前位置

首先,我们将介绍roscd,它等同于 Linux bash 的cd命令。它的优点是您只需指定包名即可移动到包的位置:

$ roscd mygopigo

这将带您进入~/catkin_ws/src/mygopigo/文件夹。您也可以通过附加目标位置的相对路径来在包文件夹结构中导航。例如,要移动到mygopigo包的src文件夹,请使用以下命令:

$ roscd mygopigo/src

roscd等同于 Linux 的cd命令。它将通过引用包名的路径来更改提示符,以指向系统中的任何 ROS 包的目录。无论实际路径是什么,ROS 都会自动带您去那里。

列出包内的文件和文件夹

接下来,我们有rosls,它是 Linux 的ls的等价物。要列出您所在位置的包的源代码,只需写下以下内容:

$ rosls mygopigo/src

rosls允许您通过引用包名和路径轻松列出系统内任何 ROS 包中的文件和文件夹。无论实际路径是什么,ROS 都会自动带您去那里。

编辑包内的任何文件

最后,我们有rosed,它打开一个终端编辑器,例如nano,以便您可以修改包中的任何文件:

$ rosed mygopigo distance-sensor.py

为了使rosed正常工作,您必须指定一个编辑器:

export EDITOR=nano

要将其添加到您的配置中,请将前面的行添加到您的.bashrc文件末尾:

echo 'export EDITOR=nano' >> ~/.bashrc

rosed等同于启动 Linux 终端编辑器,即nano。它将允许您通过简单地告诉它包名来编辑 ROS 包内的任何文件,无论文件实际位于哪个子文件夹中。

当您远程连接到机器人且只有一个终端与之交互时,这个命令是修改文件的一个方便方式。如果您在桌面会话中,您甚至可以使用桌面 IDE:

EDITOR=geany rosed mygopigo distance-sensor.py

在这种情况下,您正在即时调用编辑器并覆盖.bashrc中设置的默认值。

执行命令

在第三章“ROS 入门”,我们已经介绍了用于运行我们第一个项目的roscorerosrun命令。

ROS 环境的中心过程

roscore是你必须启动的第一个进程,以便 ROS 环境工作。roscore允许节点之间相互通信。它没有参数,所以请在终端中写下这一行:

$ roscore

roscore启动主节点,这是你的 ROS 环境的中心进程,并保持所有实际运行的节点连接。

执行单个节点

rosrun允许你从包中手动启动一个节点。语法相当简单:

$ rosrun <name_of_package> <name_of_script>

脚本包括一个节点的声明。在我们的distance-sensor.py示例中,这是在以下行中完成的:

rospy.init_node("distance_sensor")

然后,为了启动节点,请在另一个终端中写下这个命令:

$ rosrun mygopigo distance-sensor.py

在这两个命令之后,你已经拥有了一个提供传感器读数的 ROS 功能环境。节点还会在终端进程中打印当前的测量值,转换为毫米(无需打开另一个终端来监听主题)。使用毫米只是为了可视化目的。ROS 消息保持其距离单位为米,你可以通过在另一个终端订阅主题来检查:

print msg_range.range*1000," mm"

rosrun允许你从包中启动单个节点。这是在 ROS 环境中执行手动节点执行的命令。

最后,我们有roslaunch。这是最相关的执行命令,因为它允许你使用 XML 文件描述一个机器人。你可以声明其节点并将每个节点与其执行的脚本链接起来。我们将在“使用 roslaunch 自动化节点执行”部分更详细地查看这个命令。

信息命令

这个类别包含几个命令,允许你从 ROS 环境中提取信息以及交互式地修改一些值。所有命令都以ros-开头。只需在终端中写下命令,就可以提供有关如何使用每个命令的不同选项的帮助。接下来将提供每个命令的简要描述和示例。

探索主题

rostopic提供有关发布的主题的信息:

$ rostopic list

这个列表包含了所有当前活跃的主题。从列表中,你可以访问其中任何一个的实时流:

$ rostopic echo distance_sensor/distance

探索节点

rosnode提供有关活跃节点的信息:

$ rosnode list

这个列表包含了当前 ROS 图中所有的节点。从列表中,你可以访问其中任何一个的信息:

$ rosnode info distance_sensor

在这里,info将为你提供关于distance_sensor节点的有用信息。不要将其与声明节点的 Python 脚本名称distance-sensor.py混淆。rosnode命令始终指的是节点的名称。

rosmsg 命令

rosmsg提供有关运行时由主题使用的消息类型的详细信息。为了给您一个实际例子,我们选择了distance_sensor/distance主题,并获得了以下关于它的信息:

$ rostopic info distance_sensor/distance

此命令告诉我们该主题的消息类型为sensor_msgs/Range。然后,rosmsg通知我们消息结构:

$ rosmsg info sensor_msgs/Range

此命令的输出是我们展示了并在案例研究 1 – 编写 ROS 软件包 – 距离传感器部分的设置 msg_range 对象子部分中解释的内容。在下一章中,我们将提供关于我们将要在 GoPiGo3 ROS 软件包中使用的新消息类型的扩展说明。

rosbag 命令

此命令允许您保存会话并在需要时回放。让我们看看如何做到这一点:

T1 $ roslaunch mygopigo easyDistance.launch
T2 $ rosbag record /distance_sensor/distance

当您想在 T2 终端中结束录制时,请按Ctrl + C。在T2中输入rosbag info <bag filename>以获取有关记录文件的详细信息(文件的默认名称由date-time-topic_name序列组成,并赋予.bag扩展名):

T2 $ rosbag info 2019-08-15-20-36-36_distanceSensor.bag

path: 2019-08-15-20-36-36.bag
version: 2.0
duration: 46.0s
start: Aug 15 2019 20:36:37.48 (1565894197.48)
end: Aug 15 2019 20:37:23.50 (1565894243.50)
size: 14.5 KB
messages: 47
compression: none [1/1 chunks]
types: sensor_msgs/Range [c005c34273dc426c67a020a87bc24148]
topics: /distance_sensor/distance 47 msgs : sensor_msgs/Range

请记住,bag 文件位于您在T2终端启动记录会话的位置。

记录的 bag 文件允许我们在任何时候重现主题历史,就像我们播放记录的歌曲一样。一个典型的使用场景示例是,在没有机器人本身的情况下,使用笔记本电脑上的 ROS 回放机器人行为。这种方法简化了应用程序的调试,并使您能够显著减少在真实机器人上运行软件的次数。

首先,让我们在 Raspberry Pi 上通过运行以下命令集来回放,这些命令包括 ROS 图的可视化(rqt_graph命令)以及随时间测量的距离(rqt_plot命令):

T1 $ roscore
T2 $ rosbag play 2019-08-15-20-36-36_distanceSensor.bag

T3 $ rostopic echo /distance_sensor/distance
T4 $ rqt_graph
T5 $ rqt_plot

在这个片段中,我们介绍了一个新的命令rqt_plot,它将在案例研究 2- ROS GUI 开发工具- Pi 相机部分中稍后解释。简而言之,它随时间绘制所选的 ROS 主题。

现在,您可以通过启动相同的命令集在笔记本电脑上回放会话。对于rqt_plot,您将得到以下结果:

从 ROS 的角度来看,结果与您在 GoPiGo3 中运行实际的 launch 文件时完全相同。与图表相比,差异是由于您在笔记本电脑上安装了 ROS Kinetic 版本,而机器人有 Melodic 版本(这是更新的版本)。

软件包和 catkin 工作空间

本节中的一些命令已在配置您的 ROS 环境时使用过。现在让我们简要回顾一下:

  • catkin_init_workspace初始化一个新的工作空间,就像您在本章开头所做的那样。

  • catkin_create_pkg创建一个新的软件包。

  • catkin_make构建一个工作空间,每次您添加或删除一个软件包时都应该调用它。

这些是基本命令。尽管如此,还有一些额外的命令值得提及:

  • catkin_find列出你的 ROS 环境的工作文件夹。

  • rospack提供有关 ROS 包的信息,无论是核心、贡献的还是你自己制作的。如果你想了解处理 GoPiGo 时安装了哪些 ROS 包,可以使用此命令:

$ rospack list | grep gopigo
 gopigo3_node /home/pi/catkin_ws/src/gopigo3_node
 mygopigo /home/pi/catkin_ws/src/mygopigo

你唯一需要注意的警告是,它们的名字中都应该包含字母gopigo,这样grep就可以过滤它们而不会遗漏任何一个。

在本节中,我们提供了最常用命令的概述。请花所有需要的时间来熟悉它们,因为当你与 ROS 一起工作时,你将不断使用它们。在下一节中,我们将通过解释订阅者节点的语法来扩展我们对发布-订阅模式的了解,该节点将读取所选主题的消息。

创建和运行发布者和订阅者节点

如果你已经理解了distance-sensor.py发布者脚本的工作原理,那么以下订阅者脚本应该很容易理解:

#!/usr/bin/env python

import rospy
from sensor_msgs.msg import Range

def callback(msg):
    print msg.data
    rospy.loginfo(rospy.get_caller_id() + 'GoPiGo3 measures distance %s mm', msg.data*1000)

rospy.init_node('distance_sensor_subscriber')

sub = rospy.Subscriber('distance_sensor/distance', Range, callback)

rospy.spin()

这段代码对应于本章代码./pkg_mygopigo/src文件夹中的distance-sensor_subscriber.py文件。订阅者脚本中的主要区别在于,由于我们正在监听一个主题,我们不需要指定执行速率。我们只需使用以下行无限循环:

rospy.spin()

每当在主题中接收到消息时,都会执行一个回调函数:

sub = rospy.Subscriber('distance_sensor/distance', Range, callback)

在这种情况下,这个回调函数被定义为打印以毫米为单位的测量距离:

def callback(msg):
    print msg.data
    rospy.loginfo(rospy.get_caller_id() + 'GoPiGo3 measures distance %s mm', msg.data*1000)

在 Terminator 窗口中使用多个终端在 ROS 中执行脚本:

T1 $ roscore
T2 $ rosrun mygopigo distance-sensor.py
T3 $ rostopic echo distance_sensor/distance
T4 $ rosrun mygopigo distance-sensor_subscriber.py
T5 $ rqt_graph

看一下终端窗口,如下所示:

图片

以下是全球 ROS 图:

图片

有两个节点在监听同一个主题。一个是使用/distance_subscriber节点解释的订阅者脚本,另一个是rostopic echo命令创建的节点。

到目前为止,你已经手动启动了每个 ROS 节点。在下一节中,你将学习如何以编程方式执行,以将机器人软件作为自动化任务运行。

使用roslaunch自动化节点的执行

一旦你决定将哪些节点作为你机器人的部分运行,你可以通过使用roslaunch命令来自动化所有脚本的启动过程。它的语法如下:

$ roslaunch <name_of_package> <name_of_launch_file>

对于我们的例子来说,这很简单,因为只有一个节点。启动文件在./pkg_mygopigo/launch/easyDistance.launch仓库中,其语法基于 XML:

<launch>
   <node name="easyDistance_sensor" pkg="mygopigo" type="distance-sensor.py" output="screen" />
   node name="distance_subscriber" pkg="mygopigo" type="distance-sensor_subscriber.py" output="screen" />
</launch>

<launch>标签界定机器人描述。然后,为每个你想要启动的节点包含一个<node>标签。在我们的例子中,只有一个:distance_sensor节点。其属性的描述如下:

  • name:用于识别节点的名称。这取代了脚本行中给出的名称:
rospy.init_node("distance_sensor")

在此启动文件中我们设置了不同的名称,easyDistance_sensor

  • pkg:这是包的名称,是mygopigo

  • type:这是启动节点的脚本的引用,easyDistance.py

  • output:我们指定屏幕(默认是输出到$ROS_HOME/log的日志)。

一旦你理解了 XML 启动文件,重复提升机器人的过程,但这次使用自动方式:

T1 $ roslaunch mygopigo easyDistance.launch
T2 $ rostopic echo /easyDistance_sensor/distance

roslaunch隐式启动roscore。你应该看到与手动使用rosrun运行时的相同输出。显然,roslaunch在需要同时启动多个节点时非常有用。我们将在稍后看到这方面的示例。

通过在 Terminator 窗口下的几个终端中执行脚本,在 ROS 中执行脚本:

T1 $ roslaunch mygopigo easyDistance.launch
T2 $ rostopic echo distance_sensor/distance
T3 $ rqt_graph

看看以下终端窗口:

图片

这是 ROS 图:

图片

你将发现与上一节完全相同的结果。现在让我们看看 ROS 可视化工具,这些工具可以简化我们作为软件机器人开发者的生活。

案例研究 2 – ROS GUI 开发工具 – Pi Camera

正如我们在安装 ROS Melodic部分结束时提到的,为了能够使用相机,我们首先需要安装其 ROS 包。由于二进制文件对 ROS Melodic 不可用(仅对 Kinetic 可用),我们需要从源代码构建包,这是一个完美的例子,你将知道如何对任何其他包这样做。让我们按照以下步骤进行:

  1. 前往你的catkin工作空间并下载源代码:
$ cd ~/catkin_ws/src
$ git clone https://github.com/UbiquityRobotics/raspicam_node.git
  1. 对于 ROS,需要安装一些依赖项。为了执行此任务,我们将创建30-ubiquity.list文件:
$ sudo -s
$ echo “yaml https://raw.githubusercontent.com/UbiquityRobotics/rosdep/master/raspberry-pi.yaml” > /etc/ros/rosdep/sources.list.d/30-ubiquity.list
$ exit
  1. 之后,按照以下方式运行 ROS 依赖项更新:
$ rosdep update
  1. 现在安装 ROS 依赖项:
$ cd ~/catkin_ws
$ rosdep install --from-paths src --ignore-src --rosdistro=melodic -y
  1. 按以下方式编译新包:
$ catkin_make --only-pkg-with-deps raspicam_node
$ catkin_make -DCATKIN_WHITELIST_PACKAGES=""

如果你使用catkin_make没有任何选项,构建过程将遍历工作空间中的所有包。因此,此代码片段显示了如何在忽略其余包的情况下编译单个包。第二行允许你在需要编译工作空间中的下一个包时切换回启用构建所有包。

  1. 要运行 Pi Camera 节点,只需启动以下命令:
T1 $ roslaunch raspicam_node camerav2_1280x960.launch
  1. 如果你拥有之前的 Pi Camera 版本,V1,请使用以下替代方案:
T1 $ roslaunch raspicam_node camerav1_1280x720.launch
  1. 然后,在另一个终端中运行随包提供的图像查看器实用程序,以检查相机是否正常工作:
T2 $ rosrun raspicam_node imv_view.py

你应该会看到一个类似以下的新窗口:

图片

如果你移动机器人,你会发现图像也会改变,所以你正在观看实时相机流。现在我们准备进行本节范围的 ROS GUI 开发工具的实际解释。

使用 rqt_graph 分析 ROS 图

通过发出此命令,你可以可视化当前的 ROS 图形:

T3 $ rqt_graph

它将显示以下图表:

图片

raspicam_node 是与物理 Pi Camera 接口的包的根节点。它在 /raspicam_node/image/compressed 主题中发布图像。另一个节点 imv_view 来自 T2 终端的进程,它启动一个窗口,你可以在这里观看实时流(如前节所示)。

最后,检查主题的 raspicam_node 提供以下信息:

T4 $ rostopic list | grep raspicam_node

/raspicam_node/camera_info
/raspicam_node/image/compressed
/raspicam_node/parameter_descriptions
/raspicam_node/parameter_updates

你可以在列表中找到 imv_view 节点订阅的节点,即 /raspicam_node/image/compressed

使用 rqt_image_view 显示图像数据

此插件允许你可视化在 ROS 主题中发布的图像数据。关闭前两个终端,并启动以下新的终端:

T1 $ roslaunch raspicam_node camerav2_410x308_30fps.launch
T2 $ rqt_image_view

在左上角的下拉列表中,选择你想要可视化的图像主题。由于它是由 raspicam_node 发布的,所以它需要以压缩格式存在。以下截图显示了结果:

图片

如果你有几个包含图像数据的主题,此插件允许你交互式地选择要观看的源,并在需要时在它们之间切换。

使用 rqt_plot 绘制传感器数据的时间序列图

这是一个用于可视化二维数据的插件。由于我们想看到二维数据,让我们简要切换到距离传感器案例研究,以便我们可以查看随时间测量的距离。过程很简单:启动机器人,列出主题,然后启动插件:

T1 $ roslaunch mygopigo easyDistance.launch
T2 $ rostopic list | grep distance_sensor
T3 $ rqt_plot

在左上方的框中,写下你想要可视化的主题名称,即按照 T2/distance_sensor/distance。一个窗口将弹出,显示随时间变化的障碍物距离:

图片

绿线 msg_range.range 是实际测量值。msg_range 对象的其他字段(这是 topic/distance_sensor/distance 的内容)显示所有测量的最大值和最小值:msg_range.max_rangemsg_range.min_range

使用 rqt_bag 播放记录的 ROS 会话

rqt_bag 插件播放一个 bag 文件,这与在 ROS 命令 部分解释的 rosbag 命令相同。在这里,优势在于你可以对播放进行交互式控制:你可以跳转到任何瞬间,播放单个时间步,回放到开始,等等。让我们首先通过距离传感器案例研究来检验这一点,然后是 Pi Camera。

距离传感器

你可以在任何地方执行回放,使用机器人或笔记本电脑。与 rosbag 一样,你需要先访问 roscore 进程,然后可以发出 rqt_bag

T1 $ roscore
T2 $ rqt_bag

一个新的窗口弹出。选择要播放的文件包,即2019-08-15-20-36-36_distanceSensor.bag,在窗口上右键单击,然后标记/distance_sensor/distance主题以发布。运行与使用rosbag时相同的命令集:

T3 $ rostopic echo /distance_sensor/distance
T4 $ rqt_graph
T5 $ rqt_plot

在下面的屏幕截图中,你可以检查结果是否相同:

现在我们将播放 Pi Camera 案例研究中的图像流。

Pi 相机

首先,我们需要记录机器人的一个会话:

T1 $ roslaunch raspicam_node camerav2_410x308_30fps.launch
T2 $ rosbag record /raspicam_node/image/compressed

检查录制文件的详细信息:

$ rosbag info 2019-08-15-20-44-53_raspicamImage.bag

path: 2019-08-15-20-44-53_raspicamImage.bag
version: 2.0
duration: 13.3s
start: Aug 15 2019 20:44:54.09 (1565894694.09)
end: Aug 15 2019 20:45:07.38 (1565894707.38)
size: 37.5 MB
messages: 400
compression: none [47/47 chunks]
types: sensor_msgs/CompressedImage [8f7a12909da2c9d3332d540a0977563f]
topics: /raspicam_node/image/compressed 400 msgs : sensor_msgs/CompressedImage

你可以在任何地方播放回放,使用机器人或笔记本电脑。在这种情况下,图像数据更庞大。因此,在笔记本电脑上播放会话会更好。按照之前的方式启动进程:

T1 $ roscore
T2 $ rqt_bag

在启动的rqt_bag插件中,选择要播放的文件包,即2019-08-15-20-44-53_raspicamImage.bag,在窗口上右键单击,然后标记/raspicam_node/image/compressed主题以发布。之后,运行以下命令集:

T3 $ rostopic echo /raspicam_node/image/compressed
T4 $ rqt_graph
T5 $ rqt_image_view

rqt_image_view的左上角下拉列表中选择你想要可视化的图像主题。它需要以压缩格式发布,因为它是由raspicam_node发布的。在下面的屏幕截图中,你可以检查结果是否相同:

在播放文件包时,你可以检查由于你的笔记本电脑与 Raspberry Pi 相比拥有更强大的 GPU,图像流非常流畅。因此,很明显,当你处理计算机视觉任务时,你将利用笔记本电脑中机器人会话的这种可视化能力。

使用 ROS 参数自定义机器人功能

ROS 参数存储机器人的全局配置。这是一种定义你的应用程序的便捷方式,这样你可以将功能抽象到高级别,并使其对最终用户可用。我们将通过使用允许动态重新配置其中一些参数的rqt插件来展示 ROS 参数的工作原理。正如其名,你可以实时修改机器人的特性:

  1. 启动raspicam_node然后启动rqt插件:
T1 $ roslaunch raspicam_node camerav2_410x308_30fps.launch
T2 $ rqt_image_view
T3 $ rosrun rqt_reconfigure rqt_reconfigure

你的桌面应该显示以下两个窗口:

  1. 检查右侧的参数,并关注亮度(红色标记的框)。将它的值从51修改到81,然后检查结果:

哇!你可以在不重新启动机器人的情况下动态修改机器人的配置。

  1. 你还有rosbash命令,它允许你检查参数。使用以下行列出它们:
T4 $ rosparam list | grep raspicam_node

/raspicam_node/ISO
/raspicam_node/awb_mode
/raspicam_node/brightness
/raspicam_node/camera_frame_id
/raspicam_node/camera_id
/raspicam_node/camera_info_url
/raspicam_node/camera_name
/raspicam_node/contrast
/raspicam_node/enable_imv
/raspicam_node/enable_raw
/raspicam_node/exposure_compensation
...
/raspicam_node/zoom

此外,获取我们动态修改的状态:

T4 $ rosparam get /raspicam_node/brightness
 81

如果你已经到达这个阶段并且理解了实际练习中的概念,你现在几乎知道你需要用 ROS 做的一切。

摘要

在本章中,我们为 ROS 编程奠定了基础。您已经构建了自己的包,并添加了简单的 GoPiGo3 功能:读取距离传感器,以便于编程概念的学习。您还学习了如何读取 Pi Camera 图像并将它们提供给 ROS 进行进一步处理,这是执行计算机视觉任务的起点。

在下一章中,您将把两个 ROS 世界结合起来:机器人和您的笔记本电脑。这样,一旦您在机器人上运行了 GoPiGo3 包,您将能够从您强大的笔记本电脑上执行所有计算和处理任务。

问题

  1. ROS 主题和 ROS 消息之间的区别是什么?

A) 它们都代表从一个节点传输到另一个节点的数据。

B) 主题是您识别传输通道的方式,而消息是通过该通道流动的内容的一个样本。

C) 任何主题名称都必须是唯一的,而多个主题可以传输相同的信息。

  1. 您将使用哪个命令来记录 ROS 会话?

A) rosbag

B) rosrecord

C) roswrite

  1. 一个 ROS 节点可以在同一时间既是发布者又是订阅者吗?

A) 是的,如果主题订阅者与主题发布者相同。

B) 不可以,因为这会意味着编程冲突:一个具有发布者的节点以恒定速率循环,即rate.sleep(),而一个具有订阅者的节点只有在接收到消息时才运行一个迭代,即rospy.spin()

C) 是的,并且节点是由订阅者驱动的,也就是说,节点每次从它订阅的主题接收到消息时都会广播一个新的消息。

  1. 在同一个 ROS 会话中,您可以运行多少个roslaunch命令?

A) 您需要的数量;roslaunch是一个描述文件,它告诉 ROS 在调用命令时启动哪些节点。

B) 只有一个,因为roslaunch隐式运行一个roscore进程,并且会话中只能有一个 ROS 主节点。

C) 如果您需要两组节点,建议您在需要将每个节点添加到执行环境时手动启动它们。然后,启动一个roscore进程,之后为每个新节点执行一个rosrun

  1. 有没有一种程序化的方法可以可视化在 ROS 主题中发布的图像?

A) 是的,使用rqt_plot

B) 是的,使用rqt_image_view

C) 是的,使用rqt_image_view,但图像必须是压缩格式。

进一步阅读

要深入了解本章中我们解释的概念,您可以参考以下参考资料和教程:

第七章:机器人控制和仿真

在本章中,你将设置专门用于编程 GoPiGo3 的 ROS 开发环境。这种理解将通过从使用笔记本电脑键盘的键到更技术性的使用 ROS 主题的方式建立。最后,你将猜测哪些主题将允许你将基于键盘/主题的手动控制与内部编程逻辑(即智能行为)连接起来,从而使机器人能够执行自主任务。从这个意义上说,Gazebo 中的 3D 模拟是测试开发过程中的行为并在将应用程序推送到物理机器人之前的重要工具,这有助于在基于现场的工作中节省时间和精力。

到本章结束时,你将学会如何为真实机器人设置 ROS 环境。远程控制和自主控制在机器人软件开发方面建立了质的差异。通过自己尝试远程控制,你将准备好跨越机器人真正自主的边界。

最后,你将通过将物理机器人的行为与仿真进行比较,了解在机器人应用开发中使用仿真的有用性。这些观察到的差异将允许你调整 Gazebo 中虚拟机器人仿真的参数。这种方法的主要优势是,你可以在最终的开发阶段仅使用真实硬件进行测试,而在开发过程中始终使用虚拟模型进行开发和测试。

本章将涵盖以下主题:

  • 设置 GoPiGo3 开发环境,使其能够在笔记本电脑上与 ROS 进行网络连接

  • 案例研究 3 - 使用键盘远程控制物理机器人

  • 使用命令行通过 ROS 主题远程控制机器人

  • 在 Gazebo 中,比较手动遥控下物理机器人和虚拟模型的行为

我们将根据关于机器人驱动(电机和编码器)的第三个案例研究对这些概念进行解释。记住,前两个案例研究已在第六章,ROS 编程 - 命令和工具中介绍,如下:

  • 案例研究 1:发布和读取距离传感器

  • 案例研究 2:从 Pi 摄像头获取并可视化图像

到本章结束时,我们将拥有 GoPiGo3 的 ROS 包的第一个完整版本。这将成为本书其余部分构建我们智能机器人行为的基础。

技术要求

对于本章,不需要额外的硬件或软件配置。只需确保你具备以下条件:

  • 根据第六章的技术要求部分,GoPiGo3 的ROS 编程 - 命令和工具

  • 笔记本电脑,根据第三章,ROS 入门中的配置你的 ROS 开发环境部分

在下一节中,我们将准备 ROS 环境,以便你的笔记本电脑和机器人可以在一个独特的 ROS 图中相互通信。让我们回顾一下我们需要用到的代码:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter7_Robot_control_simulation ~/catkin_ws/src

代码中包含一个名为gazebo_control的新 ROS 包。重建工作空间,使其为你的 ROS 安装所知:

$ cd ~/catkin_ws
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

通过选择该包并列出文件来检查该包是否已正确安装:

$ roscd gazebo_control
$ ls -la
  • 本章代码(GoPiGo3):你将使用我们在第六章,在 ROS 中编程 - 命令和工具中开发的相同代码。记住,它对应于位于 Raspberry Pi 上的名为mygopigo的 ROS 包。

现在,你准备好跟随本章的解释,并以实际的方式理解它们。

设置 GoPiGo3 开发环境

在上一章中,你尝试了在 GoPiGo3 的 Raspberry Pi 上运行 ROS 桌面版本。对于严肃的开发策略,你应该在机器人上启动应用程序的最小配置,并在笔记本电脑上启动所有伴随的开发任务。

记住,在第三章,ROS 入门中,你在笔记本电脑上运行 ROS。因此,机器人应用程序本身是在这种环境中执行的,由于我们没有在计算机之外建立连接,所以没有移动物理机器人的可能性。

在本节中,你将学习如何在它们连接时同时使用笔记本电脑和 Raspberry Pi ROS 环境,即 Raspberry Pi 控制 GoPiGo3 机器人,笔记本电脑用于 CPU 密集型计算/可视化任务。

机器人和远程计算机之间的 ROS 网络

要创建两个 ROS 环境,我们需要满足两个先决条件:

  • 机器人和计算机 ROS 环境需要知道如何相互通信。

  • 主节点(roscore进程)应该是唯一的,并且可以在其中任何一个运行。

让我们学习如何满足这两个条件。

ROS 环境之间的通信

计算机在网络中的识别方式是通过其 IP 地址,通常使用 IPv4 协议。一般来说,你可以使用ifconfig命令找到分配给你的机器的 IP 地址:

$ ifconfig

eth0: ...
lo:   ...
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
 inet addr: 192.168.1.58 netmask 255.255.255.0 broadcast 192.168.1.255
 inet6 addr: fe80::7d9d:84a9:ec7:20cd prefixlen 64 scopeid 0x20<link>

 RX packets 212 bytes 46561 (46.5 KB)
 RX errors 0 dropped 0 overruns 0 frame 0
 TX packets 202 bytes 43986 (43.9 KB)
 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

你应该关注无线接口,通常命名为 wlan0,因为你已经为机器人配置了 Wi-Fi 访问。IP 地址是跟在 inet 单词后面的 4 x 4 字节字段。因此,对于 GoPiGo3 中的 Raspberry Pi,这个字段是 192.168.1.58

对于笔记本电脑,也以终端打开。如果你使用的是有线连接,你应该查看 eth0 而不是 wlan0。在我们的示例案例中,计算机的 IP 地址是 192.168.1.54。如果有太多的网络接口,输出可能会过于冗长。为了专注于你正在寻找的内容,你可以过滤输出,如下所示:

$ ifconfig | grep 'inet'

一旦你有了两个 IP 地址,你只需使用 ROS 环境变量来指示它们。在以下小节中,我们将向你展示如何配置每台计算机。

机器人网络配置

接下来,你必须遵循三个步骤来设置 Raspberry Pi:

  1. 在终端中执行以下两个命令来定义 ROS 所需的环境变量:
$ export ROS_HOSTNAME=192.168.1.58
$ export ROS_MASTER_URI=http://${ROS_HOSTNAME}:11311

第一条命令设置了允许 ROS 知道其当前运行的主机 IP 地址的变量。第二行是主节点的 URL。由于它被设置为 ROS_HOSTNAME,我们是在说它将在机器人计算机上运行。端口 11311 是 ROS 设置用于与主节点通信的默认端口。随后启动的每个节点都将自动分配一个新的可用端口。

  1. 有一种方法可以抽象 IP 地址本身,因为在 Ubuntu 系统中,avahi-daemon (manpages.ubuntu.com/manpages/bionic/man8/avahi-daemon.8.html) 允许你通过简单地将 .local 添加到主机名来指向本地网络中的机器。为此,配置命令将如下所示:
$ export ROS_HOSTNAME=gopigo3.local

主机名可以在终端提示符中找到,通常格式为 user@hostname :~$。如果你有任何疑问,可以在终端中使用 hostname 命令来询问:

pi@gopigo3 :~$ hostname
 gopigo3
  1. 每次你启动新的终端时都需要这个配置。因此,如果我们将其作为额外的行包含在 .bashrc 文件中,你就不必手动做这件事:
$ echo export ROS_HOSTNAME=gopigo3.local >> ~/.bashrc
$ echo export ROS_MASTER_URI=http://${ROS_HOSTNAME}:11311 >> ~/.bashrc

通过列出文件末尾来检查结果:

$ tail ~/.bashrc

你应该能看到引用的两个配置行。

现在 Raspberry Pi 已经配置好了,我们将为笔记本电脑做类似的事情。

笔记本网络配置

对于远程计算机,这些是等效的步骤:

  1. 在终端中执行以下两个命令来设置环境变量:
$ export ROS_HOSTNAME=192.168.1.54
$ export ROS_MASTER_URI=http://gopigo3.local:11311

你在第一行指定其 IP 地址为 192.168.1.54,而在第二行中,我们声明 ROS 主节点位于机器人上,即 gopigo3.local。这样,如果你的网络重启并且 Raspberry Pi 被分配了不同的 IP 地址,你的系统就不需要重新配置。

  1. 至于机器人,将以下两行添加到 .bashrc 文件中,以便每次启动新终端时自动配置:
$ echo export ROS_HOSTNAME=rosbot.local >> ~/.bashrc
$ echo export ROS_MASTER_URI=http://gopigo3.local:11311 >> ~/.bashrc

我们还展示了设置 ROS_HOSTNAME 的替代方法,即使用 rosbot.local 而不是数字 IP 地址。

启动主节点并连接

以下过程允许我们在笔记本电脑和 Raspberry Pi 之间建立连接:

  1. 使用已熟悉的 roscore 命令在机器人上启动 ROS 主节点:
pi@gopigo3 :~$ roscore
  1. 然后,在笔记本电脑上执行基本检查,以确定它是否知道主节点存在:
bronquillo@rosbot:~$ rostopic list
 /rosout
 /rosout_agg

/rosout_agg/rosout_agg 是由主节点发布的主题。

如果一切顺利,你可以远程控制你的 GoPiGo3。在进入下一节之前关闭终端,以确保 roscore 已关闭。

案例研究 3 – 使用键盘进行遥控

本案例研究将帮助你完成 GoPiGo3 ROS 软件包的第一个版本。在前一章中,你处理的是距离传感器和 Pi 摄像头,我们为每个都进行了一个案例研究。

通过将运动功能与现有的机器人驱动程序相结合,你将拥有一个能够与其环境进行基本交互的机器人:

  • 感知能力包括使用距离传感器检测障碍物,以及使用 Pi 摄像头对周围环境的视觉识别。

  • 驱动能力,其中机器人能够在地板上移动,同时通过距离传感器感知可能的障碍物,并通过其摄像头的图像流识别形状和人。

案例研究 3 侧重于驱动能力。在本节中,你将学习如何使用键盘和鼠标远程移动机器人。

在机器人上运行 gopigo3 节点

在本节中,我们将运行一个在 Raspberry Pi 上的节点,该节点将提供控制能力:

  1. 在机器人上启动 GoPiGo3 ROS 软件包:
$ roslaunch mygopigo gopigo3.launch

这是你需要直接在 Raspberry Pi 上运行的唯一命令。由于你已配置 ROS,使笔记本电脑能够与机器人通信,因此以下命令可以在笔记本电脑上运行。

当在 bash 命令前加上 T1T2T3T4 时,我们总是会指代笔记本电脑上的连续终端。

  1. 然后,在你的笔记本电脑上,确保只有一个节点,即 gopigo3
T1 $ rosnode list
 /gopigo3
 /rosout

记住,/rosout 主题对应于主节点。

  1. 接下来,列出所有可用的 ROS 主题,以找出它们的名称:
T1 $ rostopic list
/battery_voltage
/cmd_vel
...
/motor/encoder/left
/motor/encoder/right
...
/motor/status
/odom
/rosout
/rosout_agg
/servo/position/1
/servo/position/2
...
/tf

请记住,三个感兴趣的主题被称为 /battery_voltage/cmd_vel/motor/status。我们将在本节后面提供它们的详细信息。

  1. 要获取有关这些主题的更多信息,可以使用 rosnode 命令的 info 选项。这将告诉你 gopigo3 节点能做什么:
T1 $ rosnode info gopigo3
Node [/gopigo3]
Publications: 
 * /battery_voltage [std_msgs/Float64]
 * /joint_state [sensor_msgs/JointState]
 * /motor/encoder/left [std_msgs/Float64]
 * /motor/encoder/right [std_msgs/Float64]
 * /motor/status [gopigo3_node/MotorStatusLR]
...
Subscriptions: 
 * /cmd_vel
 ...
 * /motor/dps/left
 * /motor/dps/right
 ...

在这里,你可以看到 /battery_voltage/motor/status 是发布者(节点代码中的对象,将数据流到这些主题),而 /cmd_vel 是一个订阅者(以及一个在节点中声明的对象,允许我们从现有主题中消费数据):

    • 发布者允许你从电池电量和电机分别获取状态信息。

    • /cmd_vel订阅者允许机器人接受远程控制的运动命令。

在下一小节中,我们将检查发布者,以便了解它们流出的消息结构。

检查发布的主题和消息

现在我们已经确定了三个主题,让我们来看看它们,并获取一些机器人特定的信息:

  1. 要找到电池的最后五个值,请在终端中运行以下命令。这将允许你检查/battery_voltage主题:
T1 $ rostopic echo battery_voltage -n 5

data: 9.568
---
data: 9.551
---
data: 9.594
---
data: 9.568
---
data: 9.586

你可以推断出电压大约是 9.6V,这意味着电池已经充电。

  1. 让我们调查这个主题是如何构建的:
T1 $ rostopic info battery_voltage
 Type: std_msgs/Float64

它使用std_msgs/Float64消息类型,这对应于一个 64 位大小的浮点数。这种消息类型是 ROS 标准消息库的一部分(wiki.ros.org/std_msgs)。要了解 ROS 消息由什么组成,你可以使用rosmsg命令:

T1 $ rosmsg info std_msgs/Float64
 float64 data
  1. 在这个节点中还有一个主题,/motor/status,它使用一个自定义的更复杂的消息:
T1 $ rostopic info motor/status
 Type: mygopigo/MotorStatusLR
  1. 让我们找到消息的结构。请注意,消息的定义是在mygopigo包下的msg文件夹中声明的:
T1 $ rosmsg info mygopigo/MotorStatusLR

 std_msgs/Header header
 uint32 seq
 time stamp
 string frame_id

mygopigo/MotorStatus left
 bool low_voltage
 bool overloaded
 int8 power
 float32 encoder
 float32 speed

mygopigo/MotorStatus right
 bool low_voltage
 bool overloaded
 int8 power
 float32 encoder
 float32 speed

这有三个部分:

    • 包含序列号和时间戳的头部

    • 来自左侧电机的数据

    • 来自右侧电机的数据

如果我们取最后发布的消息的内容,我们可以在实践中可视化这个结构:

T1 $ rostopic echo motor/status -n 1
header: 
 seq: 177
 stamp: 
 secs: 1566220531
 nsecs: 946445941
 frame_id: ''
left: 
 low_voltage: False
 overloaded: False
 power: -128
 encoder: 0.0
 speed: 0.0
right: 
 low_voltage: False
 overloaded: False
 power: -128
 encoder: 0.0
 speed: 0.0

在这里,你可以看到主题报告了low_voltage级别警告,电机overload警告,powerencoder数据和当前的speed

现在,让我们继续到移动机器人的实际部分。

遥操作包

key_teleop(wiki.ros.org/key_teleop)是一个由 ROS 贡献的包,它提供了一个非常简单的机制,使用键盘的箭头键来控制机器人。按照常规方式克隆源代码并在你的笔记本电脑上安装该包:

$ cd ~/catkin_ws/src
$ git clone https://github.com/ros-teleop/teleop_tools
$ cd .. && catkin_make

这里还有另外两个包可用,它们也是teleop_tools包的一部分:

  • joy_teleop,一个通用的操纵杆接口,用于主题和动作

  • mouse_teleop,一个指向设备(例如鼠标、触摸板等)遥操作工具

由于你已经构建了整个仓库,这两个都对你可用。

在笔记本电脑上运行遥操作

自从你完成了在机器人中运行 gopigo3 节点这一小节,你应该已经通过$ roslaunch mygopigo gopigo3.launch命令使gopigo3节点运行。现在让我们学习如何远程控制它:

  1. 在你的笔记本电脑上启动遥操作节点:
T1 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

之前的命令启动了key_teleop节点,终端的提示被一个类似于以下截图的界面所替换:

这个灰色窗口告诉您如何使用键盘上的箭头键移动机器人:

    • 向上箭头键使机器人以 0.8 m/s 的速度向前移动。您可以在界面上看到命令速度。

    • 向下箭头键使机器人以 -0.5 m/s 的速度向后移动(负号表示向后)。

    • 向左箭头键使机器人以 1 rad/s 的速度逆时针(向左)旋转。

    • 向右箭头键使机器人以 -1 rad/s 的速度顺时针旋转(负号表示向右旋转)。

为了有效地移动它,请注意,启动 key_teleop 节点的终端必须是活动窗口。如果不是这种情况,只需点击它上的任何位置使其成为活动窗口。

  1. 使用 T2 命令,我们可以像往常一样可视化 ROS 图:
T2 $ rqt_graph

它应该看起来如下:

图片

在节点可执行脚本之后附加的 T1 命令就是我们所说的重新映射。这项技术允许两个节点进行通信,其中监听节点订阅者的字面量 /cmd_vel 与发布节点 /key_vel 的字面量不匹配。因此,重新映射包括将一个节点的发布主题连接到另一个节点的订阅主题。这样,我们知道 key_teleop 节点的输出将成为 gopigo3 节点的输入。

现在,让我们学习如何使用鼠标而不是键盘来遥控机器人。

使用鼠标进行遥操作

此过程与键盘上的操作等效:

  1. 在第三个终端中输入以下命令以应用来自 teleop_tools 包的 mouse_teleop 包:
T3 $ rosrun mouse_teleop mouse_teleop.py /mouse_vel:=/cmd_vel

在此情况下,重新映射的主题是 /mouse_vel:=/cmd_vel

  1. 刷新 rqt_graph 窗口;您将看到以下内容:

图片

前面的图表表明,机器人可以无障碍地同时接受来自鼠标和键盘的命令。这种情况发生在我们在启动鼠标控制命令之前没有在 T1 终端中终止键盘控制。

另一方面,如果您终止了 T1 终端,机器人将继续工作,但那时您只能通过鼠标控制它,因为 T3 中的进程使其保持活动状态(如果我们刷新窗口,key_teleop 节点将从 rqt_graph 中消失)。

  1. 最后,检查鼠标控制是否正常工作(如果您之前终止了它,请重新启动 T1 命令):
    • 选择 T1 的窗口。您将使用箭头键移动 GoPiGo3。

    • 通过执行 T3 命令选择出现的新窗口。现在,您可以使用鼠标移动 GoPiGo3(同时按住左键)。

下面的新窗口是用于控制鼠标的:

图片

蓝线代表前进(正速度)- 向后方向。在这种情况下,它以-0.3 m/s(向后)的速度移动。线越长,速度越快。红色圆形区域代表相同的旋转方向。在这种情况下,当向左旋转(逆时针)时,旋转速度为每秒 15.08 度。

这个例子说明了 ROS 的两个重要概念:

  • 消息的并发性:如果有两个或更多节点向映射到另一个节点订阅者的主题发布消息,该节点将接受来自两者的消息,并尝试执行所有这些消息。这是一个当你以高级水平使用 ROS 时会利用的好特性。

  • 运行环境的弹性:机器人可以以部分功能工作。它将仅丢失由已死亡的 ROS 节点提供的功能。

在这一点上,重要的是要思考 ROS 如何为编程机器人提供高级层,与使用 C 或 Python 等过程式语言相比。此外,这也意味着你将更多地关注机器人的功能,而不是编程代码:你将集成现有的提供低级层(例如来自键盘或鼠标的控制命令)的包,并通过 ROS 主题将它们联系起来,以构建高级层(使用键盘和/或鼠标控制机器人的运动)。

使用 ROS 主题进行远程控制

在上一节中,你使用人机界面(键盘和鼠标)控制了在/cmd_vel主题上发布的机器人消息。在本节中,你将直接使用命令行中的rostopic发布消息。这样,你将熟悉这个相关主题及其消息结构。理解/cmd_vel在底层的工作方式至关重要,因为你在本书剩余章节中将要覆盖的许多高级示例中都将使用它。

运动控制主题 - /cmd_vel

现在我们已经享受了与机器人的玩耍,让我们了解这种控制形式是如何工作的。gopigo3 节点订阅的/cmd_vel主题是产生机器人平移和旋转的关键。在机器人上运行gopigo3节点的同时,运行rostopic命令以从主题检索信息:

T1 $ rostopic info /cmd_vel

 Type: geometry_msgs/Twist

 Publishers: None
 Subscribers: 
 * /gopigo3 (http://gopigo3.local:40605/)

主题/cmd_vel使用geometry_msgs/Twist类型(64 位)的geometry_msgs消息库(wiki.ros.org/geometry_msgs)。这个库提供了用于处理几何原语(点、向量、姿态)的消息。命令还告诉你哪些节点订阅了该主题,在我们的例子中,只有gopigo3。现在,让我们检索消息类型的结构:

T1 $ rosmsg info geometry_msgs/Twist 
 geometry_msgs/Vector3 linear
 float64 x
 float64 y
 float64 z
 geometry_msgs/Vector3 angular
 float64 x
 float64 y
 float64 z

在这里,我们可以看到它由六个 64 位浮点数组成,这将允许你将其视为两个各由三个分量组成的三维向量。前三个分量形成线性向量,并指代沿 XYZ 轴的速度,而剩下的三个形成旋转向量,表示绕每个轴的角速度。让我们看看这在实践中是如何工作的。

使用 /cmd_vel 直接驱动 GoPiGo3

以前,我们关注的是最终用户的机器人控制视角,即键盘按键或鼠标点击和位移。现在,我们将从开发者的视角来发现如何实现相同类型的控制,这是你在 ROS 中构建新应用程序所需要的。让我们开始吧:

  1. 以 1 rad/s 的速率旋转机器人,通过命令行发布消息。记住,gopigo3 节点必须在机器人上运行:
T1 $ rostopic pub /cmd_vel geometry_msgs/Twist  'angular: {z: 1}'
 publishing and latching message. Press ctrl-C to terminate
  1. 一旦你输入命令,除了看到 GoPiGo3 向左旋转(逆时针)外,你还会收到关于该命令的伴随信息消息。

  2. 由于你没有指定发布频率,ROS 假设你想要保持该消息锁定,即永远运行。你可以通过在另一个终端使用 rostopic 来检查这一点:

T2 $ rostopic echo /cmd_vel
 linear: 
 x: 0.0
 y: 0.0
 z: 0.0
 angular: 
 x: 0.0
 y: 0.0
 z: 1.0
 ---
  1. 我们如何停止机器人?很简单——发送一个新的消息,将旋转设置为零:
T3 $ rostopic pub /cmd_vel geometry_msgs/Twist  'angular: {z: 0}'

你会看到 GoPiGo3 停止,并且 T2 抛出一个新的六值集合,告诉我们角 z 现在等于 0

  1. 下一步是引入以给定速率的消息更新。在 T1 中停止进程并写下以下命令:
T1 $ rostopic pub -r 0.5 /cmd_vel geometry_msgs/Twist  'angular: {z: 1}'

T2 中,你应该每 2 秒(= 0.5 Hz 频率)看到一个新消息。-r 选项(速率的缩写)是你用来指定消息发送频率的选项。由于你保持相同的旋转速度,你不会注意到机器人运动中的任何变化。

  1. 前往另一个终端,并在 /cmd_vel 主题上以相同的 0.5 Hz 频率发布双倍速度(2 rad/s):
T3 $ rostopic pub -r 0.5 /cmd_vel geometry_msgs/Twist  'angular: {z: 2}'

你会看到 GoPiGo3 在指定的速率下在 1 和 2 rad/s 的角速度之间交替。

  1. 前往 T4 并发送停止旋转命令:
T4 $ rostopic pub -r 0.5 /cmd_vel geometry_msgs/Twist 'angular: {z: 0}'

观察机器人每 2 秒停止一段时间。

  1. 如果你想让机器人更频繁地停止?在 T4 中停止进程,并以更高的 1 Hz 速率重新启动命令:
T4 $ rostopic pub -r 1 /cmd_vel geometry_msgs/Twist  'angular: {z: 0}'
  1. 我们如何让它停止更长时间?很简单——指定更高的频率,即 10 Hz。当使用这样的值时,你会发现 GoPiGo3 旋转非常少,因为每秒钟 10 次接收停止命令,覆盖了在 T1(1 rad/s)和 T3(2 rad/s)中设置的最近速度设置的效果。

  2. 最后,我们如何停止一切?按照以下步骤操作:

    1. 停止终端 T1。这通过将速度设置为 1 rad/s 来避免发送新消息。

    2. 停止终端T3。这将取消 2 弧度/秒的速度命令。此时,机器人只从最后一个终端接收零速度命令。机器人已经停止,但 ROS 仍在运行一些进程,尽管它们不是以机器人运动的形式可见。

    3. 停止终端T4。这确保 gopigo3 节点现在对/cmd_vel主题发布的任何消息都是空闲的。

    4. 你应该检查T2(使用$ rostopic echo /cmd_vel命令)是如何停止提供更新的。你也可以关闭这个终端。

尝试思考如果你首先采取的行动是停止T4会发生什么。通过将序列应用于物理机器人并查看结果来检查这一点。

在下一节中,你将在/cmd_vel主题中发布geometry_msgs/Twist消息,以了解你的机器人的实际XYZ轴。

检查 GoPiGo3 的 X、Y 和 Z 轴

现在,你将应用你所学的知识,实际找到机器人的XYZ轴及其正负方向。在T1中应用以下命令,并确保你准备好在之后用T2停止它,以避免机器人撞到任何障碍物:

T1 $ rostopic pub /cmd_vel geometry_msgs/Twist 'linear: {x: 0.1}'
T2 $ rostopic pub /cmd_vel geometry_msgs/Twist  'linear: {x: 0}'

你应该看到 GoPiGo3 向前移动,即距离传感器向前,而万向轮向后。对于负X轴,改变线性速度的符号:

T1 $ rostopic pub /cmd_vel geometry_msgs/Twist 'linear: {x: -0.1}'
T2 $ rostopic pub /cmd_vel geometry_msgs/Twist  'linear: {x: 0}'

你应该发现 GoPiGo 现在正在向后移动,即距离传感器向后。要猜测剩余轴Y的方向,记得在先前的子节中,你检查了围绕Z轴的旋转在机器人向左旋转(逆时针)时是正的。这意味着Z轴向上,指向天花板。由于你已经通过经验找到了XZ轴的朝向,你可以轻松推断出 Y 轴指向左侧。

为什么我们没有用rostopic pub检查Z轴?你有一个在平面上移动的机器人,即地板。如果你尝试为Z轴应用线性速度命令,你将什么也看不到,因为...好吧,GoPiGo3 不能飞!

留给你去推断为什么类似的线性速度命令对 GoPiGo3 的运动没有可见的影响。

组合运动

我们已经学习了如何找到机器人的实际 X、Y 和 Z 轴。如果你需要回忆如何做,请回到第四章,创建虚拟双轮 ROS 机器人使用 URDF 构建差速驱动机器人部分。

在考虑它们的朝向的同时,让我们通过结合平移(线性X)和旋转(角度Z)来设计一个更复杂的轨迹。目标是让 GoPiGo3 以 45º/s 的速度沿着半径为 0.25 米的圆周路径行驶:

  • 角度Z速度是 45º/s = 0.785 弧度/秒。我们只需要将单位从六十进制的度数转换为弧度。

  • 线性 X 速度可以通过将请求的半径乘以角速度来获得;即,0.25 m * 0.785 rad/s = 0.196 m

按照以下步骤进行:

  1. 在单个/cmd_vel消息中应用这两个值:
T1 $ rostopic pub /cmd_vel geometry_msgs/Twist '{linear: {x: 0.196}, angular: {z: 0.785}}'
  1. 要停止此过程,你可以发送一个消息,其中所有六个组件都等于零:
T2 $ rostopic pub -r 10 /cmd_vel geometry_msgs/Twist  '[0, 0, 0]' '[0, 0, 0]'

这是一个替代的geometry_msgs/Twist消息语法,其中你指定一个线性速度的三分量向量(按照顺序 XYZ),以及一个包含角速度三个分量的向量(同样按照顺序 XYZ)。

  1. 最后,通过发送分别针对平移和旋转的单独命令来检查叠加原理如何应用于运动合成:
T1 $ rostopic pub /cmd_vel geometry_msgs/Twist 'linear: {x: 0.196}'
T2 $ rostopic pub /cmd_vel geometry_msgs/Twist 'angular: {z: 0.785}'

T1中的命令使 GoPiGo3 以 0.196 m/s 的速度向前移动。然后,T2中的命令添加一个 0.785 rad/s 的角运动,从而产生 GoPiGo3 从直线运动到半径为 0.25 m 的圆周轨迹的预期轨迹。

停止机器人并关闭 Raspberry Pi。在下一节中,我们将切换到 Gazebo 模拟环境,因此我们只需要笔记本电脑。我们将在第五章中停止的地方返回 GoPiGo3 的虚拟模型,使用 Gazebo 模拟机器人行为

远程控制物理和虚拟机器人

到目前为止,你已经处理了一个配置片段,其中 ROS 主节点在机器人上运行。对于本章的其余部分,你将只使用笔记本电脑。因此,你需要调整你的配置,以便主节点可以再次定位在台式计算机上。否则,你会收到错误,并且无法启动任何 ROS 环境。

将 ROS 主机还原到本地计算机

解决这个问题很简单;只需遵循以下步骤:

  1. 打开你的本地.bashrc文件,并注释掉指定要指向以找到 ROS 主机的 URL 的最后一行:
$ nano ~./bashrc
 ...
 export ROS_HOSTNAME=rosbot.local
 # THIS LINE IS NOW A COMMENT # export ROS_MASTER_URI=http://gopigo3.local:11311

注意,在rosbot.local的位置,你应该使用你的当前主机名,<your-hostname>.local。如果你不确定,或者它还没有在你的配置文件中正确设置,只需在终端中运行$ hostname来回忆它。

  1. 关闭所有终端,打开一个新的终端,并检查ROS_MASTER_URI变量:
$ echo $ROS_MASTER_URI
 http://localhost:11311

你应该发现环境变量已经恢复到默认服务器(localhost)和默认端口(11311)。现在,我们准备好切换到虚拟机器人。

使用 Gazebo 模拟 GoPiGo3

回想一下我们在第五章中进行的 Gazebo 模拟,使用 Gazebo 模拟机器人行为部分。其文件已包含在本章的示例代码中,以便我们可以将它们作为起点。现在,按照以下步骤进行:

  1. 使用 GoPiGo3 模型启动 Gazebo 环境:
T1 $ roslaunch gazebo_control spawn.launch

尽管你可能发现启动文件名与第五章中“使用 Gazebo 模拟机器人行为”的代码中的文件名不同,但其内容完全相同,即gopigo_gazebo.launch

  1. 然后,在另一个终端中列出相关主题:
T2 $ rostopic list
 /clock
 /gazebo/link_states
 /gazebo/model_states
 /gazebo/parameter_descriptions
 /gazebo/parameter_updates
 /gazebo/set_link_state
 /gazebo/set_model_state
 /gazebo_gui/parameter_descriptions
 /gazebo_gui/parameter_updates
 /rosout
 /rosout_agg

所有新的主题都对应于 Gazebo 节点。

  1. 特别是,/clock主题是 Gazebo 发布时间戳的地方,从而允许具有模拟同步时间系统的存在。spawn.launch文件中/clock的参数定义如下:
<launch>
      ...
      <arg name="use_sim_time" default="true"/>
      ...
</launch>
  1. use_sim_time参数请求 ROS 模拟一个时钟,其时间戳在/clock上发布。你可以像往常一样检查主题及其消息类型:
T2 $ rostopic info /clock
 Type: rosgraph_msgs/Clock

 Publishers: 
 * /gazebo (http://rosbot.local:37865/)

 Subscribers: 
 * /rosout (http://rosbot.local:34729/)
 * /gazebo (http://rosbot.local:37865/)
 * /gazebo_gui (http://rosbot.local:38297/)

T2 $ rosmsg info rosgraph_msgs/Clock
 time clock

时钟消息类型属于rosgraph_msgs包。查看以下链接了解更多关于此包的信息:wiki.ros.org/rosgraph_msgs

现在我们已经设置了环境,我们可以添加一个虚拟控制器,这将允许我们以与之前使用物理机器人相同的方式在 Gazebo 中控制 GoPiGo3。

将控制器添加到机器人的 Gazebo 模型中

在这里,我们将使用一个名为控制器的机制,将速度/cmd_vel消息转换为机器人的左右轮的运动。对于两轮机器人的情况,这被称为差速驱动控制器。这个名字是因为通过以相同的速度旋转车轮来实现直线路径。左右角速度之间的任何差异都会使机器人描述一个圆周路径。差异越大,这样的圆的半径就越小(直线路径对应于描述一个无限半径的圆的扇形,即车轮之间无限小的速度差异)。

让我们进入实际部分:

  1. 要将差速驱动控制器包含到你的机器人模型中,请将以下片段添加到urdf/gopigo.gazebo文件的<robot>标签内:
<gazebo>
    <plugin filename="libgazebo_ros_diff_drive.so" name="differential_drive_controller">
       <alwaysOn>true</alwaysOn>
       <updateRate>20</updateRate>
       <leftJoint>joint_left_wheel</leftJoint>
       <rightJoint>joint_right_wheel</rightJoint>
       <wheelSeparation>0.4</wheelSeparation>
       <wheelDiameter>0.2</wheelDiameter>
       <torque>0.1</torque>
       <commandTopic>cmd_vel</commandTopic>
       <odometryTopic>odom</odometryTopic>
       <odometryFrame>odom</odometryFrame>
       <robotBaseFrame>base_link</robotBaseFrame>
    </plugin>
 </gazebo>

你必须设置这些对应关系,这些对应关系定义在你的机器人 URDF 模型中:

    • <robotBaseFrame>对应于base_link

    • <leftJoint>对应于joint_left_wheel

    • <rightJoint>对应于joint_right_wheel

<torque>标签是您指定每个可以施加的最大扭矩的地方。这就是您需要了解的,以便执行我们在第五章中建议的实验,即“使用 Gazebo 模拟机器人行为”章节的最后部分,“调整 Gazebo 模型的指南”。

  1. 注意你是如何通过将<commandTopic>标签设置为 ROS 的/cmd_vel主题来告诉控制器它将接收哪些运动命令的:
...
<commandTopic>cmd_vel</commandTopic>
...
  1. 然后,停止并重新启动 Gazebo 以找到差速驱动为模拟提供的新的功能:
T1 $ roslaunch gazebo_control spawn.launch
  1. 有两个新的主题,/cmd_vel/tf
T2 $ rostopic list
 ...
 /cmd_vel
 ...
 /tf
  1. 记住从上一节中我们使用/cmd_vel主题控制物理 GoPiGo3。按照相同的步骤,你可以使用键盘(箭头键)进行遥操作虚拟机器人:
T2 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

确保你处于启动T2的窗口,这样任何按键都会被捕获,你可以在 Gazebo 中看到它们对机器人产生的影响。

  1. 使用T3 $ rqt_graph可视化 ROS 图。你应该获得以下图中所示的结果:

图片

和之前一样,T2终端中重映射/key_vel:=/cmd_vel的主题允许使用箭头键控制虚拟机器人。

最后,我们将把物理机器人和虚拟机器人在同一个 ROS 环境中连接起来。

同时进行现实世界和模拟

由于我们将与机器人一起工作,我们需要在笔记本电脑上指定 ROS 主节点,使其指向 GoPiGo3。为此,在本地.bashrc文件中取消以下行的注释以切换回该配置:

export ROS_MASTER_URI=http://gopigo3.local:11311

你不需要关闭其他 bash 终端,你可以在每个终端中重新加载更新的.bashrc

$ source ~/.bashrc

现在,你可以使用两种版本的 GoPiGo3 执行 ROS:

  1. 首先,启动机器人:
$ roslaunch mygopigo gopigo3.launch

  1. 然后,在笔记本电脑上启动剩余的 ROS 环境。首先,启动键盘控制:
T1 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel 
  1. 接下来,启动虚拟模型:
T2 $ roslaunch gazebo_control spawn.launch
  1. 最后,检查 ROS 图:
T3 $ rqt_graph

如果一切顺利,你应该看到以下熟悉的图:

图片

这表示你应该能够同时使用相同的按键(key_teleop节点)控制物理机器人(gopigo3节点)和虚拟机器人(gazebo节点)。

  1. 通过点击笔记本电脑键盘的左右箭头键检查遥操作。物理和虚拟 GoPiGo3 将同时旋转。如果你按下箭头键,也会得到类似的结果。

为了使按键有效,你需要选择启动key_teleop.py节点的终端窗口,即T2

恭喜你——你已经成功完成了一次如何使用 ROS 控制机器人的全程之旅!

概述

在本章中,你已经在物理 GoPiGo3 和其在 Gazebo 中的虚拟模型之间建立了对应关系。你已经检查了从 ROS 的角度来看,控制实际机器人或虚拟机器人并没有区别。因为两者都是使用相同的主题/cmd_vel移动,ROS 不会关心你处理的是哪种类型的机器人。

这个事实解释了从 ROS 的角度来看,你可以选择用虚拟机器人测试你的代码,然后安全地将其应用到物理机器人上。我们只需要启动物理机器人的 ROS 节点。这在三种情况下很有用。首先,当你为现有的机器人开发新应用程序时,你可以用 Gazebo 中的虚拟模型进行代码调试。其次,当你没有可用的机器人硬件时——因为你还在决定购买哪一款——你可以在购买决定之前虚拟地玩耍和测试机器人。第三,当你设计新机器人时,你可以选择并行启动其机械设计和软件开发。当你这样做时,开发的代码将在进行任何制造活动之前向你的机械设计发送反馈。这个迭代循环应该通过降低开发成本来使最终产品受益。

在下一章中,你将面对机器人的第一个自主任务:让它意识到其环境,并能够导航到达预定义的目的地。这些任务将向你介绍同时定位与建图SLAM)技术,这是一种在所有类型的自主车辆中广泛使用的算法,尤其是在自动驾驶汽车中。

问题

  1. 如果你需要让两台不同的计算机通过 ROS 进行通信,你应该将 ROS 主节点放在哪里?

A) 在拥有更新版本 ROS 的那个上。

B) ROS 主节点将在你启动roscore进程的第一个计算机上运行。

C) 你可以将主节点放在你想要的位置。在一台计算机上运行roscore,在另一台计算机上,你告诉 ROS 主节点位于另一台机器上。

  1. 你有一个名为mazinger_zeta的物理机器人,它接受/walk主题中的geometry_msgs/Twist消息。使用键盘远程控制机器人时,正确的主题重映射命令是什么?

A) rosrun key_teleop key_teleop.py /walk:=/cmd_vel

B)rosrun key_teleop key_teleop.py /key_vel:=/walk

C) rosrun key_teleop key_teleop.py /walk:=/key_vel

  1. 如果你将 1 m/s 的速度命令应用到Y轴,GoPiGo3 会如何移动?

A) 机器人将向左以 1 m/s 的速度移动

B) 不会发生任何事情

C) 你需要指定一个发布频率,以便命令生效

  1. 这个命令对物理机器人有什么可见的影响?
T1 $ rostopic pub /cmd_vel geometry_msgs/Twist 'angular: {z: 90}'

A) 它将以最大可能的速度旋转,因为这是低于 90 弧度/秒的。

B) GoPiGo3 不能以如此大的角速度移动。

C) 由于它超过了 GoPiGo3 能处理的最大速度,机器人将保持静止。

  1. 如果你同时用键盘控制 GoPiGo3 和虚拟机器人,如果你告诉它们每 2 秒旋转π弧度,你会注意到什么区别?

A) 它们都会完成一个 360º的完整转弯。

B) 虚拟机器人将旋转 360 度,但物理机器人不会完成整个转弯,因为车轮与地板之间的摩擦力会产生相反的力。

C) 虚拟机器人将精确旋转 360 度,但物理机器人不会,因为轮编码器的精度有限。

进一步阅读

第八章:使用 Gazebo 的虚拟 SLAM 和导航

在本章中,您将了解机器人导航的概念和组件。通过 SLAM(即 Simultaneous Localization and Mapping)技术,您将能够使用 GoPiGo3 执行自主导航。本章涉及模拟的高级主题。因此,您理解前一章中的概念至关重要,在那里我们向您提供了与 Gazebo 中的虚拟机器人交互的基础。

SLAM 是一种在机器人中用于探索和绘制未知环境的同时估计机器人自身位姿的技术。随着它的移动,它将通过处理来自其传感器的原始数据来获取周围环境的结构化信息。

您将使用 GoPiGo3 的数字孪生以实用的方法探索这个概念,清楚地理解为什么需要 SLAM 实现才能进行适当的导航。模拟将在 Gazebo 中运行,这是 ROS 原生模拟工具,具有提供逼真结果的物理引擎。

本章将涵盖以下主题:

  • 使用 Gazebo 进行动态模拟

  • 导航中的组件

  • 机器人感知和 SLAM

  • 使用 GoPiGo3 练习 SLAM 和导航

通过涵盖这些主题,您将更加熟悉 Gazebo 环境。您将理解导航和 SLAM 的概念以及它们之间的关系。通过非常实用的方法,您将学习在 Gazebo 中使用机器人的虚拟模型运行 SLAM 和导航任务。

技术要求

为了总结和阐明本章关于虚拟机器人的步骤以及下一章关于物理 GoPiGo3 的目的,以下列表显示了我们将要使用的所有这些传感器和执行器,以及前几章中处理每个部分的章节:

  • 距离传感器:在第六章,在 ROS 中编程 – 命令和工具案例研究 1:发布和读取距离传感器部分教您如何在 ROS 中使用物理机器人使用距离传感器。

  • 线跟踪器。请参阅以下列表以获取组装和单元测试说明。

  • IMU 传感器。请参阅以下列表以获取组装和单元测试说明。

  • Pi 相机:在第六章,在 ROS 中编程 – 命令和工具案例研究 1:发布和读取距离传感器部分教您如何在 ROS 中使用物理机器人使用 Pi 相机。

  • 驱动电机和编码器:在前一章中,案例研究 3:使用键盘进行遥控部分首先教您如何在 ROS 中使用物理机器人使用这些项目,然后如何在 Gazebo 模拟器下实现差分驱动控制器。

对于所有这些,您有以下内容:

  • 组装说明,可以在 第一章 的 深入电机械学 部分、组装机器人 中找到

  • 单元测试说明,可以在 第二章 的 单元测试传感器和驱动 部分、GoPiGo3 单元测试 中找到,其中提供的软件教您如何使用 Python 进行单元测试

为了对 SLAM 主题进行最佳且易于理解的覆盖,我们将在虚拟机器人中实现 360° 覆盖的 激光测距传感器LDS)。该传感器技术有低成本版本,例如 EAI YDLIDAR X4(可在 www.aliexpress.com/item/32908156152.html 购买),这是我们将在下一章中使用的。

在本章中,我们将使用位于 github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter8_Virtual_SLAMChapter8_Virtual_SLAM 文件夹中的代码。将其文件复制到 ROS 工作空间以使其可用,并将其余部分放在 src 文件夹之外。这样,您将拥有一个更干净的 ROS 环境:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter8_Virtual_SLAM ~/catkin_ws/src/

代码包含以下两个新的 ROS 包:

  • gopigo3_description 包包含 URDF 模型以及用于完整、动态模拟的 SDF(Gazebo 标签)。此包提供了 gopigo3_rviz.launch 启动文件,用于在 RViz 中交互式可视化模型。

  • virtual_slam 包含虚拟机器人模拟本身以及运行 Gazebo 中 SLAM 所需的启动文件。

然后,重新构建工作空间,使其为您的 ROS 安装所知:

$ cd ~/catkin_ws
$ catkin_make

通过选择它们并列出文件来检查包是否已正确安装:

$ rospack list | grep gopigo3

然后您需要做一些安装和配置才能运行练习,如下所示。

ROS 导航包

以下步骤提供了在 Ubuntu 16.04 上运行的 ROS Kinetic 版本的安装说明:

  1. 首先,让我们准备您的机器,安装导航堆栈所需的 ROS 包:
$ sudo apt install ros-kinetic-navigation ros-kinetic-amcl ros-kinetic-map-server ros-kinetic-move-base ros-kinetic-urdf ros-kinetic-xacro ros-kinetic-compressed-image-transport ros-kinetic-rqt-image-view
  1. ROS Kinetic 中,您可以从二进制文件安装 slam_gmapping
$ sudo apt-get install ros-kinetic-slam-gmapping

这将安装 gmappingopenslam_gmapping 包。如果您正在使用 ROS Melodic(即,您在 Ubuntu 18.04 上):

  • 安装 Melodic 的相应版本:
$ sudo apt install ros-melodic-navigation ros-melodic-amcl ros-melodic-map-server ros-melodic-move-base ros-melodic-urdf ros-melodic-xacro ros-melodic-compressed-image-transport ros-melodic-rqt-image-view
  • 最后是 slam_gmapping 包,在撰写本文时,它已经以二进制版本提供:
sudo apt-get install ros-melodic-slam-gmapping

在本地计算机上运行的 ROS 主

由于在本章中,您将只使用您的本地计算机,因此您需要重新配置 ROS 主 URI,使其不指向机器人,而是指向您的本地计算机。然后,打开您的本地 .bashrc 文件,注释掉指定 ROS 主可以找到的 URL 的最后一行:

$ nano ~./bashrc
 ...
 export ROS_HOSTNAME=rosbot.local
 # THIS LINE IS NOW A COMMENT # export ROS_MASTER_URI=http://gopigo3.local:11311

关闭所有终端,打开一个新的终端,并检查 ROS_MASTER_URI 变量:

$ echo $ROS_MASTER_URI
 http://localhost:11311

你应该会发现环境变量已恢复到默认服务器(localhost)和默认端口(11311)。现在,我们准备切换到虚拟机器人。

使用 Gazebo 进行动态模拟

在上一章中,你执行了一个非常基本的导航版本,其中关于机器人环境的反馈始终来自你作为人类操作员。例如,你看到 GoPiGo3 正在向一个障碍物前进,所以你让它向左或向右转以避开它。

本节通过提供来自你的人类视觉以及机器人传感器的反馈,使你在遥控方面前进了一步。更确切地说,GoPiGo3 将提供来自 Pi 相机和其距离传感器的数据。目标是让你通过获取尽可能高质量的传感器数据来更精确地遥控它。你至少可以猜出在现实世界中至少两种常见的场景,这种手动遥控对于执行计划中的任务至关重要:

  • 手术机器人遥操作:在这种情况下,专家外科医生可以在患者被照顾的手术室外进行手术操作。

  • 遥操作救援机器人:这在人类无法自行到达的位置的事故中使用,例如洪水发生时山脉之间的峡谷,或者在需要避免直接人类存在的灾难中,例如在放射性水平如此之高以至于暴露的人类在几分钟内就会吸收致命辐射的核灾难中。

在心中牢记这些关键点,你应该不仅将这一部分视为进入自主导航之前的一个先前的学习步骤,而且还将其视为一种激励性的介绍,介绍一种在现实世界中与遥操作机器人共同工作的常见方式。

向 GoPiGo3 模型添加传感器

到目前为止,你应该已经为你的虚拟机器人配备了一个差速驱动控制器,该控制器可以将速度命令转换为左右轮的旋转。我们需要通过某种方式感知环境来完善模型。为此,我们将添加两个常见传感器的控制器,一个二维相机和一个 LDS。第一个对应于你物理机器人的 Pi 相机,而第二个是 GoPiGo3 套件中的单向距离传感器。

相机模型

你可以像往常一样使用<visual>标签添加相机的实体,但由于它是一个商业设备,你可以通过使用制造商提供的或开源社区中其他人制作的逼真的三维 CAD 模型来获得更好的外观。URDF 定义如下:

<link name="camera">
  <visual>
    <origin xyz="0.25 0 0.05" rpy="0 1.570795 0" />
    <geometry>
      <mesh filename="package://virtual_slam/meshes/piCamera.stl" scale="0.5 0.5 0.5"/>
    </geometry>
  </visual>
  ...
</link>

<joint name="joint_camera" type="fixed">
    <parent link="base_link"/>
    <child link="camera"/>
    <origin xyz="0 0 0" rpy="0 0 0" /> 
    <axis xyz="1 0 0" />
</joint>

我们可以在前面的片段中看到两个块:用于指定实体的<link>元素,以及用于将相机连接到机器人底盘的<joint>块。由于相机是刚性连接到身体上的,我们指定type="fixed">来模拟这种特性。

关于 <link> 元素,我们引入了 <mesh> 标签来导入来自 CAD DAE 文件类型的几何形状,如前文片段中加粗所示。以下截图显示了相机的 CAD 模型:

然后我们使用 <gazebo> 标签添加相机技术特性:

<gazebo reference="camera">
  <sensor type="camera" name="camera1">
    <update_rate>30.0</update_rate>
    <camera name="front">
      <horizontal_fov>1.3962634</horizontal_fov>
      <image>
        <width>800</width>
        <height>800</height>
        <format>R8G8B8</format>
      </image>
    <clip>
      <near>0.02</near>
      <far>300</far>
    </clip>
    </camera>
    <!-- plugin "camera_controller" filename="libgazebo_ros_camera.so" -->
  </sensor>
</gazebo>

<update_rate> 标签指定传感器以 30 Hz 的频率读取,即每秒读取 30 张图像。最后,我们添加了模拟相机行为的 Gazebo 插件。以下片段是替换先前代码块中注释行中提到的 plugin "camera_controller" 的内容:

      <plugin name="camera_controller" filename="libgazebo_ros_camera.so">
        <alwaysOn>true</alwaysOn>
        <updateRate>0.0</updateRate>
        <cameraName>gopigo/camera1</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>

相机控制器位于 libgazebo_ros_camera.so 文件中,因此你在这个块中提供的是你使用的相机的技术规格。将 <updateRate> 设置为 0.0 表示 Gazebo 应该从先前的 <sensor> 标签中获取刷新率,即 30 Hz。如指定(见粗体字字段),相机图像将在 /gopigo/camera1/image_raw 主题中发布。

启动 ROS 可视化工具以检查模型是否正确构建。由于 RViz 只代表其视觉特征——它不包括任何物理仿真引擎——因此它比 Gazebo 轻得多,你可以使用所有选项来检查模型的每个外观方面:

$ roslaunch gopigo3_description gopigo3_basic_rviz.launch

此启动文件与你在第四章创建虚拟两轮 ROS 机器人中使用的启动文件非常相似。以下截图显示了你应该看到的成果:

在下一节中,你将进行一个实际练习,以了解如何使用 Gazebo 来操作相机。

模拟相机

按照以下步骤进行模拟:

  1. 让我们先以与上一章相同的方式将机器人放置在 Gazebo 中,并启用键盘远程控制:
T1 $ roslaunch virtual_slam gopigo3_basic_world.launch 
T2 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

key_teleop 允许你使用键盘的箭头键远程控制 GoPiGo3。

  1. 现在,从预装在 ROS 中的 image_view 包启动一个节点:
T3 $ rosrun image_view image_view image:=/gopigo/camera1/image_raw

我们正在重新映射 image 主题,以便节点从相机节点主题 /gopigo/camera1/image_raw 获取数据。此主题在相机控制器插件的先前片段中定义,结合了 <imageTopicName>

<cameraInfoTopicName> 标签。使用箭头键远程操作机器人,你将在图像窗口中看到主观视图:

背景窗口对应于 Gazebo(从终端 T1 启动),在那里您可以看到虚拟机器人正在观察交通锥。主观视图显示在左侧窗口(T2),由 Pi 相机图像实时流提供,使用 image_view 包。最后,左下角的窗口(T3)是您需要选择以能够使用键盘上的箭头键移动机器人的窗口。我们已经使用它们将机器人放置在交通锥前面,如前面的截图所示。

在这一点上,让我们使用众所周知的命令 rqt_graph 获取 ROS 图,并查看图像主题重映射是如何处理的:

图片

感谢映射参数 image:=/gopigo/camera1/image_rawimage_view 包的 image 主题保持隐式,仅可见 /gopigo/camera1/image_raw

您是否意识到,当您使用预构建的 ROS 模块和自定义机器人定义时,交付机器人行为是多么快速和简单?在下一节中,我们将为第二个传感器介绍这些相同的步骤。

距离传感器

我们通过遵循我们之前介绍的用于摄像头的相同程序,在 <visual> 标签下添加此传感器的实体模型。URDF 定义如下:

<joint name="distance_sensor_solid_joint" type="fixed">
    <axis xyz="0 1 0" />
    <origin rpy="0 0 0" xyz="0 0 0" />
    <parent link="base_link"/>
    <child link="distance_sensor_solid"/>
  </joint>

  <link name="distance_sensor_solid">
    <visual>
      <origin xyz="0.2 0 0.155" rpy="1.570795 0 1.570795" />
      <geometry>
        <mesh filename="package://gopigo3_description/meshes/IR_Sensor_Sharp_GP2Y_solid.stl" scale="0.005 0.005 0.005"/>
      </geometry>
      <material name="red"/>
    </visual>
    ...
  </link>

在前面的代码片段中,我们可以看到两个块:用于指定实体的 <link> 元素,以及用于将传感器主体连接到机器人底盘的 <joint> 块。由于距离传感器是刚性连接到机器人底盘的,我们指定 type="fixed"> 来模拟这一特性。我们使用的实体模型如下截图所示。在这种情况下,我们使用 STL 格式的 CAD 模型,并通过 <mesh> 标签引用它:

图片

我们将在另一个实体中基于传感器本身,因为如果您使用前面截图中的实体,您将在 Gazebo 中看到距离射线被实体阻挡,因此传感器将始终产生零距离值。所以,我们将向您解释一个技巧,通过这个技巧您可以分离传感器的实体模型和位于链接框架原点的传感点:

<joint name="distance_sensor_joint" type="fixed">
    <axis xyz="0 1 0" />
    <origin rpy="0 0 0" xyz="0.245 0 0.13" />
    <parent link="base_link"/>
    <child link="distance_sensor"/>
</joint>
<link name="distance_sensor">
    <visual>
         <origin xyz="0 0 0" rpy="0 0 0"/>
         <geometry>
             <box size="0.01 0.01 0.01"/>
         </geometry>
         <material name="red"/>
    </visual>
</link>

此代码片段创建了一个 10 cm x 10 cm 的盒子,并将其放置在 <joint> 标签指定的坐标中。

然后我们使用 <gazebo> 标签添加传感器技术特性,您可以看到它引用了前面代码片段中定义的 distance_sensor 链接(不是 distance_sensor_solid):

<gazebo reference="distance_sensor"> 
   <sensor type="ray" name="laser_distance">
      <visualize>true</visualize>
      <update_rate>10</update_rate>
      <ray>
         ...
         <range>
            <min>0.01</min>
            <max>3</max>
            <resolution>0.01</resolution>
         </range>
      </ray>
      <!-- plugin filename="libgazebo_ros_range.so" name="gazebo_ros_ir" -->
    </sensor> 
   </gazebo>

<update_rate> 标签指定传感器以 10 Hz 的频率读取,而 <range> 标签设置测量距离值在 10 cm 到 3 m 之间,分辨率为 1 cm。

<visualize>**true**</visualize> 标签块允许您在 Gazebo 中看到距离传感器的激光射线覆盖 <range> 标签中解释的极限;也就是说,其检测范围达到 3 米。

最后,我们添加了模拟距离传感器行为的 Gazebo 插件。以下片段是替换先前代码块中注释掉的plugin "gazebo_ros_ir"行:

       <plugin filename="libgazebo_ros_range.so" name="gazebo_ros_ir">
         <gaussianNoise>0.005</gaussianNoise>
         <alwaysOn>true</alwaysOn>
         <updateRate>0.0</updateRate>
             <topicName>gopigo/distance_sensor</topicName>
             <frameName>distance_sensor</frameName>
         <radiation>INFRARED</radiation>
         <fov>0.02</fov>
       </plugin> 

距离传感器的控制器位于libgazebo_ros_range.so文件中,因此您在这个块中提供的是您所使用传感器的技术规格。将<updateRate>标签设置为0.0意味着 Gazebo 应该从先前的<sensor>标签中获取刷新率,即 10 Hz。如指定(请参阅粗体字字段),范围值将在/sensor/ir_front主题中发布。

启动 ROS 可视化工具以检查模型是否正确构建。由于RViz仅表示其视觉特征,它比 Gazebo 轻得多,您有所有选项来检查模型的每个外观方面:

$ roslaunch gopigo3_description gopigo3_basic_rviz.launch

在下面的屏幕截图中,您可以一起看到我们之前包括的相机所得到的结果:

在下一节中,您将通过实际练习看到在 Gazebo 下使用距离传感器的工作方式。

模拟距离传感器

这个测试包括距离传感器和二维相机。使用以下代码中的四个终端运行示例:

T1 $ roslaunch virtual_slam gopigo3_basic_world.launch
T2 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel
T3 $ rostopic echo /gopigo/distance_sensor
T4 $ rosrun image_view image_view image:=/gopigo/camera1/image_raw

下面的屏幕截图是您应该获得的结果的组合视图:

在前面的屏幕截图中,您可以找到以下组件:

  • 中央窗口是 Gazebo 窗口,您可以看到 GoPiGo3、一个障碍物和距离传感器的射线。

  • 顶部左边的灰色窗口是我们需要选择的窗口,以便箭头键的推力被接收为远程控制的/cmd_vel主题消息。

  • 底部左边的黑色窗口实时显示发送到距离传感器主题的消息,即/gopigo/distance_sensor。当前到障碍物的距离在range字段中找到,值为 1.13 米。

  • 右侧的窗口显示了机器人通过其二维相机看到的实时视图,该视图通过/gopigo/camera1/image_raw主题接收。

您可以手动驾驶从场景的一侧到另一侧,而不会撞到任何家具。您作为人类规划最优路径,并执行它,将机器人带到目的地目标,同时避开障碍物。您之前所做的是机器人现在必须自己做的,尽可能好地执行。这项任务被称为导航,这是我们将在下一节中要讨论的内容。

导航组件

导航是机器人按照计划轨迹从当前位置移动到目标位置的过程。在机器人中,这种能力意味着它能够确定轨迹上的任何位置,以及根据环境表示(如地图)制定行动计划。我们还应该添加避免动态障碍物或第一次构建地图时未存在的其他障碍物的能力。

在构建导航能力时,需要考虑四个组成部分:

  • 环境图,预先存在并作为输入提供给机器人,或者通过其自身手段使用其传感器收集的感官数据来构建。这个过程,即数据采集加上解释,构成了我们所说的机器人感知能力。一个利用机器人感知的著名技术被称为 SLAM,如前所述。

  • 实时姿态,理解为机器人相对于环境中的固定参考系在位置和旋转(统称为姿态)方面的定位能力。在机器人技术中,用于获取姿态的典型技术被称为死 reckoning,其中当前姿态是相对于前一个姿态加上内部里程计数据(来自电机的旋转编码器)和 IMU 传感器数据来估计的,以减少这些计算的错误。GoPiGo3 中都有这两种。

  • 机器人感知,源于传感器数据及其解释的结合,使机器人意识到周围的对象和障碍物。在 GoPiGo3 中,有助于感知的传感器是距离传感器、Pi 相机和 LDS。

  • 路径规划和执行,包括计算最佳路径及其执行,以便机器人能够达到目标位置。由于地图不包含环境的所有细节,并且可能存在动态障碍物,因此路径规划也应该是动态的。其算法将越好,因为它将能够适应环境中的变化条件。

接下来,我们将介绍成本图,这是导航基础上的一个关键概念。

安全导航的成本图

机器人导航的成本图源于机器人姿态的结合,该姿态由里程计数据(编码器)和 IMU 传感器估计,以及使用距离传感器和 LDS 在环境中感知到的对象和障碍物,以及从 SLAM 技术获得的占用栅格地图OGM)。

这些信息源提供障碍区域、可能的碰撞和机器人可移动区域的联合测量。存在一个全局成本图和一个局部成本图。全局成本图通过 SLAM 获得的固定地图来计算导航路径,而局部版本允许机器人处理其即时环境的细粒度细节,以绕过障碍物并避免碰撞。

costmap,无论是局部还是全局,都是以 8 位范围为测量单位,即网格占用图中的每个单元格的值从 0 到 255。0 值表示空闲区域,而 255 表示占用区域。接近 255 的值表示碰撞区域,而中间值从低碰撞概率(0-127)到高(128-252)。

在下一节中,我们将最终处理 SLAM,这是机器人导航的核心技术。作为一个起点,我们将通过集成 LDS 来完成 GoPiGo3 感知能力的设置。

机器人感知和 SLAM

在 ROS 中实现机器人导航最直接的方法是使用提供 360°覆盖范围的 LDS,使机器人能够感知其周围的所有物体和障碍物。

在本章的介绍中,我们确定了EAI YDLIDAR X4作为一个低成本选项,可以与我们的物理机器人集成。这将在下一章中介绍,而在本章中,我们将开发其虚拟模型以集成到 Gazebo 中。

下一个子节扩展了我们本章中工作的虚拟 GoPiGo3,以包括这个特定的 LDS 模型。之后,我们将部署一个快速 SLAM 示例,以了解该功能可以为机器人导航提供哪些概述。

添加激光测距传感器(LDS)

添加传感器的过程与我们在上一节中为距离传感器所做的工作类似。按照以下步骤进行操作:

  1. 我们按照之前介绍的方法,在<visual>标签下添加这个传感器的实体模型。URDF 定义如下:
  <link name="base_scan">
    <visual name="sensor_body">
      <origin xyz="0 0 0" rpy="0 0 0" />
      <geometry>
        <mesh filename="package://gopigo3_description/meshes/TB3_lds-01.stl" scale="0.003 0.003 0.003"/> 
      </geometry>
      <material name="yellow"/>
    </visual>
    <visual name="support">
      <origin xyz="0 0 -0.0625" rpy="0 0 0" />
      <geometry>
        <cylinder length="0.12" radius="0.1" />
      </geometry>
    </visual> 
  </link>

我们可以在前面的片段中的<link>元素内看到两个<visual>块:sensor_body是 LDS 本身,而support创建了传感器和机器人底盘之间的物理接口。我们用于传感器主体的实体模型是以下屏幕截图所示,它由 STL 格式的 CAD 模型组成,该模型引用自<mesh>标签:

图片

  1. 接下来,我们在机器人底盘上附加传感器组件,添加一个<joint>元素,其<type="fixed">
  <joint name="scan_joint" type="fixed">
    <parent link="base_link"/>
    <child link="base_scan"/>
    <origin xyz="-0.1 0 0.25" rpy="0 0 0"/>
  </joint>
  1. 然后,我们使用<gazebo>标签添加传感器技术特性,该标签指向前面片段中定义的distance_sensor链接(不是distance_sensor_solid):
  <gazebo reference="base_scan">
    <sensor type="ray" name="lds_lfcd_sensor">
      <visualize>true</visualize>
      <update_rate>5</update_rate>
      <ray>
        <scan>
          <horizontal> <samples>721</samples> ... </horizontal>
        </scan>
        <range>
          <min>0.12</min>
          <max>10</max>
          <resolution>0.015</resolution>
        </range>
      </ray>
        <!-- plugin name="gazebo_ros_lds_lfcd_controller" filename="libgazebo_ros_laser.so" -->
    </sensor>
  </gazebo>

<range>标签设置了从 12 厘米到 10 米的测量距离值,如 EAI YDLIDAR X4 的技术规范中所示。请注意<visualize>true</visualize>标签,因为对于这种具有 360°视场的传感器,屏幕将被射线填满以显示它覆盖的角度范围。建议在视觉检查确认传感器正常工作后将其设置为false

<visualize>true</visualize> 标签块对于距离传感器具有与之前章节中当我们构建其模型时相同的意义和效果,在 距离传感器 子节中解释过。唯一的区别是,LDS 以 360º 的覆盖范围覆盖所有角度,追踪 <samples> 标签内指定的样本数量那么多条射线。

<update_rate> 标签指定传感器以 5 Hz 的频率读取,但 LDS 的规格是 5,000 Hz。我们为什么不使用实际值?这是出于 CPU 使用率的原因:

请记住,如果我们将其读取频率设置为其实际物理能力,它将每秒读取 5,000 个样本,每个样本是一个包含 720 个点的向量。

由于 LDS 覆盖了所有可能的方向,为了得到 0.5º 均匀分布的 720 条射线,您需要多放一个样本,即 721,因为 0º 和 360º 实际上是同一个角度。

每个点将由两个浮点值(64 位)来表征,因此每个样本需要 720 x 2 x 64 = 92160 位 = 11 Kb。由于会有 5,000 个样本,我们需要 53 Mb/s 的带宽。这是一个巨大的值,需要由 Raspberry Pi CPU 来管理。

由于机器人将以低速移动,没有必要进行如此高频率的读取,因此我们可以将其限制为仅 5 Hz,这对机器人行为没有任何影响。这将只需要 55 Kb/s 的带宽,比传感器可以提供的低 1,000 倍。

这是一个明确的例子,说明了为什么您不应该直接在 Gazebo 中引入传感器的规格,因为它可能会影响模拟的性能。您需要批判性地分析每个传感器,并决定在它的虚拟控制器中设置哪些参数,以便它能很好地重现实际行为,同时不会不必要地过载 CPU。

  1. 下一步是添加模拟距离传感器行为的 Gazebo 插件。以下片段是替换前面代码块中注释的行,该行引用 plugin "gazebo_ros_lds_lfcd_controller"
      <plugin name="gazebo_ros_lds_lfcd_controller" filename="libgazebo_ros_laser.so">
        <topicName>/gopigo/scan</topicName>
        <frameName>base_scan</frameName>
      </plugin>

距离传感器的控制器位于 libgazebo_ros_laser_range.so 文件中,因此您在这个块中提供的是您想要覆盖前面片段中 <sensor> 标签提供的值的传感器技术规格。如指定(请参阅粗体字母中的字段),范围值将在 /gopigo/scan 主题中发布。

  1. 最后,启动 ROS 可视化工具以检查模型是否正确构建。由于 RViz 只代表其视觉特征,它比 Gazebo 轻得多,您有所有选项来检查模型的每个外观方面:
$ roslaunch gopigo3_description gopigo3_rviz.launch

在下面的屏幕截图中,您可以一起看到我们之前包括的相机和结果:

在下一个子节中,您将进行一个实际练习,以了解在 Gazebo 下激光距离传感器是如何工作的。

模拟 LDS

在虚拟机器人中包含 LDS 模型后,我们可以通过在 Gazebo 中运行模拟来查看它的工作情况:

  1. 在单独的终端中执行以下命令以查看传感器的工作情况:
T1 $ roslaunch virtual_slam gopigo3_world.launch
T2 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel
T3 $ rostopic echo /gopigo/scan

T3中,你会看到大量数据,因为每个LaserScan消息包含 720 个点来覆盖传感器周围的 360°视图。

  1. 为了测试这个传感器,最好使用一个 Python 脚本,使机器人在环境中游荡,同时避开障碍物。为此,我们在脚本中实现了以下规则:
    • 如果没有障碍物,以 0.8 m/s 的参考速度前进。

    • 如果距离传感器的范围低于 2 米,就后退并逆时针旋转,直到避开障碍物。

    • 由于距离传感器提供单向测量,我们应该检查 LDS 的测量值,以确定是否存在侧面的障碍物,阈值应低于 1.6 米。如果检测到障碍物,就后退并更快地逆时针旋转,以避开障碍物,并且不要卡在它上面。

这个简单的算法在wanderAround.py脚本中实现,可以在./virtual_slam/scripts/wanderAround.py文件夹下找到。

  1. 现在,试一试,享受观看 GoPiGo3 如何从一个世界的一边游荡到另一边,同时避开障碍物。要运行的序列如下:
T1 $ roslaunch virtual_slam gopigo3_world.launch
T2 $ rosrun virtual_slam wanderAround.py

以下截图显示了机器人四处游荡的情况:

图片

为了完成这一部分,我们将简要介绍 SLAM 理论的关键概念,以便在我们进入本章最后部分,即机器人导航实现的实际部分时,你知道底下的情况。

SLAM 概念

SLAM 允许机器人使用以下两种信息来源构建环境地图:

  • 机器人姿态估计,来源于内部里程计(旋转编码器)和 IMU 传感器数据

  • 到物体、障碍物和墙壁的距离,来自距离传感器,特别是 LDS

在其最基本版本中,地图包括二维信息,而在更高级的应用中使用工业级 LIDAR 传感器时,通过 LIDAR 和/或三维摄像头提供的三维信息构建了一个更丰富的地图。为了我们学习路径的目的,我们将处理二维 OGM,这在 ROS 项目中也很常见。

占位网格地图(OGM)

以一个有四个静态障碍物的正方形房间为例。以下图显示了在 ROS 中使用 SLAM 生成的地图(你稍后会学习如何自己生成它):

图片

在这样的二维地图中,空闲区域和占用区域以不同的灰度强度(8 位格式,0-255 范围,如前面在描述成本图时已提到)绘制。然后,每个单元格的占用概率作为 255 与强度值的差值除以 255 获得。这意味着以下:

  • 白色区域(255 值)表示 0%的概率;也就是说,其中没有障碍物。

  • 黑色区域(0 值)表示 100%的概率;也就是说,它们被占用。

这种概率分布允许构建一个成本图,帮助机器人确定选择哪个轨迹以达到目标位置。当发布到 ROS 时,占用概率转换为介于 0(0%概率,即空闲空间)和 100(100%,即占用空间)之间的整数值。未知区域分配值为-1。地图信息使用两个文件存储:

  • .pgm 格式文件,称为便携式灰度图格式。

  • 包含地图配置的 .yaml 文件。请参阅以下内容的示例:

image: ./test_map.pgm
resolution: 0.010000
origin: [-20.000000, -20.000000, 0.000000]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196

最有趣的参数是最后两个:

  • occupied_thresh = 0.65 意味着如果一个单元格的概率超过 65%,则被认为是占用的。

  • free_thresh = 0.196 确立了以下阈值值,即单元格被认为是空闲的,即 19.6%。

给定图像的像素大小,可以很容易地推断出地图中单元格的物理尺寸。此值由 resolution 参数指示,即 0.01 米/像素。

SLAM 过程

使用 Gazebo 模拟构建地图涉及采用以下工作流程:

  1. 在建模环境中启动机器人模型。

  2. 启动映射 ROS 包。

  3. 在 RViz 中启动一个特殊的可视化,让我们看到机器人移动时扫描的区域。

  4. 远程操作机器人,使其尽可能覆盖虚拟环境的地表。

  5. 一旦探索完成,保存地图,生成前面章节中指明的格式文件,即 .pgm.yaml

完成这一信息收集阶段后,我们就可以让机器人尝试并成功完成导航任务。

导航过程

一旦你的机器人生成了一张地图,它将使用它来规划到达指定目标位置的路程。执行此类计划的流程称为导航,涉及以下步骤:

  1. 在建模环境中启动机器人模型。这一步骤与前面描述的 SLAM 过程中的第一步相同。

  2. 提供机器人之前构建的成本图。请记住,地图是环境的特点,而不是机器人的特点。因此,你可以用一个机器人构建地图,并在任何其他机器人放入相同环境中进行导航时使用相同的地图。

  3. 设置导航算法。我们将使用 自适应蒙特卡洛定位AMCL)算法,这是有效导航中最常见的选择。本书的范围不包括描述此类算法,但在本章末尾的进一步阅读部分提供了有用的参考资料。

  4. 启动 RViz 可视化,这将允许你在环境中可视化机器人,并轻松标记它应达到的目标姿态(位置和方向)。

  5. 让机器人自主导航到目标位置。此时,你可以放松并享受观看 GoPiGo3 如何避开障碍物并尽量减少需要覆盖的距离,到达指定位置的过程。

如果你希望机器人导航到另一个位置,只需在它到达前一个目标后,在 RViz 中指示即可。现在,是时候看到前两个过程——SLAM 和导航——的实际操作了。这正是本章最后部分的内容范围。

使用 GoPiGo3 练习 SLAM 和导航

正如前一小节末尾提到的,我们将运行一个使用 GoPiGo3 的 SLAM 和导航的端到端示例。第一个过程是使用 SLAM 构建环境的地图。让我们回顾上一节中列出的步骤,并看看如何在 ROS 中执行每个步骤。

使用 SLAM 探索环境以构建地图

让我们按照以下步骤构建一个名为 stage_2.world 的简单 Gazebo 世界的地图:

  1. 通过运行以下代码行在建模环境中启动机器人模型:
T1 $ roslaunch virtual_slam gopigo3_world.launch world:=stage_2.world

此命令启动 Gazebo 并将 GoPiGo3 模型放置在其中,如下面的截图所示:

图片

环境由一个包含四个静态障碍物的正方形空间组成。我们在上一节 占用栅格地图(OGM) 子节中使用过的二维地图对应于这个 Gazebo 世界,其文件名为 stage_2.world

你可以看到,这个世界的结构远比本章第一部分使用的那个简单(还有一个没有障碍物的更简单环境,名为 stage_1.world)。我们使用这个来通过最小设置说明导航概念,以便更好地理解。

将重复此过程作为读者的练习,使用第一部分 使用 Gazebo 的动态模拟 章节中的 Gazebo 世界。为此,只需省略 world 参数,使其采用启动文件中指定的默认值。执行此模拟的命令是 $ roslaunch virtual_slam gopigo3_world.launch

最后,请注意,我们可以通过将 world 参数设置为所选环境的文件名来指定任何我们想要在 Gazebo 中使用的其他环境(可用世界位于本章代码的 ./virtual_slam/worlds 文件夹内):

T1 $ roslaunch virtual_slam gopigo3_world.launch world:= <OTHER_WORLD.world>
  1. 启动 SLAM 映射 ROS 包,包括一个将机器人的虚拟模型与实际扫描数据叠加的 RViz 可视化:
T2 $ roslaunch virtual_slam gopigo3_slam.launch

下面的屏幕截图显示了 RViz 窗口的外观,其中您可以实时看到虚拟机器人和扫描数据(绿色点)。浅灰色区域是机器人实际上通过其 LDS 传感器感知到的区域,而未着色的区域(障碍物后面的阴影空间)是 GoPiGo3 尚未了解的:

图片

在下一步中,我们将探索整个环境以构建地图。

  1. 通过遥控操作机器人,使其尽可能覆盖当前 Gazebo 世界的表面。让我们像往常一样使用遥控操作包来做这件事:
T3 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

随着您移动机器人,LDS 传感器将从未知区域获取扫描数据,您将在 RViz 窗口中收到反馈:

图片

在前面的屏幕截图中,您可以看到,在环境中徘徊之后,只有左下角的部分没有被扫描。然后移动机器人到那个位置,一旦所有空间都被同一种颜色(浅灰色)填满,就继续进行第 4 步以保存地图。

  1. 一旦完成探索,保存地图,生成前面SLAM 过程子节中指示的两种格式的两个文件,即,.pgm.yaml
T4 $ rosrun map_server map_saver -f ~/catkin_ws/map_stage_2

您将在工作空间根目录中获得两个文件:map_stage_2.pgmmap_stage_2.yaml

生成的地图的外观在前面占用栅格地图(OGM)子节中显示。

提供了地图,我们就可以使用 GoPiGo3 进行机器人导航了。

使用导航沿计划轨迹行驶

首先,关闭所有打开的终端。然后,就像在 SLAM 过程中一样,让我们一步一步地进行一些导航:

  1. 在建模环境中启动机器人模型。这一步与 SLAM 过程中的第一步相同:
T1 $ roslaunch virtual_slam gopigo3_world.launch
  1. 设置导航算法并启动 RViz。我们将使用 AMCL,这是最常用的有效导航选择。本书的范围不包括描述此类算法,但您可以在章节末尾的进一步阅读部分找到有用的参考资料。

在这一步中,我们还提供了机器人之前构建的成本图。为此,您只需参考之前创建的.yaml地图文件。确保相应的.pgm文件具有相同的名称,并且放置在同一位置。这一点在roslaunch命令中通过map_file参数指定:

T2 $ roslaunch virtual_slam gopigo3_navigation.launch map_file:=$HOME/catkin_ws/map_stage_2.yaml
  1. 下面的屏幕截图显示了 RViz 窗口,它让您可视化环境中的机器人并标记它应该达到的目标姿态(位置和方向):

图片

在 RViz 窗口的右上角找到 2D Nav Goal 按钮。您将使用它来标记机器人应该导航的目标位置。

首先,您必须通过按下2D 姿态估计按钮告诉机器人这是初始姿态。然后,在屏幕上标记它(在这个特定情况下,这是不必要的,因为初始姿态与机器人在前一小节开始构建地图时的姿态相同)。

之后,您可以按下2D 导航目标按钮,通过点击左鼠标按钮将目标设置为左下角。当箭头达到所需方向时释放鼠标。释放后,机器人将计算要遵循的路径并开始自主导航:

图片

上述截图显示了导航计划执行的第一瞬间。红色箭头的方向告诉 GoPiGo3 到达目标后应该朝哪个方向保持面对,从机器人到目标的曲线线是计划路径。由于它有环境地图,机器人能够规划一条避开障碍物的路径。等待几秒钟,您将看到机器人如何在没有外部帮助的情况下到达目标。

  1. 一旦机器人到达,再次按下2D 导航目标按钮,然后标记右上角。以下截图显示了下一个导航计划的执行第一瞬间:

图片

您可以看到新的计划路径以及它是如何考虑沿途障碍物的存在以避免碰撞的。机器人周围的蓝色方块代表障碍物避障规划的局部窗口。这是由动态窗口方法DWA)方法使用的,该方法生成一个能够有效避开障碍物的局部路径。DWA 方法在考虑机器人的动力学时进行计算,特别是其有限的速度和加速度。

机器人导航的 AMCL 算法根据提供的地图生成到达目标的全局路径,而 DWA 方法计算考虑机器人可能在其附近遇到的局部条件的局部路径。后者提供了处理地图中存在的障碍物以及动态障碍物的能力,例如行人穿越机器人的路径。全局路径局部路径结合在一起产生高度自主的机器人导航

以下截图显示了 GoPiGo3 在到达目标前的最后瞬间。欣赏一下,在这个点上,DWA 窗口也包含了目标:

图片

  1. 最后,你应该将机器人导航框架在一系列任务中,这些任务是机器人必须依次完成,以实现用户设定的目标。以本章中用于解释目的的导航路径为例,想象一个场景,其中 GoPiGo3 必须在位置 A(左下角)拾起一个物体,并将其运送到位置 B(右上角)。在这种情况下,任务序列如下:

    1. 导航到位置 A

    2. 在位置 A 拾起物品。

    3. 导航到位置 B

    4. 在位置 B 放下物品。

从概念上讲,这很容易,对吧?但在本章中,我们只涵盖了完成任务 1 和 3 的基础知识。稍后,在 第十章 “在机器人学中应用机器学习”中,你将获得关于目标识别的技术背景,以便你也能编程任务 2 和 4。更确切地说,它将在 “在机器人学中程序化应用机器学习的方法论” 部分提供这一见解。

摘要

本章向你介绍了机器人导航的主要任务。SLAM 和导航是机器人学中的复杂问题,也是活跃的研究课题。因此,本章为你提供了如何实现它的初步了解,以便你能够快速理解其原理,而不必深入了解算法和背后的数学。

我们希望已经激起了你对这个主题的好奇心。现在你已准备好在现实世界中用物理 GoPiGo3 执行相同的任务。在下一章中,你将使用物理机器人执行导航和 SLAM 任务。

问题

  1. 传感器规格在 Gazebo SDF 文件中包含在哪里?

A) 在 <gazebo> 标签外

B) 在 <joint> 标签内

C) 在 <sensor> 标签内

  1. 关于在 Gazebo 中传感器控制器的规格,在运行模拟时,哪个参数与 CPU 使用率最相关?

A) 扫描距离,因为传感器范围越大,CPU 执行的带宽消耗就越多。

B) 角度扫描,因为角度分辨率越高,存储读取数据在 RAM 中所需的带宽消耗就越大。

C) 传感器的最大频率,因为它们在真实传感器中非常高,很容易超载 CPU。

  1. 传感器的机械属性在 Gazebo 机器人描述中包含在哪里?

A) 在 <gazebo> 标签外

B) 在 <joint> 标签内

C) 在 <sensor> 标签内

  1. SLAM 技术为机器人提供了什么?

A) 避免环境中移动障碍物的方法

B) 建立环境地图的方法

C) 避免环境中静态和移动障碍物的方法

  1. 你如何操作性地指定导航目标给机器人?

A) 告诉它目标位置和方向

B) 在环境的二维地图中设置目标位置

C) 标记机器人预期导航到的区域边界

进一步阅读

要深入了解本章中解释的概念,你可以查阅以下参考资料:

第九章:机器人导航的 SLAM

在本章中,您将深入了解机器人导航,这是机器人工程中的一个普遍任务。典型用例包括自动驾驶汽车和工厂中的物料运输。您会发现我们之前通过应用 SLAM(同时定位与建图) 生成的地图在路径规划过程中被使用。给定一个初始姿态,机器人将沿着最佳路径移动,并且应该能够对动态事件做出反应,也就是说,它应该能够避开在地图构建后出现的障碍物(静态或动态)。

本章是前一章的自然延伸。在前一章中,您获得了对 SLAM 和导航的实践理解,并且您是在 Gazebo 模拟器中使用 GoPiGo3 的虚拟模型来做到这一点的。现在,您准备好再次使用物理机器人完成练习。通过这样做,您将发现当您在真实环境中完成机器人任务时会出现多少细节和实际问题。模拟是一个好的开始,但真正证明您的机器人按预期运行的证据是在实际场景中执行任务。

在本章中,我们将涵盖以下主题:

  • 为您的机器人准备激光测距传感器LDS

  • 在 ROS 中创建导航应用程序,包括关于在导航中使用的常见算法的解释

  • 使用 GoPiGo3 练习导航

导航任务的主要传感器将是低成本的 LDS,由 EAI 模型 YDLIDAR X4 提供(www.aliexpress.com/item/32908156152.html),我们已经在 Gazebo 中对其进行了模拟。我们将在本章中投入大量篇幅来学习如何设置 LDS,了解其工作原理以及它为机器人提供哪些实用信息。

技术要求

在本章中,我们将使用位于 Chapter9_GoPiGo_SLAM 文件夹中的代码(github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter9_GoPiGo_SLAM)。将其文件复制到 ROS 工作空间,以便它们可用,并将其余部分放在 src 文件夹之外。这样,您将拥有一个更干净的 ROS 环境:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter9_GoPiGo_SLAM ~/catkin_ws/src/

上述文件夹中的代码包含两个新的 ROS 软件包,每个软件包都位于一个具有相同名称的文件夹中:

  • ydlidar,为所选 LDS 官方支持的 ROS 软件包。

  • gopigo3_navigation,用于执行 GoPiGo3 导航的最高级软件包。

您将在笔记本电脑环境中使用这两个软件包,但在机器人(即 Raspberry Pi)中,您只需要 ydlidar,因为建议将计算密集型的导航任务在笔记本电脑上运行。这样,GoPiGo3 将通过熟悉的 cmd_vel 主题接收驱动命令,并通过 /scan 主题发布来自 LDS 的 360° 扫描。

如同往常一样,您需要分别重新构建机器人和工作站的空间,包括机器人和笔记本电脑:

$ cd ~/catkin_ws
$ catkin_make

通过选择它们并列出文件来检查包是否已正确安装:

$ rospack list | grep gopigo3
$ rospack list | grep ydlidar

接下来,我们必须将 ROS 主指向机器人。

将 ROS 主设置为在机器人上

由于您将再次与物理机器人一起工作,您需要重新配置 ROS 主 URI,使其指向 GoPiGo3。这样,您的笔记本电脑才能反映这种配置,打开您本地的 .bashrc 文件,取消注释末尾指定要指向以找到 ROS 主的 URL 的行:

$ nano ~./bashrc
 ...
 export ROS_HOSTNAME=rosbot.local
 export ROS_MASTER_URI=http://gopigo3.local:11311

关闭任何打开的终端,打开一个新的终端,并检查 ROS_MASTER_URI 变量:

$ echo $ROS_MASTER_URI
 http://gopigo3.local:11311

您应该会发现环境变量已恢复到默认服务器(localhost)和默认端口(11311)。现在,我们准备好切换到虚拟机器人。如果由于某种原因,gopigo3.local 无法解析机器人 IP,请直接设置其 IPv4 地址。您可以从机器人操作系统中获得它,如下所示:

$ ip addr # or 'ifconfig' instead
 192.168.1.51

然后,在 .bashrc 文件中,相应地修改以下行:

export ROS_MASTER_URI=http://192.168.1.51:11311

关闭笔记本电脑上的终端,打开一个新的终端,以便配置生效。然后,检查以下内容:

$ echo $ROS_MASTER_URI
 http://192.168.1.51:11311

现在,我们可以熟悉我们的新传感器。

为您的机器人准备一个 LDS

在开始之前,您应该花些时间回顾一下制造商 EAI 提供的所有文档。您可以在 www.ydlidar.com/download 找到所有资源。请特别注意以下项目:

  • 为了熟悉硬件并安全地将其与您的机器人一起安装,请参阅 YDLIDAR X4 用户手册。

  • 位于压缩文件 ROS.zip 内的 YDLIDAR X4 ROS 手册。ros 文件夹对应于 ROS 包,但您应该从 GitHub 克隆它以确保您获得最新版本并保持更新。按照 github.com/EAIBOT/ydlidar 中的说明获取代码的最新版本。

EAI 已从下载页面移除了CAD(即计算机辅助设计)模型。

  • YDLIDAR X4 开发手册,它描述了通信协议,以便您可以构建自己的驱动程序来控制设备。

现在,您已经准备好开始使用硬件了。

设置 YDLIDAR

按照用户手册中的说明,将设备物理连接到您的笔记本电脑或机器人。以下截图显示了传感器本身通过一组五色电缆连接到控制板后的样子:

图片

虽然软件说明也包含在手册中,但我们将在这里列出所有步骤,因为它们涉及与 ROS 的核心集成。首先,我们将与笔记本电脑集成,然后与机器人的 Raspberry Pi 集成。

与远程 PC 集成

就像我们与其他任何与 ROS 集成的硬件一样,我们遵循克隆制造商提供的包并使用我们的工作空间构建的标准程序:

$ cd catkin_ws/src
$ git clone https://github.com/EAIBOT/ydlidar
$ cd ..
$ catkin_make

通过运行catkin_makeydlidar_clientydlidar_node节点将可用。

此代码也包含在 YDLIDAR 模型的其余部分中,在github.com/YDLIDAR/ydlidar_ros。对于特定型号,你只需选择相应的分支,X4。在我们的情况下,这是git clone https://github.com/YDLIDAR/ydlidar_ros -b X4 --single-branch

在将 X4 连接到笔记本电脑的 USB 端口后,更改权限以访问新的 LDS:

$ sudo chown ubuntu:dialout /dev/ttyUSB0

前一个命令假设你的用户是ubuntu。如果不是,请将其替换为你的实际用户。然后,启动设备:

$ roscd ydlidar/startup
$ sudo chmod 777 ./*
$ sudo sh initenv.sh

此脚本创建一个指向/dev/ydlidar--> /dev/ttyUSB0设备的符号链接。下一步是在 ROS 内部运行一个测试,以检查一切是否按预期工作。

运行 YDLIDAR ROS 包

现在,我们将启动激光扫描节点,并使用控制台客户端可视化结果,然后再使用 RViz 进行同样的操作。

按照以下步骤进行:

  1. 使用以下命令启动 YDLIDAR 节点:
T1 $ roslaunch ydlidar lidar.launch 

对于本章的这一部分,你应该暂时将 ROS 主节点指向笔记本电脑,而不是机器人。记住,你可以通过在每个终端中指定$ export ROS_MASTER_URI=http://localhost:11311来为单个终端这样做。一旦关闭其中任何一个,临时定义将被丢弃。

  1. 在另一个终端中,使用客户端节点列出扫描数据:
T2 $ rosrun ydlidar ydlidar_client

你应该在控制台看到 YDLIDAR 节点的扫描结果,以及 ROS 图(通过在另一个终端中运行rqt_graph获得),T3

图片

注意,base_link_to_laser4/tf主题中提供坐标帧转换,而ydlidar_node/scan主题中提供传感器数据流,这些数据流通过ydlidar_client节点在终端中可视化。

  1. 最后,启动 RViz 以查看在发现障碍物位置的红点分布:
T3 $ roslaunch ydlidar display_scan.launch

现在,我们将重复此练习,将 LDS 连接到机器人。

与 Raspberry Pi 集成

我们将重复上一节中描述的过程,即设置 YDLIDAR,以便将 LDS 连接到 Raspberry Pi。在将传感器连接到 Raspberry Pi 的 USB 端口后,在机器人上打开一个终端并按照以下步骤操作:

  1. 克隆存储库并重建工作空间:
$ cd catkin_ws/src
$ git clone https://github.com/EAIBOT/ydlidar
$ cd ..
$ catkin_make
  1. 当你将 YDLIDAR 连接到 USB 端口时,检查连接是否已正确建立:
$ ls -la /dev | grep USB
 crw-rw----  1 root dialout 188,   0 ene 28  2018 ttyUSB0
  1. 然后,更改权限,以便你的普通用户pi可以访问新设备:
$ sudo chown pi:dialout /dev/ttyUSB0
  1. 现在,启动设备:
$ roscd ydlidar/startup
$ sudo chmod 777 ./*
$ sudo sh initenv.sh

此脚本创建一个指向/dev/ydlidar--> /dev/ttyUSB0设备的符号链接。如果不是这种情况,你可以手动完成,如下所示:

$ cd /dev
$ sudo ln -s ttyUSB0 ydlidar

这样,你可以确保ydlidar_node节点找到了设备。

检查 YDLIDAR 与 GoPiGo3 一起工作

就像我们在笔记本电脑上做的那样,使用ydlidar_client脚本来检查您是否已从传感器接收到数据:

r1 $ roslaunch ydlidar lidar.launch
r2 $ rosrun ydlidar ydlidar_client

前面代码片段中的字母r代表 Raspberry Pi 上的终端。如果您在r2中收到数据,那么这将证明传感器正在将其读数发送到 ROS。

在 Raspberry Pi 桌面上可视化扫描数据

现在,让我们检查 Raspberry Pi 上的 RViz 可视化效果,就像我们检查笔记本电脑上的那样。为此,您需要使用VNC(虚拟网络计算),正如我们在第六章,“在 ROS 中编程 - 命令和工具”部分中设置物理机器人一节所解释的。设置一个 VNC 服务器(x11vnc)。一旦从远程笔记本电脑连接,请在 Raspberry Pi 桌面上启动以下四个终端:

r1 $ roslaunch ydlidar lidar_view.launch
r2 $ roslaunch mygopigo gopigo3.launch
r3 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel
r4 $ rqt_graph

这是整个屏幕:

图片

RViz 中的激光扫描视图(前一个截图中的右上角窗口)由lidar_view.launch提供。ROS 图(右下角窗口)显示key_teleop节点允许您通过在/cmd_vel主题中发布消息使用箭头键远程操作机器人:

图片

让我们看看 RViz 窗口显示的内容:

图片

标记为GoPiGo3的箭头显示了机器人在房间角落的位置。外部直线红色线条代表墙壁,而指向的箭头显示了我通过入口门离开房间时的轮廓(我前面的空白空间 - 没有红色点)。

组合启动文件

为了提高效率,我们应该将 Raspberry Pi 从可视化任务中卸载,并将它们移动到远程笔记本电脑。为了做到这一点,我们需要重新工作启动文件,以便 GoPiGo3 严格运行机器人工作所需的代码,即我们在第六章,“在 ROS 中编程 - 命令和工具”中描述的mygopipo包中的gopigo3_driver.py部分,以及ydlidar包中的lidar.launch部分。这两个组件可以使用以下命令启动:

r1 $ roslaunch mygopigo gopigo3.launch
r2 $ roslaunch ydlidar ydlidar.launch

r1r2中的启动文件可以合并为一个,如下所示。我们将把这个脚本命名为gopigo3_ydlidar.launch

<launch>
  <include file="$(find mygopigo)/launch/gopigo3.launch" />
  <node name="ydlidar_node" pkg="ydlidar" type="ydlidar_node" output="screen" respawn="false" >
     <param name="port" type="string" value="/dev/ydlidar"/>
     <param name="baudrate" type="int" value="115200"/>
     <param name="frame_id" type="string" value="laser_frame"/>
      ...
     <param name="angle_min" type="double" value="-180" />
     <param name="angle_max" type="double" value="180" />
     <param name="range_min" type="double" value="0.1" />
     <param name="range_max" type="double" value="16.0" />
  </node>
  <node pkg="tf" type="static_transform_publisher" name="base_link_to_laser4"
   args="0.2245 0.0 0.2 0.0 0.0 0.0 /base_footprint /laser_frame 40" />
</launch>

多亏了这种分组,GoPiGo3 的所有代码都可以使用以下命令运行:

r1 $ roslaunch ydlidar gopigo3_ydlidar.launch

这启动了ydlidargopigo3节点,它们提供了一个软件接口,使我们能够与机器人传感器和执行器通信。这也创建了一个如下的 ROS 图:

图片

接下来,为了监听扫描数据,您需要在机器人上执行 YDLIDAR 客户端:

r2 $ rosrun ydlidar ydlidar_client

这将产生以下输出:

[YDLIDAR INFO]: I heard a laser scan laser_frame[720]:
[YDLIDAR INFO]: angle_range : [-180.000005, 180.000005]
[YDLIDAR INFO]: angle-distance : [-4.500002, 0.000000, 351]
[YDLIDAR INFO]: angle-distance : [-4.000005, 0.750000, 352]
[YDLIDAR INFO]: angle-distance : [-3.500007, 0.765000, 353]
[YDLIDAR INFO]: angle-distance : [-3.000010, 0.782000, 354]
[YDLIDAR INFO]: angle-distance : [-2.500013, 0.000000, 355]
[YDLIDAR INFO]: angle-distance : [-2.000002, 0.799000, 356]
[YDLIDAR INFO]: angle-distance : [-1.500005, 0.816000, 357]
[YDLIDAR INFO]: angle-distance : [-1.000008, 0.834000, 358]
[YDLIDAR INFO]: angle-distance : [-0.500011, 0.000000, 359]
[YDLIDAR INFO]: angle-distance : [0.000000, 0.853000, 360]

ROS 图看起来像这样:

图片

扔出前面图表的 rqt_graph 命令可以从 Raspberry Pi 或远程笔记本电脑执行。由于我们的目标是卸载 Raspberry Pi,你应该在笔记本电脑上运行它。在这种情况下,你将不再需要 Raspberry Pi 的桌面界面。

前面的图表显示,ydlidar_node/scan 主题中发布激光扫描数据,这些数据被 ydlidar_client 节点读取,并在启动该节点的终端(即 r2)中打印出来。

在远程笔记本电脑上可视化扫描数据

最后一步是从笔记本电脑的桌面上获取 RViz 激光扫描数据。这是我们将在本节中完成的内容。

在以下段落中,代码片段中的字母 r 代表机器人的终端,而 T 代表笔记本电脑的终端。

按照以下步骤构建 ROS 环境:

  1. 首先,使用我们在上一节中构建的统一启动文件在机器人上启动进程:
r1 $ roslaunch ydlidar gopigo3_ydlidar.launch
  1. 从笔记本电脑中找到在 /scan 主题中发布的最后一条消息的内容:
T1 $ rostopic echo /scan -n1
 header: 
 seq: 2118
 stamp: 
 secs: 1570384635
 nsecs: 691668000
 frame_id: "laser_frame"
 angle_min: -3.14159274101
 angle_max: 3.14159274101
 angle_increment: 0.00872664619237
 time_increment: 154166.671875
 scan_time: 111000000.0
 range_min: 0.10000000149
 range_max: 16.0
 ranges: [array of 720 items]
 intensities: [array of 720 items]
  1. 范围在 ranges 数组字段中提供,对应于 720 个方向,对应于 360° 覆盖范围的 0.5° 角分辨率。然后,找到它是哪种消息类型:
 $ rostopic info scan
 Type: sensor_msgs/LaserScan
  1. 最后,检查消息结构:
T1 $ rosmsg info sensor_msgs/LaserScan
 std_msgs/Header header
 uint32 seq
 time stamp
 string frame_id
 float32 angle_min
 float32 angle_max
 float32 angle_increment
 float32 time_increment
 float32 scan_time
 float32 range_min
 float32 range_max
 float32[] ranges
 float32[] intensities
  1. 接下来,在笔记本电脑上运行 ROS 可视化节点:
T1 $ roslaunch ydlidar display.launch
T2 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

T1 终端将启动 RViz 中的可视化,而 T2 将允许你通过修改激光扫描的范围来远程操作机器人,以检查其感知环境如何随着运动而改变。display.launch 提供的可视化添加了 YDLIDAR 的 URDF 模型到 RViz。以下图表中的黑色圆圈代表传感器:

图片

注意,由于 URDF 模型仅包括传感器,它不会像物理 GoPiGo3 机器人那样移动。扫描数据——即红色点——将根据机器人的运动而改变,但虚拟传感器将保持在初始位置,这不再是其实际位置(除非你停止 T1 并重新启动它)。因此,在这种情况下,使用 display_scan.launch(不包含 URDF 模型,仅包含扫描数据)比使用 display.launch 更为合理。在 使用 GoPiGo3 练习导航 部分中,你将链接 GoPiGo3 和 LDS 传感器的 URDF 模型,以便 RViz 显示机器人的运动。

运行 YDLIDAR ROS 软件包 部分中,你将运行一个分布式系统,其中 Raspberry Pi 收集传感器数据,远程笔记本电脑提供其可视化。

从远程笔记本电脑处理 YDLIDAR 数据

现在,是时候解释扫描数据了。这可以通过一个简单的名为 scan.py 的代码片段来完成,该代码片段包含在 ROS 软件包中:

#! /usr/bin/env python
import rospy
from sensor_msgs.msg import LaserScan

def callback(msg):
 print "\nNumber of points =", len(msg.ranges)
 print "------------------"
 print "Range (m) at 0 deg = ", round(msg.ranges[360] , 1)
 print "Range (m) at 90 deg = ", round(msg.ranges[540] , 1)
 print "Range (m) at 180 deg = ", round(msg.ranges[719] , 1)
 print "Range (m) at -90 deg = ", round(msg.ranges[180] , 1), " or 270 deg"

rospy.init_node('scan_values')
sub = rospy.Subscriber('/scan', LaserScan, callback)
rospy.spin()

在笔记本电脑的终端中输入以下命令以查看其运行效果:

T3 $ rosrun ydlidar scan.py

上一段代码列出了屏幕上沿主要轴(XY)检测到的范围。请记住有关传感器参考框架的以下照片,该框架是从 X4 文档中提取的。角度是按顺时针方向测量的,以X轴为起点。在以下照片中,你可以看到安装在 GoPiGo3 上的 LDS 以及XY轴的方向绘制在上面:

图片

回到“可视化远程笔记本电脑数据”部分的截图,你可以猜测机器人在房间中的朝向。请注意,绿色轴对应于X轴,而红色线条对应于Y轴:

图片

回调函数沿着主要轴(+X(0°)+Y(-90°)-X(180°)-Y(90°)),在这些轴上可以检测到右侧(+X),前方(+Y),左侧(-X)或后方(-Y)的障碍物。

在 ROS 中创建导航应用程序

一个提供机器人导航能力的应用程序必须考虑以下要点:

  • 感知:这为我们提供了获取运动数据的能力,以便机器人能够实时估计其位置。这类信息被称为机器人****里程计。传感器数据的主要来源有两个:编码器,它让我们知道机器人轮子的旋转,以及 IMU 传感器,它提供有关机器人整体加速度和旋转的信息。一般来说,编码器数据使用得最多,尽管它可能结合 IMU 数据以提高姿态估计的准确性。这是一个称为融合传感器的高级主题,它超出了本书的范围。

  • 定位/姿态估计:由于里程计和当前的环境地图,AMCL(自适应蒙特卡洛定位)算法允许我们在实时中更新机器人的姿态估计,正如我们在上一章中介绍的那样。

  • 路径规划:给定一个目标姿态,此类规划包括创建整个地图的全局最优路径和覆盖机器人周围小区域的局部路径,以便机器人能够跟随精确路径并避开障碍物。局部路径规划是动态的;也就是说,随着机器人的移动,机器人周围的区域会相应地改变。

  • 移动/避障:正如我们之前所做的那样,有一个全局最优路径与局部路径相结合,并且这发生在机器人移动到目标位置时的每个位置。这就像在周围环境中创建一个缩放窗口。因此,局部路径是通过考虑全局路径和附近的障碍物(例如,有人从机器人前方穿过)来计算的。局部路径规划能够避开此类障碍物而不会丢失全局路径。这个局部缩放窗口是通过使用 LDS 提供的实时信息构建的。

由于上述几点,以下数据必须提供给 ROS,以便进行导航:

  • 里程计: 由gopigo3节点在/odom主题中发布。

  • 坐标变换: 传感器在机器人坐标系中的位置发布在/tf主题中。

  • 扫描数据: 从 LDS 获取到机器人周围障碍物的距离,并在/scan主题中提供。

  • 地图: 执行 SLAM 时构建的占用栅格地图保存到map.pgm文件中,配置在map.yml文件中。

  • 目标姿态: 一旦启动 ROS 导航的设置,用户将在 RViz 窗口中指定。

  • 速度命令: 这是算法的最终输出。命令发布在/cmd_vel主题中,该主题由gopigo3节点订阅。然后,机器人相应地移动以遵循计划路径。

根据前面讨论的主题和概念,创建 ROS 导航应用程序的步骤如下:

  1. 构建环境地图。通过从 LDS 获取数据,机器人将根据来自传感器的数据范围创建环境地图。它将使用我们在前一章中讨论的 SLAM 技术来完成此操作。构建地图的过程遵循以下实用序列:

    • 在物理机器人上启动 ROS,这意味着必要的节点将公开发布传感器数据的主题,以及接收运动命令的主题。发布运动命令的规则作为获取的传感器数据的函数,符合我们所说的机器人应用程序逻辑

    • 从远程 PC 建立连接。如果配置正确,当在笔记本电脑上启动 ROS 时应该是自动的。这个主题在本章开头的技术要求部分已经介绍过。

    • 从笔记本电脑启动 SLAM 过程。这将允许 ROS 从 LDS 获取实时范围数据,以便开始构建环境地图。

    • 通过 RViz 可视化检查已映射区域和待扫描区域,远程操作机器人。在这种情况下,名为第一个项目符号的机器人应用程序逻辑由你作为人类驱动,你决定 GoPiGo3 在每一个实例中必须执行什么动作。你也可以通过让机器人随机漫游来自动化远程操作(记住前一章中提到的模拟 LDS部分,在那里你让 GoPiGo3 在应用一系列规则的同时自主探索环境,以避开可能遇到的障碍)。在这种情况下,机器人应用程序逻辑由 Python 脚本实现,没有人为干预。

  2. 一旦环境被完全探索,你必须保存地图,以便在下一步中进行自主导航。

  3. 通过告诉机器人你希望它移动到的目标位置来启动导航任务。这个自主导航的过程遵循以下顺序:

    • 在物理机器人上启动 ROS。在这种情况下,机器人应用程序逻辑是导航任务的一部分,旨在由 GoPiGo3 自主执行,无需任何人为干预。

    • 加载导航应用程序第一部分创建的环境地图。

    • 向机器人指示一个目标姿态,你可以在 RViz 的可视化中直接执行,该可视化显示了环境的地图。

    • 让机器人自行导航到目标位置,检查它是否能够规划出避开可能遇到的障碍物的最佳路径。

我们将在下一节中用一个现实世界的例子来说明这个过程。

使用 GoPiGo3 练习导航

在本节中,我们将介绍在上一章的“使用 GoPiGo3 练习 SLAM 和导航”部分所遵循的步骤,通过用实际的 GoPiGo3 和物理环境分别替换虚拟机器人和 Gazebo 模拟器。

构建环境地图

首先,让我们考虑一个足够简单,适合我们学习目的的物理环境。这可以在以下照片中看到:

注意,这个几乎正方形的区域有三个限制边和一个激光传感器无法检测到的步骤,因为它位于机器人的地板水平以下。

在 ROS 中,第一步是绘制环境地图,以便机器人能够定位其周围环境并绕其导航。按照以下步骤操作:

  1. 启动机器人上的所有 ROS 节点。从连接到树莓派的远程终端,这意味着运行控制驱动和 LDS 的 ROS 启动文件:
r1 $ roslaunch ydlidar gopigo3_ydlidar.launch

回想一下“与树莓派集成”部分:分组启动文件是我们构建一个独特的启动文件来一次性运行机器人配置的方法。这确保了 GoPiGo3 准备好在笔记本电脑上与 ROS 交互,在那里将完成与环境地图和导航命令相关的所有处理。

  1. 启动 SLAM 映射 ROS 包,其启动文件包括一个 RViz 可视化,该可视化将机器人的虚拟模型与实际扫描数据叠加:
T1 $ roslaunch gopigo3_navigation gopigo3_slam.launch
  1. 通过远程操作机器人,使其覆盖尽可能多的虚拟环境表面。我们可以这样做:
T3 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

当你探索机器人的周围环境时,你应在 RViz 窗口中看到以下类似内容:

在这里,你可以看到正方形空间的三个限制墙。地图的其余部分显示了激光在剩余方向上找到的第一个障碍物。记住,第四边的步骤无法检测到,因为它位于机器人的地板水平以下。

  1. 一旦你完成探索,将我们生成的地图保存到之前指定的两个文件中,即.pgm.yaml
T4 $ rosrun map_server map_saver -f ~/catkin_ws/test_map

再次,你将在工作区的根目录中找到地图文件,即test_map.pgmtest_map.yaml。现在,我们准备让 GoPiGo3 在物理环境中导航。

在现实世界中导航 GoPiGo3

这第二部分要求你在笔记本电脑上停止所有 ROS 进程,但不必在 GoPiGo3 上停止。记住,在机器人上,你有最小的 ROS 配置,这样机器人就能够感知环境(LDS X4 传感器)并移动(驱动)。所有剩余的导航逻辑将在笔记本电脑上运行。因此,关闭 PC 上任何打开的终端,并按照以下步骤开始新阶段:

  1. 如果之前停止了终端,请在机器人上启动 ROS 节点:
r1 $ roslaunch ydlidar gopigo3_ydlidar.launch
  1. 通过提供机器人之前构建的成本图来启动 AMCL 导航。为此,你必须参考之前创建的.yaml地图文件。确保相应的.pgm文件具有相同的名称,并且放置在同一位置:
T1 $ roslaunch gopigo3_navigation amcl.launch map_file:=/home/ubuntu/catkin_ws/test_map.yaml

此启动文件包括一个 RViz 可视化,它将允许我们与地图交互,以便我们可以设置目标位置,如下面的截图所示:

与 Gazebo 模拟的情况一样,通过按下 RViz 窗口右上角的2D Nav Goal按钮并选择目标姿态来设置目标位置,该姿态由位置和方向组成(RViz 中的绿色箭头允许你图形化定义它)。一旦选择这样的位置,AMCL 算法就开始路径规划并通过/cmd_vel主题发送运动命令。因此,当命令序列执行时,机器人将移动到指定的位置。

摘要

在本章中,你最终完成了使用 GoPiGo3 的自主任务。这仅仅是进入机器人领域应用人工智能这一迷人领域的起点。在机器人导航之上最明显的功能是自动驾驶,这是许多汽车制造商目前正在实施的功能,以制造更安全、更舒适的车辆供最终用户使用。

在本书的第四和最后一部分,你将了解机器学习技术是如何应用于当今构建更智能的机器人的。

问题

  1. 哪个传感器是 LDS 类型的?

A) 激光雷达

B) 超声波距离传感器

C) 电容式传感器

  1. ROS 主节点必须位于何处才能执行导航?

A) 在机器人上

B) 在机器人和笔记本电脑上

C) 在机器人或笔记本电脑上

  1. 如果在地图构建之后在环境中放置障碍物,会发生什么?

A) 机器人将无法检测到它,如果它与预定路径发生冲突,可能会与之相撞。

B) 考虑到局部路径规划,将提供一个避开障碍物的修改路径。

C) 在进行导航任务之前,你应该在新条件下重新构建地图。

  1. 你能否在之前没有用机器人运行 SLAM 的情况下进行导航?

A) 不可以,因为你必须使用你将用于导航的同一台机器人来构建地图。

B) 是的,唯一条件是你提供一个预先制作的环境地图。

C) 不可以,SLAM 和导航是同一枚硬币的两面。

  1. 机器人的里程计是什么?

A) 自从 ROS 应用启动以来,它所覆盖的总距离。

B) 使用运动传感器的数据来估计机器人随时间变化的姿态。

C) 使用运动传感器的数据来估计当前机器人的姿态。

进一步阅读

你可以阅读的主要资源是 ROS 导航堆栈的官方文档,它位于wiki.ros.org/navigation。对于那些感兴趣的人,这里有一些额外的参考文献:

  • ROS 导航:概念与教程,联邦技术大学,Longhi R.,Schneider A.,Fabro J.,Becker T.,和 Amilgar V. (2018),巴拉那,库里蒂巴,巴西。

  • 用于正确环境检测的激光雷达设计、使用和校准概念,在 IEEE 机器人与自动化杂志,M. D. Adams (2000) 第 16 卷,第 6 期,第 753-761 页,2000 年 12 月,doi: 10.1109/70.897786。URL: ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=897786&isnumber=19436

  • SLAM 中的激光雷达里程计,V. Kirnos, V. Antipov, A. Priorov, 和 V. Kokovkina,第 23 届开放创新协会(FRUCT)会议,博洛尼亚,2018 年,第 180-185 页。doi: 10.23919/FRUCT.2018.8588026,URL: ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8588026&isnumber=8587913

第四部分:使用机器学习实现自适应机器人行为

本节专门致力于机器人学中的人工智能。在上一节中我们讨论的机器人导航任务的基础上,我们将学习如何使这种行为具有适应性,从而使机器人能够从其行动的结果中学习,以改进其性能,也就是说,使其能够更快地实现预期目标。

本节包含以下章节:

  • 第十章,在机器人学中应用机器学习

  • 第十一章,通过强化学习实现目标

  • 第十二章,使用 OpenAI Gym 进行机器学习

第十章:在机器人中应用机器学习

本章提供了机器人中机器学习(ML)的实战介绍。尽管我们假设您尚未在这个领域工作,但拥有一些统计学和数据分析背景将有所帮助。无论如何,本章旨在对主题进行温和的介绍,更倾向于直觉而非复杂的数学公式,并将重点放在理解机器学习领域中常用的概念。

在本章中,我们将通过提供特定机器人的具体示例来讨论这些概念。这在某种程度上是原创的,因为大多数关于机器学习的参考书籍和书籍都提供了面向数据科学的应用示例。因此,随着您对机器人学的熟悉,您应该更容易以这种方式理解这些概念。

通过对深度学习的解释,您将了解这项技术对于机器人通过处理来自机器人摄像头的原始数据(2D 和/或 3D)以及特定距离传感器获取周围环境知识是多么关键。在本章中,通过解释对象识别的具体示例,您将学习原始图像数据是如何被处理以构建机器人在机器人中的知识,使其能够采取智能行动。

本章将涵盖以下主题:

  • 设置 TensorFlow 系统环境

  • 机器学习在机器人中的应用

  • 机器学习(ML)管道

  • 一种将机器学习程序化应用于机器人的方法

  • 应用于机器人的深度学习——计算机视觉

我们将为 GoPiGo3 实现的具体应用涉及计算机视觉,这是机器人中最常见的感知任务。装备了这种能力,机器人应该能够意识到它周围的物体,使其能够与之交互。通过本章的学习,我们期望您能够掌握在机器人中何时以及如何应用深度学习的基本见解。

技术要求

对于本章的示例,我们将使用 TensorFlow(www.tensorflow.org/),这是谷歌在 2015 年开源的机器学习框架,由于所有积极参与开发或作为最终用户的众多人员,它已成为数据科学社区中的大哥大。

TensorFlow 的主要 API 是用 Python 开发的,我们将使用它。为了安装它,我们需要在我们的系统中安装知名的 pip Python 软件包管理器。尽管它捆绑在 Ubuntu 操作系统中,但我们提供了安装说明。稍后,我们将介绍 TensorFlow 的安装过程。

让我们先提供本章代码的路径,然后描述配置笔记本电脑以使用 TensorFlow 的逐步过程。

在本章中,我们将使用位于github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter10_Deep_Learning_Chapter10_Deep_Learning_文件夹中的代码。

将其文件复制到ROS(即机器人操作系统)工作空间,以便它们可用,并将其余部分留在src文件夹之外:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter10_Deep_Learning_ ~/catkin_ws/src

这样,你将拥有一个更干净的 ROS 环境。像往常一样,你需要在笔记本电脑上重新构建工作空间:

$ cd ~/catkin_ws
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

然后,让我们开始设置 TensorFlow。

设置 TensorFlow 的系统环境

首先,我们将设置pip,Python 包管理器,然后是执行机器学习(ML)的框架,即 TensorFlow。

安装 pip

Ubuntu 发行版通常预装了pip。除非 Python 库要求你升级,否则你可以保持相同的版本。在任何情况下,我们建议使用最新版本,如下所述。

安装最新版本

这部分适用于你需要安装或升级pip的情况:

  1. 首先,如果有旧版本,请先删除它:
$ sudo apt remove python-pip

我们这样做是因为 Ubuntu 仓库可能没有pip的最新版本。在下一步中,你将访问原始源以获取所有更新。

  1. 下载安装脚本并执行它:
$ sudo apt update
$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
$ sudo python get-pip.py
  1. 检查已安装的版本:
$ pip --version
 pip 19.3.1 from /usr/local/lib/python2.7/site-packages/pip (python 2.7)

如果它已经存在于你的系统中,你可以很容易地使用pip本身进行升级:

$ sudo pip install --upgrade pip

现在,你已准备好进行 ML 环境的安装。

安装 TensorFlow 和其他依赖项

OpenCV,这个知名的开源计算机视觉库(opencv.org/),为 ROS 带来了图像处理的能力。它被 TensorFlow 用来处理从机器人摄像头获取的图像。要在你的系统中安装它,你需要我们之前解释过的pip包管理器:

$ pip install opencv-python --user

--user标志确保包被安装到用户主目录的~/.local/lib/python2.7/site-packages下。否则,它应该被安装到系统范围内的/usr/local/lib/python2.7/dist-packages路径,就像pip一样(在这种情况下,你应该使用sudo进行安装)。

OpenCV ROS 桥(wiki.ros.org/cv_bridge)与 ROS 的全栈安装一起提供。如果由于某种原因,你的环境中缺少这个包,你可以很容易地使用以下命令安装它:

$ sudo apt update && sudo apt install ros-<ROS_VERSION>-cv-bridge

对于<ROS_VERSION>标签,使用kinetic值或melodic,具体取决于你拥有的 ROS 发行版。

最后,按照以下步骤安装 TensorFlow:

$ pip install --upgrade tensorflow --user

使用--upgrade标志,如果你已经安装了该包,你可以更新它。如果你在使用 Ubuntu 16.04,TensorFlow V2 将会抛出兼容性问题。在这种情况下,按照以下步骤安装 TensorFlow V1:

$ pip install --upgrade tensorflow==1.14 --user

在 Ubuntu 18.04 中,你将准备好升级版的 TensorFlow。

使用 GPU 实现更好的性能

或者,你也可以使用 TensorFlow 的 GPU 版本来利用笔记本电脑上的这种硬件。GPU(即图形处理单元)卡主要用于为屏幕上的显示输出供电。因此,它在图像处理方面非常出色。

由于我们在机器学习中需要进行的计算类型非常相似(即,浮点、向量和矩阵运算),你可以通过使用 GPU 而不是 CPU 进行计算来加速你的机器学习模型的训练和使用。

通过使用 GPU,你可能在速度计算上至少能比使用 CPU 快 10 倍,即使在最便宜的 GPU 卡上也是如此。因此,选择 GPU 是值得的。在 Ubuntu 18.04 中安装相应 TensorFlow 库的命令相当简单:

$ pip install --upgrade tensorflow-gpu --user

与之前一样,如果你正在使用 Ubuntu 16.04,请安装 TensorFlow V1 以避免兼容性问题:

$ pip install --upgrade tensorflow-gpu==1.14 --user

安装了 TensorFlow 后,无论是正常版本还是具有 GPU 性能的版本,你就可以在 ROS 中使用机器学习了。

机器学习进入机器人领域

机器学习的根源在于统计学。记得当你有一个在 x-y 坐标系上的点云,并试图找到同时最适合所有这些点的直线吗?这就是我们所说的线性回归,可以用一个简单的解析公式来解决。回归 是你开始学习机器学习时通常学习的第一个算法。

为了获得视角,请注意,在 1980 年之前,人工智能和机器学习是同一知识体系的一部分。然后,人工智能研究人员将他们的努力集中在使用逻辑、基于知识的途径上,而机器学习保持算法途径,回归 是最基本的方法,其主要的算法包是基于神经网络的。因此,这一事实有利于机器学习作为一个独立的学科发展。

沿着 60 年代和 70 年代神经网络传统研究的路径,机器学习在这个领域持续发展。然后,它的第一个黄金时代出现在 90 年代。

然而,25 年前,神经网络所需的计算机资源超出了普通个人电脑的范畴,因为需要处理大量数据才能获得准确的结果。直到十多年后,计算能力才对每个人开放,然后基于神经网络算法的问题解决最终成为了一种商品。

这个事实将我们带到了当前机器学习(ML)的繁荣时期,在这个时期,内容推荐(如商店、电影和音乐)以及面部/物体识别(基于摄像头的应用程序)等功能在大多数现代智能手机中被广泛使用。

另一方面,机器人从 1950 年开始在工业领域展开其道路,最初只是执行重复动作的机械装置。随着人工智能及其伴随的学科机器学习(ML)的并行发展,这些领域的实际成果可以转移,因为机器人也由与解决机器学习问题相似的 CPU 供电。然后,机器人逐渐获得了通过意识到它们在环境中的影响来更好地完成动作的能力。来自机器人的摄像头和传感器的数据为学习系统提供反馈,使得它每次都能表现得更好。这个学习系统实际上就是一个机器学习流程。

机器学习与人类学习有何不同?嗯,我们的大脑要高效得多。要首次识别一个动物是狗还是孩子,一个小孩只需要四到五个样本,而一个机器学习算法需要数百个样本才能在答案上准确无误。这就是为什么机器人使用的机器学习模型需要用大量数据进行预训练的根本原因,这样机器人就可以通过智能动作——即从一个位置拿起一个物体并将其移动到另一个之前标记为目标的位置(这是物流行业的一个典型问题)——来准确实时地做出反应。

这个识别对象的任务就是我们将在本章的实际例子中要做的。我们将为机器人提供能够识别不同种类常见对象(如球、鼠标、键盘等)的训练模型,并将观察当将其置于这些对象之前时的反应。因此,让我们继续解释围绕这个实际例子中关于识别多种对象的相关概念。

机器学习中的核心概念

在进入图像中对象识别的使用案例之前,让我们先来看一个更为简单的例子,即根据几个独立变量(如面积、房间数量、距离市中心距离、人口密度等)预测房价。

首先,为了有一个可以工作的机器学习算法,我们需要一个基础模型,当输入数据时,它可以产生预测结果。数据必须根据我们为模型选择的特征(即独立变量)提供。然后,通过与我们简单例子的对应,我们可以解释在机器学习问题中涉及的几个概念:

  • 算法是整体计算,指定为一系列要遵循的指令或步骤,以产生一个结果。所有的指令都必须明确无误,执行算法的参与者不需要做出任何额外的决策;所有决策都包含在算法中,算法指定在需要评估条件时在某个特定点要做什么。然后,你可以轻松推断出算法是可以用计算机编程的,无论使用哪种语言。在预测房价的例子中,算法包括将给定的样本数据(即面积、房间数量等)的指令序列应用到数据上,以获得其价格预测。

  • 模型提供了一种分析函数的假设,该函数应用于输入数据以获得预测。例如,我们可以说房价模型是输入的线性函数,也就是说,给定房屋面积的百分比增加会导致其预测价格的百分比增加。对于其他独立变量,同样的推理也适用,因为我们假设了线性依赖。模型在算法的一些步骤中应用。

  • 特征是我们模型的独立变量,也就是说,你必须用这些可用的数据来预测房价。在我们的例子中,这些是面积、房间数量、距离市中心和人口密度。

  • 数据集是一个结构化数据集合,为所选的每个特征提供大量物品的值。在我们的例子中,数据集应该是一个表格,其中每一行包含一个具体房屋的可供数据,每一列包含每个所选特征的值,即面积列、房间数量列、距离市中心列、人口密度列等。

面对新的问题时,数据科学家必须决定这三个要素:算法、模型和特征。最后一个话题,特征选择,是人为解决机器学习问题提供额外价值的地方;其余任务都是自动化的,由计算机完成。下一个小节将详细解释特征是什么,并强调其选择对于获得准确预测的重要性。

机器学习中的特征选择

机器学习中的特征构成一组必须由用户选择的特性,并且数据集的构建是基于这个选择的。做出良好特征选择的专业知识更多的是一个问题,即经验和洞察力,而不是一个结构化的过程。因此,一个好的数据科学家是理解问题并能将其分解为其基本部分以找到相关特征的人。这些特征作为独立变量,可以从它们中做出准确的预测。

为了解决一个机器学习问题,进行正确的特征选择是至关重要的。如果你没有检测到相关的特征,无论你向求解器投入多少数据,你都不会得到一个好的预测。如下所示,我们将数据集输入到机器学习算法中,以获得一个结果,即预测:

图片

数据收集是根据选定的特征构建的。例如,如果你决定基于三个特征——面积、房间数量和距离市中心——为给定城市的房屋建立价格预测模型,那么对于你想要预测价格的每一座新房屋,你必须向算法提供这些特征的特定值,例如,85 平方米、4 个房间和距离市中心 1.5 公里。

接下来,理解这些特征值是如何组合起来以获得预测的是至关重要的。

机器学习流程

问题解决分为两部分。第一部分是根据此图所示的流程训练模型:

图片

由于我们假设一个简单的模型,其中输出线性依赖于特征值,因此训练的目标是确定应用于每个特征的权重,以获得预测。让我们用这个数学公式来解释它:

价格 = W1 * 面积 + W2 * 房间数 + W3 * 距离

如你所推断,权重,W1W2W3,是乘以每个特征的系数。在将三个乘积相加后,我们得到预测价格。因此,训练阶段包括找到最适合我们现有数据集的权重集。在训练集中,数据包含特征和实际价格。因此,通过应用最小二乘回归算法(www.statisticshowto.datasciencecentral.com/least-squares-regression-line/),我们确定W1W2W3的最佳值,以最适合所有提供的实际价格。此算法保证所得方程是提供所有用于训练的项目最小全局误差的方程。

但我们不想仅仅拟合提供的数据,因为我们已经知道这些价格。我们希望得到的方程也能是任何其他未知价格的房子的最佳拟合。因此,验证这种方程的方法是使用一个不同的数据集,称为测试集,而不是我们用于训练的数据集。在执行训练之前,程序化的方法是分割可用的数据。典型的方法是制作两个随机集:一个包含 70%-90%的数据用于训练,另一个包含剩余的 30%-10%用于验证。这样,训练集为我们提供了暂时的最佳拟合权重 W1、W2 和 W3,而验证集用于估计我们的机器学习模型在操作上定义得有多好,即最小平方误差。

第二部分对应于预测本身,即当我们的机器学习算法在实际应用中投入生产时。在预测(生产)阶段,我们有以下内容:

图片

实际上,机器学习的过程更像是循环的,而不是线性的,因为随着我们获得更多的训练数据,我们可以改进权重的计算,然后用新的系数集重新编写方程,即 W1、W2 和 W3。这样,机器学习是一个迭代的过程,随着更多数据的可用性和模型的反复重新训练,可以提高预测的准确性。

从机器学习到深度学习

在本节中,你将了解深度学习是什么以及它与机器学习的关系。而获得这种洞察力的最直接方法就是简要概述最常用的算法。然后,从那个角度来看,你可以理解为什么深度学习现在是研究最活跃的领域。

机器学习算法

如前图和解释所指出的,算法是机器学习问题解决的中心部分。数据科学家还必须根据他们面临的问题类型选择应用哪种算法。所以,让我们快速概述一下最常用的算法。

回归

回归试图找到最适合点云的曲线,并且已经在预测房价的案例中进行了详细描述。在这种情况下,我们一直在谈论线性依赖,但算法可以推广到任何可以用系数(权重)和自变量(特征)之间的点积之和表示的曲线,即多项式。一个常见的例子是特征的平方项。在这种情况下,曲线是抛物线,从数学上可以表示如下:

y = W1 * x + W2 * x² + W3 * 1

让我们用一个现实生活中的例子来回顾一下。给定一个独立变量,即候选人的工作经验年数,我们希望预测他们在申请工作机会时的薪资。您很容易理解,至少在工作经验的前几年,薪资的依赖性并不遵循线性依赖,也就是说,拥有 2 年工作经验的候选人不会比拥有 1 年工作经验时获得两倍的薪资。随着他们积累更多经验,薪资的百分比增长将逐渐更高。这种关系可以用抛物线来建模。然后,从独立变量x和薪资,我们定义两个特征:x

逻辑回归

逻辑回归用于分类问题,这是机器学习中非常常见的一种类型。在这种情况下,我们试图预测一个二元分类,例如通过/失败、赢/输、生/死或健康/生病。这个算法可以理解为回归的特殊情况,其中预测变量是分类的,也就是说,它只能取有限集合的值(如果是二元分类,则为两个)。基础模型是一个概率函数,给定一个独立变量的值,如果得到的概率大于 50%,我们预测通过、赢、生或健康,如果低于 50%,预测就是另一个类别,即失败、输、死或生病。

产品推荐

产品推荐是消费者领域最常用的功能,例如购物、看电影和阅读书籍,输入用户特征以及具有相似特征的其他用户的高评分项目。实现此功能有多种算法,如协同过滤或特征化矩阵分解。如果您对这个领域感兴趣,我们在本章末尾的进一步阅读部分提供了良好的介绍性参考文献。

聚类

聚类是一种场景,其中我们有许多项目,我们希望根据相似性将它们分组。在这种情况下,项目是无标签的,我们要求算法做两件事:

  • 将相似的项目组成一组。

  • 标记这些组,以便新项目既被算法分类又被标记。

例如,考虑一个关于许多主题的文本集合,您希望算法将相似文本分组并识别每个组的主要主题,即对它们进行标记:历史、科学、文学、哲学等。用于这种场景的经典算法之一是最近邻方法,其中您定义一个度量标准,为每一对项目计算它,并将足够接近的(基于定义的度量标准)对分组在一起。它可以被认为是一种计算在每一对两点之间的距离类似的函数。

多分类场景中,如果有超过两个类别——比如说 n 个——可以通过解决 n 个逻辑回归来处理,其中每个逻辑回归对每个可能的类别执行二分类。例如,如果我们想检测图像中的主要颜色(有四种可能的类别:红色、绿色、蓝色或黄色),我们可以构建一个由四个逻辑回归组成的分类器,如下所示:

  • 红色/非红色

  • 绿色/非绿色

  • 蓝色/非蓝色

  • 黄色/非黄色

可能存在第五个类别,我们可以称之为未知,对于图像没有被归类为红色、绿色、蓝色或黄色的情况。最后,这种应用于图像的多逻辑回归类型是进入最后一种算法——深度学习——的大门,我们将从现在开始直到本章结束都聚焦于此。

深度学习

深度学习现在是机器学习中最活跃的研究领域。该算法的底层模型是一个神经网络,其工作方式试图模仿人脑的工作方式。模型中的每个神经元都使用一个特殊的功能——称为sigmoid——从其输入进行回归,该功能提供了一个尖锐但连续的输出事件的概率分布。这个函数与之前描述的逻辑回归中使用的概率函数相同。在这种情况下,如果得到的概率大于 50%,则神经元被激活并向下传递给另一个或多个神经元。如果低于 50%,则神经元不活跃,因此对下游的影响可以忽略不计。

接下来,我们将提供更多关于深度学习工作原理的细节,这样当你使用 GoPiGo3 进行实际练习时,你就知道在 ROS 中发生了什么。

深度学习和神经网络

从现在开始,我们将基于图像中物体识别的实际例子来解释我们的说明,对于机器人来说,这些信息将由树莓派摄像头提供。在下面的图中,你可以看到一个表示神经网络的图示,它区分了可能存在的三种层:

  • 输入层是我们提供数据集的地方。记住,这样的数据必须根据所选特征进行结构化,也就是说,每个特征一个神经元。我们稍后将会讨论这种特定且非常常见的图像数据集案例。

  • 隐藏层(一个或多个)是深度学习管道中的中间步骤,它们提取更多特征,以便算法能够更好地区分对象。这些隐藏特征是隐含的,并且最终用户不一定需要了解它们,因为它们的提取是网络结构本身的内在(自动)属性。

  • 输出层提供预测。如果一个神经元被激活(概率大于 50%),则每个神经元提供一个逻辑 1,如果没有被激活(低于 50%),则提供一个 0。因此,输出层的最终概率将是带有一定概率的答案:

许可证 CC-BY-SA-2.5 来源:https://commons.wikimedia.org/wiki/File:Neural_Network.gif

按照顺序方法,让我们通过解释每一层对输入数据的作用来解释神经网络是如何工作的。

输入层

这是深度学习管道的第一步,这一层的最常见结构是拥有与图像像素数三倍一样多的输入神经元(特征):

  • 对于 256 x 256 像素大小的图像,这意味着 65.536 像素。

  • 通常,我们将处理彩色图像,因此每个像素将有三个通道:红色、蓝色和绿色;每个值代表从 0 到 255 的强度,颜色深度为 8 位。

  • 然后,特征的数量是 65.536 x 3 = 196.608,每个特征的价值将是一个介于 0 到 255 之间的数字。每个特征都由输入层中的一个神经元表示。

之后,神经网络被要求回答这个问题:图片中是否有猫?下一层的目的是提取图像的必要方面来回答这个问题。

隐藏层(s)

为了理解这一层是如何工作的,让我们回顾一下我们之前解释的回归算法。在那里,我们将预测变量表示为特征的线性组合——面积、房间数量和到中心的距离分别乘以权重,即 W1W2W3。将我们的神经网络与之类比,特征将应用于神经元,权重将应用于连接每一对神经元的边:

来源:https://commons.wikimedia.org/wiki/File:Artificial_neural_network_pso.png, Cyberbotics Ltd.

CC BY-SA 3.0 https://creativecommons.org/licenses/by-sa/3.0

每个特征的价值将使用其神经元的 sigmoid 函数(输入层;j 神经元)进行处理,以产生一个概率值,Sij,然后乘以连接到每个下游神经元(隐藏层;i 神经元)的边权重 Wij。因此,隐藏层中神经元 i 的特征输入是一个乘积之和,其项数与上游连接到它的神经元数量(输入层;j 神经元)一样多。

这样的结果是所有 j 的项之和,Sij,其中索引 j 是一个迭代器,它遍历连接到输入层中 i 神经元的所有神经元。连接神经元对的边的权重 Wij 更恰当地称为超参数

隐藏层的神经网络结构提供了我们所说的内在特征,这些是网络的固有属性,不需要用户选择(它们由神经网络的开发者建立)。用户需要做的是训练网络以获得最佳的一组权重,Wij,使网络尽可能准确地预测可用数据集。这就是深度学习的魔力所在,因为精心设计的层架构可以提供非常准确的预测模型。缺点是您需要大量的数据来获得一个训练良好的网络。

从一开始回顾,给定一个输入图像,您可以根据前一层,Sij,和连接到神经元i的边的权重,Wij,计算每个层神经元的特征输入,Fi

Fi = (对 j 求和) [Sij * Wij]

逐层向下进行,您最终可以获得输出层神经元的概率,因此可以回答分析图像包含的预测。

如前所述,鉴于网络结构的复杂性,您可能会猜测,为了训练这样的模型,您可能需要比传统机器学习算法(如回归)更多的数据。更具体地说,您需要计算有多少超参数作为连接成对神经元的边。一旦达到这个里程碑,您就会得到一个训练好的网络,可以应用于未标记的图像以预测其内容。

输出层

对于我们示例中的问题,即图片中是否有一只猫?如果是,则图像显示猫,否则不是。所以,我们只需要输出层中的一个神经元,如图所示。然后,如果用许多猫的照片进行训练,这个网络可以分类图像,以判断它是否包含猫(1)或不包含(0)。这里的一个重要点是,模型应该能够识别图像中猫的任何位置,中心、左、右、上、下等:

图片

来源:https://commons.wikimedia.org/wiki/File:NeuralNetwork.png

如果我们需要分类 10 种对象(例如几种类型的宠物),我们需要一个有 10 个神经元的输出层。网络的计算结果将是一个包含 10 个概率的向量——每个概率都与每个神经元相关联,提供最大值(最接近 100%)的那个将告诉我们输入图像中更可能有什么类型的宠物。

当然,您可以使网络更复杂,添加更多的输出神经元(以及可能的更多隐藏层)以获得图像的更多细节。考虑以下内容:

  • 识别是否有一只猫或两只或更多。

  • 识别面部特征,例如眼睛和/或嘴巴是张开还是闭合:

图片

来源:https://www.flickr.com/photos/55855622@N06/5173363938 by jeici1,许可:CC BY 2.0

这是一个相当复杂的话题,超出了本介绍章节的范围,其目标只是提供一个对深度学习是什么以及它是如何工作的描述性理解。无论如何,鼓励读者深入研究这个话题,为此,在本章末尾的“进一步阅读”部分包含了两个教学参考:直观深度学习第一部分和第二部分

从这个点开始,我们转向实际部分,首先陈述一个处理机器人中机器学习问题的通用方法。

在机器人中程序化应用机器学习的方法

机器学习的一个特定方面是,机器人的响应必须实时发生,没有延迟,这样采取的行动才是有效的。例如,如果它发现一个障碍物横在它正在跟随的路径上,我们期望它能避开它。为此,障碍物识别必须发生在机器人视野中出现时。因此,避免障碍物的后续行动必须立即采取,以避免碰撞。

我们将用一个端到端的示例来支持我们的方法描述,这个示例涵盖了 GoPiGo3 目前能做的所有事情。然后,通过这个示例,我们期望 GoPiGo3 能够从当前位置携带负载到目标位置(这在垃圾收集机器人中是一个常见的情况)。

应用程序编程的一般方法

解决这个挑战所涉及的步骤如下:

  1. 确定涉及哪些高级任务。

  2. 列出构成高级任务的原子任务。在这个层面上,我们在 ROS 中创建程序,编写节点脚本和启动文件。

  3. 通过调整高级任务的算法以适应我们试图解决的特定情况来编程机器人应用。

接下来,我们将对每个步骤进行分解,以便我们可以在真实机器人中实现功能:

  1. 这些是需要执行的高级任务:

    • SLAM:这是同时定位与建图(SLAM)来构建实际环境的地图。

    • 导航:设置目标姿态,GoPiGo3 可以自主移动,直到达到目标。

    • 视觉识别:GoPiGo3 可以识别它需要放置的位置,以便收集它携带的垃圾。

  2. 列出例子中涉及的原子任务。比如说,为了成功,GoPiGo3 必须能够做到以下这些:

    1. 加载环境地图。

    2. 根据地图信息计算达到目标位置的优化路径。

    3. 开始向目标导航。

    4. 避开路径上发现的障碍物。

    5. 如果在环境中发现意外条件,阻止它进一步前进,然后寻求帮助。

    6. 在收到帮助后,继续前往目标位置的路。

    7. 识别垃圾存储入口,并停在吊车将钩住装载垃圾的确切位置。

  3. 编程机器人应用程序。前面的每个原子任务将对应一个 ROS 节点脚本,这可以表示为一个只有一个<node>标签的启动文件。然后,你必须将这些七个节点放在 ROS 图中,并绘制应该使用主题连接成对的边:

    • 对于每个发布的主题,你应该确定主题应该以多高的频率发布,以便机器人能够快速反应。例如,由于 GoPiGo3 的典型速度为 1 米/秒,我们希望扫描距离每 1 米更新 10 次。这意味着机器人每前进 10 厘米(=0.1 米)就会收到一个感知更新,并且能够检测到半径为 0.1 米的圆周外的障碍物存在。为了使机器人能够反应并避开障碍物,计算机器人可以反应的最小发布速率的简单公式为:(1 m/s) / 0.1 m = 10 Hz

    • 对于每个节点订阅的主题,代码应该触发一个机器人动作,使其能够成功适应环境中的这种条件。例如,给定提供 GoPiGo3 周围距离的主题,当其值低于一个阈值,例如 20 厘米(你将看到这个数字的来源),GoPiGo3 将重新计算局部路径以避开障碍物。我们应该根据之前决定的 10 赫兹的发布速率来选择这个阈值;记住,这个速率来自机器人每前进 10 厘米就会收到一个感知更新的事实。考虑到 2 的安全系数,阈值简单地为10 厘米 * 2 = 20 厘米,为避开障碍物提供空间和时间。

目前对于原子任务 1 到 6,不需要机器学习。但是,当涉及到与垃圾停止入口对齐时,GoPiGo3 需要知道的不只是它的姿态,还有它相对于入口的位置,这样吊车才能成功地将装载的垃圾钩住。

集成机器学习任务

第 7 步的这个节点将其功能定义为识别垃圾存储入口并在吊车将装载的垃圾钩住的精确位置停止。因此,Pi 相机发挥了作用,并且必须在节点的逻辑编程中包含图像识别能力。这个逻辑可以简要地表达为发布cmd_vel消息到机器人的差速驱动器,使 GoPiGo3 能够正好放置在正确的位置。因此,这是一个视觉感知(即图像中入口形状的对齐与否)和运动命令之间的反馈机制,以纠正和居中:

  • 如果图像中的入口向左偏移,机器人应该向左旋转。

  • 如果向右偏离,它应该旋转一个与入口到图像中心的距离成比例的角度。

你应该首先问的问题应该是:我们如何将这样的机器学习任务与我们的机器人应用集成?答案将阐明 ROS 发布/订阅机制既强大又简单。其中立性允许我们通过遵守以下两个规则将任何可以封装成黑盒的任务集成进来:

  • 输入通过订阅的主题提供。

  • 输出通过发布的主题传递。

在将机器学习应用于使机器人定位在入口门的具体案例中,我们有以下内容:

  • 机器学习节点(订阅的主题)的输入来自 Pi 摄像头的图像流。

  • 机器学习节点(发布的主题)的输出是从门形状到图像中心的水平距离。

然后,GoPiGo3 驱动节点将输出主题作为数据,以确定应该发送到电机的哪个cmd_vel命令。这建立了一个与机器学习节点的反馈循环,使得机器人位置收敛,最终在入口门中定位:

图片

机器学习发布的主题object_position是一个整数,它提供了从对象(入口门)质心到图像帧中心的像素距离。

虽然这超出了本章的范围,但在此处了解 ROS 提供节点之间其他交互机制是有好处的,程序员选择使用哪个取决于要实现的具体功能:

  • ROS 服务是服务器/客户端架构的经典实现。客户端节点(驱动节点)向服务器节点(机器学习节点)发出请求,并执行计算(从入口门到图像帧中心的像素距离)。然后,将响应发送回客户端。与发布/订阅机制的关键区别在于,它不期望接收请求;节点独立地以代码中设置的速率发布消息,无论其他节点是否在监听。

  • ROS 动作类似于 ROS 服务,即它对节点的请求提供响应,不同之处在于,在这种情况下,客户端节点不会阻塞执行(直到收到答案)。也就是说,它继续执行其他代码指令,当它收到响应时,客户端触发程序化的动作(使机器人旋转以对齐)。这种行为被称为异步,与 ROS 服务不同,ROS 服务本质上是同步的,即它阻塞节点执行直到收到响应。

那么,让我们深入了解如何让 GoPiGo3 意识到它周围的对象,我们将在本章的最后部分完成这项工作,在那里我们将构建一个能够检测广泛对象类型的通用机器学习节点。

应用于机器人的深度学习——计算机视觉

本章的实践部分包括操作性地实现前面描述的 ML 节点。我们之前将其表示为黑盒,现在作为一个 ROS 包开发,你可以将其与前面章节中发现的任何功能集成:

  • 在第七章,机器人控制和仿真中,对 Gazebo 中的虚拟机器人和物理 GoPiGo3 的遥控

  • 在第八章,使用 Gazebo 的虚拟 SLAM 和导航中为虚拟机器人进行机器人导航,以及在第九章,机器人导航的 SLAM中为物理 GoPiGo3 进行导航。

因此,我们将本节分为两部分:

  • 第一部分,Gazebo 中的物体识别,为你提供了集成 Gazebo 中图像识别 ML 节点的工具,这样在完成实践后,你可以发挥你的创造力,将物体识别与遥控机器人导航中的任何驱动节点结合,使虚拟机器人变得更智能。

  • 第二部分,现实世界中的物体识别,提供了与物理 GoPiGo3 相同的集成,你将发现无论图像来自哪里,即 Gazebo 或现实世界,ML 节点黑盒都是相同的。当将 ML 节点订阅链接到任何这些场景的图像时,选择权在你手中。

此过程还提供了一种测试新机器人应用的操作方法。从 Gazebo 中的验证开始,你将主要检查开发的代码没有重大错误,机器人按预期工作;然后,将其带入现实世界——了解所有不在 Gazebo 中存在的外部变量如何影响机器人,看看它的反应,然后决定你需要对哪些代码进行优化才能使其工作。

Gazebo 中的物体识别

要获取代码,请按照本章“技术要求”部分开头提供的说明操作。在 Gazebo 中的练习将会非常简单且非常有效。你将检查虚拟 GoPiGo3 如何从来自机器人摄像头的图像流中识别一个常见的网球

  1. 让我们先在 Gazebo 中创建一个球体的模型:
T1 $ roslaunch tf_gopigo gopigo3_world.launch
  1. 然后,启动一个rqt_image_view节点来观看从机器人摄像头感知到的主观视角:
T2 $ rosrun rqt_image_view rqt_image_view

点击左上角的空白框,选择;/gopigo/camera1/image_raw`主题。然后,你将看到机器人通过其前摄像头获取的主观视角。

  1. 接下来,在 Gazebo 中创建一个球体的模型:
T3 $ sudo -s
   $ roslaunch models_spawn_library spawn_tennisball.launch

请记住,models_spawn_library包要求你以超级用户身份执行启动文件。一旦球体在 Gazebo 中创建,过程就会结束,T3将被释放。

  1. 然后,启动遥控节点,这样你就可以像往常一样用键盘控制 GoPiGo:
T4 $ rosrun key_teleop key_teleop.py /key_vel:=/cmd_vel

此软件包是在第七章中安装的,机器人控制和模拟。如果您尚未安装,请现在安装。此 ROS 软件包的来源在github.com/ros-teleop/teleop_tools

  1. 最后,启动图像识别节点并观察屏幕输出。在您已经启用sudo的地方使用T3
T3 $ sudo -s
   $ roslaunch tf_gopigo start_image_recognition.launch

通过订阅/result主题,您可以获取更简洁的流,该主题仅提供识别对象的名称:

T6 $ rostopic echo /result

看看以下截图的组成,显示如何在终端窗口(左下角)中识别网球:

是否容易复制?我们希望如此。现在,让我们使用物理机器人重复此过程。

真实世界中的物体识别

首先,记得像往常一样将 ROS 主 URI 指向机器人:

$ export ROS_MASTER_URI=http://gopigo3.local:11311

将此应用于笔记本电脑上的每个新终端,或将此行包含在.bashrc文件中。物理机器人配置如图所示,GoPiGo3 位于一个小黄色球的前面:

在 Raspberry Pi 的两个独立终端中运行以下两个命令:

r1 $ roslaunch mygopigo gopigo3.launch
r2 $ roslaunch raspicam_node camerav2_410x308_30fps.launch

您在前面使用的是第六章中的软件包,在 ROS 中使用 ROS-命令和工具进行编程。所以请确保您没有删除它们,如果已删除,请恢复它们。在笔记本电脑上运行新软件包以执行图像识别:

T1 $ rosrun image_transport republish compressed in:=/raspicam_node/image out:=/raspicam_node/image_raw

image_transport-软件包(您可以在wiki.ros.org/image_transport找到其 ROS 维基页面)在 ROS 中常用,以提供对低带宽压缩格式的图像传输的透明支持。

然后,T1使raspicam_node/image——来自r2的输出——以原始格式可用,即/raspicam_node/image_raw主题,这是T1的输出。这有助于图像流,然后可以通过start_image_recognition.launch稍后进行处理。此时,查看 ROS 图非常有用:

记住,此可视化是通过另一个终端中的rqt_graph命令启动的。找到由image_republisher_157...节点执行传输操作。然后,启动一个rqt_image_view节点来观察通过 Pi 相机感知的主观视图:

T2 $ rosrun rqt_image_view rqt_image_view

在弹出窗口中,您必须选择/raspicam_node/image_raw主题以从 Pi 相机获取主观视图。

最后,正如我们在模拟中所做的那样,启动图像识别节点并订阅/result主题:

T3 $ sudo -s
 $ roslaunch tf_gopigo start_image_recognition.launch rgb_image_topic:=/raspicam_node/image_raw
T4 $ rostopic echo /result

对于 Gazebo 场景,唯一的区别是您必须将 Pi 相机提供的raspicam_node主题重映射到名为rgb_image_topic的主题,这是图像识别节点接受的主题。

我们依次向机器人展示了三个不同的物体:黄色的球、鼠标和监控器。找出机器人如何实时识别这三个物体。这令人惊讶吗?

可以在这里看到黄色的球:

图片

然后,鼠标可以在这里看到:

图片

最后,监控器可以在这里看到:

图片

如果你已经到达这个阶段,你就有很好的条件开始创建在 ROS 中集成对象识别作为使用 GoPiGo3 执行智能动作的能力的高级应用程序。

摘要

本章提供了机器人学中机器学习的快速介绍。我们希望你已经对机器学习和深度学习有了定性理解,了解了神经网络如何处理图像以识别物体,并且能够在模拟和/或物理机器人中操作实现该算法。

机器学习是一个非常广泛的领域,你不应该期望,实际上也不需要成为该领域的专家。你需要吸收的知识是如何将深度学习能力集成到你的机器人中。

正如你在实际案例中看到的,我们使用了一个覆盖常见物体的预训练模型。然后,我们只是简单地使用了这个模型,并且不需要额外的训练。网上有大量由数据科学公司和开源开发者共享的已训练模型。你应该花时间寻找这些模型,只有在机器人面临的场景非常特定,通用机器学习模型无法以足够的准确性覆盖时,才去训练自己的模型。

在最后两章中,我们将重点关注强化学习,这是一个与本章描述的深度学习技术互补的任务。后者使机器人获得环境的感知,而前者则将几个面向目标的动作串联起来。

问题

  1. 解决机器学习任务需要更多数据科学家经验和洞察力的任务是什么?

A) 算法选择

B) 特征选择

C) 模型

  1. 机器学习和深度学习之间有什么关系?

A) 机器学习涵盖了众多算法,而深度学习仅包括用于寻找深度特征的算法。

B) 深度学习是机器学习的一个子集。

C) 深度学习处理除了神经网络之外的所有机器学习算法。

  1. 你应该如何将机器学习任务与 ROS 应用程序集成?

A) 你应该在模型外部进行训练,然后向 ROS 提供一个结果文件。

B) 你可以选择使用发布/订阅、ROS 服务或动作服务器。

C) 你必须使用 ML 模型的特定通信协议。

  1. 发布/订阅机制和 ROS 服务机制的主要区别是什么?

A) ROS 服务是同步的,而发布/订阅是异步的。

B) ROS 服务是异步的,而发布/订阅是同步的。

C) 发布/订阅不需要从其他节点接收请求来发布消息。

  1. 如果在深度学习应用于机器人——计算机视觉部分中解释的实用示例是用红球而不是黄球进行的,那么使用相同模型的预测会怎样?

A) 是的,颜色不是物体形状识别的特征。

B) 是的,除了识别球体,它还会指出它是红色的。

C) 这取决于模型是否用不同颜色的球体进行过训练。

进一步阅读

要深入了解本章中解释的概念,你可以查看以下参考资料:

第十一章:使用 OpenAI Gym 进行机器学习

在上一章中,我们向您介绍了如何使用深度学习来识别来自树莓派摄像头的实时图像流中的物体。因此,这为机器人提供了执行与识别物体相关的智能动作的能力。例如,如果物体是一个球,机器人可以收集它并把它放好,以免有人踩到球而造成事故。

在本章中,您将了解到强化学习RL),这是一个机器学习的领域,如今是一个非常活跃的研究主题,在某些场景中已经取得了超越人类性能的成功,如最近 AlphaGo 游戏的案例所示(deepmind.com/blog/article/alphago-zero-starting-scratch)。

您将使用 Python 框架OpenAI Gym以实践的方式学习,这是一个用于开发比较强化学习(RL)算法的工具包。我们将提供一个概念性的方法来处理 RL,这将使我们能够使用 Gym 环境以编程方式处理各种问题。为了做到这一点,我们将区分三个主要组件:场景任务代理。在这里,场景是机器人演化的物理环境,任务是机器人预期学习的动作,代理是做出执行动作决策的软件程序。

这种分离将允许您解耦这些组件,并在不同的范围内重用它们。例如,您可能已经训练了一个代理,使得一个两轮驱动的机器人,如我们的 GoPiGo3,能够从一个点到目标位置携带负载,并且可以使用相同的代理与不同的机器人一起使用,例如四轮驱动的 Summit XL(www.robotnik.es/robots-moviles/summit-xl)。代理的代码是相同的,因为它抽象了机器人的具体特征。

同样,您可以使用不同的生成场景来测试同一机器人。这将展示训练好的代理在不同边界条件下执行的能力。带着这些想法,本章将向您介绍 OpenAI Gym API 的基础知识,如何将其与 ROS 环境集成,以及如何通过图形化表示来跟踪训练过程。

本章将涵盖以下主题:

  • OpenAI Gym 简介

  • 运行环境

  • 配置环境文件

  • 运行模拟并绘制结果

在第一部分,您将开始在原生的 Python 环境中使用基础 Gym API。在接下来的部分中,您将学习如何添加 ROS 包装器以在 Gazebo 中训练机器人。

技术要求

在本章中,我们将使用位于github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter11_OpenAI_GymChapter11_OpenAI_Gym文件夹中的代码。将本章的文件复制到 ROS 工作空间中,将它们放在src文件夹内:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter11_OpenAI_Gym ~/catkin_ws/src/

接下来,您需要安装 Anaconda (www.anaconda.com)。这是已成为数据科学社区事实上的开源标准的 Python 发行版。它为机器学习项目提供了一个完整的 Python 环境。

访问 Anaconda 网站的下载部分www.anaconda.com/distribution/#linux,并选择 Python 2.7 捆绑包。我们选择这个包是因为 ROS Python 客户端专注于这个版本;然而,您应该知道它最近在 2019 年 12 月已经停止维护。

Open Robotics 计划在 2020 年 5 月创建一个新的 ROS 发行版,针对 Python 3:discourse.ros.org/t/planning-future-ros-1-distribution-s/6538

下载 Anaconda 后,转到下载目录并输入以下命令以执行安装代码:

$ bash Anaconda2-2019.10-Linux-x86_64.sh

被粗体字母标记的文件名应与您下载的名称匹配。如果不是这样,请运行bash命令,将文件名替换为您实际拥有的名称。

$ conda init命令可以从安装脚本中可选执行,如果成功,它将提供以下输出:

==> For changes to take effect, close and re-open your current shell. <==
If you'd prefer that conda's base environment not be activated on startup, 
 set the auto_activate_base parameter to false:

 $ conda config --set auto_activate_base false

Conda 是 Anaconda 附带的包管理器。它允许您轻松地安装、删除、列出和检查 Anaconda 安装中的 Python 包。

安装 Anaconda 后,您将在~/.bashrc文件中看到以下行被添加:

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/home/${USER}/anaconda2/bin/conda' 'shell.bash' 'hook' 2> /dev/n$
if [ $? -eq 0 ]; then
 eval "$__conda_setup"
else
 if [ -f "/home/${USER}/anaconda2/etc/profile.d/conda.sh" ]; then
 . "/home/${USER}/anaconda2/etc/profile.d/conda.sh"
 else
 export PATH="/home/${USER}/anaconda2/bin:$PATH"
 fi
fi
unset __conda_setup
# <<< conda initialize <<<

为了使添加的配置生效,请在终端提示符中 source 它:

$ source ~/.bashrc

前面的.bashrc行应将您带到(基础)默认的 Anaconda 环境。我们建议您在启动时不要激活 Conda 的基础环境。为了确保这一点,将auto_activate_base参数设置为false

$ conda config --set auto_activate_base false

如果您希望恢复默认配置,可以通过将值更改为true来还原它。最后,您可以选择在终端中手动激活默认的 Anaconda 环境,使用以下命令:

$ conda activate

在这个基础环境中,您能够安装 Jupyter 笔记本。您可以使用它们来查看、运行和修改 Python 笔记本:

(*base*) $ jupyter notebook

请记住,这是与 DexterOS 预安装的用户友好的 Python 运行时。它在第二章,GoPiGo3 单元测试中使用,以运行大多数示例。要取消激活虚拟环境,只需运行以下命令:

(*base*) $ conda deactivate

你可以在以下 URL 找到一个有用的 Conda 包管理器速查表,你应该随身携带:kapeli.com/cheat_sheets/Conda.docset/Contents/Resources/Documents/index

到目前为止,我们已经准备好使用 OpenAI Gym 及其安装;这将在下一节中解释。

OpenAI Gym 简介

在上一章中,我们提供了将 RL 应用到机器人时可以期待的内容的实用概述。在本章中,我们将提供一个一般性的视角,你将发现 RL 是如何用于训练智能 代理 的。

首先,我们需要在我们的笔记本电脑上安装 OpenAI Gym 和 OpenAI ROS,为实际示例做准备。然后,我们将解释其概念。

安装 OpenAI Gym

正如我们在上一章中所做的那样,我们将为本章的 Python 设置创建一个虚拟环境,我们将称之为 gym。以下两个命令允许创建并激活 gym

$ conda create -n gym pip python=2.7
$ conda activate gym

在此之后,安装我们将需要的特定 Python 包:

  • Keras 包 (keras.io/),这是一个高级神经网络 API,在 OpenAI Gym 中使用。记住,它也在上一章中使用过,但我们需要再次安装它,因为这是一个新的 gym 环境。Keras 将使我们能够使用 DQN(即 深度 Q 网络)算法训练代理,这是一个基于深度学习的算法。

  • 你还需要 TensorFlow,因为它被用作 Keras 的后端。

  • 最后,你需要 Gym 包 (github.com/openai/gym),这是 OpenAI Gym 的 Python 实现。

你可以连续安装三个包,如下所示:

(gym) $ pip install tensorflow keras gym box2d-py

现在,检查 gym 的版本:

(gym) $ pip show gym

输出应该是这样的:

Name: gym
Version: 0.15.4
Summary: The OpenAI Gym: A toolkit for developing and comparing your reinforcement learning agents.
Home-page: https://github.com/openai/gym
Author: OpenAI
Author-email: gym@openai.com
License: UNKNOWN
Location: ~/anaconda2/envs/gym/lib/python2.7/site-packages
Requires: pyglet, cloudpickle, six, scipy, numpy, opencv-python, enum34

此外,还需要安装 Jupyter 笔记本,因为一些 Python 示例是以这种友好的格式解释的:

(gym) $ conda install jupyter

最后,安装一个名为 pybox2d 的可选库。这是一个用于游戏和简单模拟的 2D 物理引擎,由 Gym 的一些预装环境使用:

(gym) $ conda install -c https://conda.anaconda.org/kne pybox2d

技术要求到此结束。以下小节是可选的,旨在增加你对管理 Python 和 Anaconda 的不同方式的了解。我们将向你展示如何从源代码安装 OpenAI Gym 并在您的工作文件夹中托管该包,这是一种在开发模式下使用 Python 包的典型方式。

无 Anaconda(可选)

如果你不想使用 Anaconda,而是继续在 Ubuntu 随附的 Python 环境中工作,你可以通过添加 --user 标志在本地用户目录中安装:

$ pip install --user tensorflow keras tflearn gym

这将必要的包放置在 ~/.local/lib/python2.7/site-packages 文件夹中。

以开发模式安装 gym(可选)

这允许你在工作目录中拥有 OpenAI Gym 的源代码,更改包中的文件,并立即看到它们的效果,而无需重新安装 gym 模块:

(gym) $ conda deactivate
$ cd ~/catkin_ws
$ git clone https://github.com/openai/gym
$ cd gym
$ pip install --user -e . 

-e 选项允许这种安装方式,并且适合用作开发者模式。--user 选项将安装本地化到用户的 ~/.local/lib/python2.7/site-packages 位置。

为了保持环境整洁,移除包安装,只保留 Gym 虚拟环境中的 gym Python 包:

$ rm -rf gym.egg-info
$ ls ~/.local/lib/python2.7/site-packages | grep gym | xargs rm

这段代码片段会告诉你如何手动从系统中移除一个 Python 包。

为了复现本章中的示例,我们将遵循之前的方法,即在 Conda 环境中将 Gym 安装为系统包。

安装 OpenAI ROS

为了让代码能在 ROS 中运行,你必须安装 OpenAI ROS,它建立在 OpenAI Gym 之上。执行以下命令以克隆贡献的 ROS 包并开始 ROS 的设置:

$ cd ~/catkin_ws/src
$ git clone https://bitbucket.org/theconstructcore/openai_ros.git
$ cd ~/ros_ws
$ catkin_make
$ source devel/setup.bash
$ rosdep install openai_ros

注意,我们不得不从源代码安装 ROS 包,因为编译的二进制文件在 Ubuntu 中不可用。特别是,值得注意的是,rosdep install openai_ros 命令相当于 Ubuntu 的通用 sudo apt install <package> 命令;也就是说,每次安装新组件时,它都会自动包含所需的依赖项。记住,对于一个 ROS 包,其依赖项在源代码根目录下的 package.xml 文件中声明。

一旦 OpenAI Gym 的安装完成,我们就可以继续解释其概念。

代理、人工智能和机器学习

代理的概念来源于人工智能领域,用于指代任何做出决策的事物。换句话说,这就是典型的计算机程序使用 if ... then ... else ... 类型的条件指令所做的事情。简单来说,一个 代理 是一个可以做出比纯条件更复杂决策的程序。例如,考虑一个视频游戏:当你与机器对战时,你的对手会观察你的行动并决定下一步做什么以赢得游戏。驱动对手决策的是代理。推广这个想法,代理可以用来解决许多类型的问题;例如,何时停止和启动加热器以在设定温度点保持房间温暖。

当你使用经验数据而不是解析公式——就像使用PID控制器(比例-积分-微分)来解决之前提到的温度调节问题一样——告诉智能体在数百或数千种特定情况下应该做什么(室外温度、室内温度、室内人数、一天中的时间等),你正在训练它,使其能够概括并正确应对广泛的条件。而这个训练过程就是我们通常所说的机器学习,以及本书最后两章特定范围的强化学习。

在这一点上,你也应该意识到,如果一个使用机器学习的智能体在它被训练的输入条件范围内做出好的决策。如果你或多个相关条件超出了训练范围,你不能期望它做出好的决策。因此,前面段落提供实验数据的重要性,这些数据是数百或数千种特定情况,用于训练过程。

OpenAI Gym 是一个基于强化学习技术的智能体训练的框架。一旦这个智能体被训练,它可以在类似的问题中重复使用,以增强决策能力。为了说明这些概念,我们将使用一个简单的机制,即车杆,也称为倒立摆。

车杆示例

这是倒立摆的经典控制问题,它实验了一个不稳定的平衡(看看下面的图)。通过施加横向力F,你可以补偿它的倾向向下坠落,并使其保持竖立(即当角度θ接近零时):

图片

来源:https://de.wikipedia.org/wiki/Datei:Cart-pendulum.svg

我们将使用 OpenAI Gym 来解决这个问题,方法是从一个没有任何关于问题物理知识的智能体开始,也就是说,它没有任何关于应该施加什么横向力的想法,以便车杆保持竖立。通过试错策略,智能体将学会哪些力的方向和值对于摆的每个角度是合适的。这是一个快速解决的问题,因为你只有一个自由度——角度θ——和一个独立变量——力,F

这个例子包含在本书的代码库中,我们将以此为基础来解释 OpenAI Gym 框架的常见概念:环境、观察和空间。

环境

环境是一个场景,它通过一个最小化的接口来模拟一个问题(例如保持车杆竖立),这个接口允许一个智能体与之交互。你可以通过运行以下代码片段来看到车杆环境在动作中的样子:

$ cd ~/catkin_ws/src/Chapter12_OpenAI_Gym/cart-pole
$ conda activate gym
(*gym*) $ python cart-pole_env.py

你应该看到车杆在随机移动和旋转,如下面的截图所示:

图片

脚本的内容相当简单:

import gym

env = gym.make('CartPole-v0')
env.reset()

for _ in range(1000):
    env.render()
    env.step(env.action_space.sample())

env.close()

在导入gym模块后,我们将env变量设置为预定义的CartPole-v0环境。然后,在下一行,我们对env应用.reset()方法,以便初始化环境。

脚本的主体是一个for循环,我们将其设置为 1,000 次迭代。在这些迭代中的每一次,脚本都会做两件事:

  • 使用env.render()渲染小车和杆的状态。

  • 执行模拟的一步需要随机动作,这是通过env.step(env.action_space.sample())这一行实现的。.sample()方法提供了一个随机力,F,作用于小车和杆的底部。

通过将几个步骤连接起来并让系统演化,代理完成一个剧集,其结束由以下三种可能性之一定义:

  • 杆的角度大于±12°。

  • 小车相对于轨道中心的位移超过±2.4 单位。

  • 集剧长度超过 200 步。

这个定义是环境规范的一部分,可以在github.com/openai/gym/wiki/CartPole-v0找到。现在让我们回顾一下观察和空间定义。

空间

空间描述了有效的动作(F 力)和观察(小车和杆的角度θ)。这个观察的概念将在下一小节中详细讨论。每个环境都有两个空间与之相关联:

  • 动作空间:这由env.action_space对象下的状态变量集合定义。这个空间定义了代理可以采取的可能动作。对于小车和杆的情况,只有一个变量:施加一个侧向力,F

  • 观察空间:这描述了代理的物理状态,即小车和杆的角位置θ。从操作上讲,它是env.observation_space对象下的状态变量集合。

现在让我们描述观察的概念,以便全面理解它们如何支持学习过程。

观察

给定一个环境,一个观察由一组定义环境给定状态的值组成,即角度θ。这就像是对场景进行快照。环境步骤函数env.step返回以下内容:

  • 当前状态;也就是说,它设置状态变量当前值,θ。从操作上讲,它是一个名为observation的对象类型变量。

  • 智能体从最后一步获得的奖励,即力 F,正如在描述动作空间时之前提到的。奖励就像游戏中的分数——一个累积所有奖励(分数)的定量值,即从当前剧集开始执行的动作中获得的所有奖励(分数)。如果施加的力有助于使购物车杆保持竖立,则奖励为正;如果不这样做,则给予负奖励(或惩罚)。这个变量是浮点类型,称为 reward

  • 当前剧集是否结束,通过一个名为 done 的布尔变量。当它结束时,调用 env.reset 函数来重新启动环境,为下一剧集做准备。

  • 诊断信息以名为 info 的 Python 字典对象的形式呈现。

因此,智能体将尝试最大化其得分,该得分是它为每次施加的力所获得的奖励的累积总和。这个最大化算法将教会它保持购物车杆竖立。

前面的解释应该能让你理解脚本是如何工作的。现在我们将运行一个购物车杆的训练会话,以便你能看到如何对良好的行为进行正面奖励,鼓励智能体构建有效的策略。

运行完整的购物车杆示例

我们运行的第一脚本 cart-pole_env.py 的目的是展示 1,000 个随机步骤的序列。新的脚本将为每个采取的动作提供反馈,通过给予良好行为的奖励:

$ cd ~/catkin_ws/src/Chapter12_OpenAI_Gym/cart-pole
$ conda activate
(*gym*) $ python CartPole-v0.py

脚本中的迭代块包括以下行:

env.render()

action = agent.act(state)
next_state, reward, done, _ = env.step(action)

score += reward

next_state = np.reshape(next_state, (1, 4))
agent.remember(state, action, reward, next_state, done)

state = next_state

按照行顺序,以下是每个步骤所执行的操作:

  1. 渲染环境。这是显示正在发生什么的窗口。

  2. 选择下一个动作。这是使用累积经验来决定做什么的地方,同时考虑到当前状态。

  3. 运行新的步骤;也就是说,执行所选动作并观察发生了什么。观察返回智能体的新状态 next_state、智能体获得的奖励以及一个布尔变量 done,告诉你剧集是否结束。

  4. 奖励被添加到 score 变量中,该变量累积了从剧集开始以来获得的所有奖励。

  5. 接下来,将(状态、动作和奖励)集存储在记忆中——agent.remember——这样智能体就可以利用它们过去的经验,促进在给定状态下给予它们更多奖励的动作。

  6. 最后,我们将当前 state 变量更新为 步骤 3 的输出,即 next_state 的值。

当训练完成时,一个表示得分随剧集演变的曲线被描绘出来:

图片

您可以看到,在 40 个回合之后,代理开始获得大约 200 分的良好成绩。这意味着代理已经学会了通过施加防止杆子掉落的力的方向来保持杆子的平衡。这个简单的例子需要几分钟才能在每一新回合中达到 200 分的目标,所以让我们快速了解强化学习是如何工作的。

请注意,小车杆问题并不代表真实场景。在实际情况下,状态由一组许多变量定义,代理可能采取的可能动作也有很多。真实的强化学习问题非常 CPU 密集,它们需要成千上万甚至数百万个回合才能获得相当好的性能。

在下一节中,我们将描述一个更现实的问题,其中存在 500 个状态和六个可能的行为。第二个示例的目标是理解分数最大化算法在其最基本版本中的工作原理,即通过 Q 学习算法。

Q 学习解释——自动驾驶出租车示例

我们将要解决的问题包括一辆自动驾驶出租车,它必须将乘客接送到正确的位置。它必须尽可能快地这样做,同时遵守交通规则。图形基于 ASCII 字符,我们将使用真实图像来解释目标。我们还将遵循按时间顺序的帧序列:

  • 第一序列表示出租车——黄色方块——在起始位置。它可以向上、向下、向左或向右移动,除非它遇到垂直杆;这种移动是不允许的。有四个可能的出租车停靠站,出租车可以在那里接客或送客。它们用字母RGBY标记。蓝色字母(在这种情况下是R)是接客地点,紫色字母(在这种情况下是G)是乘客需要被运送到的目的地:

图片

  • 下一个图中的第二个序列显示了模拟的一个步骤,其中乘客在车内。他们是在位置R被接走的,正在被运送到目的地。这种状态在视觉上是可以识别的,因为代表出租车的方块被填充为绿色(当它不载有乘客时,颜色保持为黄色):

图片

  • 最后一个序列对应于出租车将乘客送到目的地的场景;在这种情况下,用字母G表示。当这种情况发生时,出租车颜色再次变为黄色:

图片

这个问题的目标是训练一个强化学习智能体,使其学会在每个行程中按照最短路径驾驶出租车。最短路径通过奖励策略来操作实现,该策略为智能体在每次采取的动作中根据其效用给予预定义的奖励。因此,强化学习智能体将尝试通过将状态与有用的动作关联起来,来最大化其总奖励。这样,它将逐渐发现一个运输策略,使其在全局平均意义上最小化从接客地点到下客站点的旅行时间。奖励策略将在本节后面详细说明。

使用这样简单的例子来解释 Q 学习的优势在于,它允许你应用无模型强化学习算法。这是因为它不包含任何数学模型来预测基于当前状态,系统的下一个状态将是什么。为了理解如果存在模型会有什么不同,让我们以一个位于位置x、以速度v移动的机器人为例。经过时间t后,新的位置预计如下:

图片

由于机器人的状态由其位置y表示,下一个状态y'将是速度v的函数,即f(v)。如果应用的速度加倍—2v—机器人将达到不同的状态,x'',因为它将在相同的时间步长t内行驶双倍的距离。在这种情况下,速度值的集合构成了动作空间。基于这个预测,机器人能够预测在执行动作之前将获得什么奖励。另一方面,对于无模型的情况,无法预测奖励。机器人唯一能做的就是执行动作并看看会发生什么。

拥有这种视角,你将意识到使用无模型强化学习算法解释 Q 学习的教学理由。智能体只需学会在每个状态下选择最有奖励的动作——它不需要提前做出任何预测。经过多次尝试,它将学会一个最优的运输策略。

如何运行自动驾驶出租车的代码

代码是一个位于仓库Chapter12_OpenAI_Gym/taxi文件夹中的 Python 文件。至于小车杆,程序是用 Python 编写的,文件名为Taxi-v3.ipynb.ipynb扩展名是已知的 Jupyter 笔记本扩展。我们选择这种方式在 Python 中编码,以便通过仅仅跟随笔记本就能理解示例,因为你在同一个地方既有代码又有解释。

Jupyter 笔记本在第二章中介绍,GoPiGo3 的单元测试。在那里,我们使用笔记本环境中的 Python 代码对传感器和执行器进行了实际解释。

我们建议您打开笔记本,从头到尾阅读它,然后回到这里来完成对该主题的理解。为此,请遵循以下说明:

  1. 激活 gym 环境:
$ conda activate gym
  1. 移动到示例位置:
(gym) $ cd ~/catkin_ws/src/Chapter12_OpenAI_Gym/taxi
  1. 启动笔记本服务器。此命令将在默认浏览器的窗口中打开文件资源管理器:
(gym) $ jupyter notebook
  1. 点击 Taxi-v3.ipynb 文件,浏览器将打开另一个窗口显示笔记本内容。

阅读完毕后,我们准备返回到上一节中介绍的空间(行为和状态)以及奖励概念,并针对当前示例进行详细说明。

奖励表

此表指定代理对每个行为获得的奖励。一个设计良好的策略会通过更高的奖励来激励最期望的行为。对于出租车示例,奖励表如下:

  • 如果代理成功完成放货,将获得 +20 分。

  • 每经过一个时间步长,它会失去 1 分。这样,我们鼓励它尽可能快地解决环境:只要它在路上,它就在消耗资源,如燃料,因此这个负奖励可以理解为燃料费用。

  • 每次非法行为(在取货或放货行为期间)都会被扣除 10 分。

接下来,我们继续描述出租车必须遵守以在环境中进化的行为和状态空间。

行动空间

行动空间包括代理可以执行的可能行为。对于出租车示例,这些行为如下:

  • 四种可能的移动:向南移动 (S)、向北移动 (N)、向东移动 (E) 或向西移动 (W)

  • 接载乘客 (P)

  • 放货 (D)

因此,总共有六个可能的行为,我们将它们称为行为变量

状态空间

状态空间由定义我们问题的状态变量的所有可能值的组合组成。对于出租车示例,这些变量如下:

  • 当前位置是基于行号和列号定义的。这些共有 5 行 x 5 列 = 25 个单元格(位置)。

  • 四个目的地:用 R(红色;它在随附的 Jupyter 笔记本中显示的颜色)、B(蓝色)、Y(黄色)和 G(绿色)标记。

  • 与出租车相关的五个可能的乘客位置:

    • 在四个位置中的任何一个进行取货/放货

    • 加 1 分,如果乘客在剩余的任何单元格中(+1)

因此,我们总共有 25 x 4 x 5 = 500 种可能的状态。以下表示其中之一:

 +---------+
                    |R: | : :G|
                    | : | : : |   
                    | : : : : |
                    | |o: | : |
                    |Y| : |B: |
                    +---------+

由于编码环境知道墙壁的移动,因此当环境尝试更新状态时,如果需要穿越墙壁,出租车将无法移动,并将保持在同一单元格中。这是通过保持状态不变来实现的。否则,冒号 : 允许出租车移动到新单元格。请注意,撞击墙壁没有额外的惩罚,只是时间步长的 -1。

如果你引入这个新规则,训练应该会更快一些,因为智能体将隐式地学习墙的位置,并且在被撞到几次后不会坚持朝那个方向移动。

使用 RL 算法的自动驾驶出租车示例

如前所述,我们将使用 Q-learning 来解释学习过程,因为它简单且具有物理意义。请注意,Q-learning 算法让智能体跟踪其奖励,以学习每个状态的最好动作:

  • 每次从给定状态采取一个动作时,根据 P 获得一个奖励。

  • 与每个对(状态,动作)相关的奖励创建了一个 500 x 6 的 q 表。

对于一个具体的状态-动作对,q 值代表在该状态下该动作的质量。因此,对于一个完全训练好的模型,我们将有一个 500 x 6 的矩阵,即 3,000 个 q 值:

  • 每一行代表一个状态。

  • 每行的最大 q 值让智能体知道在该状态下应该采取什么动作。

在第一步中,q 值是任意的。然后,当智能体在与环境交互时获得奖励,每个状态-动作对的 q 值将根据以下方程更新:

Q(state,action) ← (1−α)Q(state,action) + α(reward + γ maxQ(next state,all actions))

上述方程描述如下:

  • α,或 alpha,是学习率(0<α≤1),并应用于智能体发现的新信息,即公式中求和的第二部分的第一个部分,α * reward

  • γ,或伽马,是折扣因子(0≤γ≤1),它决定了我们想要给予未来奖励的重要性程度。如果这个因子为 0,它会使智能体只考虑即时奖励,使其表现出贪婪的行为。因此,这个参数权衡了未来行动的效用。它适用于求和公式的第二部分的第二部分:γ * maxQ[next_state, all actions]

最后,我们还应该考虑探索利用之间的权衡。让我们解释一下这些概念的含义:

  • 探索指的是机器人从给定状态执行动作的行为,这是第一次。这将让它发现该状态-动作对是否有高奖励。在操作上,它包括采取随机动作。

  • 另一方面,利用指的是从给定状态执行过去奖励最多的动作的行为。在操作上,它包括采取该状态下具有最大 q 值的动作。

我们需要平衡这两种行为,为此,我们引入了ϵ(epsilon)参数,它代表应该采取探索类型动作的百分比。这防止智能体遵循单一路线,这条路线可能并不一定是最好的。

评估智能体

脚本在一分钟内运行了 100,000 个回合。然后,我们用 100 个回合评估智能体,并得到以下平均值:

  • 每个回合的步骤:12

  • 每个回合的惩罚:0

与暴力方法(你可以在 Jupyter 笔记本中找到)相比,你获得的路由从数百到数千步,以及超过 1,000 次惩罚(记住,每次非法动作都会给出 1 次惩罚)。

超参数和优化

那么,你如何选择 alpha、gamma 和 epsilon 的值呢?嗯,这个策略必须基于直觉和试错。无论如何,随着智能体学习最佳动作,这三个值都应该随时间减少:

  • 随着智能体对环境的了解越来越多,并且可能信任所获得的经验,降低学习需求 alpha。

  • 随着智能体发展出端到端策略,并不仅仅关注即时奖励,同时降低折扣因子 gamma。

  • 最后,降低利用率 epsilon,因为随着环境变得熟悉,探索的收益会失去优先级。

到这一点,你应该准备好进入 OpenAI ROS 来训练在 Gazebo 中为机器人提供动力的智能体。

运行环境

本章剩余部分的目标是将你在 RL(强化学习)一般问题中学到的知识应用到特定领域,如机器人学。为了方便知识迁移,我们将重现简单的小车杆示例,将其建模为 Gazebo 中的机器人。代码示例位于本章代码库的cart-pole_ROS文件夹中。请在您的笔记本电脑上移动到该位置:

$ cd ~/catkin_ws/src/Chapter12_OpenAI_Gym/cart-pole_ROS

在内部,你可以找到两个 ROS 包,每个包都以其名称命名文件夹:

  • cartpole_description 包含了使用 ROS 的 Gazebo 仿真框架,用于小车杆。这个包的结构与第五章中描述的非常相似,即使用 Gazebo 模拟机器人行为。因此,没有必要深入研究其细节。

  • cartpole_dqn 包含了先前的 Gazebo 模拟的 OpenAI Gym 环境。这就是引入 RL 算法的地方,我们将在接下来的段落中关注这一点。

该包相当相似。让我们通过启动文件start_training.launch进入:

<launch>
    <rosparam command="load" file="$(find cartpole_dqn)/config/cartpole_dqn_params.yaml" />
    <!-- Launch the training system -->
    <node pkg="cartpole_dqn" name="cartpole_dqn" type="cartpole_dqn.py" output="screen"/>
</launch>

带有<rosparam>标签的行是加载训练过程配置的行。我们将在下一节中解释这一点。此文件是cartpole_dqn_params.yaml,它位于config文件夹中。

另一行,带有<node>标签,启动单个 ROS cartpole_dqn节点,该节点在 Python 脚本cartpole_dqn.py下实现小车杆的训练过程。以下按顺序简要描述了此代码执行的操作:

  1. 它为小车杆创建 Gym 环境。

  2. 它从 ROS 参数服务器加载配置(这一点将在以下子节中详细说明)。

  3. 然后,它使用加载的参数初始化学习算法。

  4. 最后,它循环预定义的剧集数量,每个剧集由固定数量的步骤组成(这两个值也是配置文件的一部分)。

  5. 对于每个回合,它执行以下操作:

    • 初始化环境并获取机器人的第一个状态。

    • 对于当前回合中的每一步,智能体选择一个动作来运行,这将是在随机动作选择和最佳动作选择(取决于 epsilon 探索参数)之间的一种:

observation, reward, done, info = env.step(action)

这是循环的关键行,你可以看到,它与纯 Python 中的 cart pole 示例和上一节中的 taxi 示例中使用的是相同的。因此,步骤的输出如下:

    • observation:应用动作后环境的新状态

    • reward:表示采取的动作有效性的值

    • done:一个布尔变量,告诉你是否已经达到目标

  1. 最后,我们通过遵循两个后续步骤让算法从结果中学习:

    • 记住运行步骤:self.remember(state, action, reward, next_state, done)

    • 回放以优化动作选择:self.replay(self.batch_size)

在这种情况下,我们使用的算法与我们描述的自动驾驶出租车示例中的 Q-learning 有所不同。它被称为 DQN,并利用深度学习来为给定状态选择最佳动作。这是在 RL 问题中更广泛使用的算法,如果你想深入其公式,可以通过跟随章节末尾进一步阅读部分的最后一条参考文献。简而言之,这是它执行的操作:

  • 在每个回合的每一步中,记住过程保存正在运行的内容,并作为智能体的记忆。

  • 然后,回放过程从回合的最后几个步骤中取一个迷你批次,并用于改进神经网络。这样一个网络为智能体在每一个给定状态下提供最佳的动作。

从概念上讲,智能体使用其从先前经验中获取的记忆来猜测哪个动作可能最方便,以在回合中最大化其总奖励。

在剩余的部分中,我们将专注于使用 Gazebo 在 ROS 中的具体训练和评估。

配置环境文件

在先前的start_training.py脚本算法描述的步骤 2中,ROS 参数被加载到模型中。它们的定义来自start_training.launch文件的这一行:

<rosparam command="load" file="$(find cartpole_dqn)/config/cartpole_dqn_params.yaml" />

当执行这部分时,cartpole_dqn_params.yaml文件中的参数被加载到内存中,并可供cartpole_dqn.py脚本使用。其中更相关的是以下内容:

alpha = rospy.get_param('/cartpole_v0/alpha')
gamma = rospy.get_param('/cartpole_v0/gamma')
epsilon = rospy.get_param('/cartpole_v0/epsilon')

cartpole_v0是在yaml文件定义之前声明的命名空间。每个参数的含义在使用 RL 算法的自动驾驶出租车示例子节中已涵盖。尽管 DQN 算法比 Q-learning 更复杂,但alphagammaepsilon的概念意义与两者相同。你可以通过回顾先前的 Q-learning 算法部分来记住它们。

运行模拟并绘制结果

要运行这个模拟场景,我们遵循标准方法,首先启动一个 Gazebo 环境——cartpole_description包的一部分,包含机器人的模型——然后,我们将开始训练过程:

T1 $ roslaunch cartpole_description main.launch

在 Gazebo 窗口中的结果应该类似于以下截图。尽管这是一个 3D 环境,但模型本身的行为更像是一个 2D 模型,因为购物车杆只能沿着引导方向滑动:

截图

对于训练过程,我们有一个在其他 ROS 包中的启动文件,即cartpole_v0_training

T2 $ conda activate gym
T2 $ (gym) roslaunch cartpole_dqn start_training.launch

注意,在运行启动文件之前,你必须激活gymPython 环境,这是你安装 OpenAI Gym 的地方。

你将看到训练过程的实时演变图,它实时显示每个回合获得的奖励。训练结束后,你应该获得一个类似于这个的图表:

截图

请记住,对于购物车杆的每一个动作,都会给予+1 的奖励。因此,图表也代表了每个回合的总奖励数。为了测量收敛,更有用的是绘制每个回合过去 100 个回合的平均动作次数(=奖励)。例如,对于第 1,000 个回合,这意味着取第 901 个到第 1,000 个回合的奖励(动作次数),并计算这 100 个值的平均值。这个结果就是以下图表中第 1,000 个回合所绘制的:

截图

实际上,收敛的准则是在最后 100 个回合中获得平均奖励超过 800。你可以检查曲线实验在 2,600 个回合后进行提升,并迅速达到标准。

在本节的第二部分,我们将介绍一种友好的方法来访问 ROS 控制台日志,以便详细跟踪训练过程。

使用记录器检查你的进度

如你所观察到的,日志输出量很大,速度非常快。ROS 提供了一个名为rqt_tool的其他工具,可以轻松跟踪会话的日志。要访问它,从终端启动:

$ rqt_console

这应该显示一个与以下截图相似的窗口:

截图

在消息源下面的两个框中,你可以根据自己的标准排除或包含消息。如果你想更改节点的日志级别,运行rqt_logger_level实用程序:

$ rosrun rqt_logger_level rqt_logger_level

以下截图显示了节点的日志级别:

截图

rqt_console工具允许你实时跟踪日志并将其保存到文件以供离线分析。

摘要

本章为你提供了将强化学习应用于真实机器人的所需理论背景。通过剖析简单的购物车杆示例,你现在应该理解在经典强化学习任务中幕后发生了什么。

此外,通过首先使用原生的 OpenAI Gym 框架在 Python 中这样做,然后在内部 ROS 中,你应该已经获得了使用真实机器人 GoPiGo3 执行 RL 过程的基本技能。这就是你在本书的最后一章将要学习的内容。

问题

  1. 代理如何遵循 RL 方法进行学习?

A) 通过它从每次执行动作获得的奖励中获取的经验。

B) 通过随机探索环境和通过试错发现最佳策略。

C) 通过一个输出系统状态作为 q 值的函数的神经网络。

  1. 使用 RL 训练的代理是否必须预测动作的预期结果?

A) 是的;这是一个称为无模型强化学习(model-free RL)的特征。

B) 只有在它不采用无模型强化学习(model-free RL)方法的情况下。

C) 不是;根据定义,强化学习方法只需要意识到奖励和惩罚以确保学习过程。

  1. 如果你以学习率,alpha,为 0.7 运行 Q 学习算法,这从学习过程的角度来看意味着什么?

A) 将提供更高奖励的 30%的成对状态-动作保留下来。

B) 将 Q 矩阵所有元素的 30%的值保留下来,并将剩余的 70%从新动作的结果中获取。

C) 将每次迭代步骤中获得的 70%的最高知识保留到下一个步骤。

  1. 如果你以折扣因子,gamma,为 1 运行 Q 学习算法,这从学习过程的角度来看意味着什么?

A) 代理将只对即时奖励感兴趣。

B) 代理将只对任务的最终目标感兴趣。

C) 代理将只对一次实现目标感兴趣。

  1. 如果你以探索率,epsilon,为 0.5 运行 Q 学习算法,这从学习过程的角度来看意味着什么?

A) 代理的行为将与选择随机动作的代理相似。

B) 代理将在情节的 50%步骤中选择随机动作。

C) 代理将在所有 50%的情节中选择随机动作。

进一步阅读

要深入了解本章中解释的概念,你可以参考以下资源:

第十二章:通过强化学习实现目标

在上一章提供的强化学习背景之后,我们将进一步使用 GoPiGo3,使其不仅执行感知任务,还能按顺序触发连锁动作以实现预定义的目标。也就是说,它将不得不在模拟的每一步决定执行什么动作以实现目标。在执行每个动作的末尾,它将获得一个奖励,这个奖励将通过给予的奖励量来显示决策有多好。经过一些训练,这种强化将自然会引导其后续决策,从而提高任务的表现。

例如,假设我们设定了一个目标位置,并指示机器人必须携带一个物体到那里。GoPiGo3 将被告知表现良好的方式是通过给予它奖励。这种方式提供反馈鼓励它追求目标。具体来说,机器人必须从一组可能的行为(向前移动、向后移动、向左或向右移动)中选择,并在每一步选择最有效的行为,因为最佳行为将取决于机器人在环境中的物理位置。

处理这类问题的机器学习领域被称为强化学习,这是一个非常活跃的研究课题。在某些场景中,它已经超越了人类的表现,例如最近的Alpha Go案例,deepmind.com/blog/article/alphago-zero-starting-scratch

本章将涵盖以下主题:

  • 使用 TensorFlow、Keras 和 Anaconda 准备环境

  • 安装 ROS 机器学习包

  • 设置训练任务参数

  • 训练 GoPiGo3 到达目标位置同时避开障碍物

在实践案例中,您将看到 GoPiGo3 如何通过尝试不同的动作来学习,被鼓励为每个位置选择最有效的动作。您可能已经猜到,这是一个非常昂贵的计算任务,您将了解机器人工程师目前面临的挑战,即如何使机器人变得更聪明。

技术要求

在本章中,我们将使用位于Chapter12_Reinforcement_Learning文件夹中的代码:github.com/PacktPublishing/Hands-On-ROS-for-Robotics-Programming/tree/master/Chapter12_Reinforcement_Learning

将本章的文件复制到 ROS 工作空间中,将它们放在src文件夹内:

$ cp -R ~/Hands-On-ROS-for-Robotics-Programming/Chapter12_Reinforcement_Learning  ~/catkin_ws/src/

如往常一样,您需要在笔记本电脑上重新构建工作空间:

$ cd ~/catkin_ws
$ catkin_make

一旦您有了本章的代码,我们将下一节专门用于描述和安装我们将要开发的实际项目的软件栈。

使用 TensorFlow、Keras 和 Anaconda 准备环境

与你在上一章中被指导安装的Anaconda一起,你现在将安装机器学习工具TensorFlowKeras。你需要它们来构建解决强化学习任务所需的神经网络:

  • TensorFlow是机器学习环境中的低级层。它处理神经网络创建中涉及的数学运算。由于它们在数学上被解析为矩阵运算,你需要一个有效的框架来解决这个代数问题,而 TensorFlow 是解决这个问题的最有效框架之一。这个库的名字来源于数学概念张量,它可以被理解为具有超过两个维度的矩阵。

  • Keras是机器学习环境的高级层。这个库让你能够以声明性方式轻松定义神经网络的架构:你只需定义节点和边的结构,TensorFlow(低级层)将负责所有数学运算以创建网络。

在这里,我们将利用 Anaconda 提供的隔离功能。记住,在上一章中,你创建了一个名为gymConda环境,在其中你安装了OpenAI Gym、TensorFlow 和 Keras。现在,你将被指导在一个不同的 Conda 环境中工作,在这个环境中,你将只安装本章所需的模块。这样,你可以将每个章节的代码隔离,因为它们适用于不同的项目。实际上,你将安装 TensorFlow 和 Keras 的特定版本,这些版本可能与上一章中使用的最新版本不同。这是 Python 项目中常见的一种做法。

一旦我们明确了每个组件提供的内容,让我们安装每个组件。

TensorFlow 后端

首先,创建一个名为tensorflow的专用conda环境。它由一个虚拟空间组成,允许用户隔离用于特定项目的 Python 包集合。这有一个优点,就是可以轻松地将环境复制到另一台机器上,几乎不需要任何努力:

  1. 让我们运行以下命令:
$ conda create -n tensorflow pip python=2.7
$ conda activate tensorflow

第二行产生激活并绑定后续安装到这个tensorflow环境。

Conda 环境是包含特定项目所需的 Python 模块的隔离桶。例如,对于tensorflow环境,你使用conda installpip install安装的每个 Python 模块都将放置在~/anaconda2/envs/tensorflow/bin激活意味着每当 Python 脚本需要导入某个模块时,它将在这个项目路径中查找它。

  1. 现在你可以继续安装 TensorFlow:
(tensorflow) $ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.8.0-cp27-none-linux_x86_64.whl

  1. 此外,你还应该安装matplotlibpyqtgraph,以便绘制结果的图表:
(tensorflow) $ conda install matplotlib pyqtgraph

  1. 然后检查版本:
(tensorflow) $ conda list | grep matplotlib
(tensorflow) $ conda list | grep pyqtgraph

这最后两个命令已被添加,为您提供常见的conda命令的实际示例。

使用 Keras 进行深度学习

Keras 是一个高级神经网络 API,用 Python 编写,能够在 TensorFlow 之上运行。您可以使用以下命令轻松安装特定版本:

(tensorflow) $ pip install keras==2.1.5

我们指定了版本 2.1.5,以确保您在与我们测试代码相同的精确环境中运行代码。写作时的最新版本是 2.3.1。

ROS 依赖包

要一起使用ROSAnaconda,您还必须安装 ROS 依赖包:

(tensorflow) $ pip install -U rosinstall msgpack empy defusedxml netifaces

您可以使用pip show命令检查它们的任何版本:

(tensorflow) $ pip show rosinstall

在下一节中,我们将描述本章代码的机器学习包,您应该已经按照技术要求部分将其克隆到您的 ROS 工作空间中。

理解 ROS 机器学习包

本章的代码实现了经典的强化学习方法,即训练一个神经网络。这个神经网络在数学上与我们第十章中介绍的类似,即在机器人中应用机器学习,通过堆叠(隐藏)节点层来建立状态(输入层)和动作(输出层)之间的关系。

我们将用于强化学习的算法称为深度 Q 网络DQN),在第十一章使用 OpenAI Gym 进行机器学习运行环境部分中介绍。在下一节设置训练任务参数中,您将获得描述状态、动作和奖励的操作描述,这些是我们在 ROS 中将要解决的强化学习问题的特征。

接下来,我们将介绍训练场景,然后我们将解释如何将 ROS 包内部的文件链接起来以启动一个训练任务。

训练场景

本节致力于解释强化学习包——书中代码示例库中的内容——是如何组织的。

首先,让我们考虑我们将训练机器人为两种场景:

  • 场景 1:到达目标位置。这个场景在以下图片中显示,由四个墙壁限定的正方形组成。环境中没有障碍物。目标位置可以是墙壁限制内的任何点:

图片

  • 场景 2:避开障碍物到达目标位置。这个场景由相同的正方形加上四个静态圆柱形障碍物组成。目标位置可以是墙壁限制内的任何点,除了障碍物的位置:

图片

现在我们已经介绍了训练场景,接下来让我们描述我们将用于 GoPiGo3 强化学习的 ROS 包。

运行强化学习任务的 ROS 包结构

本章的代码堆栈由以下包组成:

  • gopigo3_model是允许我们使用gopigo3_rviz.launch启动文件在 RViz 中可视化 GoPiGo3 的 ROS 编译。机器人配置是我们熟悉的 3D 实体模型,我们在前面的章节中使用过。更确切地说,它对应于 GoPiGo3 完整版本,包括距离传感器、Pi 相机和激光距离传感器,即第八章中构建的模型,使用 Gazebo 进行虚拟 SLAM 和导航

  • gopigo3_gazebo建立在之前的包之上,使我们能够使用gopigo3_world.launch文件在 Gazebo 中模拟 GoPiGo3。该文件加载的 URDF 模型与gopigo3_model包中的可视化相同。

  • gopigo3_dqn是执行 GoPiGo3 强化学习任务的具体包。由于它是 ROS,它是模块化的,我们通过分离模型、模拟和强化学习来提供的解耦使得使用这个相同的包来训练其他机器人变得简单。

在这个 ROS 包中,我们使用DQN算法来训练机器人。记住,DQN 是在上一章的运行环境部分介绍的。简而言之,DQN 将使用神经网络建立机器人状态执行动作之间的关系,其中状态输入层,而动作输出层

强化学习理论背后的数学很复杂,对于训练简单的 GoPiGo3 机器人来说,学习如何使用这项技术并不是绝对必要的。所以,让我们专注于训练任务的配置,抽象出本章实践元素所支持的示例的具体 Python 实现。

设置训练任务参数

在这一点上,我们将简要介绍强化学习中的三个基本概念:状态动作奖励。在本节中,我们将提供最少的信息,以便你能够理解本章中的实际练习。在这种情况下,我们正在应用注重实践以真正理解理论的策略。

这种注重实践以真正理解理论的方法对于复杂主题尤为重要,如果你遵循一个带有易于运行的示例的经验方法,这些主题将更容易理解。这种初步的实践成功应该为你提供足够的动力深入研究这个主题,无论如何,这个任务在算法及其背后的数学上都会很困难。

因此,让我们继续定义机器人学习任务中涉及的核心概念:

  • 状态是环境的观察。得益于来自 LDS 的数据流,状态由到达目标位置的范围和角度来表征。例如,如果你使用一度的分辨率获取 LDS 测量值,每个状态将是一组 360 个点,每个值对应于 360 度全圆周上的每个角度。随着机器人的移动,状态会发生变化,这在新提供的 360 个范围值集中得到了反映。记住,每个范围值对应于该特定方向最近障碍物的距离。

  • 动作是机器人可以通过其电机在环境中执行的操作,即平移和/或旋转以接近目标。通过执行动作,机器人移动并改变其状态——由来自 LDS 的新范围值集定义。

  • 奖励是你每次机器人执行动作时给予它的奖品。你为每个可能状态中的每个动作提供的奖品称为奖励策略,它是强化学习任务成功的关键部分。因此,它必须由用户(即训练师)定义。简单来说,你会给机器人执行的动作赋予更大的奖励,这些动作更有助于实现目标。

对于实际案例,我们将按照以下方式运行奖励策略:

  • 如果障碍物位于机器人前半空间(覆盖前进运动方向的左到右的 180°角度),它将获得一个从 0 到 5 的基于角度的正奖励。最大值(5)对应于机器人面向目标方向的情况。我们通过相对角度在-90°到+90°之间(在这个角度中,奖励是最小的,即 0)来指定这个半空间。参考方向是穿过目标位置和机器人的直线。

  • 如果它位于机器人的后半空间(与前进半空间相反的左到右的 180°角度),获得的奖励是负的,范围从 0 到-5(与角度的线性相关:90°和-90°时为 0,-180°时为-5)。

  • 如果当前距离目标超过预设的阈值,代理将获得一个基于距离的奖励>2。如果它低于这个阈值,奖励将低于 2,随着机器人靠近目标,奖励接近最小值 1。然后,接近奖励是角度和基于距离的奖励的点积= [a] *** [b]

  • 如果机器人达到目标,将获得 200 的成功奖励

  • 如果机器人撞到障碍物,将给予 150 的惩罚,即-150 的负奖励。

奖励是累积的,我们可以在模拟的每一步添加这些术语中的任何一个:

  • 接近奖励

  • 成功奖励

  • 障碍物惩罚

模拟中的步骤是“在两个连续状态之间机器人发生了什么”。发生的事情是,机器人执行一个动作,然后——作为结果——它移动,改变其状态,并基于本节中定义的策略获得奖励。

在理解了此任务配置之后,你就可以运行在已定义的两个场景中 GoPiGo3 的训练了。

训练 GoPiGo3 在避开障碍物的同时到达目标位置

在场景中运行训练之前,我们应该注意调整一个参数,这个参数会显著影响计算成本。这是 LDS 的水平采样,因为机器人的状态由模拟给定步骤中的范围值集合来表征。在之前的章节中,当我们使用 Gazebo 进行导航时,我们使用了 LDS 的采样率为 720。这意味着我们有 1° 分辨率的周向范围测量。

对于这个强化学习的例子,我们将采样减少到 24,这意味着范围分辨率为 15°。这个决定的积极方面是,你将 状态 向量从 360 项减少到 24,这是一个 15 倍的因子。你可能已经猜到了,这将使模拟更加计算高效。相反,你会发现缺点是 GoPiGo3 失去了它的感知能力,因为它只能检测到与机器人视角的角度覆盖大于 15° 的物体。在 1 米的距离上,这相当于最小障碍物宽度为 27 厘米。

说到积极的一面,随着机器人接近障碍物,其辨别能力提高。例如,在 10 厘米的距离上,15 度的弧线意味着它可以检测到最小宽度为 5.4 厘米的障碍物。

水平采样设置在 URDF 模型中,在描述 LDS 的文件部分,位于 ./gopigo3_model/urdf/gopigo3.gazebo。为了获得 15° 间隔的光线,需要指定的数字如下:

图片

由于 LDS 覆盖从 0° 到 360°,为了得到 24 个等间隔的光线,你需要添加一个额外的样本,使其变为 25,因为 0° 和 360° 实际上是同一个角度。

然后,必须按照以下方式修改 URDF 文件的 LDS 部分:

<gazebo reference="base_scan">
    <material>Gazebo/FlatBlack</material>
    <sensor type="ray" name="lds_lfcd_sensor">
    <pose>0 0 0 0 0 0</pose>
    <visualize>true</visualize>
    <update_rate>5</update_rate>
    <ray>
    <scan>
         <horizontal>
             <samples>25</samples>
            <resolution>1</resolution>
            <min_angle>0</min_angle>
            <max_angle>6.28319</max_angle>
...

通过将 <visualize> 标签设置为 true,在 Gazebo 中显示光线追踪。

这样的采样是否足够确保机器人能够得到有效的训练?让我们通过比较每种情况下的光线数量来回答这个问题。这张第一张图显示了物理 LDS 的 0.5° 实际分辨率。光线非常接近,几乎看不到分辨率。它提供了对环境的非常忠实的感觉:

图片

这第二张图显示了 24 个样本和 15° 分辨率的案例:

图片

在这张图片中,光线追踪显示,即使只有这么少的射线,也能检测到障碍物,因为只需要一束射线就能识别障碍物。这一事实有助于减轻感知分辨率的损失。然而,请注意,机器人将无法知道障碍物的宽度,只知道它将小于 30º。为什么?因为需要三束射线来检测有限宽度的障碍物,中心射线检测它,而两端的射线不会干扰它。因此,障碍物宽度的上限等于相邻射线之间的角距离的两倍,即 2 x 15º = 30º。在某些情况下,这可能会不够精确,但,对于我们在本例中使用的简单场景,应该足够精确。

如何运行模拟

在开始每个场景的训练过程之前,让我们回顾一下在前一章中学到的关于可视化(RViz)和模拟(Gazebo)的知识,以便与学习过程建立联系,我们将使用这些工具和相关脚本:

  1. 要在 RViz 中启动可视化,你只需简单地执行以下命令:
T1 $ roslaunch gopigo3_model gopigo3_rviz.launch
  1. 在 Gazebo 中启动模拟,你可以使用以下单个命令以类似的方式继续操作:
T1 $ roslaunch gopigo3_gazebo gopigo3_world.launch
  1. 最后,要运行强化学习任务,你首先必须启动 Gazebo – 如步骤 2 中解释的那样 – 但使用选定的训练环境,而不是通用的 gopigo3_world.launch
T1 $ roslaunch gopigo3_gazebo gopigo3_stage_1.launch
T2 $ roslaunch gopigo3_dqn gopigo3_dqn_stage_1.launch

这两个命令运行了之前描述的 场景 1 中的任务。要为 场景 2 进行训练,你只需要执行相应的启动文件:

T1 $ roslaunch gopigo3_gazebo gopigo3_stage_2.launch
T2 $ roslaunch gopigo3_dqn gopigo3_dqn_stage_2.launch

第一行加载了场景 2 环境,第二行启动了针对该环境的训练任务。

以下两个子部分通过执行步骤 3 中的命令展示了 ROS 的实际应用。

场景 1 – 前往目标位置

按照以下步骤进行,以确保训练过程按预期进行:

  1. 首先,在 Gazebo 中启动虚拟机器人模型:
T1 $ roslaunch gopigo3_gazebo gopigo3_stage_1.launch

  1. 然后,你可以开始训练过程。但首先,你必须处于 tensorflow 虚拟环境中:
T2 $ conda activate tensorflow
  1. 现在,开始训练:
T2 (tensorflow) $ roslaunch gopigo3_dqn gopigo3_dqn_stage_1.launch

你可能会收到如下错误:

inotify_add_watch("/home/user/.config/ibus/bus/59ba2b2ca56a4b45be932f4cbc9c914d-unix-0") failed: "No space left on device"

不要担心,你可以通过执行以下命令来解决它:

T2 $ echo fs.inotify.max_user_watches=65536 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

因此,如果你收到上述错误,按照建议解决它,然后再次启动训练脚本。然后订阅相关主题:

T3 $ rostopic echo *get_action*
T4 $ rostopic echo *result*

get_action 是一个 Float32MultiArray 消息类型,其数据定义如下:

get_action.data = [action, score, reward]
pub_get_action.publish(get_action)

让我们看看每个组件:

  • GoPiGo3 总是保持 0.15 m/s 的线性速度。动作 项以 0.75 为步长将角速度从 -1.5 到 1.5 rad/s 进行调整,以覆盖从 0 到 4 的整数范围。

  • 每一步获得的 奖励 如在 设置训练任务参数 部分所述。

  • 得分 是机器人在每个回合中获得的累积奖励。

相应的 ROS 图可以在以下图中看到:

这个图中的关键节点是 gopigo3_dqn_stage_1,它从 Gazebo 模拟中获取机器人状态,并通过发布 cmd_vel 消息(记住,驱动机器人的速度命令是在这个主题中发布的)执行训练任务,并为 GoPiGo3 每达到的新状态获取奖励(记住,在训练过程中,机器人在该状态下收到的奖励与每个新状态相关联)。

可以在终端 T2 的控制台日志中跟踪事件记录:

红色方块是目标位置,蓝色线条是如本节开头所述的 LDS 射线。

第一个场景的目的是让您熟悉 ROS 中的训练过程。让我们继续到场景 2,我们将给出关于训练过程如何通过事件进行改进的定量信息。

场景 2 – 避开障碍物到达目标位置

开始训练的程序与场景 1 类似:

  1. 我们只需要更改文件名并使用相关的文件:
T1 $ roslaunch gopigo3_gazebo gopigo3_stage_2.launch
T2 $ conda activate tensorflow
T2 (tensorflow) $ roslaunch gopigo3_dqn gopigo3_dqn_stage_2.launch
  1. 如果您想图形化地查看 GoPiGo3 的学习过程,请执行 result_graph.launch 文件:
T3 $ roslaunch gopigo3_dqn result_graph.launch

以下截图显示了您应该在笔记本电脑屏幕上看到的所有内容:

红色图显示了每个事件中获得的总奖励。请记住,一个事件定义为当满足某个标准时结束的步骤序列。在这个问题中,如果达到目标(红色方块)或与障碍物发生碰撞,则事件结束。这些图显示了前 300 个事件中的演变。

绿色图表示训练模型的平均 Q 值。请记住,这是动作值函数 Q(s,a),它告诉您在给定状态 s 下执行动作 a 是多么好。这个概念在上一章的“Q-learning 解释”部分的基本自动驾驶出租车示例中已经解释过了。

您可以看到,随着经验的积累,GoPiGo3 的平均表现如何变得更好。但是它是如何操作性地使用这些经验的呢?答案来自于已经应用的强化学习算法,即通过将有效的动作与训练过程中机器人在该状态下收到的奖励相关联。

最后,我们应该注意,在这种执行环境中,ROS 图在两种状态之间交替。一种是当机器人通过发布 cmd_vel 消息执行动作时:

为了清晰起见,在这个图中,我们排除了在终端 T3 生成的启动文件中的节点

另一个 ROS 图表示的是代理使用来自 /scan 主题的 LDS 的 24 个值计算下一个状态的瞬间:

到目前为止,您已经完成了 GoPiGo3 的端到端训练过程。下一个挑战是测试训练好的模型。

测试训练好的模型

考虑到我们正在使用深度 Q 网络DQN)算法,我们必须从训练过程中保存的是神经网络的架构和边的权重。这正是gopigo3_dqn_stage_2节点每 10 个回合执行的操作。因此,你可以在./gopigo3_dqn/save_model文件夹中找到保存的模型,网络的权重存储在h5文件类型中。每个文件的名字中都包含场景(stage_1stage_2)和回合编号。按照以下步骤评估训练好的模型:

  1. 选择具有最高回合编号的文件,即stage_2_1020.h5

每个h5文件都包含 DQN 网络在所提及的回合结束时出现的权重。例如,stage_2_1020.h5指的是在第 1020 回合结束时场景 2 的网络。

为了使用这些权重,你基本上必须使用与训练模型相同的 Python 脚本(./nodes/gopigo3_dqn_stage_2),但初始化以下代码片段中用粗体字母标记的参数为不同的值,该代码片段重现了class ReinforceAgent()定义的前几行:

class ReinforceAgent():
    def __init__(self, state_size, action_size):
        self.pub_result = rospy.Publisher('result', Float32MultiArray, queue_size=5)
        self.dirPath = os.path.dirname(os.path.realpath(__file__))
        self.dirPath = self.dirPath.replace('gopigo3_dqn/nodes', 'gopigo3_dqn/save_model/stage_2_')
        self.result = Float32MultiArray()

        # Load model from last EPISODE
        self.load_model = True
        self.load_episode = 1020
...

然后,每个参数提供的内容如下:

    • self.load_model = True告诉脚本加载预训练模型的权重。

    • self.load_episode = 1020设置你想要加载 DQN 网络权重的回合编号,对应的文件是stage_2_1020.h5

  1. 然后将 Python 脚本重命名为gopigo3_dqn_stage_2-test,并生成新的启动文件gopigo3_dqn_stage_2-test.launch,它将调用创建的测试脚本:
<launch>
    <node pkg="gopigo3_dqn" type="gopigo3_dqn_stage_2-test" name="gopigo3_dqn_stage_2-test" output="screen" />
</launch>
  1. 要启动测试过程,遵循与运行训练场景相同的步骤,但使用测试版本的启动文件:
T1 $ roslaunch gopigo3_gazebo gopigo3_stage_2.launch
T2 (tensorflow) $ roslaunch gopigo3_dqn gopigo3_dqn_stage_2-test.launch
T3 $ roslaunch gopigo3_dqn result_graph.launch
  1. 记住,对于T2,你必须使用$ conda activate tensorflow命令激活 TensorFlow 环境。当它启动时,你将在T2中看到一个消息,告诉你将使用第 1380 集的模型:
[INFO] [1579452338.257175, 196.685000]: +++++++++++++++++++++++++++++++++++++++++++++++++++++
[INFO] [1579452338.258111, 196.686000]: STARTING TRAINING MODEL FROM self.load_episode = 1380
[INFO] [1579452338.258537, 196.686000]: =====================================================
[INFO] [1579452339.585559, 1.276000]: Goal position : 0.6, 0.0
  1. 如果你绘制了前几个回合的图表(如终端T3中的命令所示),你可以确认值相当不错,即总奖励超过 2,000,平均最大 Q 值超过 100:

图片

虽然你正在测试模型,但每十个回合,网络的权重都会保存到一个以当前回合编号命名的h5文件中。

摘要

本章简要而实用地介绍了如何应用强化学习,以便机器人能够执行有用的任务,如将材料运输到目标位置。你应该意识到,这种机器学习技术还处于其成熟度的初期,目前在实际世界中还没有多少可行的解决方案。原因是训练过程在时间和成本上都非常昂贵,因为你必须执行数千个场景才能得到一个训练良好的模型,然后使用物理机器人重新播放该过程以解决现实世界与模拟环境之间的行为差异。

请注意,Gazebo 中的训练过程不能替代现实世界的训练:模拟必然意味着对现实的简化,并且训练环境(Gazebo)与物理世界之间的每一个差异都会引入训练集中可能缺失的新状态,因此训练好的神经网络在那种情况下将无法表现良好。解决方案?更多的训练来覆盖更多状态,这也意味着更高的成本。

在本章的最后部分,我们同样介绍了使用与机器人训练相同场景来测试模型的方法。更正式的测试方法要求你检查训练好的模型在场景中不同条件下(例如,有更多障碍物或移动它们的位置)的泛化能力。这是一个复杂的话题,因为当前的强化学习算法在实现泛化方面仍然存在困难。为什么?因为,当你改变场景时,你会在模型中生成新的状态。由于这些状态对机器人来说是新的,它将不知道执行最有效动作的方法。因此,需要新的训练来探索这些新状态。

强化学习目前是一个非常活跃的研究领域,我们应该期待在未来的几年里取得重大进展。我们应该看到的是,强化学习以合理的成本(即以不需要数千个场景的节奏训练机器人)应用于实际机器人,并提供将模型泛化到训练场景之外的环境的技术。

本章介绍了机器学习在机器人应用中的入门。在这本书中,我们只是触及了其潜力的表面,如果你已经跟随了这些解释,你应该自己检查过,这是一个复杂的领域,在某个时刻你将不得不掌握统计学、数据分析以及神经网络。

同时,这也是一个以实验为重点的领域。与其试图用分析公式或计算机辅助模拟来模拟现实,不如观察现实世界,从传感器获取数据,并尝试从中推断行为模式。因此,成功将机器学习应用于机器人的能力取决于能够连续流式传输数据,以便机器人在实时中做出明智的决策。第一步是产生经过良好训练的模型。因此,机器人能够在中长期能够发展智能行为,因为它积累了可以在结构化训练模型中利用的经验。

这是一个具有挑战性的目标,对于数据科学家和软件工程师来说都是如此。他们应该共同努力,创建成熟的机器人框架,使其从机器学习中获得的好处与今天常见的网络应用和数字业务一样多。

最后,非常感谢阅读这本书。在这个时候,你被挑战去探索高级 ROS 主题,我们希望你也可以成为 ROS 开源社区的积极贡献者。

问题

  1. 强化学习的核心概念是什么?

A) 机器人动作和惩罚

B) 神经网络和深度学习

C) 状态、动作和奖励

  1. 为什么在强化学习中需要使用神经网络?

A) 因为机器人需要使用深度学习来识别物体和障碍物。

B) 因为机器人必须学会将状态与最有效的动作相关联。

C) 在强化学习中,我们不需要神经网络;我们应用不同的算法。

  1. 你如何鼓励机器人实现任务的既定目标?

A) 通过在它执行良好行为时给予奖励。

B) 通过在它执行不良行为时给予惩罚。

C) 通过在它执行良好行为时给予奖励,在执行不良行为时给予惩罚。

  1. 你可以将本章中提到的强化学习 ROS 包应用于其他机器人吗?

A) 是的,因为我们已经将机器人模型、场景和训练算法分成了不同的包。

B) 不,因为对于每个场景,你必须重写 ROS 包。

C) 不,它是特定于 GoPiGo3 训练的。

  1. 你是否需要使用来自真实 LDS 的完整数据流来训练机器人?

A) 是的:如果你想获得准确的结果;你必须使用所有数据。

B) 不,你必须将光线追踪密度作为场景中障碍物典型大小的函数来决定。

C) 不一定:这取决于你需要的精度有多少。

进一步阅读

要深入了解本章中解释的概念,你可以参考以下参考资料:

第十三章:评估

第一章:组装机器人

  1. C

  2. A

  3. C

  4. C

  5. B

第二章:GoPiGo3 的单元测试

  1. C

  2. A

  3. B

  4. A

  5. B

第三章:ROS 入门

  1. A

  2. A

  3. C

  4. A

  5. A

第四章:创建虚拟两轮 ROS 机器人

  1. C

  2. B

  3. C

  4. B

  5. B

第五章:使用 Gazebo 模拟机器人行为

  1. C

  2. C

  3. C

  4. B

  5. B

第六章:ROS 编程 - 命令和工具

  1. A

  2. A

  3. C

  4. A

  5. C

第七章:机器人控制和仿真

  1. C

  2. B

  3. B

  4. C

  5. C

第八章:使用 Gazebo 进行虚拟 SLAM 和导航

  1. C

  2. C

  3. A

  4. B

  5. A

第九章:机器人导航的 SLAM

  1. A

  2. C

  3. B

  4. B

  5. B

第十章:在机器人中应用机器学习

  1. B

  2. B

  3. B

  4. C

  5. A

第七章:机器学习与 OpenAI Gym

  1. A

  2. B

  3. B

  4. A

  5. B

第十二章:通过强化学习实现目标

  1. C

  2. B

  3. C

  4. A

  5. B

posted @ 2025-09-22 13:21  绝不原创的飞龙  阅读(52)  评论(0)    收藏  举报