Robotics, ROS

TB專欄 – Object Recognition Kitchen 透明物體辨識(演算法概念篇)

前言

這次的文章想要討論一個有趣的題目 – 透明物體辨識,這次的介紹先把題目限定在找出透明物體的位置,並把透明物體的輪廓找出來。

演算法功能簡介

我們的演算法目的是要找出影像中的透明物體,並把輪廓圈出來,就像下面這張圖一樣。

glasssegmentation

其實如果要更精確,應該把想要辨識哪些透明物體、在那些場景、辨識成功率希望有多高、如何定義辨識成功等都說清楚,不過這邊想先帶給大家一個初步的概念,就不討論得太過瑣碎。

演算法概念

為了達成這個功能,我們勢必要先收到彩色影像,所以肯定需要一台相機。不過下一個問題是,我們只需要用彩色影像資訊就能夠抓到透明物體的輪廓嗎? 還是我們需要其他的資訊 (也就是要不要考慮使用其他的 sensor) ?

要回答上面的問題,首先要來想想這個問題會什麼會困難。直覺上應該很容易發現,透明物體的顏色會隨著背景顏色不同而變,所以一般用顏色、紋理特徵來辨識的演算法都不太適用;此外,因為透明物體的邊界常常是模糊不清的,所以跟旁邊的物體很容易混在一起,造成輪廓抓錯。所以核心的問題就是,除了顏色、邊緣這些常用的特徵,我們還可以用什麼資訊來抓透明物體? 有一篇論文提出一個滿有趣的做法,他是使用 Kinect 的深度資訊,而且利用透明物體在 Kinect 的深度圖裡面會有破洞的特性,當成一個辨識的起點:

這個概念主要來自這篇論文 – Recognition and Pose Estimation of Rigid Transparent Objects with a Kinect Sensor,他提出的做法是,利用深度圖裡面有大塊破洞的位置,猜測這塊位置是透明物體,接著再根據這個線索找出透明物體的輪廓。

所以下一個問題就是,怎麼根據這個粗略的位置,把透明物體的輪廓切出來,論文中使用的方法是 Grabcut 演算法,這個演算法需要先有一個初始的前景跟背景線段,然後根據這個線段區分出前景跟背景,例如在下圖中,紅色線段是前景、藍色線段是背景,接著 Grabcut 演算法會根據顏色的近似程度還有距離關係,算出綠色框框內那些是前景、哪些是背景,然後把前景切出來。

1.PNG

切出來之後,目的就算是達成了!當然後續還有很多問題待討論(例如背景的複雜度、怎麼對深度影像中的破碎點做一些基本的處理等),不過今天先把範圍限縮在最上層的概念。

總結

演算法的應用真的是千奇百怪,但不管是簡單的演算法題目還是困難的演算法題目,解決的方法都是要看出問題的本質,然後再根據這個問題的特性去使用特定的步驟來解決。像這個範例就是巧妙地利用 Kinect 特性來處理透明物體的特徵很難抓的這個問題,畢竟會在 Kinect 的深度圖中固定呈現出破洞也算是一種特徵,至於更詳細的演算法流程或是程式碼的部分,就留待未來再介紹囉!

延伸閱讀

  1. 演算法介紹投影片
  2. ORK 的 transparent object recognition page
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 的讀者不難,但希望透過這篇文章可以幫助更多想上手的讀者降低學習的難度。

Robotics

pepper robot 程式開發的幾種選擇

這幾天剛好看了一點資料,因為一開始覺得有點混亂(表示這個生態系有前途啊),所以想要整理一下,如果想要開發 pepper 的 app,到底有哪些選擇。先簡單的條列出來:

  1. 寫 ROS 程式
  2. 用 Choregraph 開發可以在 pepper 上運行的程式
  3. 串接 Bluemix 開發在雲端平台上運行的程式
  4. 在電腦上用 C++ 或 Python Hardcore 開發
  5. 用 Android Studio 開發

