团队项目(2.3) -- 眼控小游戏

    经过2.12.2两部分的准备,再经过一定的程序调整和增加,我们便能实现一款眼控小游戏的开发。下面进行集中叙述:

一、如何进行双平台联动?
1、什么叫双平台?为什么要进行信息沟通?

    通过前两篇博客可以知道,我们的游戏部分是在Visual Studio中基于C++ / C语言开发的,而人脸检测与瞳孔检测是在Anaconda集成开发环境中基于python进行开发的,要想实现通过检测跟踪瞳孔位置来实现代替人手敲击键盘来控制游戏中飞机的移动,势必需要对瞳孔检测的位置结果反馈到游戏平台,因此不可避免地需要考虑两个平台或者说是两种编程语言如何融合统一起来的问题。

2、解决方案

    这里给出两种方案

方案一:python与C++ / C混合编程
    在网上搜索该类问题可以搜索到很多该类问题的解答博客,主要包括两个解决方向:
(1)C / C++中调用python
    对于C / C++中调用python,可以通过引用Python.h头文件的方法进行调用,这里找了一篇参考博客:使用c语言调用python小结
(2)python中调用C / C++
 python   对于python中调用C / C++,可以通过Ctypes、Swig、Boost、Cython等方法进行调用,这里也找了一篇利用Ctypes方法的参考博客: Python 与 C/C++ 交互的几种方式

    混合编程方法虽然可行,但是主要适用于"函数型编程文件"。所谓"函数型编程文件"指的是当被调用的编程文件(.c、.cpp或者.py等)输出的是类似于编程函数,即有限次循环次数且无返回值或返回值有限。这样的好处是便于编程,即只需要在需要的地方直接运行编程文件,而不需要对编程文件的内容进行拆分。大家很容易想明白,相比于将现有的代码进行解构,直接在原有基础上进行代码的增加来实现功能的拓展,不论是难度还是耗时都是更少的。因此提出另外一种方法:

方案二:Socket通信
    顾名思义,这种方法就跟日常生活中两人在社交网络上通信一样,一个人将自己所有传达的消息发送到网络上,另一个人只需要再在网络上接收消息即可实现两者之间的信息传递。这里的Socket通信方法最简单直白的用法解释就是:建立服务器端与客户端、建立两者之间的联系、进行信息的传递。较为专业的解释可以参考:socket通信简介,具体使用可以参考:Python、/C/C++ Socket编程实例

二、项目内容
1、约定

(1)人脸检测及瞳孔检测部分:作为服务器端,检测并发送瞳孔位置信息;
(2)游戏部分:作为客户端,接收服务器传输的瞳孔位置信息并转换为游戏方向控制信号。

2、人脸检测及瞳孔检测部分

    程序更改如下:

(1)引入头文件socket,建立服务器并连接客户端:

#连接客户端
import socket
#...
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建Socket描述符
server.bind(("localhost",8888)) #绑定地址
server.listen(5) #监听
connection, address = server.accept()  #接收请求

(2)发送数据

#####发送数据       
if(SuccessFlag == 1):
   SuccessFlag = 0
   if FinalxR<10:  #行位置错误
      msg = '0'
   msg = msg + str(FinalxR)
   if FinalyR<10:#列位置错误
      msg = msg + '0'
   msg = msg + str(FinalyR)
#else:
   #msg = '0000'
connection.send(bytes(msg, encoding="ascii"))

    由于在瞳孔检测过程中不可避免地会出现一些错误检测结果,因此需要对对一些明显错误的结果进行处理,这里采用的方法是直接沿用上一次处于正确范围内的结果。

3、游戏部分

    程序更改如下:

(1)将原单线程结构改为多线程:
    考虑到客户端在接收数据时可能发生拥堵而产生卡顿的情况,将游戏部分分为两个线程,其中游戏显示部分作为主线程,而数据接收及游戏控制部分作为子线程。多线程实现如下:

#include <thread>
#include <mutex>
//...
int main()
{
    //...
   thread Eyemessage(Message);//注册子线程Eyemessage,回调函数为Message()
}
//...
void Message()
{
    //...
}

(2)实现客户端,接收信息
    要想获得瞳孔的位置信息,必须与服务器端建立连接,并对接收到的信息进行解码。考虑到服务器端我们的编码形式是x坐标和y坐标共4位数字的ASCII码,因此只需将1、2位和3、4位分离再组合成各2位数字的位置信息即可。实现如下:

int main()
{
  //...
  WORD wVersionRequested;
  WSADATA wsaData;
  int err;
  wVersionRequested = MAKEWORD(2, 2);
  err = WSAStartup(wVersionRequested, &wsaData);
  if (err != 0) {
      return -1;
  }
  sockClient = socket(AF_INET, SOCK_STREAM, 0);
  SOCKADDR_IN addrSrv;
  addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");
  addrSrv.sin_family = AF_INET;
  addrSrv.sin_port = htons(8888);
  connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
  //...
}

     数据解码部分如下:

