I am trying to detect player movement on a mini-map over the course of a match. The minimap location and size is static. Player arrows are a static shape but change position and rotation.
Player arrows are typically a bright and saturated color while the background is dark and semi-transparent. I am having trouble removing the background since the transparent nature of it changes the background colors slightly each frame.
Here is my current script which can iterates through a video and tries to highlight the player arrows on each frame. The first few frames work as expected but as the background changes the mask struggles to work consistently.
Does anyone have any background removal techniques for backgrounds that change over time? I can get the hex color range of the arrows before conversion. Maybe there is a better way to remove everything besides the arrow color. Any help is greatly appreciated!
import cv2 import numpy as np cap = cv2.VideoCapture("./videos/map1.mp4") if not cap.isOpened(): print("Error: Video file not opened.") exit() # Initialize a frame counter frame_count = 0 max_frames = 25 while cap.isOpened() and frame_count < max_frames: ret, frame = cap.read() if not ret: print("Error: Frame not read.") break frame_height, frame_width, _ = frame.shape # Crop the bottom-left corner crop_height = 490 # Half of the image height crop_width = 330 # Half of the image width cropped_frame = frame[crop_height:, :crop_width] # Convert the cropped frame to HSV color space hsv_frame = cv2.cvtColor(cropped_frame, cv2.COLOR_BGR2HSV) # Define the lower and upper HSV color range lower_color = np.array([0, 0, 176]) upper_color = np.array([179, 32, 255]) # Create a mask to isolate the shapes mask = cv2.inRange(hsv_frame, lower_color, upper_color) cv2.imshow('Masked Image', mask) # Apply the mask to the cropped frame shape_only = cv2.bitwise_and(cropped_frame, cropped_frame, mask=mask) # Convert the shape_only image to grayscale and apply thresholding gray_shape_only = cv2.cvtColor(shape_only, cv2.COLOR_BGR2GRAY) _, thresholded_image = cv2.threshold(gray_shape_only, 1, 255, cv2.THRESH_BINARY) # Find contours in the thresholded image contours, _ = cv2.findContours(thresholded_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Create a copy of the cropped frame to draw the triangles on frame_with_triangles = cropped_frame.copy() # Loop through each contour and identify triangles for contour in contours: approx = cv2.approxPolyDP(contour, 0.06 * cv2.arcLength(contour, True), True) area = cv2.contourArea(contour) approxLength = len(approx) if ((2 < approxLength and approxLength <= 5) and (70 < area and area <= 90)): cv2.drawContours(frame_with_triangles, [approx], 0, (0, 255, 0), 2) # Draw a green triangle # Display the frame with detected triangles cv2.imshow('Video with Triangles', frame_with_triangles) cv2.waitKey(0) frame_count += 1 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()