ROS

ROS系列文整理

由於Robot Operating System(ROS)的中文資源還幾乎不存在(2012年),決定先撒下一點麵包屑,之後有寫出新的也會依序整理到這上面。

我覺得機器人在能源不出現問題之前,是必然崛起的一個領域。機器人的存在可以幫助人類變得更好,被解放的生產力能夠聚焦到更為重要的議題上 – 永續發展,公平正義,世界均富等等,而要在機器人產業做出能推動世界進步的成果,我認為ROS是一個很重要的工具。

現在(2015年)已經多了不少中文資源,大家可以參考ROS網站中文版。另外,最近同實驗室的同學黃昭霖也開始寫一些他自己的筆記、有一位朋友最近也開始寫ROS on Jetson的學習筆記、甚至還有朋友買了Turtlebot來玩並share自己的筆記XD 如果你需要問問題,去ROS Answers絕對是不二選擇(因為有很多ROS package的開發者會到上面回答跟自己package相關的問題),或者可以去這個中文社群另一個中文社群逛逛。

對學習ROS有興趣的朋友來說,能有多一些資源參考總是好的。希望之後有心做ROS的人學習愉快: D

———————————————–

ROS tutorials系列 (Beginner Level)

我的tutorial不詳細go through整個tutorial原文,而是就我認為重要之處詳加說明,而且有些東西原文寫得很清楚就不必重複寫了。換句話說,我仍預期你看下面這系列文章時要搭配原文看,才會比較完整。

0. 什麼是ROS?要怎麼使用ROS? (玩ROS前必看!我盡量寫得淺顯,不是工程師應該也看得懂XD)

1. 淺談ROS file system

2. 新增ROS package

3. 建立ROS packages

4. 了解ROS Node

5. 了解ROS Topics

6. 了解ROS Service跟Parameters

7. 使用rqt_console跟roslaunch

8. 使用rosed來編輯檔案

9. 建立自己的msg檔或srv檔

10.撰寫一個publisher跟subscriber(上手ROS Topic)

11.撰寫一個service跟client(上手ROS Service)

12.使用rosbag記錄和播放資料

13.使用roswtf來幫忙debug

14.探索ROS wiki

15.下一步是什麼?

———————————————–

Object Recognition Kitchen 系列

使用 Object Recogniton Kitchen 的 Linemod 演算法辨識物體

ecto 簡介 (1) – cell 與 plasm

ecto 簡介 (2) – tendrils 與 scheduler


ROS觀念文

用 DDS 開發 ROS 2.0

簡介CRAM(Cognitive Robot Abstract Machine)

簡介Knowrob (機器人知識處理的工具)

比較Topic, Service跟Actionlib

ROS Navigation stack 簡介

———————————————–

ROS實作細節文

Caffe & GoogLeNet,如何幫助機器人更好地辨識物體

使用 Gazebo 模擬器控制機器人建立 2D 地圖

改launch file中的參數值

launch file中的條件用法

安裝household object database

收到彩色影像,發布灰階影像topic的方法


ROS雜感

PR2開箱文

ROS Kong 2014照片集

ROS in DARPA Robotics Challenge!

———————————————–

補些關鍵字

ROS(Robot operating system), 機器人作業系統, 教學文章, 範例, 說明

Robotics, ROS

TB 專欄 – 如何用 ROS Topic 控制機器人移動

前言

筆者今年在 COSCUP 2016 開了一個工作坊,教大家 ROS 的基礎以及 Gazebo 的基本概念,因為是第一次辦工作坊,還在學習怎麼拿捏時間跟內容,自覺並不是講得很完整,所以希望可以再提供一個簡單的範例,讓大家對 topic 傳輸要怎麼應用在控制機器人上會更有概念。

在工作坊講的內容 – Node, Topic and Service

這份投影片裡面主要就是帶大家實際操作建立 package、寫 ROS Node、寫 ROS Topic 和 Service 傳輸資料的步驟,讓大家熟悉怎麼撰寫 ROS 程式,並帶到一點點使用 Gazebo 的基本概念,不過比較可惜的是沒有一個範例讓大家實際體會怎麼裡利用投影片裡教的機制來控制機器人,所以補充下面的章節,讓大家可以透過 Topic 簡單地控制機器人移動。

如何使用 Turtle_sim 模擬一隻可以接收 geometry_msgs/Twist 型態

接下來,來講一下怎麼透過 Topic 傳輸機制來控制 Turtlesim 裡面的機器人。首先讓我們啟動 turtlesim 的程式,使用下列指令:

roscore
rosrun turtlesim turtlesim_node

啟動 Turtlesim 後,就會看到一個 GUI 介面,裡面有一隻可愛的烏龜。

turtle_sim_1

在 ROS 的世界裡,大家通常都會透過傳送命令到 /cmd_vel 這個 topic來控制機器人的移動。 我們可以先用 rostopic list 確認是不是已經有 /cmd_vel 這個 topic 存在於系統裡,理論上得到的結果如下:

ros@ros-K401UB:~$ rostopic list
/rosout
/rosout_agg
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose

接下來就讓我們開始寫程式吧,首先建立一個 package 並開始寫後續的程式:

cd ~/catkin_ws/src
catkin_create_pkg coscup_follow_up rospy geometry_msgs
cd coscup_follow_up
vim src/circle_turtle.py

circle_turtle.py 的程式碼也很簡單,只是單純地送出一個 geometry_msgs/Twist 的 command,所以我們只要定期送出一個 command 就可以讓這隻可愛的烏龜移動了。


