之前有介紹過,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 都有),所以其他平台上應該是還無法使用的。
您好!!請問大大!! 如果使用不是彩色的xtion 可以做Grab嗎??謝謝
讚讚
Heresy 這邊沒有 Xtion Pro,所以沒有試過。不過建議可以測試看看。
讚讚
Heresy大你好
我使用您skydrive上的範例
不過執行時都會跳出以下錯誤
“應用程式無法正確啟動(0xc000007b)。請按一下[確定]關閉應用程式。"
大概自己有在網路上看一些解決方法
都是說directX的問題
我自己也有重新下載更新檔
但是問題仍依舊
所以想請問heresy大,
是否有其他解決方案??
謝謝~~
讚讚
這個範例並沒有用到 DirectX,應該和他無關。
感覺上,這個問題應該是 dll 檔的版本有問題造成的,可能是要檢查一下有用到的 dll 檔,看看版本是否相符合。
讚讚
[…] PrimeSense Grab Detector 簡單範例 […]
讚讚
[…] middleware 有:3D Hand Tracking Library、Tiptep Skeletonzer、PrimeSense Grab Detector;另外 CodePlex 上的 Candescent NUI […]
讚讚
您好 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 迴圈,所以也會在一定時間後,自動結束。
讚讚
好的 非常感謝您的回答
讚讚
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 已經成熟了,所以應該還有很多調整的空間。
讚讚