接下來簡略地說明一下各種開發的方法跟入門資源:

寫 ROS 程式

如果是原本就熟悉 ROS 開發的使用者,可以先參考一下 ROS wiki 上面的 pepper 頁面,可以對於支援 pepper 的 package 有個總覽。如果考慮到使用上,有三種使用的情況,可以參考這個連結

對於一般的使用者來說,可以先用最簡單的方法,把 pepper 開機之後,取得 pepper 的 IP 位址,然後下個指令就可以啟動 pepper 的所有感測器跟控制器了:

roslaunch pepper_bringup pepper_full.launch nao_ip:=<yourRobotIP> roscore_ip:=<roscore_ip> [network_interface:=<eth0|wlan0|vpn0>]

詳細的步驟可以參考這個頁面,後續的 tutorial 其實可以參考 Nao 的  (反正都是跟 Naoqi 溝通)。

用 Choregraph 開發可以在 pepper 上運行的程式

Choregraph 是 Aldebaran 公司 (就是開發出 Nao 跟 Pepper 的公司) 開發出來的圖形化開發介面。大概是長這樣:

choregraph.jpg

一開始比較會有問題的應該會是序號的申請,後續的使用就可以去看官方的教學文件啦!

串接 Bluemix 開發在雲端平台上運行的程式

這個做法我也沒有用過,但基本的概念大概就是,把 pepper 變成一個純粹的終端裝置。當 pepper 接收到環境的資訊之後 (透過相機、麥克風等等), 就把資訊上傳到雲端平台,然後雲端平台上面的程式決定 pepper 的行為,再把這個行為回傳給 pepper,讓 pepper 做出行為。

用這個方法的好處是可以用 javascript 開發,而且 Bluemix 雲端平台好像提供了很多很厲害很棒棒的功能,聽說開發起來特別猛。

這邊有一個好簡單的範例給大家參考,還有 Humix 也是一個例子。

在電腦上用 C++ 或 Python hardcore 開發

很久以前為了帶實驗室的專題生,有小小研究一下控制 Nao 的程式怎麼寫,那時候的寫法都是直接在電腦上寫 python 腳本,只要知道 Nao (或 pepper) 的 IP 位址,就可以透過網路讓自己寫的程式在 Nao 上面執行。附上一個簡單的例子 (say.py),在這個例子中你可以看到我直接把 IP 位址寫死:


# -*- coding: utf-8 -*-
import naoqi
from naoqi import *

tts = ALProxy(ALTextToSpeech, 192.168.1.102, 9559)
tts.setLanguage('Chinese')
tts.say(你好uot;)

執行的方法就直接在自己的電腦上的終端機打 python say.py 執行就可以了,上手超快,不過我沒有開發過更大的系統,感覺這個方法可能會有比較多問題。

用 Android Studio 開發

這個我就沒什麼概念了,雖然以前有寫過一點點 android app,不過已經都忘光了,有興趣的讀者可以看看這個教學

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實作
Robotics, ROS

Caffe & GoogLeNet,及其在機器人上的應用

這週的TB Weekly技術週刊會開始一個全新的嘗試,我們會開始產生正體中文的技術專欄(編輯群輪流囉XD),希望可以藉這個project分享更多優質的技術內容給大家,目前暫時先把內容寫在這邊,晚點再port到統一的專欄發表平台上。

首先介紹一下這次要談的內容,是有關使用深度學習的模型來做物體辨識的應用。深度學習在這幾年來變得很火紅,相關的框架也相當多,這次之所以想談caffe,是因為已經有現成的方法可以將它應用到機器人上面。(有位台灣的開發者弄了一個叫做ros_caffe的package來串接ROS(機器人作業系統)跟Caffe,可以將Caffe辨識的結果丟到一個ROS的topic,其他的node就可以自己取用。這使得機器人辨識物體的能力得以大幅增加)

