OpenNI 2 的 Listener 模式


之前已經有寫過 OpenNI 2 的基本教學,如果只是要透過 OpenNI 2 的介面,來讀取深度、彩色影像,只要照著寫,應該不會有什麼問題;而如果是希望有畫面可以呈現的話,Heresy 也有提供整合 OpenCV 的範例,可以拿來做畫面的呈現。

不過,在之前的範例裡,基本上 Heresy 使用的方式,都是在程式的主迴圈裡面,由開發者主動地透過 readFrame() 這個函式,來向 OpenNI 的 VideoStream 要求新的資料;而實際上,OpenNI 2 還有提供另一種「Event Base」的「Listener」模式,可以讓 VideoStream 有拿到新資料的時候,主動地去執行指定的功能~如果是使用這種模式的話,基本上就可以不用透過自己建立主迴圈,來做資料的更新了。

而如果要使用這種模式的話,首先,我們必須要去定義當 VideoStream 拿到新的資料的時候,要做那些事;而這邊定義的方法,就是透過繼承 VideoStream::NewFrameListener 這個類別,實作出一個屬於自己的 Listener 類別,來做自己想要做的事。

而最簡單的寫法,大概就會像下面這樣子:

class OutputDpeth : public VideoStream::NewFrameListener
{
public:
  // this function will be called when video stream get new frame
  void onNewFrame( VideoStream& rStream )
  {
    VideoFrameRef vfDepth;
    if( rStream.readFrame( &vfDepth ) == STATUS_OK )
    {
      // ... Do Something here
    }
  }
};

上面這邊的 OutputDepth 這個類別,基本上就是去繼承 VideoStream::NewFrameListener 這個類別時做出來的;而裡面唯一一個一定要實作的函式,就是 onNewFrame() 這個函式。

而實際上,onNewFrame() 這個函式,就是當 VideoStream 接收到新的畫面時,會去主動執行的動作。這個函式在被呼叫時,會把 VideoStream 本身的參考傳進來,讓程式開發者可以使用;也由於在被呼叫時,只會收到 VideoStream 的參考,所以在裡面,還是需要透過 readFrame() 來讀取新的畫面、也就是 VideoFrameRef 形式的資料。由於也是使用 readFrame() 去讀取新的 VideoFrameRef,所以之後的用法就和之前完全相同了~

在定義好自己的 listener class 後,接下來要怎麼設定呢?VideoStream 有提 addNewFrameListener()removeNewFrameListener() 這兩個函式,可以用新增、移除 listener 的物件。最簡單的使用發法,就是在呼叫 start()、要求 VideoStream 開始讀取資料之前,先建立一個 OutputDepth 的物件,並且透過 addNewFrameListener(),把這個物件的指標傳入,讓 VideoStream 知道有新畫面的時候要做哪些事。

下面就是簡單的使用範例:

OutputDpeth mOutputDepth;
vsDepth.addNewFrameListener( &mOutputDepth );

在這樣設定過後,之後就可以在程式裡面繼續做其他事情,當 VideoStream 讀到新的畫面時,就會在另一個 thread 執行 OutputDepthonNewFrame() 的內容了~

下面就是完整的範例:

// STL Header
#include <iostream>
#include <string>
  
// 1. include OpenNI Header
#include <OpenNI.h>
  
// using namespace
using namespace std;
using namespace openni;
  
/**
 * The listener for depth video stream
 */
class OutputDpeth : public VideoStream::NewFrameListener
{
public:
  // this function will be called when video stream get new frame
  void onNewFrame( VideoStream& rStream )
  {
    VideoFrameRef vfDepth;
    if( rStream.readFrame( &vfDepth ) == STATUS_OK )
    {
      // a1 get data array
      const DepthPixel* pDepth
              = static_cast<const DepthPixel*>( vfDepth.getData() );
  
      // a2 output the depth value of center point
      int w = vfDepth.getWidth(),
        h = vfDepth.getHeight();
      int x = w / 2, y = h / 2;
      cout << pDepth[ x + y * w ] << endl;
  
      // a3 release frame
      vfDepth.release();
    }
    else
    {
      cerr << "Can not read frame\n";
      cerr << OpenNI::getExtendedError() << endl;
    }
  }
};
 
int main( int argc, char** argv )
{
  // 2. initialize OpenNI
  OpenNI::initialize();
  
  // 3. open a device
  Device devDevice;
  devDevice.open( ANY_DEVICE );
  
  // 4. create depth stream
  VideoStream vsDepth;
  vsDepth.create( devDevice, SENSOR_DEPTH );
  
  OutputDpeth mOutputDepth;
  vsDepth.addNewFrameListener( &mOutputDepth );
   
  vsDepth.start();
  
  // 5 wait any input (some char and Enter) to quit
  string sInput;
  cin >> sInput;
  
  // 6 stop reading
  vsDepth.removeNewFrameListener( &mOutputDepth );
  vsDepth.stop();
  
  // destroy depth stream
  vsDepth.destroy();
  
  // close device
  devDevice.close();
  
  // 7. shutdown
  OpenNI::shutdown();
  
  return 0;
}

可以看到,上面的程式在呼叫 VideoStream vsDepthstart()、開始讀取資料後,就是透過 cin 這個標準輸入串流、來等待使用者透過鍵盤輸入一個字串(輸入任意字元、然後按下Enter)(上面的程式碼裡面、「5」的部分);而在使用者輸入之前,如果 VideoStream 有毒取道資料,就會去執行 mOutputDepthonNewFrame() 這個函式的內容,也就是把畫面中的中央點的深度做輸出。

當使用者輸入了一些文字、按下 Enter 後,就會繼續執行接下來的 removeNewFrameListener() 以及 stop()(上面的程式碼裡面、「6」的部分),然後把整個 OpenNI 環境關閉、結束程式。


上面基本上就是 OpenNI 2 提供的 Listener 模式的使用方法。而除了 VideoStreamNewFrameListener 外,OpenNI 這個用來做整個環境管理的類別,也有提供 DeviceConnectedListenerDeviceDisconnectedListenerDeviceStateChangedListener 這三種用來偵測 Device 狀態的 listener 可以使用,分別是對應到「連接上新的裝置」、「裝置離線」、「裝置狀態改變」。

因為這三種 listener 類別對應的事件不同、所以必須要實作的函式也不相同,如果有興趣的話,可以參考 OpenNI 官方的範例 Samples\EventBasedRead,這便就不詳細說明了。

而另外像是 PrimeSense NiTE 2 的 UserTackerHandTracker,也都有提供 NewFrameListener 可以使用,所以有需要的話,NiTE 也是可以使用這種 Listener 模式(Event base)來做程式開發的。

不過,透過種模式來使用 OpenNI 的時候,要注意的一點就是,實際上這種開發模型已經是 multi-thread、多執行序的程式開發了!所以在開發的時候,如果有要在不同 thread 共用資源的話,一定要非常小心。而如果是使用像是 Qt 或是 glut 這類的框架的時候,在使用上也會有一些額外的限制,基本上也都能直接在其他 thread、去修改相關的東西(也就是,不能在 Listener 裡面,直接去修改 OpenGL 的東西、或是 Qt 的 UI),這點是使用上比較麻煩的地方。


OpenNI / Kinect 相關文章目錄

對「OpenNI 2 的 Listener 模式」的想法

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.