[进阶]Game Face 用脸玩游戏 精华

1750浏览
查看: 1750|回复: 1

[进阶] Game Face 用脸玩游戏

[复制链接]
本帖最后由 云天 于 2023-9-19 16:45 编辑

【背景】
    最近在逛B站时,看到谷歌在今年召开的 I/O 2023 开发者大会上推出了一项创新性的项目——GameFace,通过面部表情控制电脑游戏角色。该项目的灵感来自来自科罗拉多州的游戏玩家Lance Carr,他患有罕见的肌肉萎缩症,无法移动身体,但他对游戏的热爱却从未减退。
    Lance Carr的房子在一场大火中被烧毁,他所需的自适应配件也被毁坏。因此,谷歌的一支团队与Carr合作,开发出了GameFace项目。这个项目的核心是一个软件,通过面部表情控制PC配件,从而实现控制游戏角色的目的。谷歌已经将该项目开源,相关代码已经上传到了 GitHub 上,对此感兴趣的网友可以下载体验。

Game Face 用脸玩游戏图1


【下载】
GitHub我访问时有些缓慢,我找到gitee,下载了这个GameFace开源项目。我打包放在下面附件里。

【安装库】
项目中带了“requirements.txt”指定项目所需的依赖项及其精确的版本号。
我根据自己电脑的情况进行了修改:
  1. flatbuffers==2.0.0
  2. matplotlib==3.7.1
  3. opencv-contrib-python==4.7.0.72
  4. psutil==5.9.4
  5. pyautogui==0.9.53
  6. customtkinter==5.1.2
  7. PyDirectInput==1.0.4
  8. pypiwin32
  9. mediapipe
复制代码
安装
pip install -r requirements.txt
【修复错误】

