1. 程式人生 > >ROS與GAZEBO實時硬體模擬(1)——urdf的編寫

ROS與GAZEBO實時硬體模擬(1)——urdf的編寫

寫在前面

接觸gazebo也差不多有一年之久了,當時使用gazebo是因為比賽的時候,機械的進度沒有那麼快,此時演算法不能停啊,因此就用了gazebo的實時模擬平臺,不得不講,gaezbo的模擬平臺做的相當不錯了(甚至是感測器你都可以新增噪聲!!!),學會了它其實很多時候能讓我們不用在去關心硬體的東西而能把時間都放在演算法上,比如SLAM,路徑規劃等等的都可以在這樣的模擬平臺上進行操作,當然,在搞演算法之前,你必須要把模擬的機器人給搭建出來。
時隔一年,終於得空回來好好的研究這方面的東西,在整個過程中,自己也踩了不少坑,參考了許多東西,最終才得以把整個過程捋順,寫下來一方面是希望可以幫助更多的人,一方面是自己的再次總結,查漏補缺。
tips:本系列教程預設你已經對ros和gazebo有一定的瞭解了,否則應該也無緣看到這些

整體的流程框架

這裡必須祭出這張官方的框架圖了
整體框架圖

還是那句老話,官方的圖第一次看起來確實是很難看,但是著實是一個越看越明白的圖,而圖片的最上面的三個模組也是這個系列要講的重中之中的東西,那麼廢話不多說,下面開始一起探索ros與gazebo的世界。

把大象裝進冰箱的第一步——urdf檔案

首先,無論是對於ros而言,還是對於gazebo而言,模擬最重要的就是要看到我們構建的帥氣的機器人,那麼看到它的關鍵檔案就是urdf檔案,該檔案的主要功能就是描述機器人各個部分(link)與關節(joint)的屬性
記住了上面這句話,其實我們的urdf也就很好寫了!下面我們就一起嘗試一下。

作用:

機器人的各個部分的描述,尤為注意的是,這裡的各個部分不是指像底盤這樣如此寬廣的指代,而是更加細分的最小組成單元,比如一個簡單的底盤是由一個鋼板和四個輪子組成的,其中鋼板和輪子就是最小組成單元了。

流程:

  1. 在catkin_ws中建立一個包,名稱我這裡為ros_gazebo_learn
  2. 在ros_gazebo_learn下建立urdf資料夾,並在其中建立一個urdf檔案(注意:這裡建立的檔案是.urdf.xacro檔案,xacro是urdf的巨集定義檔案),目錄樹如下所示:
    .
    ├── CMakeLists.txt
    ├── include
    │ └── ros_gazebo_learn
    ├── package.xml
    ├── src
    └── urdf
    └── rbo.urdf.xacro
  3. 在檔案中寫入如下內容:
<?xml version="1.0"?>
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
    <link name="car_link">

        <visual>
            <geometry>
                <box size="2.0 1.0 0.5"/>
            </geometry>
        </visual>

        <collision>
            <geometry>
                <box size="2.0 1.0 0.5"/>
            </geometry>
        </collision>
    </link>
</robot>

這時候在命令列中輸入如下語句就可以看到我們的link出現在RVIZ中(注意,輸入命令的前提是你需要有urdf_tutorial的軟體包,沒有的話apt下載就行)

roslaunch urdf_tutorial display.launch model:=rbo.urdf.xacro

效果如下:
link效果圖

link包含的元素

經過上述的過程,相信你已經能夠在ros中看到自己建立的link了,與此同時,你也應該發現,一個link的實現其實是那麼的簡單!!!沒錯,在urdf中,最最簡單易懂的就是link這個標籤了。
在一個機器人的描述檔案(urdf)中,link應主要包含以下幾個元素:

  1. visual標籤:這表示著我們是否可以在圖形介面上觀察到這個link,沒有的話就觀察不到,主要包含geometry標籤
  2. collision標籤:這表示著感測器(主要指鐳射等投射型的感測器)是否可以在物理引擎上檢測到這個link,同時標誌著是否會與別的link進行碰撞檢測,也主要包含geometry標籤
  3. inertial標籤:這表示著物理引擎是否能夠感受到link的存在,主要包含3個標籤:
    • origin:link重心的位置
    • mass:link的重量
    • inertia:link的旋轉慣量

