机器人操作系统简介:终极机器人应用框架


/**
 * 原文出处:https://www.toptal.com/robotics/introduction-to-robot-operating-system
 * @author dogstar.huang <chanzonghuang@gmail.com> 2016-03-05
 */

机器人操作系统(ROS)不是一个真实的操作系统,而是一个框架以及一系列为运行在异质计算机集群的操作系统提供基础功能的工具。
它的用处不局限于机器人,还包括大量关注与外设一起工作的工具。

ROS 分成2000多个包,每个包提供特定的功能。连续框架的工具数量可能是它的最大功率。

为什么我应该用ROS?

ROS提供了硬件抽象,设备驱动,多机器进程间的通信,测试和可视化的工具等功能。

ROS的关键特性是软件运行和沟通的方式,使得你可以设计复杂的软件而不需要知道相关的硬件如何工作。ROS提供了一种利用中央枢纽连接进程(节点)网络的方式。
节点可以运行在众多设备上,并且可以通过各种途径与中央枢纽进行连接。

创建网络的主要方式有提供可请求的服务,或者定义与其他节点的发布/订阅者连接。这两种方法都是通过指定消息类型来通信的。 一些类型由核心包提供,而消息类型则可由用户包来定义。

对于小型问题,开发人员可以通过连接已存在的解决方案来集成一个复杂的系统。这种方式的系统已经实现,它允许我们:
+ 在fly上用熟悉的接口替换组件,以便为各种变化移除停止系统的需要 + 为另一个组件把众多组件的输出混合成一个输出,以便并行解各类问题 + 只要实现消息系统对应的合适连接器就可以连接各种各样编程语言创造的组件,使得通过连接来自大量开发人员已存在的模块进行软件开发很容易 + 创建基于设备网络的节点,而不用关心代码在哪里运行以及实现进程通信(IPC)和远程过程调用(RPC)系统 + 根据远程硬件的需要,通过部署前两个要点即可直接连接馈送,而不用编写任何额外的代码

我们在计划演示通过迭代开发一个简单方案是多么地有用。相比于其他方式,这里有几个关键的好处。ROS有多平台支持以及允许通过隐藏于表面的点对点连接进行多设备的进程连接。 此设计允许支持任何包装了C++通信类的任何语言,或者手动为语言接口开发类。

ROS由我们的社区创建,意味着它也回归于社区。多亏于这个系统架构,经过若干年后,这种氛围导致了大量可重用并易于集成的包的涌现。

一些变种如:http://www.mrpt.org/http://carmen.sourceforge.net/intro.htmlhttp://lcm-proj.github.io/http://playerstage.sourceforge.net/https://msdn.microsoft.com/en-us/library/bb648760.aspx 等提供了其中一些特性,但不是全部。 大多数时候,设计的衰落在于语言支持的局限,未优化进程通信,或者按理说是最难修复的问题 -- 缺少对大量设备的支持。

我们准备构建什么?

既然我们的关注点是框架而不是针对特定问题的具体算法,所以给定的问题是相当简单的。我们的目标就是为一个车载的计算机构建通过Wi-Fi连接的软件, 从而可以通过使用电脑上的游戏把柄和安装在机器人上的摄像机来远程控制和监控机器人。

首先,为了示范ROS的基本原则,我们将会创建一个简单的程序来连接一个简单的模拟。我们会把游戏手柄绑定到电脑上并且尝试为能把游戏手柄输入转换成机器人控制信息而设计一个好的控制方案。

编写ROS代码的只要语言是C++和Python,首选C++是因为它的性能。由于代码中更少的样板和不需要明确的构建,我们将会通过Python来解释示例。

安装与配置

ROS版本由名称来组成。如现在这个日期,最新的发布版本是Jade Turtle,而最新的LTS版本是Indigo Igloo。最好是使用LTS版本,因为ROS不保证向前兼容性,所以全部的示例将使用Indigo这个版本来编写。

ROS可用于大量的*NIX平台,官方支持的版本运行于Ubuntu。OS X,Arch Linux,Debian,Raapbain 和Android版本则由社区支持。

我们将经历在Ubuntu 14.04桌面的安装过程。全部支持的版本和平台的过程都可在官网获得。带ROS的虚拟机也有。

安装是平台依赖性的(大部分平台都有自己提供的包),而工作区间的配置对于全部平台来说都是一样的。

在Ubuntu上安装

ROS提供了自己的仓库。第一步就是要添加它们。

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'  
sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv-key 0xB01FA116  
sudo apt-get update  

然后会有针对你的Ubuntu版本的全部ROS的全部托管包。例如,Ubuntu 14.04支持indeigojade

在桌面上安装基本的包可以三选一:
+ 最小化安装:sudo apt-get install ros-indigo-ros-base + 带基本额外GUI工具的:sudo apt-get install ros-indigo-desktop + 全量安装,即有全部官方特性的,包含了大量模拟器以及导航和知觉类库:sudo apt-get install ros-indigo-desktop-full

为了获得最好的工作体验,推荐全量安装。对于仅仅是用于运行节点的设备安装,基本版本则足够了。不和你选择的是哪个选项,你都可以安装任何需要的package_name包通过执行:

sudo apt-get install ros-indigo-<package-name>  

最终的名字将会把下划线替换成横线,所以stage_ros在这个包里

ros-indigo-stage-ros  

下一步是安装rosdep。在ROS的包可以声明他们依赖的包。rosdep允许你编译这些包而不用过多地人工维护依赖处理。为了安装它,调用:

sudo rosdep init  
rosdep update  

ROS有几个被它的工具使用的环境变量。默认的安装,bash脚本在/opt/ros/indigo/setup.bash初始化他们。在每个bash的会话中都需要初始化这些变量,所以最好的解决方案是把他们添加到~/.bashrc

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

一些包通过rosinstall来安装扩展的依赖,rosinstall可作为一个包来获得并可通过sudo apt-get install python-rosinstall安装。

这里到了在Ubuntu上安装的最后。接下来是安装工作区间的简短介绍。

配置

自从Groovy Galapagos后,ROS工作区间通过catkin来管理。我们需要为全部托管的负定义一个目录。在这个目录里创建一个src目录,并在里面调用catkin_init_workspace。 这将会创建大量的链接到当前ROS版本源的符号。下一步是把这个工作音区也添加到环境变量。

为了演示整个工作区间的配置,选择一个空的目录并执行以下命令:

mkdir src  
cd src  
catkin_init_workspace  
cd ..  
catkin_make  
echo "source $(pwd)/devel/setup.bash" >> ~/.bashrc  
source ~/.bashrc  

现在你已经创建了一个可以在里面创建自己ROS包的工作区间。

熟悉工具

马上创建代码是一个很大的跨越。让我们先来熟悉一些运行在屏幕后的系统。第一步是运行基本的GUI并看下它生成了什么消息。

为了在ROS中运行一些东西,需要加载一个核心的进程。这一点很简单,只要在一个新的终端窗口并输入:

roscore  

在你的整个连接设备网络中,roscore 仅需要在为通信分发托管中央枢纽的设备上加载一次。

让节点只关心他们想知道的数据,而不是他们想要连接的节点。  