#!/usr/bin/env python
import rospy
from geometry_msgs.msg import Twist

def circle_walker():
 pub = rospy.Publisher('turtle1/cmd_vel', Twist, queue_size=10)
 rospy.init_node('cmd_publisher_node')
 rate = rospy.Rate(10) # 10hz

cmd = Twist()
 count = 0;

while not rospy.is_shutdown():
 cmd.linear.x = 1
 cmd.angular.z = 0.5

pub.publish(cmd)
 rate.sleep()

if __name__ == '__main__':
 try:
 circle_walker()
 except rospy.ROSInterruptException:
 pass

最後只要把程式跑起來,就可以看到結果了。

chmod +x src/circle_turtle.py
cd ../..
catkin_make
source devel/setup.bash
rosrun coscup_follow_up circle_turtle.py

turtle_sim_2

這邊要介紹一個簡單的機器人模型,也就是一般的兩輪機器人模型。在這種模型底下,其實你只能夠控制機器人的前後移動以及左右旋轉,所以程式中就只有指定 linear.x 跟 angular.z 的值 (因為就算其他值不給 0,也不會有任何反應)。

總結

這篇文章算是將 COSCUP 工作坊的內容再稍微延伸一下,讓大家比較有見樹又見林的感覺,不會寫了一堆 node ,卻都還沒有碰到機器人,雖然內容對於已經熟悉 ROS 的讀者不難,但希望透過這篇文章可以幫助更多想上手的讀者降低學習的難度。

ROS

TB 專欄 – 使用 Gazebo 模擬器控制機器人建立 2D 地圖

前言

ROS 很吸引人的一個地方在於,他跟模擬器 Gazebo 很好地結合在一起,讓使用者可以使用一台筆記型電腦就開始撰寫機器人程式,而且可以在筆電上看到程式執行的結果。模擬器裡的機器人不會撞壞、不會沒電,所以就算在程式設計過程中有錯誤,也不會有任何硬體成本的損失 (時間當然是會損失),而且很容易大量測試演算法,因為可以在模擬器中設置各種場景,而不需要一個很大的空間和昂貴的機器人。這篇文章會簡單介紹怎麼在 Gazebo 裡面啟動 PR2,並遙控他來建立一個 2D 的地圖。

啟動 Gazebo 跟 PR2

我的測試環境是 Ubuntu 14.04 + ROS Indigo,不過使用的指令都很 general,即便你使用其他版本,應該也不會有什麼問題。

首先我們確定已經安裝所需要的套件:

sudo apt-get install ros-indigo-gazebo-*

接下來在終端機輸入 gazebo 應該就可以成功啟動 Gazebo 模擬器。然後我們可以用現成的 package 啟動一個有 PR2 的空白模擬環境。

roslaunch pr2_gazebo pr2_empty_world.launch

Gazebo 裡面已經有很多現成的模型,選擇左邊的 Insert 頁籤,裡面就可以找到一些現成的模型,在這邊我先加入了 Cube 跟 Dumpster 的模型,加入後的結果如下圖:

1

對於機器人來說,最重要的第一個環節就是感知功能,他必須要可以感知環境中的資訊,才能做出相對應的動作。所以我們會先關心的就是 Gazebo 裡面的 PR2 機器人可不可以接收到環境中的資訊。

答案當然是可以的,我們可以透過 ROS 提供的 visualizer Rviz 來觀看 PR2 接收到的資訊,如果是已經熟悉 ROS 的讀者,想必對 Rviz 不陌生,只要開一個新的終端機,輸入 rviz 就可以啟動了。啟動之後,可以 Add 一個 Image 的顯示框,然後選擇現在可以看到的 topic,就可以看到 Gazebo 裡面的 PR2 看到的畫面。

rviz

啟動 gmapping package 建地圖

環境都已經啟動了,接下來就要開始建地圖啦! 這邊我們使用的工具是 gmapping ,首先我們在隨便一個路徑新增一個 launch 檔 –  vim pr2_build_map.launch 。

然後輸入這一串文字,儲存檔案後離開。

<launch>

  <!-- dynamic map generation -->
  <node name="gmapping_node" pkg="gmapping" type="slam_gmapping" respawn="false" >
    <remap to="base_scan" from="scan"/>
    <param name="odom_frame" value="odom_combined" />
  </node>

</launch>
然後可以用 roslaunch pr2_build_map.launch啟動。

啟動 gmapping 之後,我們還是透過 Rviz 來觀看地圖建立的狀態,這邊需要加入 Map 的顯示,然後 topic 選擇 /map 就好。地圖中的黑色線就是障礙物,淺灰色區域表示是可以走的無障礙物區域,深灰色則是未知區域。

rviz-2

讓我們近看一下 map 的 topic 要怎麼選:

rviz-3

但如果機器人不能移動的話,就只能顯示眼前看到的這一塊地圖。所以我們先使用簡單的遙控程式來控制 Gazebo 裡面的 PR2 移動。

這邊只要啟動 teleop_keyborad 就可以用鍵盤控制機器人移動:

roslaunch pr2_teleop teleop_keyboard.launch

WASD 四個按鍵分別代表前左後右四個方向的平移、QE兩個按鍵是原地旋轉,這邊要注意必須選到啟動 teleop_keyboard的視窗,按按鍵才有用。使用 teleop 來控制 PR2 走一走之後,就可以看得出我們已經使用 gmapping 建立了一個看起來有模有樣的地圖了。

2

如果你想要把地圖存起來,提供未來要做 Navigation 使用,或只是單純想留個紀念,可以用一個指令把地圖存起來 – rosrun map_server map_saver 。

 

