PrimeSense Grab Detector 簡單範例


之前有介紹過,PrimeSense 有推出一個可以偵測到手部「握起」(grab)和「放開」(release)這兩種動作的函式庫、Grab Detector 了。雖然這個函式庫在使用上有一些限制,不過在符合條件的情況下,還算是值得用看看的。

實際上,PrimeSense 針對這個函式庫,有提供一個名為「GrabViewer」的範例可以參考(檔案在 \Samples\GrabViewer),程式的架構和 OpenNI 2 或 NiTE 2 的範例架構大致相同,所以如果有研究過之前的官方範例的話,應該很好上手。

而針對 Grab Detector 的使用方法、流程,Heresy 在上一篇文章已經有概略性地介紹過了,如果還沒看過,請先看一下之前的文章。這邊基本上就是以範例為主了。

OpenNI 與 NiTE 初始化

由於 GrabDetector 除了需要用到深度和彩色影像,也需要透過 NiTE 的 HandTracker 來知道手的位置,所以在使用 GrabDetector 之前,需要先針對 OpenNI 2 和 NiTE 2,做好相關的設定。

以 OpenNI 的部分來說,主要就是建立出深度和彩色影像的 VideoStream,並完成相關的設定,其程式碼如下:

// Initial OpenNI
OpenNI::initialize();
 
// Open Device
Device  devDevice;
devDevice.open( ANY_DEVICE );
 
// create depth stream
VideoStream vsDepthStream;
vsDepthStream.create( devDevice, SENSOR_DEPTH );
 
// set video mode
VideoMode mMode;
mMode.setResolution( 640, 480 );
mMode.setFps( 30 );
mMode.setPixelFormat( PIXEL_FORMAT_DEPTH_1_MM );
vsDepthStream.setVideoMode( mMode);
 
// Create color stream
VideoStream vsColorStream;
vsColorStream.create( devDevice, SENSOR_COLOR );
 
// set video mode
mMode.setResolution( 640, 480 );
mMode.setFps( 30 );
mMode.setPixelFormat( PIXEL_FORMAT_RGB888 );
vsColorStream.setVideoMode( mMode);
 
// image registration
devDevice.setImageRegistrationMode( IMAGE_REGISTRATION_DEPTH_TO_COLOR );
//devDevice.setDepthColorSyncEnabled( true );

而由於 GrabDetector 會使用彩色影像來做輔助分析,所以一定要把彩色影像和深度影像,透過 DevicesetImageRegistrationMode() 來做位置校正的處理。也因此,如果是使用不支援 setImageRegistrationMode() 的 Kinect 感應器的話,就需要使用第三方修改過的 Kinect.dll 模組才行了(參考)。

另外,根據官方的說法,是一定要透過 setDepthColorSyncEnabled() 來開啟彩色影像和深度影像的同步,不過實際上通常兩者的時間差異不會太大,不開啟一般應該也是可以的。

至於 NiTE 的部分,主要就是建立出 HandTracker 來追蹤手部位置了~這邊一樣,是透過 click 和 wave 這兩種手勢,來開始手部位置的追蹤。

// Initial NiTE
NiTE::initialize();
 
// create hand tracker
HandTracker mHandTracker;
mHandTracker.create( &devDevice );
 
// set gesture
mHandTracker.startGestureDetection( GESTURE_WAVE );
mHandTracker.startGestureDetection( GESTURE_CLICK );

由於 GrabDetector 只是要手部的位置,所以理論上使用 UserTracker 追蹤到的人體骨架裡面的手部位置,應該也是可行的。


GrabDetector 初始化

在前一篇文章已經有提過了,基本上要使用 GrabDetector,主要是要使用 PSLabs::CreateGrabDetector() 這個函式,建立出 PSLabs::IGrabDetector 的物件、pGrabDetector 來做進一步的操作。

而在建立時,除了需要告訴他要使用哪個 Device 外,也需要指定資料檔(Redistributable\Common\Data\grab_gesture.dat)所在的位置;在這邊 Heresy 是把這個檔案複製出來,放在執行目錄下、名為「GrabDetector」的目錄下:

PSLabs::IGrabDetector* pGrabDetector 
     = PSLabs::CreateGrabDetector( devDevice, "GrabDetector/" );
if( pGrabDetector == NULL
    || pGrabDetector->GetLastEvent( NULL ) != openni::STATUS_OK)
{
  cerr << "Can't initialize grab detector: " 
       << pGrabDetector->GetLastEvent( NULL ) << endl;
  return -1;
}

而建立完成之後,基本上在做一些簡單的檢查,確認他是正常可以使用的,就可以繼續了~


