codesnippetpythonhighperfdriving

The below Python script accepts a screen capture of Gran Turismo 7 gameplay and outputs a table of the recorded throttle and brake positions (0-100) in the form Frame,Brake,Throttle.

Constraints:

  • Must only contain race gameplay. Non-race game navigation could yield useless data
  • Capture must only be of bumper view
  • Car data must be visible as HUD
  • Must be footage directly exported from the PS5’s native export format
  • The exported footage must be 1920x1080. Upscale or downscale as needed

Dependencies:

import cv2
 
# You definitely wanna to edit these
INPUT_FILE = '/path/to/screencap.mp4'
OUTPUT_FILE = '/path/to/pedal-data.csv'
# If something's not working, use this as
# the range of frames to see on screen for debugging
DEBUG_FRAME_RANGE = [-1, -1]
# Edit colors for debug views.
# Note colors are (b, g, r) for some reason
INPUT_EXPECTED_BOUNDS_COLOR = (255, 0, 0)
TEXT_COLOR = (0, 255, 0)
CONTOUR_COLOR = (0,255,0)
MEASURED_INPUT_BOUNDS_COLOR = (0, 0, 255)
# Misc. If you're editing these then you already
# know what you're doing
MAX_PEDAL_INPUT_VALUE_PIXELS = 83
PEDAL_INPUT_BAR_WIDTH = 10
PEDAL_INPUT_VERTICAL_BOUNDS = [867, 953]
THROTTLE_INPUT_HORIZ_BOUNDS = [1120, 1140]
BRAKE_INPUT_HORIZ_BOUNDS = [782, 797]
DEBUG = False
 
def extract_pedal_inputs(frame):
    # Init values
    brake_pct = 0
    throttle_pct = 0
    # Convert frame to grayscale
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # Threshold the image to get any white parts of the screen
    _, thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY)
    # Find contours in the thresholded image. These are essentially outlines of white parts of the screen
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    throttle_found = False
    brake_found = False
    if DEBUG:
        # Draw expected input bounds
        cv2.rectangle(frame, (BRAKE_INPUT_HORIZ_BOUNDS[0], PEDAL_INPUT_VERTICAL_BOUNDS[0]), (BRAKE_INPUT_HORIZ_BOUNDS[0] + (BRAKE_INPUT_HORIZ_BOUNDS[1] - BRAKE_INPUT_HORIZ_BOUNDS[0]), PEDAL_INPUT_VERTICAL_BOUNDS[0] + (PEDAL_INPUT_VERTICAL_BOUNDS[1] - PEDAL_INPUT_VERTICAL_BOUNDS[0])), INPUT_EXPECTED_BOUNDS_COLOR, 2)
        cv2.rectangle(frame, (THROTTLE_INPUT_HORIZ_BOUNDS[0], PEDAL_INPUT_VERTICAL_BOUNDS[0]), (THROTTLE_INPUT_HORIZ_BOUNDS[0] + (THROTTLE_INPUT_HORIZ_BOUNDS[1] - THROTTLE_INPUT_HORIZ_BOUNDS[0]), PEDAL_INPUT_VERTICAL_BOUNDS[0] + (PEDAL_INPUT_VERTICAL_BOUNDS[1] - PEDAL_INPUT_VERTICAL_BOUNDS[0])), INPUT_EXPECTED_BOUNDS_COLOR, 2)
    for contour in contours:
        # Get the bounding box of the contour
        x, y, w, h = cv2.boundingRect(contour)
 
        is_input_bar = w > PEDAL_INPUT_BAR_WIDTH and (y > PEDAL_INPUT_VERTICAL_BOUNDS[0] and y < PEDAL_INPUT_VERTICAL_BOUNDS[1])
        is_throttle_bar = x > THROTTLE_INPUT_HORIZ_BOUNDS[0] and x < THROTTLE_INPUT_HORIZ_BOUNDS[1]
        is_brake_bar = x > BRAKE_INPUT_HORIZ_BOUNDS[0] and x < BRAKE_INPUT_HORIZ_BOUNDS[1]
 
        if is_input_bar:
            input_pct = int((h / MAX_PEDAL_INPUT_VALUE_PIXELS) * 100)
            if is_throttle_bar:
                throttle_pct = input_pct
                throttle_found = True
            elif is_brake_bar:
                brake_pct = input_pct
                brake_found = True
            else:
                # Keep searching for the input bars
                continue
            if DEBUG:
                # Show the computed contours
                cv2.drawContours(frame, [contour], -1, CONTOUR_COLOR, 3)
                # Draw a bounding box around the input bar
                cv2.rectangle(frame, (x, y), (x + w, y + h), MEASURED_INPUT_BOUNDS_COLOR, 2)
                if is_throttle_bar:
                    input_name = "Throttle"
                else:
                    input_name = "Brake"
                cv2.putText(frame, f'{input_name}: {input_pct}%', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, TEXT_COLOR, 2)
            if throttle_found and brake_found:
                break
    # Display the frame with overlays
    if DEBUG:
        # cv2.drawContours(frame, contours, -1, (0,255,0), 3) # Draw all contours
        # Show image with overlays on top
        cv2.imshow('Throttle and Brake Percentages', frame)
        cv2.waitKey(0) # Wait for any key before closing window
        cv2.destroyAllWindows()
    return brake_pct, throttle_pct
 
# For testing just one image frame at a time
# if __name__ == "__main__":
#     image_path = '/path/to/bumper-view.png'
#     frame = cv2.imread(image_path)
#     brake_pct, throttle_pct = extract_pedal_inputs(frame)
#     print(brake_pct, throttle_pct)
 
if __name__ == "__main__":
    vid_capture = cv2.VideoCapture(INPUT_FILE)
    if not vid_capture.isOpened():
        print("Error: Couldn't open video")
        exit(1)
    with open(OUTPUT_FILE, 'w') as inputs_csv:
        inputs_csv.write('Frame,Brake,Throttle\n')
        frame_index = 0
        while True:
            if frame_index >= DEBUG_FRAME_RANGE[0] and frame_index <= DEBUG_FRAME_RANGE[1]:
                DEBUG = True
            else:
                DEBUG = False
                if DEBUG_FRAME_RANGE[0] > 0 and frame_index == DEBUG_FRAME_RANGE[0] - 1:
                    print("Beginning debug of frames. Take a close look")
                if DEBUG_FRAME_RANGE[1] > 0 and frame_index == DEBUG_FRAME_RANGE[1] + 1:
                    print("Debugging frames complete. Finishing write to file. This could take a while")
            ret, frame = vid_capture.read()
            if not ret:
                print("Error: Couldn't retrieve frame")
                break
            brake_pct, throttle_pct = extract_pedal_inputs(frame)
            # Note the + 1 below
            inputs_csv.write(f'{frame_index + 1},{brake_pct},{throttle_pct}\n')
            frame_index += 1
    vid_capture.release()