這是第二個範例,小斤將介紹如何使用OpenNI讓Kinect識別出手勢,並顯示輸出。目前版本的OpenNI支援四種手勢:RaiseHand, Wave, Click和MovingHand,分別代表手的“舉起”,“揮動”,“前推”和“移動”四種動作。值得一提的是,當前微軟官方的Kinect SDK還不支援手勢識別,也可以說是使用OpenNI的好處之一吧。
有了手勢識別後,其實可以利用Kinect做一些實際的東東,比如利用滑鼠鍵盤控制一些應用程式等等。
閑話不說了,速度搞起。代碼一部分參考了Heresy童鞋的文章,手勢部分寫得很完備了,在巨人的肩膀上,小斤使用OpenCV進行顯示,紅色點表示手的位置軌跡,藍色實心圈表示前推的位置,黃色直線表示手揮動的軌跡(從起點到終點)。
#include <stdlib.h>#include <iostream>#include "opencv/cv.h"#include "opencv/highgui.h"#include <XnCppWrapper.h>using namespace std;using namespace cv;// output for XnPoint3Dostream& operator<<( ostream& out, const XnPoint3D& rPoint ){out << "(" << rPoint.X << "," << rPoint.Y << "," << rPoint.Z << ")";return out;}//【4】// callback function for gesture recognizedvoid XN_CALLBACK_TYPE gestureRecog( xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie ){cout << strGesture<<" from "<<*pIDPosition<<" to "<<*pEndPosition << endl;int imgStartX=0;int imgStartY=0;int imgEndX=0;int imgEndY=0;char locationinfo[100];imgStartX=(int)(640/2-(pIDPosition->X));imgStartY=(int)(480/2-(pIDPosition->Y));imgEndX=(int)(640/2-(pEndPosition->X));imgEndY=(int)(480/2-(pEndPosition->Y));IplImage* refimage=(IplImage*)pCookie;if(strcmp(strGesture,"RaiseHand")==0){cvCircle(refimage,cvPoint(imgStartX,imgStartY),1,CV_RGB(255,0,0),2);}else if(strcmp(strGesture,"Wave")==0){cvLine(refimage,cvPoint(imgStartX,imgStartY),cvPoint(imgEndX,imgEndY),CV_RGB(255,255,0),6);}else if(strcmp(strGesture,"Click")==0){cvCircle(refimage,cvPoint(imgStartX,imgStartY),6,CV_RGB(0,0,255),12);}cvSetImageROI(refimage,cvRect(40,450,640,30));CvFont font;cvInitFont( &font, CV_FONT_VECTOR0,1, 1, 0, 3, 5);cvSet(refimage, cvScalar(255,255,255));sprintf(locationinfo,"From: %d,%d to %d,%d",(int)pIDPosition->X,(int)pIDPosition->Y,(int)(pEndPosition->X),(int)(pEndPosition->Y));cvPutText(refimage, locationinfo ,cvPoint(30, 30), &font, CV_RGB(0,0,0));cvResetImageROI(refimage);}void clearImg(IplImage* inputimg){CvFont font;cvInitFont( &font, CV_FONT_VECTOR0,1, 1, 0, 3, 5);memset(inputimg->imageData,255,640*480*3);cvPutText(inputimg, "Hand Raise!" ,cvPoint(20, 20), &font, CV_RGB(255,0,0));cvPutText(inputimg, "Hand Wave!" , cvPoint(20, 50), &font, CV_RGB(255,255,0));cvPutText(inputimg, "Hand Push!" , cvPoint(20, 80), &font, CV_RGB(0,0,255));}//【5】// callback function for gesture progressvoid XN_CALLBACK_TYPE gestureProgress( xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie ){cout << strGesture << ":" << fProgress << " at " << *pPosition << endl;}int main( int argc, char** argv ){IplImage* drawPadImg=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);IplImage* cameraImg=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);cvNamedWindow("Gesture",1);cvNamedWindow("Camera",1);clearImg(drawPadImg);XnStatus res;char key=0;// contextxn::Context context;res = context.Init();xn::ImageMetaData imgMD;// create generator xn::ImageGenerator imageGenerator;res = imageGenerator.Create( context ); //【1】xn::GestureGenerator gestureGenerator;res = gestureGenerator.Create( context ); //【2】// Add gesture//gestureGenerator.AddGesture( "MovingHand", NULL );gestureGenerator.AddGesture( "Wave", NULL );gestureGenerator.AddGesture( "Click", NULL );gestureGenerator.AddGesture( "RaiseHand", NULL );//gestureGenerator.AddGesture("MovingHand",NULL); /【3】// 6. Register callback functions of gesture generatorXnCallbackHandle handle;gestureGenerator.RegisterGestureCallbacks( gestureRecog, gestureProgress, (void*)drawPadImg, handle );//start generate datacontext.StartGeneratingAll();res = context.WaitAndUpdateAll(); while( (key!=27) && !(res = context.WaitAndUpdateAll()) ) { if(key=='c'){clearImg(drawPadImg);}imageGenerator.GetMetaData(imgMD);memcpy(cameraImg->imageData,imgMD.Data(),640*480*3);cvCvtColor(cameraImg,cameraImg,CV_RGB2BGR);cvShowImage("Gesture",drawPadImg);cvShowImage("Camera",cameraImg);key=cvWaitKey(20);}cvDestroyWindow("Gesture");cvDestroyWindow("Camera");cvReleaseImage(&drawPadImg);cvReleaseImage(&cameraImg);context.StopGeneratingAll();context.Shutdown();return 0;}
【1】看過上一篇文章的童鞋應該已經瞭解了產生器的用法(傳送門),對於手勢識別,小斤使用了GestureGenerator這個產生器,它的建立方式與ImageGenerator一致,使用Context作為參數調用Create方法。
【2】GestureGenerator建立後,只有我們告訴它,要識別什麼手勢它才幹活。這邊使用AddGesture方法,把手勢加入。
【3】這一步小斤註冊了GestureGenerator相關的回呼函數:
XnStatus xn::GestureGenerator::RegisterGestureCallbacks (GestureRecognized RecognizedCB, GestureProgress ProgressCB, void * pCookie, XnCallbackHandle & hCallback )
其中定義RecognizedCB是手勢識別後的回呼函數,ProgressCB是手勢進行中的回呼函數。
pCookie是傳給回呼函數的指標,可以放一些使用者資料,代碼中,小斤把程式的畫板映像指標傳給了回呼函數,這樣便可以在回呼函數中直接繪圖了。
phCallback是一個回呼函數的handle,可用來登出回呼函數,登出的方法是調用UnregisterGestureCallbacks ( XnCallbackHandle hCallback ) 。
【4】這個gestureRecog()便是RecognizedCB回呼函數的真身了,它有五個參數:
generator指定發現手勢的產生器。
strGesture告訴我們哪種手勢已被識別。
pIDPosition和pEndPosition代表了該手勢的起始位置和終止位置。對於RaiseHand的手勢,兩個值是一樣的,但對於Click和Wave手勢,由於手的位置是發生過變化的,值是不同的。
pCookie是傳入的使用者資料。
在【4】中,小斤針對不同的手勢進行了繪圖,此外,還顯示了手的當前pIDPosition和pEndPosition。
【5】gestureProgress()回呼函數與gestureRecog()類似,因為是在手勢進行中被調用的,它只有一個position,表示當前手的位置,此外,還有一個fProgress表示當前的進度。
程式編譯執行後,效果如下:
小斤這邊測試結果是,RaiseHand最容易觸發,基本上都在RecognizedCB中出現,而MovingHand沒有被觸發過。Click和Wave手勢在RecognizedCB和ProgressCB都可能觸發,雖然Wave手勢的軌跡不一定準確,但Wave軌跡的用處不大。
如果組合使用RaiseHand,Click和Wave,通過一些位置判定和調整,實現應用程式的部分控制是沒有多大問題的,大家自由發揮想象吧,有好點子歡迎討論!
----------------------------------
作者:小斤(陳忻)
本文屬於原創文章,如需轉載引用請註明原文作者和連結,謝謝。