對於上述的三個標籤而言,前面兩個是比較好理解的,visual表示能否被看到(不僅僅是在上位機上,同時對於gazebo中的攝像頭等裝置同樣有效),collision表示是否會被碰撞,沒有這個標籤的話,就說明這個link是可以悄無聲息的進入到另一個link的裡面,amazing:D,而對於第三個標籤,由於上面的程式碼中沒有出現,現在我們對它還不夠敏感,實際上,在ros中,這個標籤作用不大(這只是我個人的推測,如有錯誤,大神輕噴~),而在gazebo中,這個標籤卻直接決定了這個link能否被看到!其中的原因很重要,我一定要另起一行說三遍!
在ros中,機器人節點的驅動靠的是TF,而在gazebo中,機器人節點的驅動主要靠物理引擎!!!
在ros中,機器人節點的驅動靠的是TF,而在gazebo中,機器人節點的驅動主要靠物理引擎!!!
在ros中,機器人節點的驅動靠的是TF,而在gazebo中,機器人節點的驅動主要靠物理引擎!!!
所以,在ros中,你想改變兩個link的相對位置?好啊,寫個node修改TF就行了!但是,在gazebo中,你想改變兩個link的相對位置?也可以啊,只要符合物理公式的,你用什麼方法都可以,那麼問題來了,當你的link連最起碼的質量(轉動慣量其實在旋轉的公式中其實是類比於質量的)都沒有的時候,物理公式恐怕是無從下手了。
因此,沒有inertial標籤的link在gazebo中是完全看不到的,不信的朋友可以在上述程式碼的情況下輸入以下命令:

roslaunch urdf_sim_tutorial gazebo.launch model:=rbo.urdf.xacro

你會看到一個空曠的世界,這裡不再貼效果圖了。
到這裡,我們基本上完成了link的知識點,相信你已經可以寫出一個在ros和gazebo中都能看到的link了,這裡不再敘述inertial的相關內容,官方對於標籤的講解可以參考這裡here

urdf中的巨集定義——xacro

作用

把xacro這部分內容放在這裡實際上是一個偷懶的舉動了,類比C語言中的巨集定義可知,它並不會影響整個程式碼的完整性,但是它卻能幫你大大的提高修改效率與簡化程式碼量。

流程

通過上面的例子,我們發現在visual和collision的標籤裡面出現了相同的元素geometry,並且這個標籤的值是一模一樣的,這就引發了一個問題:修改的時候很麻煩!試想每個link裡面的這兩個標籤的內容如無意外都是相同的,如果我們有一個link是經常要被重複的(例如輪子),那麼我們在修改的時候豈不是十分的耗費精力?是的,如果沒有xacro的存在,我們修改一個機器人模型的話確實需要這麼做了…..但是有了xacro之後,一切都將變得很簡單而且完美。
1. 首先我們要告訴robot我們要用xacro,也就是在robot的標籤中新增xmlns:xacro=”http://www.ros.org/wiki/xacro”,就像上面的例子一樣;
2. 隨後我們就可以在robot這個標籤內部使用巨集定義了,從實用角度講,一般我們會用到的巨集定義有兩種,一是屬性值的巨集定義,另一種是巨集定義的巨集定義(這裡不知道如何描述這個型別的巨集定義,但是確實就是這樣),具體的形式如下:

<xacro:property name="PI" value="3.1415926"/>
<xacro:macro name="default_inertial" params="mass gain">
  <inertial>
    <mass value="${mass*gain}" />
    <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="${PI/2}" />
  </inertial>
</xacro:macro>

<xacro:default_inertial mass="1.0" gain="10.0"/>

如上面的例子所示,這就是最常用的兩種巨集定義了,第一種就是你定義了一個值,這個值在以後的過程中一定是經常出現的,而後如果你某天數學變了,PI不等於3.14了,你就可以只更改這一個地方的值而修改程式中所有用到PI的地方,所謂牽一髮而動全身也不錯如此吧:D;第二種就是你定義了一個巨集定義,這個巨集定義的名字就是name後面的字串,他的引數在params中,因為是複數形式,因此引數值可以是多個值,宣告的時候用空格隔開就可以了。如上面的例子所示,使用的時候照著形式來就可以了。
至此你應該可以將最上面的rbo的描述檔案簡化了,下面是我簡化後的程式碼,同時為了讓rbo能在gazebo接納,我也添加了inertial標籤到描述檔案中