基本的安裝方法可以參考這個連結,假設已經裝成功,那至少就已經有基本的環境可以用(有一個caffe的資料夾被放在你安裝的路徑),接下來需要下載GoogLeNet的model,只要用caffe/scripts資料夾裡的程式幫忙就行了:

$./scripts/download_model_binary.py ./models/bvlc_googlenet

假設已經下載好model,接下來就可以用一個小程式來跑跑看GoogLeNet了:


import numpy as np
import matplotlib.pyplot as plt

# Make sure that caffe is on the python path:
caffe_root = '../' # this file is expected to be in {caffe_root}/examples
import sys
sys.path.insert(0, caffe_root + 'python')
sys.path.append(&amp;quot;/usr/lib/python2.7/dist-packages/&amp;quot;)

import caffe

# Set the right path to your model definition file, pretrained model weights,
# and the image you would like to classify.
MODEL_FILE = '../models/bvlc_googlenet/deploy.prototxt'
PRETRAINED = '../models/bvlc_googlenet/bvlc_googlenet.caffemodel'
IMAGE_FILE = 'images/cat.jpg'

caffe.set_mode_cpu()
net = caffe.Classifier(MODEL_FILE, PRETRAINED,
 mean=np.load(caffe_root + 'python/caffe/imagenet/ilsvrc_2012_mean.npy').mean(1).mean(1),
 channel_swap=(2,1,0),
 raw_scale=255,
 image_dims=(256, 256))

input_image = caffe.io.load_image(IMAGE_FILE)
plt.imshow(input_image)
plt.show()

prediction = net.predict([input_image])
plt.plot(prediction[0])
plt.show()
print 'predicted class:', prediction[0].argmax()

接下來只要執行(因為程式放在examples資料夾底下):

$python ./examples/googlenet_example.py

就可以看到一隻貓的影像,關掉影像之後就會看到貓的類別被輸出在terminal。

到目前為止算是驗證了可以跑起GoogLeNet。接下來,如果想往下跟ros_caffe的串接可以參考外國鄉民的文章,裡面有完整而詳細的步驟。如果你已經安裝過caffe,可以參考這個issue。另外,需要注意的是,外國鄉民跑的只有global的結果,也就是一張影像中只有一個最顯著的物體會被辨識,如果要辨識一張影像中的各個物體,可能就要自己在中間串接一個負責做segmentation的node,再把各個切出來的區塊餵給ros_caffe來做辨識。

 

Robotics

TensorFlow課程筆記-Assignment 1

作業一-Not MNIST,是要練習把目前學到的Logistic Regression套到notMNIST這個dataset來得到一個可以辨識英文字母的model。

先放個圖當作目前學到概念的整理:

1

因為現在手邊只有一台小筆電,OS是32-bit的Win7,只能考慮用docker,但看了一下安裝方法發現Windows要裝docker得是64-bit,好的那今天就收工囉~等可以用到Ubuntu的時候再繼續!

好了總算重新摸到我的Ubuntu,雖然也是可以直接裝個TensorFlow,不過既然有docker可以玩,那就順便學一下好了!

首先要先裝docker,主要就Follow一下這邊的步驟就可以了~前面的prerequisites記得要做,然後我還要額外upgrade kernel的版本(因為我還是12.04的系統QQ),然後就照步驟安裝docker囉!

安裝完之後,記得再設定一下docker group,就不用每次都得在跑docker之前加上sudo了。就可以來跑作業要用到的docker image:

sudo service docker start
docker run -p 8888:8888 -it --rm b.gcr.io/tensorflow-udacity/assignments

嗯嗯好總算是把環境跑起來了,第一次跑的時候會下載比較久,可以去喝杯水,對身體好。跑起來之後就可以看到這個畫面:

1.png

接下來去瀏覽器輸入127.0.0.1:8888就可以看到iPython的notebook了!

