问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

为什么在kinect设计空气鼠标的时候要进行坐标转换

发布网友 发布时间:2022-04-27 01:56

我来回答

1个回答

热心网友 时间:2022-06-22 03:54

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

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

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

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

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

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

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


[cpp] view plain copy

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
// 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相关的回调函数:

[cpp] view plain copy
//【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,该回调函数定义如下:


[cpp] view plain copy

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

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


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

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
写论文值得推荐的app 写论文app大全汇总 有啥好用又免费的文献查阅网站吗? 高仿苹果手机关机关不了机?是怎么回事?有那个朋友请告知! 高仿苹果6开机就是白屏黑苹果,关机关不了 怎么把电子相册保存到QQ空间相册 整个乡村音乐史上,最动人的25首歌 写给狗的歌 右侧肢体肌力4级是什么意思 肌力分级的评定标准 肌力4级是什么意思 空气鼠标是什么 郑人买履文言文及译文,8个词语解释 对方卖车我想办按揭怎么弄 为什么卖车的都喜欢让客户贷款 怎样贷款卖车! 想让客户贷款买车怎么推 一般4S店是喜欢全款买车的客户 还是贷款买车的客户 买车为什么卖车的反而希望车主贷款 去4s店购车时,为何销售都会推荐贷款买车? 去4s店特别推荐贷款买车,这是怎么一回事? 车贷按揭怎么给客户介绍? 如何引导客户做汽车按揭??? 怎样才能删除抖音里的聊天记录 如何从iPhone里打开QQ的照片访问限制 iphone5 qq此应用程序没有权限来访问您的照片或视频,可是在隐私设置-照片里没有选项,谁能解答为什么? 请在设置隐私照片中允许QQ访问你的照片。可是我怎么在隐私里找不到QQ。怎么回事只是想把照片发给好友 iphone4在qq空间下照片,弹出&quot;qq空间想访问你的照片&quot; iphone4里面的设置-隐私-照片-QQ访问照片是什么意思?会透露你的照片吗 ?别人会看到你的手机相册吗? 苹果6s怎么允许qq访问照片? 怎么让iphone6s允许qq访问照片 负离子空气净化鼠标有好用吗? 空气鼠标键盘,终于解决了鼠标键盘占空间的问题了 凯利尔鼠标都是空气净化鼠标吗? 世界上最贵的键鼠套装是多少..? 凯利尔功能型空气净化鼠标会有什么问题吗 有没有什么办公电子产品好物推荐? 梦见自己被人拿*杀我,之后再割我脖子,但是还活着,受了点伤,没出血,是什么意思? CS专用鼠标和键盘哪个型号的比较好? 鼠标和电脑桌面之间有一个空气墙 鼠标可以滑动 但是琢磨任何东西操作不了? 梦见脖子被坏人用锯条划伤没留血? 铝制鼠标垫有什么优点缺点啊? 鼠标自己会抖动 一松手就不动了 周公解梦梦见自己脖子被砍了一刀一直都没流血能看见伤口快死了又被两个男人救活什么意思 鼠标和强电空气开关冲突,是什么原因 无线鼠标和有线鼠标哪个灵敏度高? 梦见自己脖子被捅没有血还住院了? 军团要塞2的问题 梦见脖子被割断了、快要死了? 玻璃桌子是不是对蓝牙鼠标键盘有影响?选择什么的桌子好· 计算机病毒主要的传播途径是( )a.空气和水b.鼠标和键盘c.磁盘和网络d.显示器和光盘