主迴圈

主迴圈的部分,由於要透過 HandTracker 來追蹤手部位置,所以一開始還是一樣,要透過檢查手勢,來開始手部位置的追蹤(process gestures 的部分):

vsDepthStream.start();
vsColorStream.start();
PSLabs::IGrabEventListener::GrabEventType eLastEvent
     = PSLabs::IGrabEventListener::NO_EVENT;
HandId mHandID = 0;
for( int t = 0; t < 500; ++ t )
{
  // get new frame
  HandTrackerFrameRef mHandFrame;
  if( mHandTracker.readFrame( &mHandFrame ) == nite::STATUS_OK )
  {
    // process gestures
    const nite::Array<GestureData>& aGestures = mHandFrame.getGestures();
    for( int i = 0; i < aGestures.getSize(); ++ i )
    {
      const GestureData& rGesture = aGestures[i];
      const Point3f& rPos =rGesture.getCurrentPosition();
      cout << "Get gesture: " << rGesture.getType() << " at " ;
      cout << rPos.x << ", " << rPos.y << ", " << rPos.z << endl;
 
      mHandTracker.startHandTracking( rPos, &mHandID );
    }
 
    // process hands
    // ...
  }
}

而這邊,Heresy 是透過 mHandID 這個變數,來記錄最後一個開始追蹤的手,用來給 GrabDetector 做手部狀態的分析。

至於「process hand」的部分,一開始和一般的 HandTracker 的用法相同,需要先透過 getHands() 來取得手部的位置;然後,透過迴圈找到是我們要處理的手的話,則在進入處理的程序。

// process hands
const nite::Array<HandData>& aHands = mHandFrame.getHands();
for( int i = 0; i < aHands.getSize(); ++ i )
{
  const HandData& rHand = aHands[i];
  if( rHand.getId() == mHandID )
  {
    if( rHand.isLost() )
    {
      cout << "Hand Lost";
      mHandID = 0;
    }
    if( rHand.isTracking() )
    {
      // update hand position
      const Point3f& rPos =rHand.getPosition();
      pGrabDetector->SetHandPosition( rPos.x, rPos.y, rPos.z );
 
      // read color frame
      VideoFrameRef mColor;
      vsColorStream.readFrame( &mColor );
 
      // update depth and color image
      pGrabDetector->UpdateFrame( mHandFrame.getDepthFrame(), mColor );
 
      // check last event if not using listener
      PSLabs::IGrabEventListener::EventParams mEvent;
      if( pGrabDetector->GetLastEvent( &mEvent ) == openni::STATUS_OK )
      {
        // if status changed
        if( mEvent.Type != eLastEvent )
        {
          switch( mEvent.Type )
          {
          case PSLabs::IGrabEventListener::GRAB_EVENT:
            cout << "Grab" << endl;
            break;

          case PSLabs::IGrabEventListener::RELEASE_EVENT:
            cout << "Release" << endl;
            break;

          case PSLabs::IGrabEventListener::NO_EVENT:
            break;
          }
        }
        eLastEvent = mEvent.Type;
      }
    }
    break;
  }
}

而接下來,則是要把新的資料傳給 pGrabDetector,讓他進行分析。這邊首先是要呼叫他提供的 setHandPosition() 函式,把手的位置傳進去,然後再呼叫 UpdateFrame(),把深度、彩色的 VideoFrameRef 傳進去,讓他進行分析。

而這樣把新的資料傳進去後,pGrabDetector 就會開始做分析,並且根據輸入的資料,在內部產生對應的事件(event)。

以官方的範例來說,他是使用 Listener 的架構(callback function),來做資料的取得與處理,不過這邊 Heresy 是以他提供的 GetLastEvent(),來取得最後的狀態作為範例。這邊取得的資料,會是 PSLabs::IGrabEventListener::EventParams 的型別,代表 pGrabDetector 最後一次產生的事件的內容;而一般來說,是可以直接透過他的 Type,來判斷是哪一種事件。

而目前 GrabDetector 的事件有三種,分別是 GRAB_EVENTRELEASE_EVENT 以及 NO_EVENT;主要要使用的,基本上會是前兩種事件,透過 grab 和 release,就可以用來做手部狀態的判讀、並做進一步的應用了。不過 Heresy 這邊,基本上就是把它輸出文字而已。

而由於這邊是每一個 frame 都會去做這個資料的讀取,所以如果全部都輸出的話,會讓畫面太亂,所以這邊又另外用 eLastEvent 來紀錄上一次的狀態,並且只有在狀態有變化的時候,才做輸出;這樣整個城市的輸出會乾淨很多。


Listener 模式

而如果是要使用 listener 模式的話,基本上就會變成是事件導向的程式,只有當有事件、也就是手的狀態改變的時候,才會觸發到事件。實際上,雖然 Heresy 沒有特別提過,不過 OpenNI 2 和 NiTE 2 也是有提供這樣的模式的。

以 GrabDetector 來說,如果要使用 listener 模式的話,是要先繼承 PSLabs::IGrabEventListener,寫出一個自己的 Listener,然後在裡面實作 ProcessGrabEvent() 這個函式;下面就是一個簡單的範例:

class GrabEventListener : public PSLabs::IGrabEventListener
{
public:
  GrabEventListener(){}
 
  virtual void DLL_CALL ProcessGrabEvent( const EventParams& params )
  {
    switch( params.Type )
    {
    case PSLabs::IGrabEventListener::GRAB_EVENT:
      cout << "Grab" << endl;
      break;
 
    case PSLabs::IGrabEventListener::RELEASE_EVENT:
      cout << "Release" << endl;
      break;
 
    case PSLabs::IGrabEventListener::NO_EVENT:
      break;
    }
  }
};

可以看到,實際上 ProcessGrabEvent() 裡面做的事,和上面、放在主迴圈裡面的程式碼是相同的。

而在定義好 GrabEventListener 這個類型後,如果要使用的話,就是要在完成 GrabDetector 的初始化後,透過 AddListener() 這個函式,來指定要用這個方法來處理 GrabDetector 產生的事件。設定方法,基本上大致上就是:

GrabEventListener mEventListener;
pGrabDetector->AddListener( &mEventListener );

如果做了這樣的設定的話,那上面主迴圈裏面,本來透過 GetLastEvent() 來分析事件的「// check last event if not using listener」整段程式碼,就都可以不用了~因為當透過 pGrabDetectorsetHandPosition()UpdateFrame() 這兩個函式更新資料後,如果有觸發新的事件,pGrabDetector 就會主動去呼叫 mEventListenerProcessGrabEvent() 這個函式了~


這邊完整的範例程式,可以參考 Heresy 放在 SkyDrive 上的檔案(連結:http://sdrv.ms/161z1sS);在 main() 裡面,一開始有一個 bUseListener 的變數,就是用來控制是要使用 Listener 模式,還是要使用 GetLastEvent() 來做處理的。

而這個範例程式執行起來後,只會有一個命令提示字元視窗,接下來就是要靠揮手、或是 click,來開始追蹤手的位置,並開始使用 GrabDetector 來進行分析。

在 Heresy 這邊測試的時候,是發現其實可以不用使用 Color 的 Video Stream,雖然效果略差,不過也算是堪用的。個人覺得比較可惜的是,他是以狀態改變為基礎來做事件的觸發,所以應該無法同時套用在複數個手上,只能針對單手使用;或許之後有需要,可以再試試看能不能產生多個 PSLabs::IGrabDetector 的物件,個別對應一隻手吧~

另外要注意的是,PrimeSense 目前只有釋出 Windows 版的 Library(x86 和 x64 都有),所以其他平台上應該是還無法使用的。


OpenNI / Kinect 相關文章目錄

對「PrimeSense Grab Detector 簡單範例」的想法

  1. Heresy大你好
    我使用您skydrive上的範例
    不過執行時都會跳出以下錯誤
    “應用程式無法正確啟動(0xc000007b)。請按一下[確定]關閉應用程式。"
    大概自己有在網路上看一些解決方法
    都是說directX的問題
    我自己也有重新下載更新檔
    但是問題仍依舊

    所以想請問heresy大,
    是否有其他解決方案??
    謝謝~~

    • 這個範例並沒有用到 DirectX,應該和他無關。
      感覺上,這個問題應該是 dll 檔的版本有問題造成的,可能是要檢查一下有用到的 dll 檔,看看版本是否相符合。

  2. 您好 H大大
    我剛在在跑您的範例時遇到這個錯誤 ‘GrabDetector/GrabDetector.h’ : No such file or directory
    請H大大指教
    謝謝

    • 你有下載 GrabDetector 這個函式庫嗎?
      請找到他的 header 檔所在位置、並在專案內加入對應的設定。

      • 您好 H大大
        我在成功編譯您的範例后 終端顯示 “Grab Detector can’t work without image registration." 不知道為什麽
        請H大大指教
        謝謝

        • 你是使用 Kinect 感應器嗎?如果是的話,文章內已經有說明了:

          如果是使用不支援 setImageRegistrationMode() 的 Kinect 感應器的話,就需要使用第三方修改過的 Kinect.dll 模組才行了

          • 您好 H大大
            我已經按您說的把grab_gesture.dat這個檔案複製出來,放在執行目錄下、名為「GrabDetector」的目錄下 可是爲什麽執行完了 終端依然顯示 Could not find data file ‘GrabDetector//grab_gesture.dat’
            還有在哪指定資料檔(Redistributable\Common\Data\grab_gesture.dat)所在的位置呀
            請H大大指教

          • PSLabs::CreateGrabDetector() 的第二個參數就是要指定這個檔案所在的位置。
            請確認你的設定和執行檔的相對位置符合

          • 您好H大大
            根據您的指點 我把PSLabs::CreateGrabDetector() 的第二個參數的位置設定為grab_gesture.dat這個檔案的位置 PSLabs::IGrabDetector* pGrabDetector = PSLabs::CreateGrabDetector( devDevice, “C:/Users/helene/Documents/Visual Studio 2012/Projects/Grab Detector/Redistributable/Grab Detector/Data/grab_gesture.dat" );
            可是終端什麽都沒有顯示 只是黑屏加_
            請指教
            謝謝

    • 請參考本文範例,他要指定的是 grab_gesture.dat 所在的目錄,而非檔案本身。
      強烈建議你多看一下官方的範例程式、還有本文範例程式的寫法,其實根據範例程式,就可以看的出來這個函示庫該怎麼用的。

      • 您好H大大
        不好意思 我是菜鳥 還有一個問題 困擾我兩天 解決不了 只有您向請教
        當程序可以跑了 可是終端顯示 <> 不知道是爲什麽
        請指教
        謝謝

        • 为何我的程序也这样设置一直显示Grab Detector can’t work without image registration

          • 錯誤訊息已經告訴你原因了。
            考本文以及本串討論也有針對此做說明。

            請確定你使用的裝置能支援 image registration。

      • 您好
        終端顯示 press any key to continue 不知道是爲什麽
        剛才的回覆沒有顯示出 這個問題 所以再發了一次

          • 您好 H大大
            我的程序沒有加上錯誤檢查,也沒有設定終端點,我只是直接用了您的範例程式,想了幾天, 沒有想明白應該在哪裡加上錯誤檢查或是中斷點呢
            請指教
            謝謝

      • 中斷點的偵錯概念請參考 MSDN: http://msdn.microsoft.com/zh-tw/library/5557y8b4.aspx

        如果是使用 SkyDrive 上的完整範例,本身就已經有錯誤偵測了。
        請確認你的程式到底執行到哪些地方,然後結束的。

        基本上,文章中已經有說明了,這個範例程式「沒有」圖形介面,只有在做出對應的動作的時候,才會有輸出。而由於是使用 for 迴圈,所以也會在一定時間後,自動結束。

  3. Heresy你好
    想請教一下本範例程式中的
    if( mHandTracker.readFrame( &mHandFrame ) == nite::STATUS_OK )
    這句CODE應該是在判斷readFrame的工作有沒有成功進行吧?
    可是實際跑了這個程式後,發現不論是WAVE還是CLICK不管怎麼做都沒有回應
    將這一段CODE拿掉之後,貌似是做了WAVE或是CLICK黑窗就會直接關閉
    不過在這偵測手勢的過程
    我也看過Heresy以前寫的NiTE手勢偵測
    CODE裡並沒有上述的那一段CODE
    不曉得這段CODE最主要是在做什麼樣的判斷呢
    又為什麼之前的手勢偵測不用用也可以呢

    謝謝Heresy!!!

    • 的確是錯誤檢查沒錯。
      之前的範例,只是單純為了簡化程式,所以省略了大部分的錯誤檢查而已。
      建議還是要加的。

      • 好的,了解
        關於Grab的使用上
        想請教一下若將這個功能與 OpenNI2 & NiTE2 的程式一同使用
        會有什麼使用上的限制或是條件嗎?
        因為在加入Grab的CODE後,讀取Frame變得相當的慢
        而且有90%的機率會出現以下訊息
        「0x5651AE80 (NiTE2.dll) (於 xtion.exe) 中發生未處理的例外狀況: 0xC0000005: 讀取位置 0x3DC93098 時發生存取違規。」

        謝謝Heresy

        • Heresy 也有碰到效能低落的問題。
          個人是覺得,這個函式庫對 PrimeSense 應該還只算是展示階段,不像 NiTE 已經成熟了,所以應該還有很多調整的空間。

回覆給Heresy 取消回覆

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料