總結

這篇文章跟大家簡單介紹怎麼使用 Gazebo 模擬器來模擬機器人,並利用他實際操作了一個小小的 SLAM 功能,也建出了自己的一張地圖。我們能利用 Gazebo 來做的事情當然遠不止於此,不過時間有限,我們下次見。

延伸閱讀

  1. gmapping 演算法原理簡介
  2. SDF 網站中文版
Observation

馬祖東引島 – 魚多多小吃店

這次來馬祖玩,有很多好笑的事情,不過這一篇要說的故事沒有很好笑、反而算是有點慚愧。

故事發生在東引島 downtown 的魚多多小吃店,這家店不是很起眼,離我們住的安逸旅店也只有10公尺,看起來只是一間普通的小吃店(因為招牌就只寫小吃店啊),所以我們也只是想進去吃個粗飽而已。

IMG_3839.JPG

進去之後,自然就是先看一下菜單(可惡…忘記拍菜單),果然…魚還滿多的,但因為實在有太多種菜了,我們實在不太知道怎麼點,而且又想多吃一點馬祖當地的海產,所以我們就請老闆幫我們配 3000 的合菜。

於是老闆就開始配了,他一開始說有一道叫作”夢幻魚”的很好吃,但是會比較貴,問我們要不要,這時我感覺在場至少有 5/7 的人都覺得他應該是想推銷我們貴的魚,但想說既然都來了,所以就點了下去。接著他又幫我們配了幾道海鮮跟熱炒類的食物,但是配到約莫有10道之後,他說這樣可能會到4000多,又讓我們有點小小的懷疑他是不是故意要配貴的 (因為一開始就已經說希望幫我們配3000的菜了,而且4000多平均起來一道要400多也好像有點不對勁),這時候大家的語氣就不是很友善 (也不到沒禮貌喇我覺得,但就是有想要把一些菜取消、趕快點到3000出頭,結束點菜活動的感覺)。總之後來經過一番抉擇,還是點了一桌3800的。

IMG_3835

但是,隨著菜一道一道的上來(上面這些並不是全部喔,有好幾道已經被收走了),我們發現菜都超大盤(原本想說可能是跟我們一般吃的熱炒一樣小盤、所以才覺得平均一盤400很貴)、老闆推薦的夢幻魚也真的超好吃。我們才開始發現,原來…心靈被汙染的是我們啊…我們很直覺地用都市人的眼光來看待其他人,以為老闆是要推銷我們。

後來跟老闆聊了一下,發現原來是夢幻魚那一條就要1000左右,而且可以感覺到他的出發點比較像是想推薦我們好吃的料理,雖然缺點是會比較貴,但也會有比較深刻的印象。他還拿天下雜誌的報導給我們看,想不到魚多多居然這麼猛,只好趕忙多叫老闆幾聲大大。

最後,慚愧的我,還是跟老闆拍了一張照,並且立下承諾說回來要寫一篇推薦文,希望老闆,可以從看到這篇文章去吃的客人身上間接感受到我們的誠意😄

IMG_3838

至於晚上去山猴叔叔的店,本來老闆娘想要關店、不太想理我們,但聽到我們是安逸旅店的客人,立馬叫我們坐下,讓老闆出來跟我們聊的故事,又是另一段佳話了XDD

(噢然後山猴叔叔的兒子居然是SBL球員宋宇軒!! 還好我偶爾還會看緯來體育新聞,因為山猴叔叔先問我有沒有打籃球,我說有之後他就立馬問我知不知道宋宇軒哈哈)

Robotics, ROS

ecto框架簡介(1) – cell 與 plasm

前言

這次想要跟大家介紹 ROS ORK (Object Recognition Kitchen) 這個函式庫實作物體辨識 pipeline 的機制,其背後運用到的一個重要的函式庫叫做 ecto ,接下來會介紹 ecto 的基本觀念和用法。但因為我想把內容講解得比較詳細,所以不會只花一篇文章的篇幅就介紹完這個工具,這一篇會講到最基本的 cell 與 plasm,讓大家先有初步的認識,更進階的用法甚至是實例會在之後的文章介紹。

ecto 是什麼 & 為什麼要使用 ecto

可以把 ecto 想成一個框架,這個框架可以讓你很方便地把程式用 DAG (Directed Acyclic Graph) 的方式來實作,這樣實作的兩大好處在於模組化跟彈性。

DAG的一個範例:

DAG

首先談到模組化,在 ecto 的世界裡,你首先可以用 C++ 或 Python 寫出一個個的 cell ,這個 cell 就是執行一個功能的單位 (所以命名為 cell,細胞的意思),這種設計的方式讓你在撰寫 cell 的時候,比較不會把好多個功能硬寫在一起,增加了程式的可讀性、也讓後續的擴充彈性變強。

模組化所衍生出的好處就是彈性,因為你只要抽換某個模組,就可以改變整個程式的行為。

以 ORK 為例,在撰寫物體辨識的 pipeline 時,假設有三個步驟: (這邊只是為了方便理解舉例,不是真實情況)

  1. 讀取 Kinect 影像
  2. 使用 Linemod 演算法進行 template matching
  3. 將辨識結果輸出

那用 ecto 實作就會寫成三個 cells,然後再建立一個 ecto 的 plasm,plasm 其實就只是 graph,把寫好的三個cell相連接。 所以,如果我想要實作另一個物體辨識的演算法,我只要改寫第二個 cell 就好,當然有個前提是兩個演算法的 input 和 output 要一致,才不會影響到第一個和第三個 cell。

基本中的基本 – Cells & Plasm 的簡單用法

為了讓大家有見樹又見林的感覺,我們先看一下 ecto 大致上要怎麼用。最簡單的用法大概就是建立兩個 cell ,再用一個 plasm 將這兩個 cell 串成 graph 。

現在我們只要先知道要寫一個 cell 需要在裡面定義四個函式:

static void
declare_params(tendrils&amp;)
static void
declare_io(const tendrils&amp;, tendrils&amp;, tendrils&amp;)
void
configure(const tendrils&amp;, const tendrils&amp;, const tendrils&amp;)
int
process(const tendrils&amp;, const tendrils&amp;)

這四個函式顧名思義就是要定義這個 cell 有哪些參數可以設定、輸入跟輸出是什麼、怎麼設定參數以及 cell 運作時的功能,很符合直覺上的需求。

那假設我們已經定義了兩個 cell – MyAwesomeCell1 跟 MyAwesomeCell2,ru, 剩下的就是建立一個 Plasm 來串接這兩個 cell 並執行,他的程式碼會像這樣:

#!/usr/bin/env python
import ecto
import my_awesome_cpp_ecto_module
import my_awesome_python_ecto_module

# create a plasm
plasm = ecto.Plasm()

# create some cells
cell1 = my_awesome_cpp_ecto_module.MyAwesomeCell1(param1=whatever1)
cell2 = my_awesome_python_ecto_module.MyAwesomeCell2(param2=whatever2)

# connect those cells in the plasm
plasm.connect(cell1['output'] &gt;&gt; cell2['input'])

# execute the graph
plasm.execute(niter=2)

重點其實只有 Plasm 的初始化、串接 cell 成 graph 與執行這三個部分:

# create a plasm
plasm = ecto.Plasm()

# connect those cells in the plasm
plasm.connect(cell1['output'] &gt;&gt; cell2['input'])

# execute the graph
plasm.execute(niter=2)

Cell 的機制詳解

從上面的例子,大家應該可以明顯的看出,plasm 因為只是要串連寫好的 cell ,所以設定相對單純(目前我們先不討論 scheduling 等複雜的狀況),但 cell 就不太一樣,上面只提到需要寫四個函式,卻沒有實例讓大家了解怎麼實作,接下來就會介紹比較實際的例子,讓大家了解 cell 要怎麼寫。

首先我們看個簡單的例子,這個 class 是一個 Printer ,我們在產生這個 Printer 的 instance 時,可以設定裡面的兩個 data member – prefix_ 和 suffix_ (或可以理解成參數)

struct Printer
{
  Printer(const std::string&amp; prefix, const std::string&amp; suffix)
      :
        prefix_(prefix),
        suffix_(suffix)
  {
  }
  void
  operator()(std::ostream&amp; out, const std::string&amp; message)
  {
    out &lt;&lt; prefix_ &lt;&lt; message &lt;&lt; suffix_;
  }
  std::string prefix_, suffix_;
};

假設要改寫成 ecto 的 cell,首先我們來宣告參數,透過 declare_params 這個函式可以做到,在這個例子裡,只是先單純宣告有兩個 params,提供這兩個參數的說明(這個說明可以用來自動生成文件,不過我們先忽略),以及預設值。

大家可能會疑惑的地方是,params.declare 這個用法怎麼突然就跑出來了。這是因為在 ecto 裡面,cell 之間的溝通是透過 tendrils 這個類別來處理,但這一篇先不提到 tendrils 的細節,所以才會有點混亂,不過如果去看 tendrils 的 API,就會清楚這中間是怎麼一回事。

static void declare_params(tendrils&amp; params)
{
  params.declare&lt;std::string&gt;(&quot;prefix&quot;, &quot;A string to prefix printing with.&quot;, &quot;start&gt;&gt; &quot;);
  params.declare&lt;std::string&gt;(&quot;suffix&quot;, &quot;A string to append printing with.&quot;, &quot; &lt;&lt;stop\n&quot;);
}

 

接著我們來定義 IO 的介面,透過 declare_io 來做,因為這個 cell 只需要接收需要印出的 message ,所以只需要宣告一個 input ,不需要宣告 output 。

static void
declare_io(const tendrils&amp; params, tendrils&amp; inputs, tendrils&amp; outputs)
{
  inputs.declare&lt;std::string&gt;(&quot;message&quot;, &quot;The message to print.&quot;);
}

目前我們已經指定了對外的兩個重點 – 有哪些參數以及 IO 介面。接著該考慮內部使用的設定了,所以第一步是將 declare_params 裡面宣告的參數 (此例中是 prefix 跟 suffix) 跟類別裡面的 data member (此例中是 prefix_ 跟 suffix_) 連接。

void
configure(const tendrils&amp; params, const tendrils&amp; inputs, const tendrils&amp; outputs)
{
  params[&quot;prefix&quot;] &gt;&gt; prefix_;
  params[&quot;suffix&quot;] &gt;&gt; suffix_;
}

最後的重點就是,實作這個 cell 的功能,我們要把實際做的事情寫在 process 這個函式裡面。

int
process(const tendrils&amp; inputs, const tendrils&amp; outputs)
    {
      std::cout &lt;&lt; prefix_ &lt;&lt; inputs.get&lt;std::string&gt;(&quot;message&quot;) &lt;&lt; suffix_;
      return ecto::OK;
    }

所以如果把四個函式合起來看,就會像這樣:

