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的内容:

[cpp]  view plain copy
  1. #include <stdlib.h>  
  2. #include <iostream>  
  3. #include "opencv/cv.h"  
  4. #include "opencv/highgui.h"  
  5. #include <XnCppWrapper.h>  
  6. #include "KinectGesture.h"  
  7. #include "Appmessage.h"  
  8.   
  9. using namespacestd;  
  10. using namespacecv;  
  11.   
  12. //Generator  
  13. xn::GestureGenerator gestureGenerator;  
  14. xn::HandsGenerator handsGenerator;  
  15. xn::ImageGenerator imageGenerator;  
  16. int isRealMouseControl=0;  
  17.   
  18. //【1】  
  19. // main function  
  20. void main()  
  21. {  
  22.     IplImage* drawPadImg=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);  
  23.     IplImage* cameraImg=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);  
  24.    
  25.     cvNamedWindow("Gesture",1);  
  26.     cvNamedWindow("Camera",1);  
  27.   
  28.     clearImg(drawPadImg);  
  29.   
  30.     CvFont font;  
  31.     cvInitFont( &font,CV_FONT_VECTOR0,1, 1, 0, 3, 5);  
  32.   
  33.     XnStatus res;  
  34.     char key=0;  
  35.   
  36.     // Context  
  37.     xn::Context context;  
  38.     res = context.Init();  
  39.     xn::ImageMetaData imageMD;  
  40.   
  41.     // Generator  
  42.     res = imageGenerator.Create( context);  
  43.     res = gestureGenerator.Create( context);  
  44.     //【2】  
  45.     res=handsGenerator.Create(context);  
  46.   
  47.     // Add gesture  
  48.     gestureGenerator.AddGesture("Wave", NULL);  
  49.     gestureGenerator.AddGesture("Click", NULL);  
  50.   
  51.     // Register callback functions  
  52.     XnCallbackHandle gestureCBHandle;  
  53.     XnCallbackHandle handsCBHandle;  
  54.     gestureGenerator.RegisterGestureCallbacks(GRecognized, GProgress,(void*)drawPadImg,gestureCBHandle );  
  55.     //【3】  
  56.     handsGenerator.RegisterHandCallbacks(Hand_Create, Hand_Update,Hand_Destroy, (void*)drawPadImg, handsCBHandle);  
  57.   
  58.     // Start generate  
  59.     context.StartGeneratingAll();  
  60.     res = context.WaitAndUpdateAll();   
  61.     while( (key!=27) && !(res = context.WaitAndUpdateAll())  )  
  62.     {   
  63.        res = context.WaitAndUpdateAll();  
  64.        imageGenerator.GetMetaData(imageMD);  
  65.        memcpy(cameraImg->imageData,imageMD.Data(),640*480*3);  
  66.        cvCvtColor(cameraImg,cameraImg,CV_RGB2BGR);  
  67.        cvPutText(drawPadImg,"Wave Your Hand to Start Tracking",cvPoint(20, 20), &font, CV_RGB(255,0,0));  
  68.        cvShowImage("Gesture",drawPadImg);  
  69.        cvShowImage("Camera",cameraImg);  
  70.        key=cvWaitKey(20);  
  71.        switch(key){  
  72.               case 'c':  
  73.                   clearImg(drawPadImg);  
  74.                   break;  
  75.               //【4】  
  76.               case 'm'://simulate real mouse  
  77.                   isRealMouseControl=1-isRealMouseControl;  
  78.                   break;  
  79.               default:  
  80.                   if(key != -1) printf("You Press%d\n",key);  
  81.        }  
  82.     }  
  83.     cvDestroyWindow("Gesture");  
  84.     cvDestroyWindow("Camera");  
  85.     cvReleaseImage(&drawPadImg);  
  86.     cvReleaseImage(&cameraImg);  
  87.     context.StopGeneratingAll();  
  88.     context.Shutdown();  
  89. }  

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

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

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

[cpp]  view plain copy
  1. XnStatusxn::HandsGenerator::RegisterHandCallbacks ( HandCreate  CreateCB,  HandUpdate UpdateCB,  HandDestroy  DestroyCB, void *  pCookie,  XnCallbackHandle &  hCallback )   

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

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

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