###rqt
运行```roscore```后,可以为ROS加载主要的GUI工具:```rqt```。我们会看到很一般的东西 -- 一个空白的窗口。 ```rqt```托管了非常大量的可以在可视化配置中进行配置的插件
以及任何数量的预定义视图。  
![](http://7xrk7q.com1.z0.glb.clouddn.com/toptal-blog-image-1457014170149-0bef37c523adbc07d4813957aa98d1a1.jpg)  

为了能够开始,先运行插件_Robot Steering_,可以通过```Plugins > Robot Tools > Robot Steering```来选择它。我们会看到有两个滑块,代表着我们的机器人将会拥有的线性和旋转运动。
在插件的顶部可以看到一个有```/cmd_vel```的输入框。我们可以任意进行重命名。它代表了此操舵发布的主题。终端工具是看到在后台运行了什么的最好地方。  
![](http://7xrk7q.com1.z0.glb.clouddn.com/toptal-blog-image-1457014311910-57816f9e758bdf2e5985d4c4aef59ef3.jpg)    

###终端工具
ROS有几个非常有用的工具来检查系统中正在发生什么。第一个我们将会介绍的工具是```rostopic```。 它允许我们检查节点可以评阅和发布的主题。运行```rostopic list···将会产生:  

/cmdvel /rosout /rosoutagg

后两个主题通常都会运行并且与中央ROS系统相关。```/cmd_vel```主题则由我们的操舵发布。在操舵中重命名这个主题也会在这里重全名。现在,我们对于在这个主题里面发生的东西感兴趣。
运行```rostopic echo /cmd_vel```将看不到什么东西(除非你对滑块作了调整)。这个进程会一直执行,直到我们取消它。让我们把垂直滑块移到20 m/s。看着输出,我们会看到以下一次又一次重复的内容:  

linear:
x: 0.2 y: 0.0 z: 0.0 angular:
x: 0.0 y: 0.0 z: 0.0

这个消息重复的频率是多少? ```rostopic hz /cmd_vel```说平均频率是10Hz。好吧,通过我很慢的Wi-Fi连接可以运行多少像这样的主题?```rostopic bw /cmd_vel```表明平均是480B/s。 

到目前为止一切运行良好,但我们将要讨论消息类型。这种数据对于人类是友好的,但应用需要raw格式的数据,并且需要知道消息类型以便它能解析数据。
类型可以通过```rostopic type /cmd_vel```来决定,告诉我们这是一个```geometry_msgs/Twist```。任何ROS终端的工具无参数调用时都会返回一个标准的帮助消息。  

ROS的wiki非常赞,在网页搜索这个字符串会找到一系列的解释,从它包括了什么到它的结构是怎样的应有尽有。但是我们不用依赖于它。```rosmsg```是针对消息类型的通用工具。  
运行```rosmsg show geometry_msgs/Twist```会返回:  

geometrymsgs/Vector3 linear
float64 x float64 y float64 z geometry
msgs/Vector3 angular
float64 x float64 y float64 z

这个消息包含了两个3D向量,表示在三维空间中的线性和旋转速度。  

如果我们想知道一个节点连接了什么主题,```rosnode info <node-name>```会给出关于节点的详细数据。```rostopic```,```rosmsg```和```rosnode```是检查raw ROS功能的主要工具。
ROS有大量的GUI和终端工具,但这已超出了本章的范围。  

运行ROS节点的主要工具是```rosrun```和```roslaunch```。```rosrun```通过```rosrun <package_name> <node_name>```来运行节点,而```roslaunch```则基于加载
由于是ROS自动化中最为复杂的元素所以我们知之甚少的文件来运行节点。  

我们可以关闭运行的全部东西以开启我们最初的代码。在下文中,我们将省略说明运行任何和ROS相关的东西都需要一个活跃的```roscore```实例。你遇到的很多问题都可以通过关闭
运行```roscore```对应的窗口来解决,并且新开一个窗口重新加载。这样的话会更新全部需要重新加载的依赖,包括在```bash```和在```roscore```中。  

##创建游戏柄遥
我们第一个目标是通过创建一个发布```geometry_msgs/Twist```数据给基于游戏柄输入的```/cmd_vel```来模拟```Robot Steering```的功能。第一步的产出是```joy```包。  

### ```joy```包
这个```joy```包为操纵杆和游戏柄提供了通用的ROS驱动。它没有包含在默认的安装,所以需要这样进行安装:  

sudo apt-get install ros-indigo-joy

安装完成后,可以运行```rosrun joy joy_node```。这会把我们和默认的操纵杆或者游戏连接起来。运行```rostopic list```可以看到有一个叫```/joy```的主题。通过```rostopic echo```可以
看到以下格式的消息(请注意你需要使用游戏柄或操纵杆解析待发布的消息)。

header:
seq: 4156 stamp: secs: 1450707466 nsecs: 204517084 frame_id: '' axes: [0.0, 0.0, 0.0, -0.0, 0.0, 0.0, 0.0, 0.0]
buttons: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

现在你可以忽略头部。除此之外,```axes```和```buttons```恰到好处地解析了他们代表什么。移动轴和按下在控制器上的按钮会导致这些数字发生变化。使用我们的工具,可以决定这个消息类型
是```sensor_msgs/Joy```并且对应的格式是:  

stdmsgs/Header header
uint32 seq time stamp string frame
id float32[] axes
int32[] buttons

##创建我们的遥
编写代码的第一步是创建一个包。在工作区间的```src```目录里,运行:  

catkincreatepkg toptaltutorial rospy joy geometrymsgs sensor_msgs

在这里我们声明了将要创建的包名,紧跟随后的是所要依赖的包。无须担心,依赖可以稍候手动更新。  

现在我们有了```toptal_tutorial```目录。在这个目录里,创建一个将会放置全部Python脚本的```scripts```目录。  

现在来创建一个```teleop.py```文件,并在里面放置:  

!/usr/bin/env python

import rospy
from sensor_msgs.msg import Joy

def joy_callback(data):
print data

def main():
rospy.initnode('teleop') rospy.Subscriber('joy', Joy, joycallback)

while not rospy.is_shutdown():
    pass

if name 'main':
main()

我们还需要设置```chmod +x teleop.py```以便脚本能被执行。在某个终端上运行```rosrun joy joy_node```并有另一个终端上运行```rosrun toptal_tutorial teleop.py```
将会导致运行```teleop.py```的终端被输出的Joy消息填充。  

让我们来检查一下代码做了什么。  

首先,导入托管了与ROS框架交互的类库的rospy。每一个定义了消息的包都有一个带消息定义的```msg```子包。我们导入了```Joy```以便可以处理输入。
这里不需要导入嵌入式的消息类型(如来自在```Joy```消息中的```std_msgs.msg```的```Header```),除非我们想明确地提及他们。  

第一步是初始化一个指定名字的节点(在这里,我们把它叫作“teleop”)。然后创建一个订阅者来订阅```sensor_msgs.msg.Joy```类型的“joy”主题,并且通过
回调```joy_callback```函数来处理每一个消息。回调接收一个参数,即来自消息的数据。访问这个数据的成员非常简单。如果想打印第一个轴的状态,或者想
重新调用这个消息类型,可以调用```print data.axes[0]```,并且是一个浮点类型。在最后的循环会一直循环直到ROS关闭。  

下一步是处理未知的数据。我们会创建一个根据输入而改变的Twist消息,然后会把它发布到```cmd_vel```主题。  

!/usr/bin/env python

import rospy
from sensormsgs.msg import Joy
from geometry
msgs.msg import Twist # new
from functools import partial # new

def joycallback(pub, data): # modified
cmd
vel = Twist() # new cmdvel.linear.x = data.axes[1] # new cmdvel.angular.z = data.axes[0] # new pub.publish(cmd_vel) # new

def main():
rospy.initnode('teleop') pub = rospy.Publisher('cmdvel', Twist, queuesize=1000) # new rospy.Subscriber('joy', Joy, partial(joycallback, pub)) # modified

while not rospy.is_shutdown():
    pass

if name 'main':
main()

首先,添加```Twist```消息,并且为通过```functools.partial```绑定函数参数添加支持。然后创建一个发布者,```pub```,来把一个```Twist```
消息类型发布给```cmd_vel```。我们把这个发布者和回调函数进行绑定,并使它为每一个由前两个轴代表的速度的输入都发布一个Twist消息。
这些代码做了我们期望的它做的事情,而且可以通过```rostopic echo /cmd_vel```看到结果的输出。  

但我们还有一个问题。```joy```主题发布的速度会很大。如果监控```rostopic hz /cmd_vel```并在圆圈中移动模拟遥杆,可以看到超级大量的消息。
这不仅仅导致了大量的通信,还导致了接收这些消息的进程需要一个个地处理他们。其实并不需要如此频繁地发布这些数据,最好是以一个稳定的速率如10Hz来发布。
可以通过以下代码来实现。  

!/usr/bin/env python

import rospy
from sensormsgs.msg import Joy
from geometry
msgs.msg import Twist
from functools import partial

def joycallback(cmdvel, data): # modified
cmdvel.linear.x = data.axes[1] cmdvel.angular.z = data.axes[0] # moved pub.publish(cmd_vel) to main loop

def main():
rospy.init_node('teleop')

cmd_vel = Twist()  # new

pub = rospy.Publisher('cmd_vel', Twist, queue_size=1000)
rospy.Subscriber('joy', Joy, partial(joy_callback, cmd_vel))  # modified

rate = rospy.Rate(10)  # new
while not rospy.is_shutdown():
    pub.publish(cmd_vel)  # new
    rate.sleep()  # new

if name == 'main':
main()

我们修改了回调函数以便接收可变的```Twist```对象并且在循环中作了修改。来自```rospy.Rate```的```sleep```函数维护了一个稳定的输出频率。  

最后的代码将会导致```/cmd_vel```主题以10Hz来获得速度命令,以模拟_Robot Steering_ ···rqt```插件的输出。  

##运行一个模拟系统
###模拟世界
首先的目标是创建一个可以在其中模拟想要实现的脚本的环境。在```stage_ros```包中的```stageros```节点允许我们在一个由一张图片定义的2D图形中运行机器人。
在[```stage_ros```包](http://wiki.ros.org/stage_ros)中有一个完整的针对世界文件和如何生成他们的语法。这些非常简单,但超出了我们的范围。幸运的是,
这些包都带有几个demo世界。首先,去到文件的目录:  

roscd stage_ros
cd world

在这个目录里有几个文件。运行其中一个,  

rosrun stage_ros stageros willow-erratic.world

这样就创建了几个主题。每一个的意义在包的文档中都有说明。重要的部分是它有```cmd_vel```。  
[](http://7xrk7q.com1.z0.glb.clouddn.com/toptal-blog-image-1457014474650-f9d0e6ed99361078d798b335e5d91d87.jpg)  

在显示的图形中,有一个蓝色的广场,代表你控制机器人。通过使用我们的代码或者_Robot Steering_,可以控制这个机器人。试试吧!  

###通过加载文件来启动系统
在我们的包中创建一个```launch```目录,并且在它里面创建一个```teleop.launch```文件。最后目录的结构应该看起来像这样:  

toptal_tutorial/
├── CMakeLists.txt ├── launch │ └── teleop.launch ├── package.xml ├── scripts │ └── teleop.py └── src

在```teleop.launch```文件中我们定义了一系列节点以及他们的互联。  


新的世界包括了四个机器人,并且他们每一个主题都有前缀```robot_<n>```。所以,0号机器人有一个叫作```robot_0/cmd_vel```的速度命令主题。
这就是为什么我们在命名空间中放置名叫```robot_0```的控制器,以便把他们的名字调整到新的格式。在这样的场景下,你可以想象得到在文件系统中这些主题的目录名字。  
![](http://7xrk7q.com1.z0.glb.clouddn.com/toptal-blog-image-1457014518567-95be785402390df35c82c8b5e5426f10.jpg)  

为了运行启动文件,不需要```roscore```。在某些场景,```roscore```只是无作为的启动文件的一个特别案例。如果缺少```roscore```,仅是第一个启动文件被加载时会运行
一个core,剩下的则连接到此。现在,我们通过这样来运行加载器:  

roslaunch toptal_tutorial teleop.launch

如果全部都正确,结果会有一个带4个机器人的模拟器,其中一个机器人由我们的游戏柄或者操纵杆控制。这个世界比前一个的引擎盖要多得多。这四个机器人每一个都有:  

/robot/baseposegroundtruth /robot/basescan0 /robot/basescan1 /robot/camerainfo0 /robot/camerainfo1 /robot/cmdvel /robot/depth0 /robot/depth1 /robot/image0 /robot/image1 /robot_/odom

可用0,1,2,3来替换```<n>```。这把我们带到了最后一个主题。  

##使用```rqt```查看数据
我们不会深入研究```rqt```,但它是查看更复杂数据的完美工具。你可以尝试全部主题,但这里我们关注于```image_0```,```image_1```,```depth_0```,和```depth_1```这几个主题。  

让我们加载```rqt```并移除全部开启的插件。现在打开4个图片展示台(```Plugins > Visualization > Image View```),并且在2x2的表格结构中放置他们。最后,在每个视图的左上角,
在四个状态主题中选择```robot_0```。  
![](http://7xrk7q.com1.z0.glb.clouddn.com/toptal-blog-image-1457014561287-1ce027bcf7efb23314bdd3f8b44a7583.jpg)  

我们得到的是带尝试知觉的立体视觉,和低分辨率的摄像机。记住我们甚至可以获得这个结果而不用输入系统。如果我们只是这样运行(在```stage_ros/world```目录里面):  

rosrun stage_ros stageros willow-four-erratics-multisensor.world
```

并且通过一个叫作/robot_0/cmd_vel主题添加Robot Steering插件,也可以获得带在屏幕滑块进行控制的相同的结果。

将结果应用到真实系统

大量的硬件都能完整支持ROS,这些经常由第三方志愿者提供。很多机器人平台有生成这些消息类型的驱动,而ROS有采取网络摄像头以及发布图片反馈的节点。

最后的结果是我们想要实现的一个模拟,通过以下修改也可以达到同样的效果:
+ 在你的机器人的车载计算器上安装ROS + 在车载计算机上创建一个启动文件把ROS和底层平台,全部高层的传感器如摄影机以及激光测距义等连接起来。需要的节点可能已经存在,或者可以通过在某一边为ROS创建发布者/订阅者,同时在另一边创建针对序列通信的驱动来实现 + 在启动时运行启动文件 + 在你的远程计算机中把export ROS_MASTER_URI=http://<robot_hostname>:11311/添加到你的bash启动中,使得远程计算机在给定的域名和端口寻找roscore + 加载rqt并且/或者任意监控和控制机器人的脚本

这样下来其实只是在远程设备上放置合适的环境变量,其余的则是处理它本身。在计算机集群上运行ROS仅需要在每一个机器上都完成某一个步骤。

结论

我们已经通过非常少量的代码,演示了你怎样才能随心所欲操纵一个复杂系统的变量。简单的发布者/订阅者系统允许你快速开发在计算机集群中处理数据的软件管道,而不需要担心底层相关元素的实现。

我们只是使用了一个简单的模拟器,更复杂的模拟器如gazebo(也包含在完整的桌面版本中)可以让你创建有物理和复杂传感器的3D世界, 并且可以给你最终结果的体验和远在它被开发前的产品。

这个只是一个很基本的简介,但希望你对和这个多才多艺的框架一起工作的更感兴趣。

dogstar

一位喜欢翻译的开发人员,艾翻译创始人之一。

广州