读书人

Kinect开发课程四:用Kinect控制鼠标玩

发布时间: 2012-07-05 07:59:17 作者: rapoo

Kinect开发教程四:用Kinect控制鼠标玩水果忍者PC版

??? 最近Kinect连接Xbox玩水果忍者的视频非常红火,可惜小斤只有本本和Kinect,没法玩Xbox上的体感游戏。幸运的是,寻寻觅觅后,小斤发现水果忍者有PC版本,既然上一个教程我们已经可以让Kinect认出我们手势,在这基础上,我们用手来控制鼠标,就可以在PC上玩咯!

?

???视频地址:http://v.youku.com/v_show/id_XMjk2OTU3MjYw.html,徒手切还需要多练练。

?

??? 上个教程,我们通过RaiseHand来捕捉举起后手的位置,于是小斤决定,用RaiseHand来触发鼠标移动事件,用Click来触发鼠标单击,但测试结果不让人满意,鼠标移动一卡一卡的,原因是RaiseHand识别需要时间,达不到实时的标准,怎么办呢?小斤翻阅了OpenNI的文档,找到了tracking的相关API。这样,在我们识别出手后,使用跟踪的办法得到手的实时位置,移动鼠标的问题迎刃而解!这就好比在茫茫人海中,跟着一个人走比找到一个人更容易!

?

??? 因为这个教程代码量稍微多了点,小斤就不一股脑全抛上来了,先上主函数,再解释回调函数。

?

???以下是Main.cpp的内容:

?

#include <stdlib.h>#include <iostream>#include "opencv/cv.h"#include "opencv/highgui.h"#include <XnCppWrapper.h>#include "KinectGesture.h"#include "Appmessage.h"using namespacestd;using namespacecv;//Generatorxn::GestureGeneratorgestureGenerator;xn::HandsGeneratorhandsGenerator;xn::ImageGeneratorimageGenerator;int isRealMouseControl=0;//【1】// main functionvoid main(){    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);    CvFont font;    cvInitFont( &font,CV_FONT_VECTOR0,1, 1, 0, 3, 5);    XnStatus res;    char key=0;    // Context    xn::Context context;    res = context.Init();    xn::ImageMetaDataimageMD;    // Generator    res = imageGenerator.Create( context);    res = gestureGenerator.Create( context);    //【2】    res=handsGenerator.Create(context);    // Add gesture    gestureGenerator.AddGesture("Wave", NULL);    gestureGenerator.AddGesture("Click", NULL);    // Register callback functions    XnCallbackHandle gestureCBHandle;    XnCallbackHandle handsCBHandle;    gestureGenerator.RegisterGestureCallbacks(GRecognized, GProgress,(void*)drawPadImg,gestureCBHandle );    //【3】    handsGenerator.RegisterHandCallbacks(Hand_Create, Hand_Update,Hand_Destroy, (void*)drawPadImg, handsCBHandle);    // Start generate    context.StartGeneratingAll();    res = context.WaitAndUpdateAll();     while( (key!=27) && !(res = context.WaitAndUpdateAll())  )    {        res = context.WaitAndUpdateAll();       imageGenerator.GetMetaData(imageMD);       memcpy(cameraImg->imageData,imageMD.Data(),640*480*3);       cvCvtColor(cameraImg,cameraImg,CV_RGB2BGR);       cvPutText(drawPadImg,"Wave Your Hand to Start Tracking",cvPoint(20, 20), &font, CV_RGB(255,0,0));       cvShowImage("Gesture",drawPadImg);       cvShowImage("Camera",cameraImg);       key=cvWaitKey(20);       switch(key){              case 'c':                  clearImg(drawPadImg);                  break;              //【4】              case 'm'://simulate real mouse                  isRealMouseControl=1-isRealMouseControl;                  break;              default:                  if(key != -1) printf("You Press%d\n",key);       }    }    cvDestroyWindow("Gesture");    cvDestroyWindow("Camera");    cvReleaseImage(&drawPadImg);    cvReleaseImage(&cameraImg);    context.StopGeneratingAll();    context.Shutdown();}

?

【1】程序执行后,窗体和显示和上一个手势识别的例程是一样的,由于要使用mouse_event来控制鼠标,小斤选择了MFC框架,主要是一个Dialog和一个按钮。由于我们的程序执行时,是使用OpenCV的highgui进行图像显示的,这里点击窗体上按钮后创建并开始线程,线程函数KinectGestureMain(),也就是这里的主函数。

?

【2】KinectGestureMain()中的内容,大部分和上一个例程是一样的,在【2】这里,小斤创建了一个HandsGenerator,这个生成器主要帮我们负责跟踪的工作。它的创建方法和其它的生成器是一样的,传一个Context给Create()方法。

