前言
因為OpenNI可以獲取到kinect的深度信息,而深度信息在手勢識別中有很大用處,因此本文就來使用OpenNI自帶的類來做簡單的手勢識別。識別的動作為4種,揮手,手移動,舉手,往前推手。通過后面的實驗可以發現,其實提供的類的效果非常不好。
開發環境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.2
實驗說明
跟手勢相關的是GestureGenerator這個類,它的初始化過程和depth_metadata,image_metadata都一樣,因此首先在上2篇文章的COpenNI類中增加一個public類對象GestureGenerator gesture_generator;為什么不放在private里呢?因為我們的COpenNI對象需要調用這個變量來設置手勢獲取的一些屬性,比如手勢識別的種類等,總之就是這個變量外部需要能夠訪問得到,因此這里我將其放在public里面。另外在COpenNI類的Init()函數中需要加入下面的代碼:
status = gesture_generator.Create(context); if(CheckError("Create gesture generator error!")) { return false; } /*添加手勢識別的種類*/ gesture_generator.AddGesture("Wave", NULL); gesture_generator.AddGesture("click", NULL); gesture_generator.AddGesture("RaiseHand", NULL); gesture_generator.AddGesture("MovingHand", NULL);
OpenNI進行手勢識別的方式是采用函數回調,即如果一個手勢發生了或者正在發生時可以觸發相應的回調函數,從而去執行回調函數,這有點類似於Qt中的信號與槽的關系。在OpenNI中設置回調函數的原型為:
XnStatus RegisterGestureCallbacks(GestureRecognized RecognizedCB, GestureProgress ProgressCB, void* pCookie, XnCallbackHandle& hCallback);
其中前2個參數為回調函數,第一個回調函數表示手部某個動作已經執行完畢,第二個參數表示收部某個動作正在執行;參數三為一個空指針,即可以指向任何數據類型的指針,其作用為給回調函數當額外的參數使用;參數四為回調函數的處理函數,用來記錄和管理回調函數的。參數三在本實驗中設置為NULL,參數四實際上本實驗中也沒有用到。
上面2個回調函數的名稱可以自定義,但是這2個函數參數的個數和類型不能改變,這2個回調函數的參數個數都為5,但是其類型有些不同,具體的可以參考后面提供的代碼。
由於在程序中添加了4種動作的捕捉,所以打算在檢測到某個手勢動作時,在窗口顯示欄的圖片上添加相應的手勢動作文字提示。很明顯,只有當手勢檢測到時才能在圖片上添加文字,該部分在回調函數中實現。但是如果我們單獨在回調函數中給圖片添加相應的文字,然后在主程序中顯示圖片,則因為回調函數一結束完就回到了主函數的while循環中,而這時圖片的內容已經更新了(即有文字的圖片被重新覆蓋了),因此人眼一瞬間看不到有文字提示的圖片。最后個人的解決方法是用一個標志來表示檢測到了某個手勢動作,如果檢測到了則顯示存儲下來的有文字的圖片,反正,顯示正常的圖片。本程序提供的圖片為深度圖。
實驗結果
舉手的顯示結果如下:
其實從本人的實驗過程來看,大部分的手勢動作都被檢測為舉手RaiseHand,少部分為揮手Wave,其它的基本上沒出現過。說明OpenNI自帶的手勢識別類的功能不是很強。
實驗主要部分代碼及注釋(附錄有實驗工程code下載鏈接):
copenni.cpp:
#include <XnCppWrapper.h> #include <QtGui/QtGui> #include <iostream> using namespace xn; using namespace std; class COpenNI { public: ~COpenNI() { context.Release();//釋放空間 } bool Initial() { //初始化 status = context.Init(); if(CheckError("Context initial failed!")) { return false; } context.SetGlobalMirror(true);//設置鏡像 //產生圖片node status = image_generator.Create(context); if(CheckError("Create image generator error!")) { return false; } //產生深度node status = depth_generator.Create(context); if(CheckError("Create depth generator error!")) { return false; } //視角校正 status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator); if(CheckError("Can't set the alternative view point on depth generator!")) { return false; } status = gesture_generator.Create(context); if(CheckError("Create gesture generator error!")) { return false; } /*添加手勢識別的種類*/ gesture_generator.AddGesture("Wave", NULL); gesture_generator.AddGesture("click", NULL); gesture_generator.AddGesture("RaiseHand", NULL); gesture_generator.AddGesture("MovingHand", NULL); return true; } bool Start() { status = context.StartGeneratingAll(); if(CheckError("Start generating error!")) { return false; } return true; } bool UpdateData() { status = context.WaitNoneUpdateAll(); if(CheckError("Update date error!")) { return false; } //獲取數據 image_generator.GetMetaData(image_metadata); depth_generator.GetMetaData(depth_metadata); return true; } public: DepthMetaData depth_metadata; ImageMetaData image_metadata; GestureGenerator gesture_generator;//外部要對其進行回調函數的設置,因此將它設為public類型 private: //該函數返回真代表出現了錯誤,返回假代表正確 bool CheckError(const char* error) { if(status != XN_STATUS_OK ) { QMessageBox::critical(NULL, error, xnGetStatusString(status)); cerr << error << ": " << xnGetStatusString( status ) << endl; return true; } return false; } private: XnStatus status; Context context; DepthGenerator depth_generator; ImageGenerator image_generator; };
main.cpp:
#include <QCoreApplication> #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/core/core.hpp> #include "copenni.cpp" #include <iostream> using namespace cv; using namespace xn; Mat depth_image; Mat depth_image_result;//深度結果圖,且在該圖上顯示手勢動作的類型 COpenNI openni; bool test_flag = false; // callback function for gesture recognized //回調函數,該函數的函數名字可以隨便取,但是其參數的格式必須不能改變 //這里該函數的作用是表示上面4種手勢發生完成后調用 void XN_CALLBACK_TYPE GRecognized ( xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie ) { depth_image_result = depth_image.clone(); putText(depth_image_result, strGesture, Point(50, 150), 3, 0.8, Scalar(255, 0, 0), 2 ); test_flag = true; } // callback function for gesture progress //該函數表示上面4種手勢某一種正在發生時調用 void XN_CALLBACK_TYPE GProgress ( xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie ) { ; } int main (int argc, char **argv) { if(!openni.Initial()) return 1; XnCallbackHandle handle; openni.gesture_generator.RegisterGestureCallbacks(GRecognized, GProgress, NULL, handle); if(!openni.Start()) return 1; namedWindow("depth image", CV_WINDOW_AUTOSIZE); putText(depth_image, "YES!", Point(50, 150), 3, 0.8, Scalar(255, 0, 0), 2 ); while(1) { if(!openni.UpdateData()) { return 1; } /*獲取並顯示深度圖像,且這2句代碼不能放在回調函數中調用,否則后面的imshow函數會因為執行時找不到圖片(因為此時回調函數不一定執行了)而報錯*/ Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(), CV_16UC1, (char *)openni.depth_metadata.Data());//因為kinect獲取到的深度圖像實際上是無符號的16位數據 depth_image_src.convertTo(depth_image, CV_8U, 255.0/8000); if(!test_flag) imshow("depth image", depth_image); else imshow("depth image", depth_image_result); waitKey(30); test_flag = false; } }
錯誤總結
如果用Qt的控制台建立程序,運行程序時出現下面的錯誤提示:
這是因為控制台程序不能使用Qt的界面(本程序中使用了QMessageBox),因此需要在工程pro的代碼中把QT – gui給去掉,否則會報類似的這種錯誤。
如果是在OpenCV中出現如下錯誤:
則表示是imshow函數需要還來不及顯示完成就被其它的函數給中斷了,這可能在回調函數中出現這種情況。
實驗總結
通過本次實驗對OpenNI自帶的手勢識別類的使用有了初步的了解。
參考資料:
附錄:實驗工程code下載。