将“project-gameface-main\project-gameface-main\assets\themes”目录下“google_theme.json”
  1. {
  2.     "CTk": {
  3.       " fg_Color": ["white"]
  4.     },
  5.     "CTkToplevel": {
  6.       " fg_Color": ["gray95", "gray10"]
  7.     },
  8.     "CTkFrame": {
  9.       "corner_radius": 8,
  10.       "border_width": 1,
  11.       " fg_Color": ["white", "white"],
  12.       "top_ fg_Color": ["white" ,"white"],
  13.       "border_color": ["white"]
  14.     },
  15.     "CTkButton": {
  16.       "corner_radius": 4,
  17.       "border_width": 0,
  18.       " fg_Color": ["white"],
  19.       "hover_color": ["#E8F0FE"],
  20.       "border_color": ["#3E454A"],
  21.       "text_color": ["black"],
  22.       "text_color_disabled": ["white"]
  23.     },
  24.     "CTkLabel": {
  25.       "corner_radius": 0,
  26.       " fg_Color": ["transparent", "transparent"],
  27.       "text_color": ["#444746"]
  28.     },
  29.     "CTkEntry": {
  30.       "corner_radius": 6,
  31.       "border_width": 2,
  32.       " fg_Color": ["#F9F9FA", "#343638"],
  33.       "border_color": ["#979DA2", "#565B5E"],
  34.       "text_color": ["gray14", "gray84"],
  35.       "placeholder_text_color": ["gray52", "gray62"]
  36.     },
  37.     "CTkCheckbox": {
  38.       "corner_radius": 6,
  39.       "border_width": 3,
  40.       " fg_Color": ["#3a7ebf", "#1f538d"],
  41.       "border_color": ["#3E454A", "#949A9F"],
  42.       "hover_color": ["#325882", "#14375e"],
  43.       "checkmark_color": ["#DCE4EE", "gray90"],
  44.       "text_color": ["gray14", "gray84"],
  45.       "text_color_disabled": ["gray60", "gray45"]
  46.     },
  47.     "CTkSwitch": {
  48.       "corner_radius": 500,
  49.       "border_width": 0,
  50.       "button_length": 0,
  51.       " fg_Color": ["#444746", "#4A4D50"],
  52.       "progress_color": ["#64DD17", "#1f538d"],
  53.       "button_color": ["#8F8F8F", "#D5D9DE"],
  54.       "button_hover_color": ["gray20", "gray100"],
  55.       "text_color": ["gray14", "gray84"],
  56.       "text_color_disabled": ["gray60", "gray45"]
  57.     },
  58.     "CTkRadiobutton": {
  59.       "corner_radius": 1000,
  60.       "border_width_checked": 8,
  61.       "border_width_unchecked": 3,
  62.       "border_color": ["#191C18"],
  63.       " fg_Color": ["#0B57D0"],
  64.       "hover_color": ["#191C18"],
  65.       "text_color": ["gray14"],
  66.       "text_color_disabled": ["gray60"]
  67.     },
  68.     "CTkProgressBar": {
  69.       "corner_radius": 1000,
  70.       "border_width": 0,
  71.       " fg_Color": ["gray90"],
  72.       "progress_color": ["#34A853"],
  73.       "border_color": ["gray", "gray"]
  74.     },
  75.     "CTkSlider": {
  76.       "corner_radius": 1000,
  77.       "button_corner_radius": 1000,
  78.       "border_width": 6,
  79.       "button_length": 0,
  80.       " fg_Color": ["#D2E3FC"],
  81.       "progress_color": ["#1A73E8"],
  82.       "button_color": ["#1A73E8"],
  83.       "button_hover_color": ["#174EA6"]
  84.     },
  85.     "CTkOptionMenu": {
  86.       "corner_radius": 6,
  87.       " fg_Color": ["white"],
  88.       "button_color": ["gray95"],
  89.       "button_hover_color": ["gray95"],
  90.       "text_color": ["#5F6368"],
  91.       "text_color_disabled": ["#5F6368"]
  92.     },
  93.     "CTkComboBox": {
  94.       "corner_radius": 6,
  95.       "border_width": 1,
  96.       " fg_Color": ["white"],
  97.       "border_color": ["gray50"],
  98.       "button_color": ["gray95"],
  99.       "button_hover_color": ["gray95"],
  100.       "text_color": ["#5F6368"],
  101.       "text_color_disabled": ["#5F6368"]
  102.     },
  103.     "CTkScrollbar": {
  104.       "corner_radius": 1000,
  105.       "border_spacing": 4,
  106.       " fg_Color": "transparent",
  107.       "button_color": ["gray55", "gray41"],
  108.       "button_hover_color": ["gray40", "gray53"]
  109.     },
  110.     "CTkSegmentedButton": {
  111.       "corner_radius": 6,
  112.       "border_width": 2,
  113.       " fg_Color": ["#979DA2", "gray29"],
  114.       "selected_color": ["#3a7ebf", "#1f538d"],
  115.       "selected_hover_color": ["#325882", "#14375e"],
  116.       "unselected_color": ["#979DA2", "gray29"],
  117.       "unselected_hover_color": ["gray70", "gray41"],
  118.       "text_color": ["#DCE4EE", "#DCE4EE"],
  119.       "text_color_disabled": ["gray74", "gray60"]
  120.     },
  121.     "CTkTextbox": {
  122.       "corner_radius": 6,
  123.       "border_width": 0,
  124.       " fg_Color": ["gray100", "gray20"],
  125.       "border_color": ["#979DA2", "#565B5E"],
  126.       "text_color": ["gray14", "gray84"],
  127.       "scrollbar_button_color": ["gray55", "gray41"],
  128.       "scrollbar_button_hover_color": ["gray40", "gray53"]
  129.     },
  130.     "CTkScrollableFrame": {
  131.       "label_ fg_Color": ["gray80", "gray20"]
  132.     },
  133.     "DropdownMenu": {
  134.       " fg_Color": ["gray90", "gray20"],
  135.       "hover_color": ["gray75", "gray28"],
  136.       "text_color": ["gray14", "gray84"]
  137.     },
  138.     "CTkFont": {
  139.       "macOS": {
  140.         "family": "Google Sans",
  141.         "size": 14,
  142.         "weight": "normal"
  143.       },
  144.       "Windows": {
  145.         "family": "Google Sans",
  146.         "size": 14,
  147.         "weight": "normal"
  148.       },
  149.       "Linux": {
  150.         "family": "Google Sans",
  151.         "size": 14,
  152.         "weight": "normal"
  153.       }
  154.     }
  155.   }
复制代码
fg_color 修改为 fg_Color。因“C:\Users\Lenovo\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\customtkinter\windows\widgets”目录下“ctk_switch.py”中使用的是“ fg_Color”。
【原理】
不需要太多的硬件辅助,一个网格摄像头、一台电脑还有 AI ,这装备就齐活儿了。具体来看, GameFace 是通过 MediaPipe 和 Tensor Flow Lite 两组工具将识别到的头部、面部动作转换成了鼠标指令的。其中 TensorFlow Lite 的作用,是让 AI 模型能够在 PC 设备上运行。而 MediaPipe ,则是一个集成了人脸检测、头像分割和姿态识别等多个 AI 模型的工具库。它们会在人脸上创建一个有 468 个点的实时动态网格,这些点能够追踪你的头部运动情况,并识别你的鼻子、嘴还有其他五官的位置。

接着,再通过 AI 模型,将这些点转化成为鼠标移动或点击的指令。就比如你在转头的时候,鼠标也会跟着在屏幕上移动,或者张开嘴的时候表示点击 “ 确认 ” 。目前, GameFace 能识别包括挑眉等在内的多种表情,而哪种表情代表着什么指令,都可以自定义。

Game Face 用脸玩游戏图6

而且 GameFace 还能调节灵敏度,如果灵敏度低,动作就需要很夸张才能让 AI 识别到你的意图,反之,即使表情很细微,也会被捕捉到。

Game Face 用脸玩游戏图3

测试“张嘴”控制鼠标左键。
Game Face 用脸玩游戏图4

调整光标移动速度。
Game Face 用脸玩游戏图5

控制按键
这样一来用脸玩游戏,就不成问题了,或许这也算得上是另一种角度的 “ 嘴强王者 ” 吧。有意思的是, GameFace 在国内已经支持了《荒野行动 》和《 UNO! ™ 》两款游戏,恰好呢,有人也在谷歌开发者大会的现场,就先替你们体验了一把用脸来吃鸡。

坐在电脑面前,头轻轻地晃了晃,就能看到鼠标也在跟着移动,如果要调整视角的话转转头就行。如果要往左边移动就把嘴往左歪,往右移就歪右嘴,张开嘴是确认键,整个打游戏的过程,用 “ 面目狰狞 ” 四个字来形容也不为过。

【个人测试】
参数有待继续调整,使鼠标指针移动更稳定、识别动作更有效。
【鼠标控制代码】
根据个人理解,进行了简单注释,有不妥之处,请各位批评指正。
  1. import concurrent.futures as futures
  2. import logging
  3. import threading
  4. import time
  5. import tkinter as tk
  6. import numpy as np
  7. import numpy.typing as npt
  8. import pyautogui
  9. import src.utils as utils
  10. from src.accel_graph import SigmoidAccel
  11. from src.config_manager import ConfigManager
  12. from src.singleton_meta import Singleton
  13. logger = logging.getLogger("MouseController")
  14. pyautogui.PAUSE = 0
  15. pyautogui.FAILSAFE = False
  16. # Max buffer number for apply smoothing.
  17. N_BUFFER = 100
  18. #鼠标控制类
  19. class MouseController(metaclass=Singleton):
  20.     def __init__(self):
  21.         logger.info("Intialize MouseController singleton")
  22.         self.prev_x = 0
  23.         self.prev_y = 0
  24.         self.curr_track_loc = None
  25.         self.smooth_kernel = None
  26.         self.delay_count = 0
  27.         self.top_count = 0
  28.         self.is_started = False
  29.         self.is_destroyed = False
  30.         self.stop_flag = None
  31.         self.is_active = None#由页面上Face control按钮控制
  32.     def start(self):
  33.         if not self.is_started:#加载运行一次
  34.             
  35.             logger.info("Start MouseController singleton")
  36.             # Trackpoint buffer x, y
  37.             self.buffer = np.zeros([N_BUFFER, 2])
  38.             self.accel = SigmoidAccel()
  39.             self.pool = futures.ThreadPoolExecutor(max_workers=1)
  40.             self.screen_w, self.screen_h = pyautogui.size()
  41.             self.calc_smooth_kernel()
  42.             self.is_active = tk.BooleanVar()
  43.             self.is_active.set(ConfigManager().config["auto_play"])#从配置文件读取,默认为假
  44.             
  45.             self.stop_flag = threading.Event()
  46.             self.pool.submit(self.main_loop)#提交主循环任务到线程池中
  47.             
  48.             self.is_started = True
  49.             
  50.             
  51.             
  52.     #指针移动平滑度
  53.     def calc_smooth_kernel(self):
  54.         new_pointer_smooth = ConfigManager().config["pointer_smooth"]
  55.         if self.smooth_kernel is None:
  56.             self.smooth_kernel = utils.calc_smooth_kernel(new_pointer_smooth)
  57.         elif new_pointer_smooth != len(self.smooth_kernel):
  58.             self.smooth_kernel = utils.calc_smooth_kernel(new_pointer_smooth)
  59.         else:
  60.             pass
  61.     #指针上下左右移动比例
  62.     def asymmetry_scale(self, vel_x, vel_y):
  63.         if vel_x > 0:
  64.             vel_x *= ConfigManager().config["spd_right"]
  65.         else:
  66.             vel_x *= ConfigManager().config["spd_left"]
  67.         if vel_y > 0:
  68.             vel_y *= ConfigManager().config["spd_down"]
  69.         else:
  70.             vel_y *= ConfigManager().config["spd_up"]
  71.         return vel_x, vel_y
  72.     def act(self, track_loc: npt.ArrayLike):
  73.         self.curr_track_loc = track_loc
  74.     def main_loop(self) -> None:
  75.         """ Separate thread for mouse controller         
  76.         """
  77.         
  78.         if self.is_destroyed:
  79.             return
  80.         
  81.         while not self.stop_flag.is_set():
  82.             #等待用户点击 Face control按钮或“Mouse unpause”
  83.             if not self.is_active.get():
  84.                 time.sleep(0.001)
  85.                 continue
  86.             
  87.             self.buffer = np.roll(self.buffer, shift=-1, axis=0)
  88.             self.buffer[-1] = self.curr_track_loc
  89.             # Get latest x, y and smooth.
  90.             #进行平滑计算
  91.             smooth_px, smooth_py = utils.apply_smoothing(
  92.                 self.buffer, self.smooth_kernel)
  93.             vel_x = smooth_px - self.prev_x
  94.             vel_y = smooth_py - self.prev_y
  95.             self.prev_x = smooth_px
  96.             self.prev_y = smooth_py
  97.             # In delay state
  98.             self.delay_count += 1
  99.             if self.delay_count < N_BUFFER:
  100.                 time.sleep(0.001)
  101.                 continue
  102.             #指针移动与人脸移动比例,根据用户设置调整
  103.             vel_x, vel_y = self.asymmetry_scale(vel_x, vel_y)
  104.             if ConfigManager().config["mouse_acceleration"]:
  105.                 vel_x *= self.accel(vel_x)
  106.                 vel_y *= self.accel(vel_y)
  107.             # pydirectinput is not working here
  108.             #控制指针移动
  109.             pyautogui.move(xOffset=vel_x, yOffset=vel_y)
  110.             
  111.             time.sleep(ConfigManager().config["tick_interval_ms"] / 1000)
  112.     def set_active(self, flag: bool) -> None:#激活人脸控制函数
  113.         self.is_active.set(flag)
  114.         if flag:
  115.             self.delay_count = 0
  116.         
  117.     def toggle_active(self):#激活切换
  118.         logging.info("Toggle active")
  119.         curr_state = self.is_active.get()
  120.         self.set_active(not curr_state)
  121.     def destroy(self):#注销
  122.         if self.is_active is not None:
  123.             self.is_active.set(False)
  124.         if self.stop_flag is not None:
  125.             self.stop_flag.set()
  126.         self.is_destroyed = True
复制代码


木子呢  管理员

发表于 2023-9-20 15:14:35

“ 面目狰狞 ”哈哈哈哈哈
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail