OpenNI 的手勢偵測

在使用 OpenNI 進行手勢偵測的時候,主要是要靠 xn::GestureGenerator 這個 node;他的主要功能就是用來偵測 depth generator 在空間中所抓到的手勢,並進行識別。而目前 OpenNI NITE 能支援的手勢有四種(註 2),包括了:Wave(揮手)、Click(點,實際上就是手往前推)、RaiseHand(舉手)、MovingHand(移動手);如果只是針對這幾種手勢的話,是可以非常簡單地透過 OpenNI 的 xn::GestureGenerator 進行偵測的∼

而它的使用方式,基本上和之前在《透過 OpenNI / NITE 分析人體骨架(上)》、《透過 OpenNI / NITE 分析人體骨架(下)》裡所用到的 xn::UserGenerator 比較接近,都是採用 callback function 來做的 event 系統。有興趣的話,也建議先回去看看這兩篇的內容。

下面則就是簡單的範例程式:

// STL header
#include <stdlib.h>
#include <iostream>

// OpenNI header
#include <XnCppWrapper.h>

// namespace
using namespace std;

// output operator for XnPoint3D
ostream& operator<<( ostream& out, const XnPoint3D& rPoint )
{
out << "(" << rPoint.X << "," << rPoint.Y << "," << rPoint.Z << ")";
return out;
}
// callback function for gesture recognized
void XN_CALLBACK_TYPE GRecognized( xn::GestureGenerator &generator,
const XnChar *strGesture,
const XnPoint3D *pIDPosition,
const XnPoint3D *pEndPosition,
void *pCookie )
{
cout << strGesture<<" from "<<*pIDPosition<<" to "<<*pEndPosition << endl;
}

// callback function for gesture progress
void XN_CALLBACK_TYPE GProgress( xn::GestureGenerator &generator,
const XnChar *strGesture,
const XnPoint3D *pPosition,
XnFloat fProgress,
void *pCookie )
{
cout << strGesture << ":" << fProgress << " at " << *pPosition << endl;
}


// main function
int main( int argc, char** argv )
{
XnStatus eRes;

// 1. initial context
xn::Context mContext;
eRes = mContext.Init();

// 3. create depth generator
xn::DepthGenerator mDepthGenerator;
eRes = mDepthGenerator.Create( mContext );

// 4. initial gesture generator
xn::GestureGenerator mGesture;
eRes = mGesture.Create( mContext );

// 5. Setting gesture
mGesture.AddGesture( "Wave", NULL );
mGesture.AddGesture( "Click", NULL );
mGesture.AddGesture( "RaiseHand", NULL );
mGesture.AddGesture( "MovingHand", NULL );

// 6. Register callback functions of gesture generator
XnCallbackHandle hHandle;
mGesture.RegisterGestureCallbacks( GRecognized, GProgress, NULL, hHandle );

// 7. start generate data
mContext.StartGeneratingAll();
while( true )
{
// 8. Update date
mContext.WaitAndUpdateAll();
}

return 0;
}
主程式

首先,先來看主程式的部分。這部分大部分的程式和其他 OpenNI 的程式一樣,都是先產生 OpenNI 的 context,並進行初始化的動作,然後再建立所需要的 node;而在這邊是要做手勢的偵測,所以需要的 node 是 xn::DepthGeneratorxn::GestureGenerator 這兩者(實際上 xn::DepthGenerator 應該也是可以省略、不用手動產生的,OpenNI 會自己在內部處理)。

由於這部分的程式碼基本上都一樣,所以就不額外提了(註 1);程式碼裡不同的部分,應該就只有「5. Setting gesture」和「6. Register callback functions of gesture generator」這兩段、針對 xn::GestureGenerator 做設定的部分了。

在「5. Setting gesture」的部分,是透過 AddGesture() 這個函示針對 gesture generator 去做設定,控制要處理那些手勢;這個函式的第一個參數就是手勢的名稱(註 2),第二個參數則是針對要去偵測的區域做限制(真實空間座標軸),讓他只在指定的範圍內去找這個手勢,而不要找整個空間。

