`
小斤陈
  • 浏览: 33431 次
社区版块
存档分类
最新评论

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;

//Generator
xn::GestureGeneratorgestureGenerator;
xn::HandsGeneratorhandsGenerator;
xn::ImageGeneratorimageGenerator;
int isRealMouseControl=0;

//【1】
// main function
void 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 recognized

void 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 progress
void 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的版本,点这里下载

 

 

 

 


 

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

作者:小斤(陈忻)

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

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

相关推荐

    Kinect模拟水果忍者

    《Kinect模拟水果忍者》是一款利用微软的Kinect设备,通过C#编程语言实现的在个人电脑(PC)上玩“水果忍者”的应用程序。这个项目不仅展示了Kinect的体感交互技术,还体现了C#在游戏开发中的应用。 首先,Kinect是...

    Unity结合Kinect2体感开发:KinectForWindows_UnityPro

    "Unity结合Kinect2体感开发:KinectForWindows_UnityPro"项目旨在利用这些技术,打造一种全新的交互方式。 首先,我们来看一下Unity3D。它是一款基于C#编程语言的可视化开发工具,支持实时3D渲染,拥有丰富的内置...

    kinect2.0控制鼠标

    标题“kinect2.0控制鼠标”涉及到的技术是利用微软的第二代Kinect传感器来实现对计算机鼠标的操控。Kinect 2.0是一款先进的体感设备,它能够捕捉到用户的三维空间信息,包括深度图像和人体骨骼追踪数据。在这个项目...

    Kinect v2开发教程完整版C++

    本文是整理中的Kinect for Windows v2 的C++ 程式开发教程。内容包括但不限于: • Kinect for Windows SDK v2 基本介绍 • Kinect for Windows SDK v2 C++ API 简介 • K4W v2 C++ Part 1:简单的深度读取方法 • ...

    Kinect开发教程.pdf

    2. Kinect SDK Beta:Kinect软件开发包(Software Development Kit)是微软官方提供的,用于开发Kinect应用程序的一套工具和API接口。开发人员可以通过微软官方网站下载Kinect SDK的beta版本。 *** Framework 4.0:...

    Kinect手势控制鼠标

    使用OpenNI类库实现。在Kinect的支持下,通过对手势的...该程序使人能够徒手控制PC,如用Firefox浏览网页,玩水果忍者等等。此程序为非MFC版本。开发教程:http://blog.csdn.net/chenxin_130/article/details/6693390

    C#版Kinect水果忍者

    《C#版Kinect水果忍者》是一款基于微软Kinect for Windows SDK 1.6开发的游戏,灵感来源于广受欢迎的手机游戏《水果忍者》。它利用了C#编程语言的强大功能,结合Kinect设备的体感技术,为玩家提供了一种全新的、互动...

    Kinect手势代替鼠标控制PC

    在玩游戏如“水果忍者”时,用户可以直接用手切水果,带来全新的游戏体验。这种技术的应用不仅限于娱乐,还可以用于各种需要精细操作但触控不便的场景,比如医疗、教育或工业自动化等领域。 在压缩包文件...

    双手控制(Two hands)_Hands_Kinect2控制_pc_手势_鼠标_

    标题中的“双手控制(Two hands)_Hands_Kinect2控制_pc_手势_鼠标”表明了这个项目是关于使用微软的Kinect v2传感器通过手势控制PC上的鼠标操作。Kinect是一款能够捕捉人体动作的设备,最初是为Xbox游戏主机设计的,...

    Kinect应用开发实战:用最自然的方式与机器对话(书中源代码)

    《Kinect应用开发实战:用最自然的方式与机器对话》这本书是针对Kinect设备的开发者...总的来说,《Kinect应用开发实战:用最自然的方式与机器对话》是一本全面且实用的教程,适合那些希望进入体感技术领域的开发者。

    Kinect模拟鼠标 水果忍者云云

    利用kinect手势代替鼠标,玩切西瓜,可更改模拟键盘等

    用kincet玩水果忍者代码

    标题中的“用kinect玩水果忍者代码”指的是利用微软的Kinect体感设备与C#编程语言结合,实现玩家可以通过身体动作来控制游戏《水果忍者》的玩法。这是一项将传统游戏与体感技术融合的创新应用,旨在提供更直观、更具...

    Kinect1Demo04 控制鼠标

    【Kinect1Demo04 控制鼠标】是一个基于C#编程语言和Microsoft Kinect体感设备的应用示例,它展示了如何利用Kinect传感器的数据来控制计算机的鼠标操作。这个项目的核心目标是实现用户的手部动作与鼠标指针的联动,...

    Kinect 滑鼠移動及點擊控制

    【标题】"Kinect 滑鼠移動及點擊控制"是一个利用C#编程语言实现的项目,它允许用户通过微软的Kinect设备来控制鼠标移动和点击操作。这个程序为那些可能由于身体障碍而难以使用传统输入设备的人提供了便利,同时也...

    用kinect控制鼠标

    标题“用Kinect控制鼠标”涉及的技术点是利用微软的Kinect设备来操纵计算机的鼠标指针,这一技术主要基于人机交互与计算机视觉。在描述中提到,虽然定位可能不太灵敏,但通过VS2010(Visual Studio 2010)使用C#编程...

    kinect鼠标控制源码

    标题中的“kinect鼠标控制源码”指的是使用微软的Kinect设备来实现对鼠标的控制功能的编程代码。Kinect是微软开发的一款体感输入设备,它能够捕捉人体的动作,并将其转化为数字信号,用于游戏操作或其他交互式应用。...

    Kinect2.0鼠标控制体验

    《Kinect2.0鼠标控制体验:探索手势交互的新维度》 在当今的科技领域,人机交互方式正经历着前所未有的变革。其中,微软的Kinect2.0技术为这一进程注入了新的活力,尤其是它在鼠标控制方面的创新应用。这款设备允许...

    kinect+unity切水果游戏源文件

    "kinect+unity切水果游戏源文件" 这个标题明确指出,这是一个基于Kinect体感技术和Unity游戏引擎开发的切水果游戏的源代码包。Kinect是微软推出的一种能够识别用户身体动作的外设,而Unity则是一款强大的跨平台游戏...

Global site tag (gtag.js) - Google Analytics