如何在Python中记录xbox/gamepad控制器的状态?

How to record state of xbox/gamepad controller in Python?

我需要知道特定时间 xbox 控制器所有按钮的值。原因是我正在为神经网络构建训练集,并且我正在尝试同时拍摄屏幕快照和拍摄控制器状态的 "snapshot"。请注意,我能够为该项目的键盘版本成功执行此操作,但 xbox 控制器给我带来了困难。

我尝试过创建一个按钮和值的字典,并在每次从控制器收到事件时更新字典。然后我会将图像和字典保存为训练数据的实例。然而,输入最终与图像完全不同步。我在想这个问题可能与用于读取控制器的包之一中的线程或子进程有关,但我不够熟练,不知道如何修复它。

下面是我的代码。

from inputs import get_gamepad
import time
import cv2
import numpy as np
from mss.windows import MSS as mss

#Only track relevant inputs
gp_state = {#'ABS_HAT0X' : 0, #-1 to 1
             #'ABS_HAT0Y' : 0, #-1 to 1
             #'ABS_RX' : 0, #-32768 to 32767
             #'ABS_RY' : 0, #-32768 to 32767
             'ABS_RZ' : 0, #0 to 255
             'ABS_X' : 0, #-32768 to 32767
             'ABS_Y' : 0, #-32768 to 32767
             #'ABS_Z' : 0, #0 to 255
             'BTN_EAST' : 0,
             'BTN_NORTH' : 0,
             #'BTN_SELECT' : 0,
             'BTN_SOUTH' : 0,
             #'BTN_START' : 0,
             #'BTN_THUMBL' : 0,
             #'BTN_THUMBR' : 0,
             'BTN_TL' : 0,
             'BTN_TR' : 0,
             'BTN_WEST' : 0,
             #'SYN_REPORT' : 0,
             }

dead_zone = 7500

def screen_record(): 
    last_time = time.time()
    while(True):
        # 800x600 windowed mode
        printscreen =  np.array(ImageGrab.grab(bbox=(0,40,800,640)))
        last_time = time.time()
        cv2.imshow('window',cv2.cvtColor(printscreen, cv2.COLOR_BGR2RGB))
        if cv2.waitKey(25) & 0xFF == ord('q'):
            cv2.destroyAllWindows()
            break

def process_img(image):
    original_image = image
    processed_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    contrast = 1
    brightness = 0
    out = cv2.addWeighted(processed_img, contrast, processed_img, 0, brightness)
    return out

def main():

    #Give myself time to switch windows
    #Screen should be in top left
    for _ in range(4):
        time.sleep(1)

    controller_input = np.zeros(5)
    training_data = []
    training_files = 0

    with mss() as sct:
        while True:
            #Get screen and display
            bbox = (150,240,650,490)
            screen =  np.array(sct.grab(bbox))
            new_screen = process_img(screen)
            cv2.imshow('window', new_screen)
            new_screen = cv2.resize(new_screen, (100,50))

            #Map events to dictionary
            events = get_gamepad()
            for event in events:
                gp_state[event.code] = event.state

            #Set to zero if in dead zone
            if abs(gp_state['ABS_X']) < dead_zone:
                gp_state['ABS_X'] = 0 

            if abs(gp_state['ABS_Y']) < dead_zone:
                gp_state['ABS_Y'] = 0 

            #Set values to be between 0 and 1.
            controller_input[0] = (gp_state['ABS_X'] + 32768) / (32767 + 32768)
            controller_input[1] = gp_state['ABS_RZ'] / 255
            controller_input[2] = gp_state['BTN_SOUTH']
            controller_input[3] = gp_state['BTN_EAST']
            controller_input[4] = gp_state['BTN_TR']


            record = gp_state['BTN_NORTH'] #Record while holding y button
            if record:
                training_data.append(np.array([new_screen, controller_input]))
                print(controller_input)
                time.sleep(1)

            if len(training_data) % 500 == 0 and record:
                filename = f"training_data/rlb_XBOXtrain_{time.time()}.npy"
                np.save(filename, training_data)
                training_files += 1
                print(f"Trained {training_files} files!")
                training_data = []


            if cv2.waitKey(25) & 0xFF == ord('q'):
                cv2.destroyAllWindows()
                break

main()

我觉得我让这条路变得比需要的更难。但是有没有更简单的方法来获取控制器在某个时间点的状态?

请注意,我找到了一些适用于 Linux 的解决方案,但我在 Windows 10 中是 运行。这是 Linux 解决方案的示例: https://github.com/FRC4564/Xbox

I feel like I am making this way harder than it needs to be.

不,这其实很难。这很难,因为您不需要 只是 知道特定时间的游戏手柄状态,您还想知道哪个游戏手柄状态用于绘制特定帧。游戏手柄状态采样的时间总是早于绘制帧的时间,并且可能由于应用程序本身添加的延迟而延迟。增加的延迟对于整个应用程序可能是恒定的,也可能在应用程序的不同部分之间有所不同。这不是你可以轻易解释的事情。

您的 python 脚本会在收到游戏手柄输入后立即记录它们,因此我希望它总是 运行 至少比屏幕捕获提前一两帧。

I'm thinking that the issue might be related to threading or subprocesses in one of the packages used to read the controller, but I'm not skilled enough to know how to fix it.

这可能只是您正在测量的应用中游戏手柄输入代码增加的延迟,无法修复。大多数应用不会在收到游戏手柄输入后立即做出任何响应,而是在每帧更新步骤中一次性处理所有这些输入。平均而言,增加的延迟等于帧速率的一半。

如何解决这个问题?我认为由于延迟问题,从另一个应用程序测量游戏手柄状态会很困难。如果可以,最好让应用程序在其主循环期间记录游戏手柄状态,这样您就知道您正在记录实际使用的内容。在 Windows 上,应该可以通过提供您自己的 XInput DLL 版本来做到这一点,只要调用 XInputGetState 就可以记录当前状态。

TensorKart 项目已经解决了这个问题:https://github.com/kevinhughes27/TensorKart/blob/master/utils.py