[cpp]  view plain copy
  1. // callback function for gesture recognized  
  2.   
  3. void XN_CALLBACK_TYPEGRecognized( xn::GestureGenerator &generator,  
  4.                               const XnChar *strGesture,  
  5.                               const XnPoint3D *pIDPosition,  
  6.                               const XnPoint3D *pEndPosition,  
  7.                               void *pCookie )  
  8. {  
  9.     int imgStartX=0;  
  10.     int imgStartY=0;  
  11.     int imgEndX=0;  
  12.     int imgEndY=0;  
  13.   
  14.     //【5】  
  15.     imgStartX=(int)(640/2-(pIDPosition->X));  
  16.     imgStartY=(int)(480/2-(pIDPosition->Y));  
  17.     imgEndX=(int)(640/2-(pEndPosition->X));  
  18.     imgEndY=(int)(480/2-(pEndPosition->Y));  
  19.   
  20.     IplImage* refimage=(IplImage*)pCookie;  
  21.     if(strcmp(strGesture,"Wave")==0)  
  22.     {  
  23.        cvLine(refimage,cvPoint(imgStartX,imgStartY),cvPoint(imgEndX,imgEndY),CV_RGB(255,255,0),6);  
  24.        //【6】  
  25.        handsGenerator.StartTracking(*pEndPosition);  
  26.     }  
  27.     else if(strcmp(strGesture,"Click")==0)  
  28.     {  
  29.        cvCircle(refimage,cvPoint(imgStartX,imgStartY),6,CV_RGB(0,0,255),12);  
  30.        //【7】  
  31.        if(isRealMouseControl)  
  32.        {  
  33.            messageHandler(cvPoint(imgStartX,imgStartY),0,REAL_MOUSE_CLICK);  
  34.        }  
  35.     }  
  36. }  
  37.   
  38. // callback function forgesture progress  
  39. void XN_CALLBACK_TYPEGProgress( xn::GestureGenerator &generator,  
  40.                             const XnChar *strGesture,  
  41.                             const XnPoint3D *pPosition,  
  42.                             XnFloat fProgress,  
  43.                             void *pCookie )  
  44.   
  45. {  
  46. }  

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

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

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

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

[cpp]  view plain copy
  1. //【8】  
  2.   
  3. void XN_CALLBACK_TYPEHand_Create(xn::HandsGenerator& generator,XnUserID nId,const XnPoint3D*pPosition, XnFloatfTime, void*pCookie)  
  4. {  
  5.     addTrackingId(nId);  
  6. }  
  7.   
  8.    
  9.   
  10. void XN_CALLBACK_TYPEHand_Update(xn::HandsGenerator& generator,XnUserID nId,const XnPoint3D*pPosition, XnFloatfTime, void*pCookie)  
  11. {  
  12.     int imgPosX=0;  
  13.     int imgPosY=0;  
  14.     char locationinfo[100];  
  15.     imgPosX=(int)(640/2-(pPosition->X));  
  16.     imgPosY=(int)(480/2-(pPosition->Y));  
  17.     IplImage* refimage=(IplImage*)pCookie;  
  18.     cvSetImageROI(refimage,cvRect(40,450,640,30));  
  19.     CvFont font;  
  20.     cvInitFont( &font,CV_FONT_VECTOR0,1, 1, 0, 3, 5);  
  21.     cvSet(refimage,cvScalar(255,255,255));  
  22.   
  23.     if(isRealMouseControl)  
  24.     {  
  25.        sprintf(locationinfo,"MouseCtrl: %dth HandLoc: %d,%d",nId,(int)pPosition->X,(int)pPosition->Y);  
  26.     }  
  27.     else  
  28.     {  
  29.        sprintf(locationinfo,"Normal: %dth HandLoc: %d,%d",nId,(int)pPosition->X,(int)pPosition->Y);  
  30.     }  
  31.     cvPutText(refimage,locationinfo ,cvPoint(30,30), &font, CV_RGB(0,0,0));  
  32.     cvResetImageROI(refimage);  
  33.     CvPoint thisLocation=cvPoint(imgPosX,imgPosY);  
  34.     //【9】  
  35.     if(isRealMouseControl)  
  36.     {  
  37.        //cvCircle(refimage,cvPoint(imgPosX,imgPosY),1,CV_RGB(255,0,0),2);  
  38.        messageHandler(thisLocation,nId,REAL_MOUSE_MOVE);  
  39.     }  
  40.     else  
  41.     {  
  42.        cvCircle(refimage,cvPoint(imgPosX,imgPosY),1,CV_RGB(255,0,0),2);  
  43.     }  
  44. }  
  45.   
  46. void XN_CALLBACK_TYPEHand_Destroy(xn::HandsGenerator& generator,XnUserID nId,XnFloat fTime,void* pCookie)  
  47. {  
  48.     //printf("Lost Hand: %d\n", nId);  
  49.     removeTrackingId(nId);  
  50. }  

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