?

【3】与GestureGenerator类似,我们需要为HandsGenerator注册回调函数:

XnStatusxn::HandsGenerator::RegisterHandCallbacks ( HandCreate  CreateCB,  HandUpdate UpdateCB,  HandDestroy  DestroyCB, void *  pCookie,  XnCallbackHandle &  hCallback ) 

?

??? 其中定义HandCreate是一个新的手(跟踪)被创建时调用的,HandDestroy 则相反,在手消失后被调用,UpdateCB在手变化位置时被调用。另外,pCookie是传给回调函数的指针,可以放一些用户数据,小斤把程序的画板图像指针传入,这样可在回调函数中直接绘图了。phCallback是一个回调函数的handle,可用来注销回调函数。

?

【4】这边设定了,按m键,进入鼠标控制模式。

??? 其它代码都和上一个例程差不多,我们来看看回调函数。

// callback function for gesture recognizedvoid XN_CALLBACK_TYPEGRecognized( xn::GestureGenerator &generator,                              const XnChar *strGesture,                              const XnPoint3D *pIDPosition,                              const XnPoint3D *pEndPosition,                              void *pCookie ){    int imgStartX=0;    int imgStartY=0;    int imgEndX=0;    int imgEndY=0;    //【5】    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,"Wave")==0)    {       cvLine(refimage,cvPoint(imgStartX,imgStartY),cvPoint(imgEndX,imgEndY),CV_RGB(255,255,0),6);       //【6】       handsGenerator.StartTracking(*pEndPosition);    }    else if(strcmp(strGesture,"Click")==0)    {       cvCircle(refimage,cvPoint(imgStartX,imgStartY),6,CV_RGB(0,0,255),12);       //【7】       if(isRealMouseControl)       {           messageHandler(cvPoint(imgStartX,imgStartY),0,REAL_MOUSE_CLICK);       }    }}// callback function forgesture progressvoid XN_CALLBACK_TYPEGProgress( xn::GestureGenerator &generator,                            const XnChar *strGesture,                            const XnPoint3D *pPosition,                            XnFloat fProgress,                            void *pCookie ){}

?

【5】由于pIDPosition和pEndPosition的坐标,是以屏幕中心为(0,0)点的坐标系,在OpenCV中的显示,是以屏幕左上角为(0,0)点,所以这里做了一个转换。

?

【6】这一段代码,我们的GestureGenerator识别出“挥动”手势后,调用了HandsGenerator的StartTracking()方法来开始在pEndPosition这个位置跟踪手,pEndPosition便是“挥动”“手势的结束位置。

?

【7】中,如果识别出了“前推”手势,并开启了鼠标控制模式,就调用小斤定义的messageHandler()方法模拟鼠标点击。这个messageHandler()待会会在AppMessage.cpp中实现。

?

?? 接着,小斤实现了Hands相关的回调函数:

//【8】void XN_CALLBACK_TYPEHand_Create(xn::HandsGenerator& generator,XnUserID nId,const XnPoint3D*pPosition, XnFloatfTime, void*pCookie){    addTrackingId(nId);} void XN_CALLBACK_TYPEHand_Update(xn::HandsGenerator& generator,XnUserID nId,const XnPoint3D*pPosition, XnFloatfTime, void*pCookie){    int imgPosX=0;    int imgPosY=0;    char locationinfo[100];    imgPosX=(int)(640/2-(pPosition->X));    imgPosY=(int)(480/2-(pPosition->Y));    IplImage* refimage=(IplImage*)pCookie;    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));    if(isRealMouseControl)    {       sprintf(locationinfo,"MouseCtrl: %dth HandLoc: %d,%d",nId,(int)pPosition->X,(int)pPosition->Y);    }    else    {       sprintf(locationinfo,"Normal: %dth HandLoc: %d,%d",nId,(int)pPosition->X,(int)pPosition->Y);    }    cvPutText(refimage,locationinfo ,cvPoint(30,30), &font, CV_RGB(0,0,0));    cvResetImageROI(refimage);    CvPoint thisLocation=cvPoint(imgPosX,imgPosY);    //【9】    if(isRealMouseControl)    {       //cvCircle(refimage,cvPoint(imgPosX,imgPosY),1,CV_RGB(255,0,0),2);       messageHandler(thisLocation,nId,REAL_MOUSE_MOVE);    }    else    {       cvCircle(refimage,cvPoint(imgPosX,imgPosY),1,CV_RGB(255,0,0),2);    }}void XN_CALLBACK_TYPEHand_Destroy(xn::HandsGenerator& generator,XnUserID nId,XnFloat fTime,void* pCookie){    //printf("Lost Hand: %d\n", nId);    removeTrackingId(nId);}

