之前有介紹過,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 會使用彩色影像來做輔助分析,所以一定要把彩色影像和深度影像,透過 Device 的 setImageRegistrationMode() 來做位置校正的處理。也因此,如果是使用不支援 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_EVENT、RELEASE_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」整段程式碼,就都可以不用了~因為當透過 pGrabDetector 的 setHandPosition() 和 UpdateFrame() 這兩個函式更新資料後,如果有觸發新的事件,pGrabDetector 就會主動去呼叫 mEventListener 的 ProcessGrabEvent() 這個函式了~
這邊完整的範例程式,可以參考 Heresy 放在 SkyDrive 上的檔案(連結:http://sdrv.ms/161z1sS);在 main() 裡面,一開始有一個 bUseListener 的變數,就是用來控制是要使用 Listener 模式,還是要使用 GetLastEvent() 來做處理的。 而這個範例程式執行起來後,只會有一個命令提示字元視窗,接下來就是要靠揮手、或是 click,來開始追蹤手的位置,並開始使用 GrabDetector 來進行分析。 在 Heresy 這邊測試的時候,是發現其實可以不用使用 Color 的 Video Stream,雖然效果略差,不過也算是堪用的。個人覺得比較可惜的是,他是以狀態改變為基礎來做事件的觸發,所以應該無法同時套用在複數個手上,只能針對單手使用;或許之後有需要,可以再試試看能不能產生多個 PSLabs::IGrabDetector 的物件,個別對應一隻手吧~ 另外要注意的是,PrimeSense 目前只有釋出 Windows 版的 Library(x86 和 x64 都有),所以其他平台上應該是還無法使用的。
|