<?xml version="1.0"?>
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
    <!-- variable -->
    <xacro:property name="PI" value="3.1415926"/>
    <xacro:property name="car_width" value="1.0"/>
    <xacro:property name="car_length" value="2.0"/>
    <xacro:property name="car_height" value="0.4"/>

    <!-- macro -->
    <xacro:macro name="default_inertial" params="mass">
        <inertial>
            <mass value="${mass}" />
            <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
        </inertial>
    </xacro:macro>

    <xacro:macro name="box_geometry" params="width length height">
        <geometry>
            <box size="${width} ${length} ${height}"/>
        </geometry>
    </xacro:macro>

    <!-- links -->
    <link name="car_link">
        <visual>
            <xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
        </visual>
        <collision>
            <xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
        </collision>
        <xacro:default_inertial mass="5.0"/>
    </link>
</robot>

這時候看程式碼的話是不是就感覺很舒服了,同時修改也方便了很多,一般的,把屬性值的巨集定義當在前面的好處是修改的時候能快速的找到我們需要修改的值,命名也最好表徵出這個變數是幹什麼的。
官方的教程可以參考here

Joints關節——ros的TF與gazebo的連線

終於到了最後的一個知識點,同時也是robot描述檔案中最重要的一點——joint

作用

在ros中,joint_state_publisher這個節點會通過joint來了解到每個link之間的連線關係,從而根據一個link值推算出所有的link的TF狀態,為開發人員省去了不少時間(其實不得不承認,如果人為去計算的話,很少一部分人能把這個地方理的很通順的);在gazebo中,joint指示了兩個link之間的連線方式,如果兩個連線為非固定的,還需要配合gazebo的標籤指示摩擦係數等等,這裡再次重申:gazebo是依靠物理引擎來進行機器人模擬的,因此所有在gazebo中的物體和聯絡都要符合物理定律,否則要麼gazebo跪給你看(壓根兒就不顯示給你),要麼gazebo浪給你看(例如兩個link之間沒有摩擦係數,你輕推一下一個link,它就會以無限的加速度前行)。

流程

類似與link,要想讓joint工作,你必須宣告它所必須的標籤,主要包括下面的主要標籤:
1.name:標籤的名稱
2.type:指明這個標籤的型別,主要有以下的屬性

旋轉(revolute) - 具有由上限和下限指定的有限範圍,例如機械臂上的肘關節。
連續(continuous) - 繞軸旋轉,沒有上限和下限,例如底盤上的輪子
稜柱形(prismatic) - 滑動接頭,沿軸線滑動,並具有由上限和下限指定的有限範圍,例如某個關節是絲桿連線
固定(fixed) - 所有自由度都被鎖定,比如你把攝像頭用3M膠粘在了底盤上,那麼這個關節就是固定的
浮動 - 此關節允許所有6個自由度的運動,這個著實不好舉例,6個自由度都可以運動的話總感覺這個關節好像只是一個約束一樣
平面 - 此關節允許在垂直於軸的平面內運動,用的比較少,暫時想不到用例了

3.parent:這個關節的一端,稱為父端
4.child:這個關節的另一端,稱為子端
5.origin:父端與子端的初始transform,例如相機的座標系與底盤的座標系方向相同,但是在x方向上差了0.1m,那麼就在這個標籤上填充上相應的值,該標籤預設的值是兩個質心是完全重合的。
6.axes:旋轉軸,type標籤裡面反覆提到的旋轉軸就是這個標籤,它指示了繞什麼軸進行旋轉,例如繞父節點的z軸旋轉,那麼該標籤的值就為xyz=“0 0 1”
7.limit:有限值旋轉型別必備標籤,指示旋轉所能到到的上下限
實際上,joint還是有很多標籤的,這裡列舉了最常用的幾個,實際上,只有前4個標籤是必須的,其他的標籤是要隨著你的要求進而確定的。
百說不如一練,下面我們就來寫一個輪子與底盤的關節