?

【8】中的Hand_Create()就是CreateCB,该回调函数定义如下:

void XN_CALLBACK_TYPEHand_Create(xn::HandsGenerator& generator,XnUserID nId,const XnPoint3D*pPosition, XnFloatfTime, void*pCookie)

?

??? 其中,generator指定触发Hands的生成器。

??? nId为被创建的手(跟踪)的id,这个id会随着手的不断创建和消失增加,用以标识每一个手。

??? pPosition是手当前的位置,fTime是一个Timestamp,而pCookie就是用户传入的数据指针了。

??? 在该方法中,小斤使用了自己定义的addTracking()方法,该方法也在AppMessage.cpp中实现。小斤在该文件中维护了一个数组,用以标识每个id的手是否在跟踪状态。

【9】Hand_Update()和Hand_Destroy()的参数和Hand_Create()是一样的,除去一堆绘图过程之外,小斤使用messageHandler()方法模拟鼠标移动。

以下是AppMessage.cpp的内容:

?

#include "AppMessage.h"//Location and move anglelast time for each userId(Hand Id)CvPoint lastLocation[MAX_HAND_NUM];int isHandTracking[MAX_HAND_NUM]={0};int isClickDown=0;void addTrackingId(int userId){    isHandTracking[userId]=1;}void removeTrackingId(int userId){    isHandTracking[userId]=0;}CvPoint getLastLocation(int userId){    return lastLocation[userId];}void messageHandler(CvPoint &location,int userId,int flag){    //initialize the lastLocation from the location obtained bythe first time    if(lastLocation[userId].x==0&&lastLocation[userId].y==0)    {       lastLocation[userId].x=location.x;       lastLocation[userId].y=location.y;    }       if(flag==REAL_MOUSE_CLICK)       {           if(!isClickDown)           {              mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);           }           else {              mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);                }           isClickDown=1-isClickDown;       }       else if(flag==REAL_MOUSE_MOVE)       {       //【10】           int firstHandId=-1;           for(int i=0;i<MAX_HAND_NUM;i++)           {              if(isHandTracking[i]!=0)              {                  if(firstHandId==-1)                  {                     firstHandId=i;                     break;                  }              }           }           if(abs(location.x-lastLocation[userId].x)<5)           {              location.x=lastLocation[userId].x;           }           if(abs(location.y-lastLocation[userId].y)<5)           {              location.y=lastLocation[userId].y;           }           //【11】           if(userId==firstHandId)           {                                 mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,                  (location.x-160)*65536/640*2,(location.y-120)*65536/480*2,0,0);              }            }       lastLocation[userId].x=location.x;       lastLocation[userId].y=location.y;}

?

【10】根据程序维护的isHandTracking数组,找到被跟踪的第一个手。

?

【11】考虑到可能有多个手被捕捉并跟踪的情况,先来先得,这里小斤让鼠标只听第一个出现的手的指挥,进行移动。

?

??? 这里使用了MOUSEEVENTF_ABSOLUTE,所谓的绝对坐标,就是把计算机屏幕定义为65536*65536个点的坐标系。如果分辨率为640*480,想把鼠标移动到屏幕正中,就可以调用mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,320/640*65536,240/480*65536,0,0);

?

??? 这段代码中,由于OpenNI输出的分辨率为640*480,而小斤习惯抬起右手来切水果,所以只使用了右半边区域来控制鼠标,同时做了些微调,大家可以根据自己的喜好来设置。

?

? ??另外,在水果忍者PC版中,切的动作是按住鼠标左键(MOUSE_LEFTDOWN)并移动(MOUSE_MOVE)来触发的。因此,小斤在程序中设定使用push手势来切换MOUSE_LEFTDOWN和MOUSE_LEFTUP。进入游戏画面后,push一下,使鼠标处于MOUSE_LEFTDOWN状态,屏幕就会出现刀光剑影咯,不想玩的时候,再push一下即可。


??? 好了,小斤要去切几盘水果休息一下咯,希望这篇文章对大家有所帮助和启发。

?

?

??? 如果懒得建工程贴代码,本教程中的源程序,可以点此下载。

??? 另外还有一个MFC的版本,点这里下载。

?

?

Kinect开发课程四:用Kinect控制鼠标玩水果忍者PC版

?

?


?

----------------------------------

作者:小斤(陈忻)

本文属于原创文章,如需转载引用请注明原文作者和链接,谢谢。

1 楼 tianzhilishu 2012-02-22 我使用过opencv,看到你的程序非常的激情,希望能够获得你的帮助~大神求带QQ:1073179095

读书人网 >开源软件

热点推荐