一開始有點不知道怎麼在iPython環境下工作~看起來是可以自由增加Cell然後寫一些程式去跑。如果要起頭可以直接點code cell然後按下Run的按鈕,第二個cell執行到一半確實會在docker notebook首頁看到notMNIST_large.tar.gz的檔案(不過這一段code應該要抓train跟testing的兩組檔案,所以網路不夠快的話大約需要等個幾分鐘),這表示確實有在下載dataset。等到把Problem 1之前的code cell都執行完一次,就可以開始玩Problem 1了。

Problem 1只是要把圖片show出來,這邊有提示可以用IPython.display,查了一下發現滿簡單的。但問題是我不知道image的路徑,假設回到首頁想用點擊的方式看,每次只要一進到notMNIST_large/A/就會當掉(應該是因為檔案太多),於是只好在程式裡把檔案路徑先印出來,再用Image函式show出來,實作上只要開一個cell,在裡面寫

#os.listdir('notMNIST_small/A/')
Image(filename='notMNIST_small/A/RnV0dXJhRUYtTGlnaHRDb25kLm90Zg==.png')

就會跑出來了!
1 (1)

接下來進到Problem 2,這邊一樣是要把dataset裡的image畫出來,不過這次的image是經過讀檔跟一些處理,已經變成ndarray格式的影像。這邊儲存影像的邏輯是dataset[0:num_images, :, :],第一個維度表示第幾張影像,先看一下我們擁有的變數 – train_datasets跟test_datasets:

train_datasets[0] == notMNIST_large/A.pickle
...
train_datasets[9] == notMNIST_large/J.pickle

它們儲存的東西並不是ndarray,而是.pickle檔。到這邊就不得不了解一下pickle是什麼東西,查了一下發現居然是Python裡面做serialization的工具,所以應該要多做一步讀檔的動作:

f = open(train_datasets[0], 'r')
a = pickle.load(f)
#print(a[1])
plt.imshow(a[1,:,:])
plt.show()

不過奇怪的是,執行這段程式碼的結果是…什麼也沒發生,我明明已經把a[1]印出來看過,是一個28×28的二維矩陣沒錯。首先確保Ipython notebook對於matplotlib支援沒有問題,應該先隨便畫個東西出來看看:

Robotics

TensorFlow課程筆記-Softmax實作

今天受到趙大神的啟發決定來上一下TensorFlow的課,遇到第一個程式作業就有些看不懂的東西,決定用一種意識流的方法來寫寫看筆記,幫助自己更加理解!

原始的程式碼挖了個空,要自己寫softmax

<pre>"""Softmax."""

scores = [3.0, 1.0, 0.2]

import numpy as np

def softmax(x):
 """Compute softmax values for each sets of scores in x."""
 pass # TODO: Compute and return softmax(x)

print(softmax(scores))

# Plot softmax curves
import matplotlib.pyplot as plt
x = np.arange(-2.0, 6.0, 0.1)
scores = np.vstack([x, np.ones_like(x), 0.2 * np.ones_like(x)])

plt.plot(x, softmax(scores).T, linewidth=2)
plt.show()

當務之急是先了解函式的特性

1

所以假設我把[3.0, 1.0, 0.2]傳進來,我想得到的應該是[exp(3.0)/(exp(3.0)+exp(1.0)+exp(0.2)), …],所以就先算出分母,然後把最後的陣列湊出來應該就行了吧。

啊不過要怎麼算exponential咧? 查了一下發現numpy裡就有好用的函式可以算整個陣列的exponential值~

寫出來就像這樣:

import numpy as np

def softmax(x):
 """Compute softmax values for each sets of scores in x."""
 sum=0
 for item in x:
 sum += np.exp(item)
 
 return np.exp(x)/sum

不過我很好奇後面那一坨code詳細做了什麼,怎麼有辦法產生出一張有點酷炫的圖:
1

嗯就來看看這一坨東東:

# Plot softmax curves
import matplotlib.pyplot as plt
x = np.arange(-2.0, 6.0, 0.1)
scores = np.vstack([x, np.ones_like(x), 0.2 * np.ones_like(x)])