<?xml version="1.0"?>
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
    <!-- variable -->
    <xacro:property name="PI" value="3.1415926"/>
    <xacro:property name="car_width" value="1.0"/>
    <xacro:property name="car_length" value="2.0"/>
    <xacro:property name="car_height" value="0.3"/>
    <xacro:property name="wheel_length" value="0.1"/>
    <xacro:property name="wheel_radius" value="0.2"/>
    <xacro:property name="wheel_origin_xyz" value="0.0 0.0 0.0"/>
    <xacro:property name="wheel_origin_rpy" value="0.0 ${PI/2} 0.0"/>


    <!-- macro -->
    <xacro:macro name="default_inertial" params="mass">
        <inertial>
            <mass value="${mass}" />
            <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
        </inertial>
    </xacro:macro>

    <xacro:macro name="box_geometry" params="width length height">
        <geometry>
            <box size="${width} ${length} ${height}"/>
        </geometry>
    </xacro:macro>

    <xacro:macro name="cylinder_geometry" params="length radius">
        <geometry>
            <cylinder length="${length}" radius="${radius}"/>
        </geometry>
    </xacro:macro>

    <xacro:macro name="default_origin" params="xyz rpyaw">
        <origin xyz="${xyz}" rpy="${rpyaw}"/>
    </xacro:macro>

    <!-- links -->
    <link name="car_link">
        <visual>
            <xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
        </visual>
        <collision>
            <xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
        </collision>
        <xacro:default_inertial mass="5.0"/>
    </link>

    <link name="wheel">
        <visual>
            <xacro:cylinder_geometry length="${wheel_length}" radius="${wheel_radius}"/>
            <xacro:default_origin xyz="${wheel_origin_xyz}" rpyaw="${wheel_origin_rpy}" />
        </visual>
        <collision>
            <xacro:cylinder_geometry length="${wheel_length}" radius="${wheel_radius}"/>
            <xacro:default_origin xyz="${wheel_origin_xyz}" rpyaw="${wheel_origin_rpy}" />
        </collision>
        <xacro:default_inertial mass="0.2"/>
    </link>

    <!-- joints -->
    <joint name="car_base_wheel" type="continuous">
        <origin xyz="${(wheel_length+car_width)/2.0} 0.0 0.0" rpy="0.0 0.0 0.0"/>
        <parent link="car_link"/>
        <child link="wheel"/>
        <axis xyz="0.0 1.0 0.0"/>
    </joint>

</robot>

以上就是一個很簡單的例子,但是縱然是如此簡單的例子,當你使用urdf_tutorial的display節點進行顯示的時候,卻意外的不行,效果如下:
這裡寫圖片描述

可以看到兩個關節是重疊在了一塊,仔細看錯誤之後發現,原來是整個描述裡面缺了base_link這個老大哥,通常情況下,因為機器人都是有高度的,因此base_link一般都是與機器人最下方的link進行固定連線,同時高度上相差合適的高度,因此上述程式碼加入以下程式碼之後就能變得正常了

    <!-- base_link -->
    <link name="base_link"/>
    <joint name="base_link_car" type="fixed">
        <origin xyz="0.0 0.0 ${wheel_radius}" rpy="0.0 0.0 0.0"/>
        <parent link="base_link"/>
        <child link="car_link"/>
    </joint>

效果如下圖所示:
這裡寫圖片描述
如果你使用gazebo開啟的話,就會看到比較有趣的地方,由於重力的作用,一邊重重的掉在了地上

這裡寫圖片描述

總結

到這裡,我們的第一步urdf的編寫就完成了,同時也知道了一個機器人要想在ros和gazebo中都觀察到的話應該具備怎樣的標籤和值,總之這部分的內容相對來說會簡單一些,消化起來也不是那麼的難。
實際上,即使有xacro,你想通過一條語句一條語句的進行機器人的搭建也是很費時間的,好在“懶”是科技進步的第一動力,SW提供了一個外掛可以很方便的將SW的模型轉化為urdf檔案,有興趣可以移步至here