#include &lt;ecto/ecto.hpp&gt;
#include &lt;ecto/registry.hpp&gt;
#include &lt;iostream&gt;
#include &lt;string&gt;;
using ecto::tendrils;
namespace overview
{
  struct Printer01
  {
    static void
    declare_params(tendrils&amp;amp;amp;amp; params)
    {
      params.declare&lt;std::string&gt;(&quot;prefix&quot;, &quot;A string to prefix printing with.&quot;, &quot;start&gt;&gt; &quot;);
      params.declare&lt;std::string&gt;(&quot;suffix&quot;, &quot;A string to append printing with.&quot;, &quot; &lt;&lt;stop\n&quot;);
    }
    static void
    declare_io(const tendrils&amp; params, tendrils&amp; inputs, tendrils&amp; outputs)
    {
      inputs.declare&lt;std::string&gt;(&quot;message&quot;, &quot;The message to print.&quot;);
    }
    void
    configure(const tendrils&amp; params, const tendrils&amp; inputs, const tendrils&amp; outputs)
    {
      params[&quot;prefix&quot;] &gt;&gt; prefix_;
      params[&quot;suffix&quot;] &gt;&gt; suffix_;
    }
    int
    process(const tendrils&amp;amp;amp;amp; inputs, const tendrils&amp;amp;amp;amp; outputs)
    {
      std::cout &lt;&lt; prefix_ &lt;&lt; inputs.get&lt;std::string&gt;(&quot;message&quot;) &lt;&lt; suffix_;
      return ecto::OK;
    }
    std::string prefix_, suffix_;
  };
}
ECTO_CELL(ecto_overview, overview::Printer01, &quot;Printer01&quot;,
          &quot;A simple stdout printer with prefix and suffix parameters.&quot;);

雖然整個類別被寫成很長,但其實只要熟悉 cell 的基本用法,就不會被這一堆程式碼嚇到。最後想跟大加補充說明一下,上面提到的 cell 寫法有個不直覺的地方是,沒有明顯的繼承關係,所以會覺得不符合我們的 cell 應該要繼承一個 base 的 cell class 的直覺。其實這邊是被 ECTO_CELL 這個 MACRO 給處理掉了,所以才會看起來只有宣告幾個函式就寫完一個 cell 的感覺。

總結

這篇文章簡介了 ecto 的 cell 和 plasm,下一篇將會介紹 tendril 跟 scheduler 的機制,幫助大家更加理解 ecto ,並在未來能運用這個框架來建立自己的應用。

延伸閱讀

  1. When to use DAG (Directed Acyclic Graph) in programming?
  2. ecto 官方網頁的 plasm 介紹
  3. ecto 官方網頁的 cell 詳細介紹
Robotics, ROS

TechBridge專欄 – 使用Object Recogniton Kitchen的Linemod演算法辨識物體

前言

這次要介紹的工具是 ROS 裡面專門用來作物體辨識的 Object Recognition Kitchen (以下簡稱為 ORK ),這個工具比較像是一個框架,裡面包含了好幾種演算法(你可以根據自己的需求使用不同的演算法),這篇文章要介紹的只是其中一種 – Linemod 。 Linemod 是一個辨識效果很不錯的 3D 物體辨識演算法(不過一個使用條件是物體須是剛體),所以想藉此機會分享給大家,以後只要你想要,就可以直接拿這個工具來串自己的機器人應用。

安裝 ORK & Linemod

雖然 ORK 的開發者已經寫了一份滿不錯的安裝tutorial,不過我覺得還是值得為他再寫一份自己的整理筆記,可以把過程中遇到的一些問題都整理下來供大家參考。

我目前跑起來的環境是 Ubuntu 14.04+ROS Indigo,首先來裝一些 ORK 需要用到的 package !

export DISTRO=indigo
sudo apt-get install libopenni-dev ros-${DISTRO}-catkin ros-${DISTRO}-ecto* ros-${DISTRO}-opencv-candidate ros-${DISTRO}-moveit-msgs

接下來就要安裝 ORK 啦,然後因為我比較喜歡 build from source,所以我會下面會放上一份複雜版的安裝方法,裡面會有比較多跟 error 奮鬥的過程,如果你比較喜歡直接玩應用,安裝什麼的懶得管,那看簡單版的安裝方式其實就可以了。

I.超簡單版安裝方式

超簡單版顧名思義就是超簡單,完全不要管我們會用到哪些package,只要是 ORK 底下的 package,都裝下去,缺點就是會多浪費一些硬碟空間。只要用一行指令就搞定:

sudo apt-get install ros-indigo-object-recognition-kitchen-*

I.稍微理解自己裝了什麼的安裝方式

sudo apt-get install ros-indigo-object-recognition-core ros-indigo-object-recognition-linemod ros-indigo-object-recognition-msgs ros-indigo-object-recognition-renderer ros-indigo-object-recognition-ros ros-indigo-object-recognition-ros-visualization

I.Build From Source版安裝方式

首先來先裝跟 ROS 銜接的 package,首先要先開啟 terminal,切到 catkin_workspace/src 底下

git clone http://github.com/wg-perception/object_recognition_msgs
git clone http://github.com/wg-perception/object_recognition_ros
git clone http://github.com/wg-perception/object_recognition_ros_visualization

然後因為今天的主角是 linemod,所以需要安裝相關 package

git clone http://github.com/wg-perception/object_recognition_core
git clone http://github.com/wg-perception/linemod
git clone http://github.com/wg-perception/ork_renderer
cd ../ && catkin_make

在 catkin_make 的過程中可能會碰到因 error 而中斷,會看到類似如下的訊息:

error-1

這時候不要怕,繼續給他 catkin_make 下去,你就會發現編譯進度會神奇地有進展:

