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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. 利用Kinect实现用指尖隔空控制鼠标(源码放出)

    简介 此程序为利用Kinect实现用手指隔空控制鼠标,是我另一个项目的一部分,因为在另外那个项目中鼠标的click是通过一种特殊的方式实现的,因此这个程序只实现了用手控制鼠标的移动,并没有点击的功能. ...

  2. 开发教程(四) MIP组件平台使用说明

    组件审核平台用于上传 MIP 组件.经过自动校验之后,提交审核,通过审核的组件会定时推送到线上,供网站使用. 平台地址:https://www.mipengine.org/platform/ 1. 使 ...

  3. 运动控制卡应用开发教程之激光振镜控制

    今天,正运动技术为大家分享一下应用C++开发一个激光振镜的运动控制例程. 我们主要从新建MFC项目,添加函数库讲起,最后通过项目实战--激光振镜打标例程讲解,来让大家熟悉它的项目开发. 在正式学习之前 ...

  4. PC用Kinect玩水果忍者核心代码

    /*******************************PC用Kinect玩水果忍者核心代码*******************************/private const doub ...

  5. 水果忍者激战版的开发心得

    先放下载地址:http://dl.uu.cc/p2p/(在android 手机浏览器上输入后点击下载就OK) 去年8月份一开始接到任务,是要做一个手机间实时通信的桥梁,大家都知道国外的视频通话早已成熟 ...

  6. Kinect开发笔记之二Kinect for Windows 2.0新特性

    这是本博客的第一篇翻译文档,笔者已经苦逼的竭尽全力的在翻译了,但无奈英语水平也是很有限,不对或者不妥当不准确的地方必然会有,还恳请大家留言或者邮件我以批评指正,我会虚心接受.谢谢大家.         ...

  7. ROS1结合自动驾驶数据集Kitti开发教程(四)画出自己车子模型以及照相机视野

    注意: 再学习本系列教程时,应该已经安装过ROS了并且需要有一些ROS的基本知识 ubuntu版本:20.04 ros版本:noetic 课程回顾 ROS1结合自动驾驶数据集Kitti开发教程(一)K ...

  8. 代号夏娃在电脑上怎么玩 代号夏娃PC版玩法教程

    <代号夏娃>是腾讯推出的一款全新次时代RPG手游大作,逼真的游戏画风,畅快的战斗对决,更有跌宕起伏的游戏剧情等你来细细品味,恢弘壮阔的世界观,让你的冒险不虚此行! 下面小编就给亲们介绍下代 ...

  9. Kinect开发笔记之五使用PowerShell控制Kinect

    这是第一次用MarkDown编辑器来写博客,挺喜欢这种没有任何格式舒服的编辑器,自由洒脱更加易读,留一个不自然的自然段纪念下找到舒服的编辑器. 这次要记录使用win7/win8内建的PowerShel ...

最新文章

  1. CentOS7编译安装LNMP
  2. 前序遍历(递归、非递归)、层序遍历(递归、非递归)
  3. c语言冒泡法加逗号,Ubuntu 10.04 LTS 无法添加计算机的问题
  4. 深入解析QML引擎, 第4部分: 自定义解析器
  5. 论文浅尝 | 基于超平面的时间感知知识图谱嵌入
  6. 错误使用sym MEX文件 'D:\matlab\toolbox\maple\maplemex.mexw64' 无效: 缺少依赖共享库 的解决办法
  7. C语言基础学习教程基本语法
  8. 三维重建之环境搭建1-VS2017安装
  9. 高通about.html 文件,关于高通校准调用文件的说明文档
  10. JAVA毕设项目宠物店管理系统设计与实现(Vue+Mybatis+Maven+Mysql+sprnig+SpringMVC)
  11. 【Jsp】第七课 Jsp内置对象的学习和使用
  12. 零基础一周学会PIC单片机视频教程
  13. 樊登讲亲密关系_《亲密关系》
  14. [深入理解Android卷一全文-第六章]深入理解Binder
  15. Java笔记——08.面向对象(中级)
  16. 红帽子Linux7安装Oracle,RedHat Enterprise Linux7.0安装Oracle 12C
  17. Verilog HDL语言中always敏感信号对比分析
  18. IDEA download sources 报Cannot reconnect错误解决方法
  19. android n 状态栏分析,Android N状态栏图标白底问题
  20. 全新整理 微软 谷歌 百度等公司经典面试100题 第101 160题

热门文章

  1. 多商户商城系统功能拆解35讲-平台端营销-拼团商品
  2. 私域流量如何运营?具体该怎么做?
  3. 天津师范计算机考研录取分数线,天津师范大学研究生录取分数线
  4. Java异步并发和线程池
  5. SEO编辑必看:撰写搜索引擎喜爱的标题
  6. Java学习-35天
  7. 一个人民币小写转大写的js例子[如123.1对应为壹佰贰拾叁元壹角]
  8. 谷歌Guava LoadingCache介绍
  9. 智慧城管业务流程系统建设
  10. 一个外键对应多条数据的删除