不過由於這邊 Heresy 並不想針對手勢的位置做限制,所以就直接給他 NULL,就可以設定成整個空間了。如果有需要限定範圍的話,則可以透過兩個三度空間座標(型別為 XnBoundingBox3D),來做限制的條件。

6. Register callback functions of gesture generator」的部分,則是透過 RegisterGestureCallbacks() 這個函式,來登記 xn::GestureGenerator 的兩個主要的 callback function:GestureRecognizedGestureProgress;前者代表他偵測到一個已經完成的手勢,後者則是代表抓到手勢正在進行中。而當手勢被偵測到的時候,xn::GestureGenerator 就會執行這兩個 callback function,對偵測到的手勢進行處理。

使用 RegisterGestureCallbacks() 時要傳入四個參數,前兩個依序就是 GestureRecognizedGestureProgress 這兩個最主要的 callback function;第三個參數的型別則是 void*,可以傳入任何型別的指標,用來給 callback funcion 當作額外的資料使用。最後一個參數則是型別為 XnCallbackHandle 的物件參考,是用來記錄、管理這組 callback function 用的;如果之後有可能要取消現在的 callback function 的話,就得把它紀錄下來。

而這邊所使用的 callback function,就是在主程式前所定義的兩個函式:GRecognized()GProgress();在這兩個函示裡,Heresy 都是透過 C 的 output stream 把得到的資訊做輸出,拿來當作程式執行的結果。而為了方便輸出 OpenNI 所定義的 3D 座標資料 XnPoint3D,這邊也先定義了他的 output operator(operator<<()),來方便輸出。

在所有的東西都設定完成後,接下來就是開始產生資料(7. start generate data )、並用一個無窮迴圈去不停地讀取新的資料了∼而如果在讀取資料的過程中,OpenNI 有偵測到指定的手勢的話,就會去呼叫所登記的 callback function,執行我們所寫的程式碼了∼

Callback Function : Gesture Recodnized

接下來,大概來講一下這兩個 callback function 比較細東西。首先先看 GRecognized() 的介面:

void (XN_CALLBACK_TYPE* GestureRecognized)( GestureGenerator& generator,
const XnChar* strGesture,
const XnPoint3D* pIDPosition,
const XnPoint3D* pEndPosition,
void* pCookie);