error-2

不過我有碰到一個問題,再怎麼重新編譯都沒有用:

In file included from /home/rosindigo/catkin_ws/src/ork_renderer/src/renderer3d.cpp:50:0:
/home/rosindigo/catkin_ws/src/ork_renderer/src/renderer3d_impl_osmesa.h:39:23: fatal error: GL/osmesa.h: 沒有此一檔案或目錄
 #include <GL/osmesa.h>
 ^
compilation terminated.
make[2]: *** [ork_renderer/src/CMakeFiles/object_recognition_renderer_3d.dir/renderer3d.cpp.o] Error 1
make[1]: *** [ork_renderer/src/CMakeFiles/object_recognition_renderer_3d.dir/all] Error 2
make: *** [all] Error 2
Invoking "make -j8 -l8" failed

因為是少了 GL/osmesa.h,所以需要額外下一個指令 sudo apt-get install libosmesa6-dev來安裝。

裝到這邊還會有一個問題,就是雖然編譯都已經過了,但 roscd object_recognition_core 時都會出現無法找到此 package 的 error,由於這會對後續要執行演算法時造成問題,所以還是要來處理一下。

我們先重新理一下思路,理論上,編譯過之後就會被加入到 roscd 可以找到的 path 中,但是 roscd 顯然就表示沒有被加進  ROS_PACKAGE_PATH 中,google 了一下發現到有人也遇過類似的問題,解法也確實就是把我們 git clone 下來的那些 package 加入到 ROS_PACKAGE_PATH 中,這樣的話問題就簡單啦!

先 vim ~/.bashrc一下,然後在最下面補上一行:

export ROS_PACKAGE_PATH="$ROS_PACKAGE_PATH:/home/rosindigo/catkin_ws/src"

補完的結果看起來就像:

bashrc

接上並從 RGB-D Sensor 收資料

接下來我們要先處理感測器這塊,畢竟如果沒有感測器,那就根本不用辨識物體的對吧。原本官方教學上是建議用 roslaunch openni2_launch openni2.launch,但我跑的時候一直碰到錯誤,後來是用 freenect 的 launch 檔才成功,大家可以試試用 roslaunch freenect_launch freenect.launch

跑起來之後應該就可以在 Rviz 看到以下的畫面:

kinect

安裝物體辨識資料庫 & 加入 model 

ORK 是一套以 template matching 方法為主的辨識工具,也就是說,他會把感測器看到的物體跟資料庫中的物體比對,夠相似的就算是辨識成功,所以我們接著要來處理資料庫這一塊。首先要安裝 CouchDB 這個工具 (用 sudo apt-get install couchdb )。

