URDF(Unified Robot Description Format), 是一种特殊的xml文件格式, 作为机器人的一种描述文件, 在ROS里面大量使用. 接触ROS比较久的同学, 应该会经常见到一种类似命名的包(package) – xxx_description. 这个包里面就是包含某个机器人的描述文件. 比如pr2_decription, baxter_descrition, 以及ur_description等. 上述举例的机器人描述包都是可以通过apt-get
的方式进行安装. 使用命令格式如斯: sudo apt-get install ros-indigo-pr2-descrition
, 其中indigo
是你所安装的ROS版本名, 后面就是所需要安装的包名, 下划线用中画线代替.
对机器人使用gazebo进行仿真时, 需要加载的机器人模型就是urdf模型, 当然, 单纯的urdf是不能精确描述机器人以及所需要仿真的世界的. gazebo对其进行了扩展, 感兴趣的朋友可以查看gazebo官网的一些教程. 其中会提供一些标签, 对系统动态, 重心等的设定.
如果想使用开源库moveit对机器人进行路径规划, 在moveit setup assistant教程中, 第一步就是关于如何将机器人模型导入进来, 导入的机器人模型就是urdf(导入xacro格式时也是先将其解析为urdf).
诸如这些应用, 当然还不限于这些应用, 可以看到, 了解URDF以及会使用URDF是一件很重要的事情. 而xacro文件, 是提供了一些更为高级编辑方式的宏文件. 这种格式的文件, 在使用时, 均先会调用rosrun xacro xacro.py xxx.urdf.xacro > xxx.urdf
, 将其解析成对应的urdf文件. 然后再使用.
下述内容, 仅仅是对urdf文件编辑和查看由最基础的了解, 以方便能够看得懂机器人描述文件, 以及该怎么利用别人已经写好的描述文件进行组合, 重构得到自己的机器人描述文件. 如果是需要更详细, 精确的描述, 则还需要自己再去对urdf文件进行学习.
在URDF教程中, 第一个出现的就是下图, 很形象的将URDF要定义的主要内容给展现了出来. 一般, 机器人都是由link和joint进行描述. 都会呈现为树状(想象数据结构里面的树), 如图1, 由一个根link(link1)向上, 分别出现了两个分支–link2和link3, 分别由joint连接link. link4就可以类似的理解.
了解机器人的一般描述方式之后, 我们来看一下下图中这个形状的机器人该怎么来描述它.
<robot name="test_robot">
<link name="link1" />
<link name="link2" />
<link name="link3" />
<link name="link4" />
<joint name="joint1" type="continuous">
<parent link="link1"/>
<child link="link2"/>
joint>
<joint name="joint2" type="continuous">
<parent link="link1"/>
<child link="link3"/>
joint>
<joint name="joint3" type="continuous">
<parent link="link3"/>
<child link="link4"/>
joint>
robot>
从上述内容可以看到, 机器人最重要的两个内容 – link和joint, 是如何进行定义. 文件都定义了哪些标签, 以及该怎么使用. 其中内容很简单, 我就不浪费篇幅了.
细心的朋友肯定能够感受到, 虽然能够想象的出来大概是一个什么样子, 但是上面的内容是不能够唯一确定一个机器人或者一个…东西…, 因为每个link长什么样子?他们又是一个什么样子的位置关系? 这些在上面的内容中都是没有定义的. 如图中所示, 可能现在每一个不同的同学, 所联想到的样子都是不一样的, 一个仙人掌? 一个两指的夹持器? anythin..当然, 上面所示内容的urdf文件是不能够被正确解析的, 也是不能够可视化出来的. 但上面内容就类似于整个机器人的骨架, 机器人就由这些东西组成. 一共拥有4个link, 和3个joint, 像图中所示的样子连接起来.
在定义好了机器人的骨架后, 进一步我们可以使用origin
子标签进行定义link所应该在的位置. 但是有一点应该注意到, link和link之间是使用joint进行连接, 那么link的位置, 就由连接他的joint确定. 所以, 该子标签是定义在joint内. 在三维空间中, 要精确描述一个刚性体的姿态, 仅仅使用他的xyz坐标是不够的, 还需要使用rpy. rpy角是描述船舶在海中航行时姿态的一种方法. 将船的行驶方向取为z轴, 绕z轴旋转称为滚动(Roll), 绕y轴旋转称为俯仰(Pitch), 绕x轴旋转称为偏转(Yaw). 这种描述方式大量运用于各个领域. 依稀记得, kinect2关于脸部模型匹配的DEMO程序里面, 对脸部的描述就用到了这种描述方式来描述姿态. 在机器人中, 当然运用就更多了. 现在对之前的内容进行扩充. 其中rpy代表的是角度, 用弧度表示.
<robot name="test_robot">
<link name="link1" />
<link name="link2" />
<link name="link3" />
<link name="link4" />
<joint name="joint1" type="continuous">
<parent link="link1"/>
<child link="link2"/>
<origin xyz=".5 .3 0.7" rpy="0 0 0" />
joint>
<joint name="joint2" type="continuous">
<parent link="link1"/>
<child link="link3"/>
<origin xyz="-.2 .5 -0.3" rpy="0 0 1.57" />
joint>
<joint name="joint3" type="continuous">
<parent link="link3"/>
<child link="link4"/>
<origin xyz=".5 0 0.2" rpy="0 0 -1.57" />
joint>
robot>
上述位置关系定义如下图所示. xyz就如同一个平移向量, 将下一个link的原点坐标移动到下一个位置(起点是父link的原点). 同时, 你也可以多尝试几次rpy, 体会下一个坐标系是如何进行变换的. 虽然现在还没有任何东西出来, 但每个link的空间位置以及姿态已经被我们所指定了.
在解决了每个link的相对位置之后, 还有一个很关键的问题, 就是每个link长什么样子呢? 圆的? 扁的? 还是奇形怪状的? 下面示例了一些常用的形状, 以及可能会导入的一些奇形怪状的东西. 不但可以导入stl格式的文件, 还可以导入dae格式.下述示例中导入了一个小刀.
<robot name="test_robot">
<link name="link1">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2" />
geometry>
visual>
link>
<link name="link2">
<visual>
<geometry>
<box size="0.6 0.2 .1" />
geometry>
visual>
link>
<link name="link3" >
<visual>
<geometry>
<sphere radius="0.2"/>
geometry>
visual>
link>
<link name="link4" >
<visual>
<origin rpy="0 -1.57 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://urdf_csdn/urdf/mesh/knife.stl"/>
geometry>
visual>
link>
<joint name="joint1" type="fixed">
<parent link="link1"/>
<child link="link2"/>
<origin xyz="0.22 0 0.6" rpy="0 1.57 0" />
joint>
<joint name="joint2" type="continuous">
<parent link="link1"/>
<child link="link3"/>
<origin xyz="-0.1 0.5 0" />
joint>
<joint name="joint3" type="fixed">
<parent link="link3"/>
<child link="link4"/>
<origin xyz=".5 0 0" rpy="0 0 -1.57" />
joint>
robot>
当然, 每个link一般是不会产生重合的, 在运动规划的时候, 也会去避免碰撞到自己, 所以针对于每一个link, 还有一个collision标签, 和visual标签内容完全一样.
前面内容可以看到, 每个link可以看作是一个刚体, 刚体和刚体之间是通过joint进行连接, 那么, 问题就来了. 这个joint是固定的? 还是可以任意的动? 如果可以动, 那么, 问题又来了, 极限位置是多少? 等等等等…
比如, 我们限定joint2只能沿着y轴旋转, 则需要添加
, 类似的, 可以指定其他关节的转动轴, 例如
.
比如, 我们要限定joint2的移动范围, 则需要添加
, 从标签中可以看到, 上下限以及速度, 力矩等都是可以指定的.
<robot name="test_robot">
<link name="link1">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2" />
geometry>
visual>
<collision>
<origin xyz="0.0 0 0.0" rpy="0 0 0" />
<geometry>
<cylinder length="0.6" radius="0.2" />
geometry>
collision>
link>
<link name="link2">
<visual>
<geometry>
<box size="0.6 0.2 .1" />
geometry>
visual>
link>
<link name="link3" >
<visual>
<geometry>
<sphere radius="0.2"/>
geometry>
visual>
link>
<link name="link4" >
<visual>
<origin rpy="0 -1.57 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://urdf_csdn/urdf/mesh/knife.stl"/>
geometry>
visual>
link>
<joint name="joint1" type="fixed">
<parent link="link1"/>
<child link="link2"/>
<origin xyz="0.22 0 0.6" rpy="0 1.57 0" />
joint>
<joint name="joint2" type="continuous">
<parent link="link1"/>
<child link="link3"/>
<origin xyz="-0.1 0.5 0" />
<axis xyz="0 1 0"/>
<limit lower="-3.14" upper="3.14" effort="150.0" velocity="3.15"/>
joint>
<joint name="joint3" type="fixed">
<parent link="link3"/>
<child link="link4"/>
<origin xyz=".5 0 0" rpy="0 0 -1.57" />
joint>
robot>
查看urdf文件, 可以使用urdf_tutorial包, 命令格式roslaunch urdf_tutorial display.launch model:=path/to/your/xxx.urdf
, 会使用Rviz进行显示, 如上述内容, 进行显示之后, 可以得到下图所示内容. 坐标系图示, 需要添加TF条目进行显示. 值得注意的地方, 将Global Options中的fixed frame设定为link1. 如果你设定了可移动的关节, 想查看以下关节移动的效果以及设定等, 使用roslaunch urdf_tutorial display.launch model:=path/to/your/xxx.urdf gui:=true
, 你会看到弹出一个控制面板(如果没找到, 再好好找一找, 或许真的很小).
通过上述方式, 在rviz中正确显示模型之后, 新打开一个命令行, 输入rostopic list
命令, 可以查看到类似如下的输出. 可以看到, 开启了/joint_states
话题(Topic), 使用rostopic echo /joint_states
可以看到话题数据.
前面也提到了, XACRO文件和URDF实质上是等价的. XACRO格式提供了一些更高级的方式来组织编辑机器人描述. 主要提供了三种方式来使得整个描述文件变得简单. 借用在教程中一句话来形容xacro的优势: “Fortunately, you can use the xacro package to make your life simpler”.
Usage:
类似于C语言中的宏定义, 在头部定义, 如
, 以${WIDTH}
的方式进行使用. 经常会看到的一个常量定义, <property name="PI" value="3.14159265" />
. 还有定义一个前缀, 这样后面关节名都可以方便的进行修改. 比如<property name="prefix" value="my_"/>
, 后面关节名字就可以类似的进行更新. <joint name="${prefix}joint1" type="revolute"/>
.
在有了上面的常量定义之后, 类似于宏定义, 完成字符串替换, 同时还可以进行一些简单的数学运算.
Usage: ${1/2}, ${PI*(WIDTH*0.5)}
这个才是xacro文件中最重要的部分. 就像宏函数一样, 完成一些最小模块的定义, 方便重用, 以及可以使用参数来标识不同的部分.
Usage:
<xacro:macro name="default_origin">
<origin xyz="0 0 0" rpy="0 0 0"/>
xacro:macro>
<xacro:default_origin />
前面三行对宏进行定义, 第四行是使用.
Usage:
<xacro:macro name="default_link" params="prefix">
<link name="${prefix}_link1" />
xacro:macro>
<xacro:default_link prefix="my" />
类似, 前三行定义, 第四行是进行使用. 当然, 不单由这样简单的参数, 还可以使用块参数.
Usage:
<xacro:macro name="default_link" params="prefix *origin">
<link name="${prefix}_link1" >
<xacro:insert_block name="prigin" />
link>
xacro:macro>
<xacro:default_link prefix="my">
<origin xyz="0 0 0" rpy="0 0 0" />
xacro:default_link>
一般情况下, 很多已有的机器人模型, 都是以xacro格式提供描述, 而在xacro文件中, 整个机器人定义为一个很大的宏. 例如, barrett hand, 想进一步了解的朋友可以点击前面的链接, 查看以下barrett hand是如何进行描述的.
很多模型都是已宏的形式进行定义, 并以最小集团分成很多个文件. 而最终的机器人描述就变得非常简单了. 下面摘录一个ur5的描述文件. 从中可以看出来xacro的强大优势. 在最后的示例中我们还能够看到, urdf文件也是能够直接导入进来的.
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="ur5" >
<xacro:include filename="$(find ur_description)/urdf/ur5/common.gazebo.xacro" />
<xacro:include filename="$(find ur_description)/urdf/ur5/ur5.urdf.xacro" />
<xacro:ur5_robot prefix="" joint_limited="false"/>
<link name="world" />
<joint name="world_joint" type="fixed">
<parent link="world" />
<child link = "base_link" />
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
joint>
robot>
当然, 此时的简单是建立在之前的复杂的基础上的. 从上述内容中可以看到, 首先是在ur_description包中找到另外几个xacro文件, 将其包含进来. 当然应该注意到, include类似于C语言中的include, 先将该文件扩展到包含的位置. 但包含进来的文件很有可能只是一个参数宏的定义. 并没有被调用. 所以, 示例中调用了一个宏(
), 产生一个ur5机器人.
urdf_tutorial包也是可以查看xacro文件的. 使用roslaunch urdf_tutorial xacrodisplay.launch model:=path/to/your/xxx.urdf.xacro
.
前面提到的可视化都是使用urdf_tutorial包进行的. 分别调用了两个launch文件. 在上面的示例中我们还看到了不但会使用rviz进行可视化, 还会发起一些话题等. 其实这些我们能够从他的launch文件中一窥究竟.
打开命令行, 输入: rosed urdf_tutorial dispaly.launch
. 会使用vim打开该文件. 可以看到下述内容.
<launch>
<arg name="model" />
<arg name="gui" default="False" />
<param name="robot_description" textfile="$(arg model)" />
<param name="use_gui" value="$(arg gui)"/>
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher" />
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find urdf_tutorial)/urdf.rviz" required="true" />
launch>
由上可以看到, 参数model是没有默认值的, 所以调用该launch文件必须指定model参数. 其他都比较易懂, 主要解释以下robot_description, 可以看到, 其前面是param. 这个是指定ros 参数服务器中的参数值. 而打开rviz之后, rviz就是直接从参数服务器中读取机器人描述文件, 也就是这个参数. 然后进行显示. use_gui也是如此对显示产生的影响. 另外, 还发起了两个发布者节点, 分别发布joint_states和robot_state. 这也就是会由joint_states话题的原因. xacrodispaly.launch文件和上面类似, 但在处理文件时, 使用的是: . 在launch中将xacro文件解析为urdf.
至于其中启动的两个节点, joint_state_publisher 和 robot_state_publisher, 可以查看ROS Answer上相关的解释. 另外, 在robot_state_publisher概述中提到, robot_state_publisher从/joint_states
话题中获取机器人joint角度作为输入, 使用机器人的运动学树模型计算出机器人link的3D姿态, 然后将其发布到话题/tf
和 /tf_static
. joint_state_publisher从ROS参数服务器中读取robot_description
参数, 找到所有non-fixed joint, 发布他们的JointState
消息到/joint_states
话题.
有了前面的基础之后, 我们就可以进行一些属于我们自己的私人定制了. 比如说, 使用ur5作为手臂, 接上barrett hand或者shadow hand. 这样就可以得到一个完整的操作模型了. 先看一下效果.
UR Description
首先需要下载所需要的UR机械臂的包, 打开命令行, 输入: git clone https://github.com/ros-industrial/universal_robot.git
, 下载下来之后, 在universal_robot文件夹内有一个叫ur_description文件夹, 这里面就是我们需要的机器人描述文件.
* Barrett Hand Description*
在https://github.com/RobotnikAutomation/barrett_hand/tree/hydro-devel中, 下载得到bhand_description, 就是我们所需要的barrett hand描述文件.
* 组合 *
将前面得到的ur_description和bhand_description拷贝到catkin_ws目录下(确保能够找到这两个包), 到catkin_ws目录下, 运行catkin_make
. 注意查看提示信息, 是否发现ur_description和bhand_description这两个包.
以下述内容新建一个文件, 以xxx.urdf.xacro
格式命名. 运行roslaunch urdf_tutorial xacrodisplay.launch model:=path/to/your/xxx.urdf.xacro
来进行显示. 最终结果就如上图所示. 从下面的描述来看, 是不是很简洁?
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="ur5bh" >
<xacro:include filename="$(find ur_description)/urdf/common.gazebo.xacro" />
<xacro:include filename="$(find ur_description)/urdf/ur5.urdf.xacro" />
<xacro:include filename="$(find bhand_description)/urdf/bh282.urdf.xacro" />
<xacro:ur5_robot prefix="" joint_limited="true"/>
<link name="world" />
<joint name="world_joint" type="fixed">
<parent link="world" />
<child link = "base_link" />
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
joint>
<xacro:bhand_macro parent="ee_link" name="bh">
<origin xyz="0.012 0.0 0.0" rpy="${pi/2.0} ${pi/2.0} ${pi/2.0}"/>
xacro:bhand_macro>
robot>
有些时候, 我们不但需要机器人, 同时还需要一些场景, 比如机器人面前的一个桌子, 桌子上面放一些物品等, 或者一些稀奇古怪的东西, 放在机器人旁边, 和机器人一起导入来运行. 就比如下图所示的样子.
* Baxter *
可以使用另外一种方式来获取Baxter的描述文件. 打开命令行, 以此输入:
$ sudo apt-get install ros-indigo-baxter-description
$ cd ~/catkin_ws/src/
$ cp -r /opt/ros/indigo/baxter_description ./
$ rosrun xacro xacro.py --inorder baxter_description/urdf/baxter.urdf.xacro\
> baxter_description/urdf/baxter_new.urdf
如上所述, 一定得注意最后一条指令, 可以参考Github上xacro的一条Issues, 其中一定需要添加--inorder
, 参数. 否则生成模型会失败. 所以我们提前将baxter的xacro文件转换成urdf格式.
* The YCB Object and Model Set *
在http://rll.eecs.berkeley.edu/ycb/上, 可以下载到一些物体的三维模型以及urdf文件. 进入网站之后, 点击下载ROS packages for setup descriptions of ‘Pitcher-Mug Protocol’ and ‘Table Setting Protocol’ in Gazebo simulation environment. 将下载下来的文件解压到和前述文件中的同级目录中. 其中有一些launch文件, 可以运行起来, 看一看效果. 挺好玩的.
* 排列 *
所有东西都准备好之后, 将下述内容拷贝到一个新的xacro文件中. 和之前一样, 运行并显示. 可以得到前述图像中的样子. 和之前的类似, viz打开之后, 需要将Fixed Frame设定成world, 模型才能正常显示.
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="baxter_and_world" >
<link name="world"/>
<joint name="world_to_baxter" type="fixed">
<parent link="world"/>
<child link="base"/>
joint>
<xacro:include filename="$(find baxter_description)/urdf/baxter_new.urdf" />
<xacro:include filename="$(find ycb_object_models)/models/urdf/table.urdf" />
<joint name="base_table" type="fixed" >
<parent link="world"/>
<child link="table_top_link"/>
<origin xyz="1 0 -0.7" />
joint>
robot>