With this code below, I am only getting on average 20 frames per second. I want it to be at least 30 frames per second. Could you optimize this code, Frame skip? 


Import necessary libraries
import torch
import cv2
import numpy as np
import pygetwindow as gw
import os
import ctypes
import time
import mss

# Step 2: Print the current working directory
print("Current working directory:", os.getcwd())

# Step 3: Load YOLOv5 Model
class YOLOv5:
    def __init__(self, model_path=None, device='cuda'):
        if model_path is None:
            # Load a pretrained model
            self.model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
        else:
            # Load a custom model using the provided model_path
            self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=model_path)

        self.model = self.model.to(device).eval()

# Step 4: Handling Game State Verification
def is_game_open():
    # Use pygetwindow to get the game window
    game_window = gw.getWindowsWithTitle('Superfighters Deluxe')
    return bool(game_window)

# Step 5: Calculate frames per second
def calculate_fps(start_time, frames):
    current_time = time.time()
    elapsed_time = current_time - start_time
    fps = frames / elapsed_time
    return fps

# Step 6: Main Real-Time Detection Loop
def main():
    # Step 6.1: Adjust the path to your custom-trained YOLOv5 model
    model_path = r"C:\Windows\System32\yolov5\models\best.pt"

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # Step 6.2: Load YOLOv5 model
    model = YOLOv5(model_path, device=device)

    # Step 6.3: Variables for calculating frames per second
    start_time = time.time()
    frames = 0

    # Step 6.4: Set the target frame rate
    target_fps = 30
    target_frame_time = 1 / target_fps

    # Step 6.5: Create a window with a fixed size
    cv2.namedWindow("YOLOv5 on Superfighters Deluxe", cv2.WINDOW_NORMAL)
    cv2.resizeWindow("YOLOv5 on Superfighters Deluxe", 300, 300)

    with mss.mss() as sct:
        while True:
            # Step 6.6: Record the start time for each iteration
            iteration_start_time = time.time()

            # Step 6.7: Bring the game window to the foreground
            game_window = gw.getWindowsWithTitle('Superfighters Deluxe')
            if game_window:
                game_window = game_window[0]
                ctypes.windll.user32.ShowWindow(game_window._hWnd, 9)  # SW_RESTORE
                ctypes.windll.user32.SetForegroundWindow(game_window._hWnd)

            # Step 6.8: Use mss to capture the screen
            monitor = {"top": game_window.top, "left": game_window.left, "width": game_window.width, "height": game_window.height}
            frame = np.array(sct.grab(monitor))

            # Step 6.9: Real-Time Detection Loop
            results = model.model(frame)
            labels, cord = results.xyxy[0][:, -1], results.xyxy[0][:, :-1]

            # Step 6.10: Draw boxes on frame
            for i, detection in enumerate(cord):
                x, y, w, h = detection[:4]  # Extract the first 4 values if there are more
                x1, y1, x2, y2 = int(x), int(y), int(w), int(h)

                # Replace label values with names
                label_name = "local" if int(labels[i]) == 0 else "enemy"

                # Use green for "local" and red for "enemy"
                color = (0, 255, 0) if label_name == "local" else (255, 0, 0)

                cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
                cv2.putText(frame, label_name, (x1, y1), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2)

            cv2.imshow("YOLOv5 on Superfighters Deluxe", frame)

            # Step 6.11: Increment frames counter
            frames += 1

            # Step 6.12: Print frames per second every 3 seconds
            if time.time() - start_time > 3:
                fps = calculate_fps(start_time, frames)
                print(f"Frames per second: {fps:.2f}")
                start_time = time.time()
                frames = 0

            # Step 6.13: Calculate the time taken for the iteration
            iteration_time = time.time() - iteration_start_time

            # Step 6.14: Introduce a dynamic delay to achieve the target frame rate
            remaining_time = max(0, target_frame_time - iteration_time)
            delay_time = remaining_time + 0.001  # Add a small constant to avoid zero delay
            time.sleep(delay_time)

            key = cv2.waitKey(1)  # You can reduce the delay to 1ms for better responsiveness

            # Step 6.15: Break the loop if 'q' key is pressed
            if key == ord("q"):
                cv2.destroyAllWindows()
                break

if __name__ == "__main__":
    main()