简体   繁体   English

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

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

I need to know at a specific time the value of all buttons of an xbox controller. 我需要在特定时间知道xbox控制器的所有按钮的值。 The reason being that I'm building a training set for a neural network, and I'm trying to simultaneously take a snapshot of the screen and take a "snapshot" of the controller state. 原因是我正在为神经网络构建训练集,并且试图同时拍摄屏幕快照和拍摄控制器状态的“快照”。 Note that I was able to successfully do this for a keyboard version of this project, but the xbox controller is giving me difficulty. 请注意,对于该项目的键盘版本,我能够成功完成此操作,但是xbox控制器给我带来了困难。

What I've tried is creating a dictionary of buttons and values, and updating the dictionary every time I receive an event from the controller. 我所尝试的是创建按钮和值的字典,并在每次收到控制器事件时更新字典。 Then I would save the image and dictionary as an instance of training data. 然后,我将图像和字典另存为训练数据的实例。 However, the inputs end up not at all synced with the images. 但是,输入最终根本不与图像同步。 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. 我认为该问题可能与用于读取控制器的软件包之一中的线程或子进程有关,但是我不够熟练,无法知道如何解决它。

Below is my code. 下面是我的代码。

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()

I feel like I am making this way harder than it needs to be. 我觉得我正在使这种方式变得比所需的更加困难。 But is there an easier way to just get the state of the controller at a certain point in time? 但是,是否有一种更简单的方法来仅在特定时间点获取控制器的状态?

Note that I've found some solutions that work for Linux, but I am running in Windows 10. Here is an example of a Linux solution: https://github.com/FRC4564/Xbox 请注意,我已经找到了一些适用于Linux的解决方案,但是我正在Windows 10中运行。这是Linux解决方案的示例: https : //github.com/FRC4564/Xbox

I feel like I am making this way harder than it needs to be. 我觉得我正在使这种方式变得比所需的更加困难。

No, this is actually hard. 不,这实际上很难。 It's hard because you don't need to just know what the gamepad state is at a particular time, you also want to know which gamepad state was used to draw a particular frame. 这很难,因为你不需要知道游戏手柄的状态是在特定的时间什么,你也想知道哪个游戏手柄状态被用于绘制特定帧。 The time that the gamepad state was sampled will always be earlier than the time the frame was drawn, and may be delayed due to latency added by the app itself. 采样游戏手柄状态的时间将始终早于绘制框架的时间,并且可能由于应用程序自身添加的延迟而延迟。 The added latency might be constant for the whole app or it might vary between different parts of the app. 对于整个应用程序而言,增加的延迟可能是恒定的,或者在应用程序的不同部分之间可能有所不同。 It's not something you can easily account for. 这不是您可以轻松解决的问题。

Your python script is recording gamepad inputs as soon as they are received, so I'd expect it to always run at least a frame or two ahead of the screen captures. 您的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. 我认为该问题可能与用于读取控制器的软件包之一中的线程或子进程有关,但是我不够熟练,无法知道如何解决它。

It's probably just latency added by the gamepad input code in the app you're measuring and not something that can be fixed. 这可能只是您正在测量的应用程序中游戏手柄输入代码所增加的延迟,而不是可以解决的问题。 Most apps don't make any attempt to respond to gamepad inputs as soon as they're received and instead handle them all at once during the per-frame update step. 大多数应用程序不会在收到游戏手柄输入后立即做出任何尝试,而是在每帧更新步骤中一次全部处理它们。 On average, that adds latency equal to half the frame rate. 平均而言,这会增加等于帧速率一半的延迟。

How to fix this? 如何解决这个问题? I think measuring gamepad state from another application is going to be difficult due to the latency issues. 我认为由于延迟问题,从另一个应用程序测量游戏手柄状态将很困难。 If you can, it would be best to instrument the app to record the gamepad state during its main loop, that way you know you are recording what was actually used. 如果可以的话,最好是对应用进行检测,以在其主循环中记录游戏手柄的状态,这样您就可以知道正在记录的实际使用情况。 On Windows it should be possible to do this by providing your own version of the XInput DLL that can record the current state whenever XInputGetState is called. 在Windows上,应该可以通过提供自己的XInput DLL版本来做到这一点,该版本可以在每次调用XInputGetState时记录当前状态。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM