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 都有),所以其他平台上應該是還無法使用的。

10 thoughts on “PrimeSense Grab Detector 簡單範例”

  1. 博主,我运行release版得grab,显示couldn’t set image to depth registration. disabling image stream。可能是什么原因。

  2. @wilson
    你是使用 Kinect 感應器嗎?如果是的話,文章中有提到了,你需要使用修改版的 Kinect.dll 模組。

  3. 参考您之前的一个帖子,可以了,谢谢,我这里只是替换了grabdetector里的kinect.dll。
    这么说,无论NiTE还是openNI2里面的Kinect.dll都要替换掉了?

  4. @wilson
    現在這些 dll 檔案是可以跟著不同的程式、各自去使用各自的檔案的。
    只要修改要使用的地方就可以了。

  5. OK,多谢博主。
    其实我有去试一下,用这个修改版dll替换掉用到NiTE中间件的程序中,会出错的。呵呵,看来只可以用到Grab Detector中。

  6. @wilson

    Heresy 之前測試搭配 NiTE 是可以用的,有可能是新版本之間有相容性問題。
    不過相關功能已經被整合進 OpenNI 2.2 的原始碼了,等下次更新 應該就可以了。

  7. 你好,请问GrabDetector/GrabDetector.h这个显示无法打开 咋处理的呢?谢谢!

  8. to wangyi

    你有去下載 GrabDetector 這個函式庫,並且把裡面的 header 檔放到對應的位置嗎?

  9. 你好,我从https://github.com/occipital/OpenNI2下载了份代码,在windows编译,提示找不到windows.h,然道还要设置工程环境?

  10. to helloworld
    請先參考官方的 Building Prerequisites,他有列出你需要的東西。
    基本上,請至少確定你是使用 Visual Studio 2010 以上的版本;如果是使用 express 版的話,請另外安裝 Windows SDK

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。