接下來檢查一下是否有安裝成功 (用 curl -X GET http://localhost:5984 )。

couchdb

安裝成功之後,你就已經擁有一個 local 的資料庫了,現在要做的事情是加入一個 model。對於 ORK 來說,他需要的是物體名稱、物體的 3D 模型等資訊,就讓我們先加一個叫做 coke 的模型。

rosrun object_recognition_core object_add.py -n "coke " -d "A universal can of coke" --commit

執行上面這個指令之後,你可以去 http://localhost:5984/_utils/database.html?object_recognition/_design/objects/_view/by_object_name 看看自己的資料庫裡是否已經新增了這個物體:

object_id

接下來就是要指定這個物體的 3D 模型是什麼,這邊就需要先下載個 package 並編譯。

git clone https://github.com/wg-perception/ork_tutorials

ork_tutorials裡面有一個 coke.stl 檔,他就是一個可樂罐的 3D 模型,足夠讓我們先用來辨識。

rosrun object_recognition_core mesh_add.py 0be612246c9b0a00baaa4adefb0009eb /home/rosindigo/catkin_ws/src/ork_tutorials/data/coke.stl --commit

 

執行 Linemod 演算法 (Training & Detection)

好了!終於要進入正題了,這一塊一樣有 官方tutorial 可以參考,我們先從 Training 開始。

rosrun object_recognition_core training -c `rospack find object_recognition_linemod`/conf/training.ork

這個 training 指令會利用資料庫裡的 3D 模型建立辨識時所需要的 template,如果執行成功,你會看到如下的訊息:

training

如果已經訓練完畢,下一步就是用他來辨識物體啦。

演算法簡介

既然 Linemod 是一個這麼強大的演算法,試著去稍微了解一下演算法也是很合理的,原始的論文在這邊 (2011 ICCV 的 oral 論文),以下的圖片也是出自這篇論文。

首先來看一下這篇論文的辨識結果:

1

這個演算法的核心概念就是整合多種不同的 modalities,把 modality 想成物體的不同特徵可能比較好懂,例如下圖中就有兩種 modalities – gradient 跟 surface normal,而因為這兩種特徵所表達的特性不一樣,所以可以互補,進而達到更好的辨識效果。

modalities.jpg

所以說,Linemod 需要先有已知的物體模型,然後先取得這個物體各種 modlaities 的template,這樣在辨識的時候就可以拿 template 來比對。

不過這概念在電腦視覺領域中並不特別,因為同時使用不同特徵來加強物體辨識的效果是很直覺的,也有很多不同的方法可以做到這件事情,所以這篇論文還提出了一個方法來增進 similarity measurement 的效率 ( similarity measurement 的意思是 measure 儲存的 template 跟現在看到的影像資料有多接近)。

總結

這篇文章很簡略地介紹了 ORK 的安裝、基本的使用方式(使用 Linemod )、還有演算法簡介,有興趣的讀者可以自己動手玩玩看 (如果沒有實體的 RGB-D sensor,你也可以試著用 Gazebo 裡面的 Kinect 來模擬)!

延伸閱讀

  1. 使用 ORK 的 tabletop 演算法
  2. 建立自己的 ORK 模型
  3. ORK 開發者介紹演講
  4. Linemod的OpenCV實作
ROS

Techbridge專欄 – 用DDS開發ROS 2.0?

很快地就過了一個月了,很快地又要來寫TB Weekly專欄啦!

前言

這週想要向大家介紹ROS 2.0的底層實作概念,雖然比較不會有程式實作的討論,但我覺得這一塊的深度滿值得介紹的,因為使用ROS有好幾種層次:

  1. 使用ROS的各種工具來建立自己的應用
  2. 在開發上碰到一些問題,修改現成的package來滿足自己的需求
  3. 開發自己的演算法,發布自己的package給其他人使用
  4. 協助開發與維護ROS的核心

這篇文章要討論的議題已經落在第四個層次,所以對於一般的使用者來說,可能不太具有直接應用的價值,但如果對於ROS的底層實作有更深入的理解(知道他是怎麼開發出來的、有哪些限制、有哪些優點),就可以在利用ROS撰寫自己的應用時,更能開發出效能最佳化的應用。

為什麼要開發ROS 2.0?

事實上,如果ROS 1.0 已經足夠完美,那我們就沒有必要討論ROS 2.0。不過事情當然不是這樣,因為ROS 1.0在開發的時候,是圍繞著一隻機器人來開發的,雖然當初的設計已經讓ROS變得很有彈性,可以被應用在各式各樣的機器人上,但是隨著使用者越來越多,超乎開發者想像的使用情境也越來越多。

也就是說,如果開發者們不積極地開始開發下一代的ROS,遲早會無法滿足越來越複雜而多樣化的需求。對於這些使用情境的具體描述,可以參考這裡

開發ROS系統需要實現的模塊

首先來談論一下建立整個系統上,需要考慮的幾個重點:

  • Discovery功能
  • Publisher-Subscriber功能
  • Service 與 Action功能

Discovery功能的意義是,只要有新的node啟動,就能在整個ROS node的網路中被看見(概念很像是我打開手機的wifi熱點分享,其他裝置就應該要可以發現有這個wifi熱點)。

接下來的Publisher-Subscriber功能、Service功能跟Action功能其實就是ROS使用者熟悉的Topic、Service跟Actionlib啦,本質上這幾種功能在處理的都是node之間的溝通(也就是程式之間的溝通,大家可以想像要讓一隻機器人正常運作,電腦上需要運行的程式一定是很多個,而且需要彼此溝通,所以底層的溝通機制需要有人來實作,不然就是…想開發機器人程式的你得自己實作)。如果你不太確定自己知不知道我在說什麼,可以看看這一篇區分Topic、Service跟Actionlib的文章

DDS的系統層概念

想要實作上面這些功能,DDS並不是唯一的選擇,但是,OSRF的開發者經過嘗試之後,覺得這是最好的開發選項。細節理由可以看延伸閱讀的第3篇文章,這部分已經有中文翻譯了。

api_levels.png

從上面這張圖可以清楚地看出,使用者所需要接觸到的只有最上面的兩層。使用者自己寫的code就屬於Userland Code,而使用者自己寫的code中呼叫到的ROS API (例如ros::init())就屬於ROS client library API,而DDS的API則是在更底層被ROS client library API所使用。

有趣的地方是,為了保持彈性,OSRF的開發者們希望使用者可以自己選擇底層使用的是哪一個版本的DDS (DDS像是一種標準,所以可以有不同公司提供自己的實作版本)。

一點細節的延伸

上面討論的都是概念的理解,對於技術有興趣的你想必沒辦法接受,所以就讓我們來看一點技術細節吧!

我們還是一樣先站在開發者的角度,最基本我們需要提供的工具就是Node初始化的函式對吧,這個函式的長相就像:


Node::Node(std::string name): running_(true)
{
 /*----------------------親切的中文註解來囉!!!----------------------*/
 nodes_.push_back(this);
 subscription_iterator_ = subscriptions_.end();
 name_ = name;
 //取得了DDS的DomainParticipantFactory的instance,很像是一個node產生器的感覺
 dpf_ = DDS::DomainParticipantFactory::get_instance();
 checkHandle(dpf_.in(), "DDS::DomainParticipantFactory::get_instance");
 DDS::DomainId_t domain = DDS::DOMAIN_ID_DEFAULT;

 //實際產生一個participant,應該就是一個node
 participant_ = create_participant( domain, PARTICIPANT_QOS_DEFAULT, NULL,DDS::STATUS_MASK_NONE);
 checkHandle(participant_.in(), "DDS::DomainParticipantFactory::create_participant");
 /*----------------------看到這裡就好囉!!!----------------------*/

 // Create the default QoS for Topics
 DDS::ReturnCode_t status = participant_get_default_topic_qos(default_topic_qos_);
 checkStatus(status, "DDS::DomainParticipant::get_default_topic_qos");
 default_topic_qos_.reliability.kind = DDS::BEST_EFFORT_RELIABILITY_QOS;

 // Create the default QoS for Publishers
 status = participant_get_default_publisher_qos(default_publisher_qos_);
 checkStatus(status, "DDS::DomainParticipant::get_default_publisher_qos");
 default_publisher_qos_.partition.name.length(1);
 default_publisher_qos_.partition.name[0] = "ros_partition";

 // Create the default QoS for Subscribers
 status = participant_get_default_subscriber_qos(default_subscriber_qos_);
 checkStatus(status, "DDS::DomainParticipant::get_default_publisher_qos");
 default_subscriber_qos_.partition.name.length(1);
 default_subscriber_qos_.partition.name[0] = "ros_partition";

 // Create a waitset for spin
 waitset_ = new DDS::WaitSet();

 // Create a parameter server for this node
 create_parameter_server(name);
}

大家先不要嚇到,一下有太多細節本來就不可能看懂,大家只需要看我用註解標記起來的區域,體驗一下什麼叫做ROS client library API呼叫DDS API的感覺就好。

那對於一個使用者來說,假設我今天要啟動一隻機器人,那就需要呼叫建立node的函式,看起來就像:


TurtleApp(int& argc, char** argv): QApplication(argc, argv)
{
rclcpp::init(argc, argv);
nh_ = rclcpp::create_node("turtlesim");
}

你一定覺得奇怪,rclcpp::create_node()跟上面我講的Node::Node()建構子根本接不起來啊? 所以這邊就要補上一點點程式碼,想必你就懂了:


void rclcpp::init(int argc, char** argv)
{
if (globally_initialized)
{
throw AlreadyInitializedError();
}
/* Register a signal handler so DDS doesn't just sit there... */
if (signal(SIGINT, Node::static_signal_handler) == SIG_ERR)
{
fputs("An error occurred while setting a signal handler.\n", stderr);
}
globally_initialized = true;
}

Node::Ptr rclcpp::create_node(const std::string &name)
{
return Node::Ptr(new Node(name));
}

OK!簡介就到這邊啦,如果對於實作細節有興趣深入的讀者,不妨去看看ROS 2.0的github repo,詳細的程式碼全部都是開源的,所以可以從中學習開發的細節。

延伸閱讀:

  1. 為什麼要開發ROS 2.0?
  2. 使用ZeroMQ跟相關的函式庫來開發ROS
  3. 使用DDS來開發ROS (仍在趕工中,歡迎開issue催促翻譯者QQ)
  4. ROS 2.0 wiki
  5. ROS DDS Prototype (Github Repo)
Practical

數位濾波器的學習筆記

因為濾波器的應用實在太廣了,趁著有空之餘,來學習一些基礎知識,順便將自己的筆記分享出來。

首先談一下濾波器的基本觀念。如果大家仔細觀察,就會發現,在我們的世界裡,訊號是無所不在的,你現在看到的這段文章就是透過光的訊號傳遞、你說的話是聲音訊號、你上網是透過網路訊號,這些訊號是濾波器想要處理的對象,所以你可以想像濾波器是一個黑盒子,吃進訊號、輸出訊號。而濾波器這個黑盒子一定有做一些事情,讓輸出的訊號比輸入的訊號”好”,不然就不需要這個濾波器了,所以接下來要談談到底會怎麼個變好法。

數位濾波器分成兩種類型 – IIR (Infinite Impulse Response) 跟 FIR (Finite Impulse Response)。

首先來看一下相對簡單的FIR,推薦一個youtube上的影片,裡面有一張圖片滿清晰地說明了FIR的基本觀念:

fir

左上方的訊號就是原始的訊號,右邊是我們希望可以取出的訊號,如果說左邊訊號中所顯示的一些凹陷是由雜訊造成的,那右邊的訊號相對起來就是比較好的,因為不受雜訊干擾。從理論上比較簡單的想法是先做傅立葉轉換得到頻率域的成分,接著套一個low-pass filter就可以把主要的sine成分取出來,最後再做反轉換得到右邊的訊號。而FIR在做的事就可以達到跟以上過程一樣的效果。

FIR的作法在上圖的左下角呈現,在這個例子中,一次處理訊號中的4個點,每個都乘上一個係數f,最後再加總起來,得到一個新的訊號點輸出。所以可以想像成我吃進4的原始訊號的點,只對應到右邊輸出訊號的一個點而已。

看到這邊,你應該就可以發現FIR設計的兩個重點:

  1. 我要使用幾個輸入訊號點來產生一個輸出訊號點?
  2. 我的係數f要怎麼設計?

這個部分就看你的應用中想要用濾波器來做什麼事情,可以自行調整這兩個變數來測試濾出來的訊號是否符合你的要求。

以上是layman’s term的講法,幫助理解最簡單的概念,如果你想繼續深入,可以來看看理論的說明(來自陽明大學盧家鋒老師的教學影片,講得很清楚)。

接下來看一下IIR,推薦這部影片,裡面畫了一張圖清晰地把IIR的概念呈現出來:

cmpr.jpg

看了上圖應該可以比較 清楚的比較出,FIR用來產出輸出訊號的”原料”只有輸入訊號;而IIR用來產生輸出訊號的原料除了輸入訊號之外、還包含了更早之前的輸出訊號。

最後附上幾張用比較數學的方式來呈現FIR跟IIR概念的圖結束這回合: (來自盧家鋒老師的投影片)

io

上面這張表示我們只需要設計a和b這兩組係數,就可以設計出一個濾波器,而這個濾波器在時間域對訊號的作用方式由第二個式子表示。

firr

FIR就只用到b這組係數,由時間域的式子可以看出,產生一個訊號的輸出點,只會用到K個輸入訊號點,所以這也是為什麼被稱作”有限”的原因。

iir

IIR會用到a的係數,所以輸出的訊號點y(n)還會包含到前面的輸出訊號點y(n-l),會持續用到整段訊號,這種不斷使用到前面訊號的特性,有一種不斷輪迴的感覺,所以被稱作”無限”。