他有五個參數,分別為:

  • 是發現這個手勢的 xn::GestureGenerator 物件(generator
  • 手勢的名稱(strGesture
  • 發現手勢時手的位置(pIDPosition
  • 手勢結束時手的位置(pEndPosition
  • 使用者自定義的額外資料(pCookie

其中,儲存手勢名稱的參數 strGesture 型別雖然是 OpenNI 自己定義的 XnChar 的指標,但實際上它的意義就是一般的 C 字串。而代表位置的 pIDPositionpEndPosition 的型別則是 OpenNI 為了 3D 座標定義的 XnPoint3D,儲存了 x / y / z 的位置資訊。

而這邊 Heresy 所定義的函式內容也相當簡單,就是直接用 cout 把上述的三個參數都印出來而已。

Callback Function : Gesture Progress

GProgress() 也是類似的,他的介面是:

void (XN_CALLBACK_TYPE* GestureProgress)( GestureGenerator& generator,
const XnChar* strGesture,
const XnPoint3D* pPosition,
XnFloat fProgress,
void* pCookie);

他的參數基本上和 GestureRecognized 差不多,唯一不同的是第三個和第四個參數,也就是 pPosition 以及 fProgress;前者是代表現在手所在的位置,而後者則是代表目前這個手勢的進度。

而在函式的內容部分,Heresy 一樣是簡單地把參數透過 iostream 做輸出而已。

小結

基本上目前所支援的四種手勢都有可能是這兩種狀態,不過實際上 Heresy 在測試的時候,有下面一些發現:

  • WaveMovingHand 這兩種動作似乎比較容易會觸發到 GestureProgress、觸發到 GestureRecognized 的機會則相對低許多。
  • RaiseHand 則是反過來,絕大部分都是觸發到 GestureRecognized
  • Click,基本上兩種都有可能。
  • 感覺上 RaiseHandMovingHand 的觸發條件似乎比較容易達成,而 WaveClick 的成功率似乎就沒這麼高了。
  • GestureProgress 所得到的 fProgress 這個值,Heresy 好像沒看過 0.5 以外的數字?

不過這些都只是 Heresy 自己玩出來的一些結果,也有可能是 Heresy 這邊其他因素造成的,在別的地方試起來會不會也得到同樣的結果,Heresy 就不知道了。

另外 Heresy 覺得比較可惜的是,OpenNI / NITE 目前所提供的 xn::GestureGenerator 並不能自己新定義手勢,而只能針對目前有的這四種手勢進行操作;如果真的需要額外的手勢的話,似乎是得自己去寫新的 xn::GestureGenerator middleware 了…

附註
  1. 在這個範例裡 Heresy 刻意省略了以往範例程式都有、針對 depth generator 或 image generator 做輸出模式設定的步驟(SetMapOutputMode(),也就是程式碼中缺少的「2」的段落)!這是由於 OpenNI 1.1(介紹)開始可以自己去抓他的輸出模式,所以如果是採用裝置的預設值的話,就可以省略這部分的設定了。

  2. 可以用的手勢名稱,可以透過 xn::GestureGenerator 的函式 EnumerateGestures() 來取得;下面是簡單的範例:

    // pre-allocate data
    XnUInt16 uNum = 10;
    XnChar** asName = new XnChar*[uNum];
    for( unsigned int i = 0; i < uNum; i )
    asName[i] = new XnChar[100];

    // get data
    eRes = mGesture.EnumerateGestures( *asName, uNum );

    // output data
    cout << "There are " << uNum << " available gestures:" << endl;
    for( unsigned int i = 0; i < uNum; i )
    cout << asName[i] << endl;

33 thoughts on “OpenNI 的手勢偵測”

  1. 我將 Kinect 的初始程式全部加上,此程式就能正常運作了感謝 Heresy 大大的耐心指導@@

  2. 感謝Heresy大大不厭其煩得貢獻也希望能有更多這方面的資料或範例謝謝^^:D

  3. 小弟想請教heresy大大一个問題怎麽樣能實現一個動作觸發輸出一行字呢?比如用“Wave”的返回函數值輸出一行字“Wave recognized”

  4. 這個範例程式已經是在做這件事了。GRecognized() 這個函式,就是在便是到手勢的時候做輸出之用,只是目前的輸出字串和你要的不一樣而已。

  5. 您好 想請教一下 如果想要自己增加手勢的話不知道您是否有看到有國外的網站有提到如何做??或者是該如何修改middleware的部分??

  6. to kl40 NITE 所提供的 middle ware 應該還是沒有辦法做到,而且由於 NITE 本身並非 OpenSource 的形式,所以也無法修改,有需要的話是必須要自己寫一個模組。相關的討論在官方論壇裡也滿多的,建議可以去看看。

  7. 如何多次識別手勢?譬如招手了,過一會再招手。是否能夠多次觸發 GRecognized 囘調。

  8. to Hello!您好,理論上他是會多次觸發的。但是如同文章裡的說明,Wave 這個動作似乎比較不會觸發到 GestureRecognized 這個事件,建議改用 GestureProgress 試試看。

  9. GestureProgress 也不怎麼觸發啊,時有時無。你推薦使用FAAST嗎。謝謝。

  10. @Hello!那比較可能就是所做的動作比較無法觸發到 NITE 的辨識條件了。基本上,個人也是覺得 NITE 在手勢辨識上並沒有很好。FAAST 是把動作對應到鍵盤滑鼠等輸入設備的,沒有什麼推薦不推薦,只是看你有沒有需求。

  11. 在你那邊測試的話,多次的揮手動作,會觸發多次囘調函數嗎。不好意思,我是大陸的,所以名詞都是大陸版本。希望你能明白我的問題。

  12. 你好!我這邊如果要觸發多次Wave的話,必須LostUser之後,才能再次識別偵測手勢。請問這可能是什麽原因導致的呢。

  13. @Hello!您好。Heresy 這邊有又試過了,在 Heresy 這邊,以最新版的 OpenNI 和 NITE,並沒有你所說的問題。不知道你以本篇的程式碼執行測試的話,也會有一樣著狀況嗎?

  14. 謝謝您的測試。我這邊找到原因了。原來是不小心將Wave手勢,註冊為QuickRefoucs姿勢所導致的。

  15. 您好,我又来了:D

    我测试了您的代码,kinect检测手势很容易出错啊

    大部分都是
    RaiseHandfrom(65.5409,36.2546,591.803)to(65.5409,36.2546,591.803)
    RaiseHandfrom(33.6097,24.4943,585.772)to(33.6097,24.4943,585.772)

    有时候有
    Wave:0.5at(-0.349738,12.8995,578.252)
    Clickfrom(-20.6336,24.6686,992.673)to(-17.8883,19.0541,998.862)

    但是没有MovingHand

    而且,检测好快啊,我一个动作,检测出来却是十几个RaiseHandfrom

    您有没有考虑过sleep一下呢?

  16. 您好,再问您个问题:D

    您的代码是2011年的,现在是2012年。

    除了这四个手势,今年还有没有更新其他的手势呢?如何查看?

    谢谢

  17. to 林雄民

    手勢的支援數量主要是看 middleware(NITE)的支援程度,列舉方法請參考附註 2。

    而這個程式本身就只是使用範例而已,所以不會去考慮他實際上應用所需的狀況,因為個別程式所需要的狀況也是不同的,請自行視需要做調整。

  18. 您好,我想请教一个问题:我想不通过动作就能得到手的位置信息,也就是我手只要放在kinect面前就能获取坐标,请问改该怎样实现呢?谢谢

  19. to 超级小强

    OpenNI 目前應該沒有提供這樣的功能,必須要自己實作。
    一個方法,就是假設手是最近的東西,然後從 depth map 裡把最近的物體抓出來。

  20. 您说想要增加新的手勢就要自己寫一個模組,那我是不是可以用opencv来写相关的手势定义

  21. to 新之助
    基本上,如果要自己寫模組的話,要用什麼函式庫應該都不會有什麼大問題。

  22. 你好,请问那个手势识别后的position的值是什么意思,就是电脑屏幕的鼠标坐标吗,如果不是,怎么变成屏幕上的坐标 呢

  23. 我怎么运行不出来呢?
    [下面的框架可能不正确和/或缺失,没有为 OpenNI.dll 加载符号]
    >xn::GestureGenerator::EnumerateGestures(char * & astrGestures, unsigned short & nGestures) 行 2871 0x17 字节 C

    请问大师怎么回事啊??

  24. xn::GestureGenerator mGesture
    eResult=mGesture.Create(mContext)

    创建失败怎么回事?

  25. to 非专业游客

    抱歉,由於不知道所謂的「下面的框架可能不正确和/或缺失」對應到英文版的錯誤訊息是什麼…
    所以請問一下,這是編譯時出錯?還是在連結的時候出錯?抑或是執行時才出錯?
    你使用的OpenNI 是哪一版?

  26. to 游客

    建議請檢察 eResult 代表的意義,藉此判別失敗的原因。

  27. 我想问下检测出来的raiseHand等等的坐标是怎么算的,为什么X,Y会有负值。而且我试着把检测到的坐标画在RGB图像上完全无法定位手的位置!能否告诉我如何将手的位置标定在图像上?谢谢

發佈留言

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