plt.plot(x, softmax(scores).T, linewidth=2)
plt.show() 

首先我們可以先預測這一段code到底要幹嘛,解讀起來看看是否印證預測,會比較快。如果光看圖的話,感覺就是想要畫出一個X軸,根據X軸的變化改變scores裡面的三個值,然後把三個值經過softmax之後的值畫在Y軸上(所以Y軸的最大值是1)。

好喔好像滿合理的~

所以首先我們要產生X軸的值,從圖上可以明顯看到最小值是-2、最大值是6,恩所以np.arange應該就是用來產生一系列的X軸值,查到的結果也的確是這樣。

接下來這一行有點噁心,一下vstack一下ones_like的,到底是什麼東西?不要急,一個一個來~

ones_like就只是產生一個全部都是1的list而已,不過list的維度會跟input一樣,所以ones_like(x)會產生一個跟x一樣大小,只是裡面的元素全部都是1。

啊vstack不過就是把幾個list疊在一起而已,上個圖就瞭了:
1

接下來就是最不直覺的地方了,我傳進去softmax(scores)的scores不應該是把三個score值放在同一個list裡嗎?啊怎麼反而分散在三個list裡面?這傳進我的softmax裡面應該會爆吧?

原來這個(.T)偷偷做了transpose!不過這還是不足已解釋傳進去的時候為什麼會有那樣的行為,不過這個問題不是很重要,我懶得再花時間了,有需要的話會再遇到的XD

Robotics, ROS

Qualcomm也在做機器人!

最近因緣際會,看到相關的訊息,發現Qualcomm已經做出支援ROS的開發板了,核心的處理器是Qualcomm® Snapdragon™ 600處理器(採用ARM指令集架構),支援的開發板包含Inforce 6xxx系列的SBC(Single-Board Computer),例如:Inforce Computing 6410™ Single Board Computer 跟 Inforce Computing 6410 Plus™ Single Board Computer。

先列出一些我自己好奇的問題:

  1. Qualcomm為什麼要做這件事?
  2. ROS怎麼運行的?是不是有porting?
  3. 怎麼寫出可以在Qualcomm SBC上面執行的ROS程式?
  4. Snapdragon™ 600處理器的硬體規格大概是什麼等級?能負荷多少運算量?

接下來是我理出的答案:

1.眾所皆知,Qualcomm是做通訊晶片的IC設計大廠,依賴著智慧型手機市場的大幅增加,Qualcomm的晶片也賣得嚇嚇叫(畢竟手機都需要好的通訊功能啊,尤其是多媒體傳輸需求不斷增加),不過問題是,如果智慧型手機成長停滯了、其他IC設計廠追上來了該怎麼辦?

如果Qualcomm沒有收一收不做了的打算,那就得再尋找下一個會大量用到他們設計的IC的產品,而機器人是其中一個選項。

2.Qualcomm跟OSRF合作,把比較重要的package(450個以上)移植到ARM指令集架構的硬體上(其實就是運行在Ubuntu for ARM的環境之下,可以參考ROS wiki上的UbuntuArm頁面),所以他們確實有做porting,但是我找不到他們的程式碼,不知道他們是怎麼實作的。

3.安裝方面可以參考IFC 6410的guide。而在寫程式方面,因為Qualcomm跟OSRF已經做了porting,所以使用者在撰寫程式的時候並不會跟寫PC上運行的ROS程式有任何差別,看起來在編譯的時候會有些許差別,但資料實在還太少,不太知道詳細步驟,我想2016年應該就會釋出開發套件讓大家玩了。

4.根據這個網頁,如果是用IFC 6410的SBC,都有1.7 GHz Quad Core Qualcomm® Krait™ CPU的運算能力,還可以執行gmapping讓我覺得滿驚訝的,也許在不久的將來真的可以用小型開發板來當作機器人的運算中樞,然後VSLAM、Object Recognition等feature都能讓不同的IC來處理。