[cpp]  view plain copy
  1. 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的内容:

[cpp]  view plain copy
  1. #include "AppMessage.h"  
  2.   
  3. //Location and move anglelast time for each userId(Hand Id)  
  4. CvPoint lastLocation[MAX_HAND_NUM];  
  5.   
  6. int isHandTracking[MAX_HAND_NUM]={0};  
  7. int isClickDown=0;  
  8.   
  9. void addTrackingId(int userId)  
  10. {  
  11.     isHandTracking[userId]=1;  
  12. }  
  13.   
  14. void removeTrackingId(int userId)  
  15. {  
  16.     isHandTracking[userId]=0;  
  17. }  
  18.   
  19. CvPoint getLastLocation(int userId)  
  20. {  
  21.     return lastLocation[userId];  
  22. }  
  23.   
  24.   
  25. void messageHandler(CvPoint &location,int userId,int flag)  
  26. {  
  27.     //initialize the lastLocation from the location obtained bythe first time  
  28.     if(lastLocation[userId].x==0&&lastLocation[userId].y==0)  
  29.     {  
  30.        lastLocation[userId].x=location.x;  
  31.        lastLocation[userId].y=location.y;  
  32.     }  
  33.        if(flag==REAL_MOUSE_CLICK)  
  34.        {  
  35.            if(!isClickDown)  
  36.            {  
  37.               mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);  
  38.            }  
  39.            else {  
  40.               mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);       
  41.            }  
  42.            isClickDown=1-isClickDown;  
  43.        }  
  44.        else if(flag==REAL_MOUSE_MOVE)  
  45.        {  
  46.        //【10】  
  47.            int firstHandId=-1;  
  48.            for(int i=0;i<MAX_HAND_NUM;i++)  
  49.            {  
  50.               if(isHandTracking[i]!=0)  
  51.               {  
  52.                   if(firstHandId==-1)  
  53.                   {  
  54.                      firstHandId=i;  
  55.                      break;  
  56.                   }  
  57.               }  
  58.            }  
  59.            if(abs(location.x-lastLocation[userId].x)<5)  
  60.            {  
  61.               location.x=lastLocation[userId].x;  
  62.            }  
  63.            if(abs(location.y-lastLocation[userId].y)<5)  
  64.            {  
  65.               location.y=lastLocation[userId].y;  
  66.            }  
  67.            //【11】  
  68.            if(userId==firstHandId)  
  69.            {                     
  70.               mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,  
  71.                   (location.x-160)*65536/640*2,(location.y-120)*65536/480*2,0,0);     
  72.            }       
  73.        }  
  74.        lastLocation[userId].x=location.x;  
  75.        lastLocation[userId].y=location.y;  
  76. }  

【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。

    使用方法如下:打开程序,按下M键进入鼠标控制模式,进入游戏后,push一下,使鼠标处于MOUSE_LEFTDOWN状态,屏幕就会出现刀光剑影咯,不想玩的时候,再push一下即可。


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

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

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

    

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kinect 2.0是微软公司推出的一款运动感应设备,它可以以全身动作控制并感应人体姿态,用于与电脑进行互动。要实现使用Kinect 2.0控制鼠标移动和点击,需要通过相应的软件和编程技术来实现。 首先,我们需要连接并设置Kinect 2.0设备。将Kinect 2.0摄像头通过USB接口连接到电脑上,并安装相关的驱动程序。然后,使用Kinect SDK(软件开发工具包)或其他相关的编程语言和库来编写程序进行控制。 接下来,需要编写控制鼠标移动和点击的代码。借助Kinect 2.0的深度感应和运动追踪功能,我们可以获取到用户的手部、头部和身体的动作信息。通过对这些信息的分析和处理,可以将用户的手势转化为鼠标移动的动作。 例如,当用户将手伸直并向左移动时,可以将这个手势解析为鼠标向左移动的指令;当用户做出点击的手势时,可以将这个手势解析为鼠标点击的动作。通过编写相应的代码逻辑,将Kinect获取到的用户手势信息与鼠标控制命令相结合,实现控制鼠标移动和点击。 在编写完控制代码后,将其编译并运行。当用户通过Kinect 2.0设备进行身体动作时,程序将解析这些动作并进行相应的鼠标控制操作。用户只需站在Kinect 2.0的感应范围内,并做出指定的手势,就能实现控制鼠标移动和点击的功能。 总之,通过连接Kinect 2.0设备并编写相应的控制代码,我们可以实现使用Kinect 2.0来控制鼠标的移动和点击。这种方式可以在一定程度上提供更加直观、自然的人机交互体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值