void get_pythonmess(int& x, int& y)
{
  char recvBuf[21] = { 0 };//输入缓冲区
  int count = 0;
  x = 0;
  y = 0;
  while(1)
  {   
      recv(sockClient, recvBuf, 20, 0);
      if (recvBuf[count * 4] != 0)
      {
          if ((recvBuf[count * 4] == '0' && recvBuf[count * 4 + 1] == '0'))
          {
              continue;
          }
          else
          {
              x += (recvBuf[count * 4] - '0') * 10 + recvBuf[count * 4 + 1] - '0';
              y += (recvBuf[count * 4 + 2] - '0') * 10 + recvBuf[count * 4 + 3] - '0';
              count++;
              if (count == 5) break;
          }
      }
  }
  x = (int)(1.0*(x + 0.5)/5) ; 
  y = (int)(1.0*(y + 0.5)/5);//均值滤波
}

(3)游戏开始前进行瞳孔参考中心位置进行标定
    为了利用瞳孔位置进行玩家飞机的运动控制,需要找到参考点,在这里我采用的是,先进行瞳孔位置标定直到收敛到位置变化较小时,以该点作为参考点,之后的瞳孔位置与该点作差并结合一定的滤波处理输出控制信号Drrorx和Drrory。具体实现如下:
    子线程回调函数Message():

void Message()
{
  int x = 0; int y = 0;
  int x_pre = 30; int y_pre = 20;
  //参考中心获取
  cout<<"开始"<<endl;
  getch();
  while (1)
  {
      get_pythonmess(x, y);
      if ((x - x_pre) * (x - x_pre) + (y - y_pre) * (y - y_pre) < 10)
      {
          CenterX = x;
          CenterY = y;
          break;
      }
      else if(x*y != 0)
      {
          x_pre = x;
          y_pre = y;
      }
  }
  //CenterX = 12;
  //CenterY = 7;
  int prex = CenterX; 
  int prey = CenterY;
  while (1)
  {
      get_pythonmess(x, y);
      //根据不同输入进行玩家飞机的移动
      Drrorx = x - CenterX;
      if (x - prex < 0 && Drrorx == 0) Drrorx = -1;
      else if (x - prex > 0 && Drrorx == 0) Drrorx = 1;
      Drrory = y - CenterY;
      if (y - prey < 0 && Drrory == 0) Drrory = -1;
      else if (y - prey > 0 && Drrory == 0) Drrory = 1;
      
  }
}

    飞机控制保留原来的键盘输入控制,增加以Drrorx及Drrory为控制量的控制部分:

if (Drrorx < -e)
{
   player.point.y -= PlayerSpeed;// +abs(Drror);
   player.point.y = Limit_num(0, player.point.y, WindowW - 1 - PlayerBlock);
}
else if (Drrorx > e) 
{
   player.point.y += PlayerSpeed;// +abs(Drror);
   player.point.y = Limit_num(0, player.point.y, WindowW - 1 - PlayerBlock);
}
if (Drrory < -e)
{
   player.point.x -= PlayerSpeed;// +abs(Drror);
   player.point.x = Limit_num(WindowH / 2, player.point.x, 7 * WindowH / 8 - 1 - PlayerBlock);
}
else if (Drrory > e) 
{
   player.point.x += PlayerSpeed;// +abs(Drror);
   player.point.x = Limit_num(WindowH / 2, player.point.x, 7 * WindowH / 8 - 1 - PlayerBlock);
}

    至此,瞳孔检测和游戏部分的对接程序修改完成。

四、成果展示

    B站视频链接:https://www.bilibili.com/video/av87721304,录制动图如下:

20899536-60ae324caf0d3462.gif

五、总结

(1)从上演示动图可以明显看出,人眼的移动与飞机的实际的运动控制存在较大的时延,原因可能有:
  a. 传输时延:从服务器端发送到客户端接收之间存在的延时;
  b. 信息接收:代码中采用的均值滤波以及缓冲区长度可能不合理;
  c. 运动控制:采用的是当前与上次的位置移动趋势(即微分量控制),不排除不合理的可能;
  d. 瞳孔检测精度:个别帧可以看出有明显不正确的情况,可能对控制造成影响。
(2)当前的瞳孔检测精度欠佳,相较于网上其他专家学者的研究成果来说确实是弟弟水平。一方面可以通过提高硬件设施的水平,另一方面在软件算法上仍有很大的提升空间,深度学习的方法属实牛逼。

    下一篇是本项目的文献综述。

猜你喜欢

转载自blog.csdn.net/qq_41658212/article/details/104274904
今日推荐