OpenNI 的手勢偵測


關於 OpenNI 程式開發的文章停了好一陣子了,這篇算是再繼續開始吧~不過,接下來還會寫幾篇,Heresy 自己也不知道就是了。 ^^"

在使用 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;

OpenNI / Kinect 相關文章目錄

廣告

關於 Heresy
https://kheresy.wordpress.com

66 Responses to OpenNI 的手勢偵測

  1. jays says:

    Heresy老師你好我是工業產品設計系的學生
    目前我是使用Kinect1+OpenNI+processing2.2.1
    文章都大致看過了一遍
    已經開啟了攝像頭也都測試過simpleopenNI的範本
    因為本身沒有學過寫程式
    想請問如果我想用手部骨架的辨識讓雙手手掌靠近
    雙手碰觸到後會啟動arduino驅動步進馬達
    關於Kinect的部分我要看哪一篇會比較適合呢
    抱歉打擾了謝謝

    喜歡

    • Heresy says:

      你好,Heresy 這邊寫得基本上都是 OpenNI + C++ 的開發,使用 processing 的方式 Heresy 沒有研究。

      喜歡

  2. Baldur Liao says:

    您好:
    請問一下XnPoint3D對應到openni2.2有沒有類似的函數?

    喜歡

    • Heresy says:

      XnPoint3D 基本上只是一個用來記錄三度空間點位的資料結構。
      在 OpenNI 2 應該是沒有定義這樣的資料型別,但是 NiTE2 裡面有。

      喜歡

  3. Vincent says:

    Heresy大你好,
    目前正在研究如何使用Kinect+OpenNI+Opencv實現如何使用手指將一張照片縮放與旋轉,現在只能實現指尖的五個圈,不知該如何進行?故想請教您給我一個方向!謝謝!
    因為我是一個很嫩的初學者,所以很多不懂,煩請不要見怪!

    喜歡

    • Heresy says:

      你如果已經抓到手指的話,接下來就是要去自己定義手指的動作代表的意義。
      在怎樣的情況下是開始操作、怎樣結束,如何縮放、旋轉;基本上這些都是要自己針對手指的位置來做判斷的。

      喜歡

      • Vincent says:

        不好意思,我是個初學者,請問該如何定義手指的動作?及定義後如何用動作去操作縮放一張照片?可否提供一些文章或方向可參考?
        謝謝!

        喜歡

        • Heresy says:

          基本上,就是要自己針對手指的位置去作計算和判斷,看在怎樣的條件下、要做那些處理。
          例如其中兩隻手指往相反方向移動、就代表是要放大,諸如此類。

          喜歡

          • Vincent says:

            謝謝你!我再試試看!有問題再請教您!

            喜歡

  4. 引用通告: NiTE 2 的手勢偵測 « Heresy's Space

  5. 引用通告: Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别) « 成人免费资源三级分享网站

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: