本帖最后由 云天 于 2023-9-19 16:45 编辑
【背景】
最近在逛B站时,看到谷歌在今年召开的 I/O 2023 开发者大会上推出了一项创新性的项目——GameFace,通过面部表情控制电脑游戏角色。该项目的灵感来自来自科罗拉多州的游戏玩家Lance Carr,他患有罕见的肌肉萎缩症,无法移动身体,但他对游戏的热爱却从未减退。 Lance Carr的房子在一场大火中被烧毁,他所需的自适应配件也被毁坏。因此,谷歌的一支团队与Carr合作,开发出了GameFace项目。这个项目的核心是一个软件,通过面部表情控制PC配件,从而实现控制游戏角色的目的。谷歌已经将该项目开源,相关代码已经上传到了 GitHub 上,对此感兴趣的网友可以下载体验。
【下载】
因 GitHub 我访问时有些缓慢,我找到gitee ,下载了这个GameFace开源项目。我打包放在下面附件里。
【安装库】
项目中带了“requirements.txt”指定项目所需的依赖项及其精确的版本号。
我根据自己电脑的情况进行了修改:
flatbuffers==2.0.0
matplotlib==3.7.1
opencv-contrib-python==4.7.0.72
psutil==5.9.4
pyautogui==0.9.53
customtkinter==5.1.2
PyDirectInput==1.0.4
pypiwin32
mediapipe 复制代码
安装
pip install -r requirements.txt
【修复错误】
将“project-gameface-main\project-gameface-main\assets\themes”目录下“google_theme.json”
{
"CTk": {
" fg_Color": ["white"]
},
"CTkToplevel": {
" fg_Color": ["gray95", "gray10"]
},
"CTkFrame": {
"corner_radius": 8,
"border_width": 1,
" fg_Color": ["white", "white"],
"top_ fg_Color": ["white" ,"white"],
"border_color": ["white"]
},
"CTkButton": {
"corner_radius": 4,
"border_width": 0,
" fg_Color": ["white"],
"hover_color": ["#E8F0FE"],
"border_color": ["#3E454A"],
"text_color": ["black"],
"text_color_disabled": ["white"]
},
"CTkLabel": {
"corner_radius": 0,
" fg_Color": ["transparent", "transparent"],
"text_color": ["#444746"]
},
"CTkEntry": {
"corner_radius": 6,
"border_width": 2,
" fg_Color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"placeholder_text_color": ["gray52", "gray62"]
},
"CTkCheckbox": {
"corner_radius": 6,
"border_width": 3,
" fg_Color": ["#3a7ebf", "#1f538d"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"checkmark_color": ["#DCE4EE", "gray90"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkSwitch": {
"corner_radius": 500,
"border_width": 0,
"button_length": 0,
" fg_Color": ["#444746", "#4A4D50"],
"progress_color": ["#64DD17", "#1f538d"],
"button_color": ["#8F8F8F", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkRadiobutton": {
"corner_radius": 1000,
"border_width_checked": 8,
"border_width_unchecked": 3,
"border_color": ["#191C18"],
" fg_Color": ["#0B57D0"],
"hover_color": ["#191C18"],
"text_color": ["gray14"],
"text_color_disabled": ["gray60"]
},
"CTkProgressBar": {
"corner_radius": 1000,
"border_width": 0,
" fg_Color": ["gray90"],
"progress_color": ["#34A853"],
"border_color": ["gray", "gray"]
},
"CTkSlider": {
"corner_radius": 1000,
"button_corner_radius": 1000,
"border_width": 6,
"button_length": 0,
" fg_Color": ["#D2E3FC"],
"progress_color": ["#1A73E8"],
"button_color": ["#1A73E8"],
"button_hover_color": ["#174EA6"]
},
"CTkOptionMenu": {
"corner_radius": 6,
" fg_Color": ["white"],
"button_color": ["gray95"],
"button_hover_color": ["gray95"],
"text_color": ["#5F6368"],
"text_color_disabled": ["#5F6368"]
},
"CTkComboBox": {
"corner_radius": 6,
"border_width": 1,
" fg_Color": ["white"],
"border_color": ["gray50"],
"button_color": ["gray95"],
"button_hover_color": ["gray95"],
"text_color": ["#5F6368"],
"text_color_disabled": ["#5F6368"]
},
"CTkScrollbar": {
"corner_radius": 1000,
"border_spacing": 4,
" fg_Color": "transparent",
"button_color": ["gray55", "gray41"],
"button_hover_color": ["gray40", "gray53"]
},
"CTkSegmentedButton": {
"corner_radius": 6,
"border_width": 2,
" fg_Color": ["#979DA2", "gray29"],
"selected_color": ["#3a7ebf", "#1f538d"],
"selected_hover_color": ["#325882", "#14375e"],
"unselected_color": ["#979DA2", "gray29"],
"unselected_hover_color": ["gray70", "gray41"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkTextbox": {
"corner_radius": 6,
"border_width": 0,
" fg_Color": ["gray100", "gray20"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_ fg_Color": ["gray80", "gray20"]
},
"DropdownMenu": {
" fg_Color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],
"text_color": ["gray14", "gray84"]
},
"CTkFont": {
"macOS": {
"family": "Google Sans",
"size": 14,
"weight": "normal"
},
"Windows": {
"family": "Google Sans",
"size": 14,
"weight": "normal"
},
"Linux": {
"family": "Google Sans",
"size": 14,
"weight": "normal"
}
}
}
复制代码
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 能识别包括挑眉等在内的多种表情,而哪种表情代表着什么指令,都可以自定义。
而且 GameFace 还能调节灵敏度,如果灵敏度低,动作就需要很夸张才能让 AI 识别到你的意图,反之,即使表情很细微,也会被捕捉到。
测试“张嘴”控制鼠标左键。
调整光标移动速度。
控制按键
这样一来用脸玩游戏,就不成问题了,或许这也算得上是另一种角度的 “ 嘴强王者 ” 吧。有意思的是, GameFace 在国内已经支持了《荒野行动 》和《 UNO! ™ 》两款游戏,恰好呢,有人也在谷歌开发者大会的现场,就先替你们体验了一把用脸来吃鸡。
坐在电脑面前,头轻轻地晃了晃,就能看到鼠标也在跟着移动,如果要调整视角的话转转头就行。如果要往左边移动就把嘴往左歪,往右移就歪右嘴,张开嘴是确认键,整个打游戏的过程,用 “ 面目狰狞 ” 四个字来形容也不为过。
【个人测试】
参数有待继续调整,使鼠标指针移动更稳定、识别动作更有效。
根据个人理解,进行了简单注释,有不妥之处,请各位批评指正。
import concurrent.futures as futures
import logging
import threading
import time
import tkinter as tk
import numpy as np
import numpy.typing as npt
import pyautogui
import src.utils as utils
from src.accel_graph import SigmoidAccel
from src.config_manager import ConfigManager
from src.singleton_meta import Singleton
logger = logging.getLogger("MouseController")
pyautogui.PAUSE = 0
pyautogui.FAILSAFE = False
# Max buffer number for apply smoothing.
N_BUFFER = 100
#鼠标控制类
class MouseController(metaclass=Singleton):
def __init__(self):
logger.info("Intialize MouseController singleton")
self.prev_x = 0
self.prev_y = 0
self.curr_track_loc = None
self.smooth_kernel = None
self.delay_count = 0
self.top_count = 0
self.is_started = False
self.is_destroyed = False
self.stop_flag = None
self.is_active = None#由页面上Face control按钮控制
def start(self):
if not self.is_started:#加载运行一次
logger.info("Start MouseController singleton")
# Trackpoint buffer x, y
self.buffer = np.zeros([N_BUFFER, 2])
self.accel = SigmoidAccel()
self.pool = futures.ThreadPoolExecutor(max_workers=1)
self.screen_w, self.screen_h = pyautogui.size()
self.calc_smooth_kernel()
self.is_active = tk.BooleanVar()
self.is_active.set(ConfigManager().config["auto_play"])#从配置文件读取,默认为假
self.stop_flag = threading.Event()
self.pool.submit(self.main_loop)#提交主循环任务到线程池中
self.is_started = True
#指针移动平滑度
def calc_smooth_kernel(self):
new_pointer_smooth = ConfigManager().config["pointer_smooth"]
if self.smooth_kernel is None:
self.smooth_kernel = utils.calc_smooth_kernel(new_pointer_smooth)
elif new_pointer_smooth != len(self.smooth_kernel):
self.smooth_kernel = utils.calc_smooth_kernel(new_pointer_smooth)
else:
pass
#指针上下左右移动比例
def asymmetry_scale(self, vel_x, vel_y):
if vel_x > 0:
vel_x *= ConfigManager().config["spd_right"]
else:
vel_x *= ConfigManager().config["spd_left"]
if vel_y > 0:
vel_y *= ConfigManager().config["spd_down"]
else:
vel_y *= ConfigManager().config["spd_up"]
return vel_x, vel_y
def act(self, track_loc: npt.ArrayLike):
self.curr_track_loc = track_loc
def main_loop(self) -> None:
""" Separate thread for mouse controller
"""
if self.is_destroyed:
return
while not self.stop_flag.is_set():
#等待用户点击 Face control按钮或“Mouse unpause”
if not self.is_active.get():
time.sleep(0.001)
continue
self.buffer = np.roll(self.buffer, shift=-1, axis=0)
self.buffer[-1] = self.curr_track_loc
# Get latest x, y and smooth.
#进行平滑计算
smooth_px, smooth_py = utils.apply_smoothing(
self.buffer, self.smooth_kernel)
vel_x = smooth_px - self.prev_x
vel_y = smooth_py - self.prev_y
self.prev_x = smooth_px
self.prev_y = smooth_py
# In delay state
self.delay_count += 1
if self.delay_count < N_BUFFER:
time.sleep(0.001)
continue
#指针移动与人脸移动比例,根据用户设置调整
vel_x, vel_y = self.asymmetry_scale(vel_x, vel_y)
if ConfigManager().config["mouse_acceleration"]:
vel_x *= self.accel(vel_x)
vel_y *= self.accel(vel_y)
# pydirectinput is not working here
#控制指针移动
pyautogui.move(xOffset=vel_x, yOffset=vel_y)
time.sleep(ConfigManager().config["tick_interval_ms"] / 1000)
def set_active(self, flag: bool) -> None:#激活人脸控制函数
self.is_active.set(flag)
if flag:
self.delay_count = 0
def toggle_active(self):#激活切换
logging.info("Toggle active")
curr_state = self.is_active.get()
self.set_active(not curr_state)
def destroy(self):#注销
if self.is_active is not None:
self.is_active.set(False)
if self.stop_flag is not None:
self.stop_flag.set()
self.is_destroyed = True
复制代码