LeagueVision

2024-12-14 : previous : next : index

CONTENT

Detecting champions on the League of Legends minimap involves several key steps and components. The goal is to identify champion icons on the minimap in a given gameplay video using traditional computer vision techniques.

Data Preparation

First, we start with two main data sources:

Before attempting detection, we run a script crop_minimap.py that extracts only the minimap region from the full gameplay footage. This results in a simplified, smaller video showing only the minimap—making our detection task more focused and efficient.

Approach

With the minimap-only video, we use LeagueVisionCV.py to apply OpenCV’s template matching technique.

  1. Load and Resize Icons: The code reads in icons from the champion_icons directory and resizes them based on a ratio to ensure they match the scale of the minimap.
  2. Icon Cropping: A portion of the icon is cropped (using ICON_SEARCH_RATIO) so that only the central, most distinctive part of the icon is used. This can improve matching accuracy.
  3. Template Matching: For each frame of the minimap-only video, the code converts the frame to grayscale and runs cv2.matchTemplate against each champion icon. If a match exceeds the THRESHOLD value, we consider it a valid detection.
  4. Drawing Bounding Boxes: When a match is found, the code draws a bounding box around the detected champion icon’s location on the minimap. It also prints the champion’s name and the match confidence score.

Code Snippets Explained

Below are some key code snippets from LeagueVisionCV.py and their purposes:

Loading and Resizing Icons

# Compute icon_size based on minimap size
minimap_size = min(h, w)
icon_size = int(minimap_size * ICON_RATIO)

# Load and process icons
icons = load_icons(ICONS_FOLDER, icon_size)

Here, we determine icon_size by taking the smallest dimension of the minimap frame and multiplying by ICON_RATIO. This ensures the icons scale proportionally to the minimap.

Cropping Icons

# Crop the icon according to ICON_SEARCH_RATIO
h, w = icon.shape[:2]
crop_margin_w = int(w * ICON_SEARCH_RATIO / 2)
crop_margin_h = int(h * ICON_SEARCH_RATIO / 2)
icon = icon[crop_margin_h:h - crop_margin_h, crop_margin_w:w - crop_margin_w]

This snippet takes the loaded icon and crops it to focus on the center. By removing some of the icon’s border areas, you may get more reliable template matches.

Template Matching

res = cv2.matchTemplate(gray_frame, gray_icon, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

if max_val >= THRESHOLD:
    # Draw bounding box
    ...

Using cv2.matchTemplate, we scan the entire minimap frame for the presence of the champion icon. If max_val (the best match score) is greater than our chosen threshold, we consider it a successful detection.

Drawing Bounding Boxes

top_left = max_loc
bottom_right = (top_left[0] + w_i, top_left[1] + h_i)
cv2.rectangle(detected_frame, top_left, bottom_right, (0, 255, 255), 2)
cv2.putText(detected_frame, f"{champion} ({max_val:.2f})",
            (top_left[0], top_left[1]-5),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.5, (0, 255, 255), 1)

Once a valid match is found, this code draws a yellow bounding box and labels it with the champion’s name and confidence score. The result is a visual overlay on the minimap video.

Iterative Improvements

We experimented with different values for THRESHOLD and ICON_RATIO to find a balance between detecting champions accurately and minimizing false positives.

Results

After these adjustments, the final output shows bounding boxes over recognized champions on the minimap. By initially showing a video of the raw minimap and then showing it with detection overlays, readers can see how the code successfully identifies champions.


video1

video2

video3


In all of these examples we see we get a couple champions detected. This is at .8 threshold which i felt like yielded the best results. The threshold is the confidence level of the match. If the match is below the threshold it will not show.

Lets talk about the first video. It accurately detects 6 of the 9 champions on screen. Along with this it also have some ghosts like for example on the bottom left sion is detected but is never there. I couldn't get this to go away after tuning the threshold. Im surprised that it detected some of the champions in the bottom right of the minimap since they were overlapping a lot and the screen indicator which is the white box was obscuring the icon most of the time. With the other clips i had similar results where it would detect ghost champions that weren't on the map. Funny enough sion and pantheon were the most consistent. I wonder if this is because the openCV algorithm thinks those sections resemble the icons i provided.

Full Code: LeagueVisionCV.py

import os
import cv2
import numpy as np
import argparse

ICONS_FOLDER = 'champion_icons'
THRESHOLD = 0.8
ICON_RATIO = 25 / 280      # Reduced ratio for smaller icons
ICON_SEARCH_RATIO = 0.5    # Percentage of the champion icon to use in matchTemplate

def load_icons(folder, icon_size):
    icons = []
    for fname in os.listdir(folder):
        champ, ext = os.path.splitext(fname)
        if ext.lower() == '.png':
            path = os.path.join(folder, fname)
            icon = cv2.imread(path, cv2.IMREAD_COLOR)
            if icon is not None:
                # Resize to the icon size
                icon = cv2.resize(icon, (icon_size, icon_size), interpolation=cv2.INTER_CUBIC)

                # Crop the icon according to ICON_SEARCH_RATIO
                h, w = icon.shape[:2]
                crop_margin_w = int(w * ICON_SEARCH_RATIO / 2)
                crop_margin_h = int(h * ICON_SEARCH_RATIO / 2)
                icon = icon[crop_margin_h:h - crop_margin_h, crop_margin_w:w - crop_margin_w]

                icons.append((champ, icon))
    return icons

def process_video(input_video_path, icons_folder):
    cap = cv2.VideoCapture(input_video_path)
    if not cap.isOpened():
        print(f"Error: Cannot open video {input_video_path}")
        return

    # Read first frame to determine icon_size
    ret, frame = cap.read()
    if not ret:
        print("Error: Cannot read the first frame.")
        return

    # Compute icon_size based on the frame's minimap size assumption
    # Here we assume the entire frame is the minimap for simplicity.
    h, w = frame.shape[:2]
    minimap_size = min(h, w)
    icon_size = int(minimap_size * ICON_RATIO)

    # Now load and adjust icons based on computed icon_size
    icons = load_icons(ICONS_FOLDER, icon_size)
    if not icons:
        print("No icons found. Place PNG icons in the champion_icons folder.")
        return

    print(f"Loaded {len(icons)} icons.")
    # Reset video to first frame
    cap.set(cv2.CAP_PROP_POS_FRAMES, 0)

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        detected_frame = frame.copy()
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        for champion, icon in icons:
            gray_icon = cv2.cvtColor(icon, cv2.COLOR_BGR2GRAY)

            # Ensure icon fits in the frame
            if gray_icon.shape[0] > gray_frame.shape[0] or gray_icon.shape[1] > gray_frame.shape[1]:
                continue

            # Run matchTemplate on the entire frame
            res = cv2.matchTemplate(gray_frame, gray_icon, cv2.TM_CCOEFF_NORMED)
            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

            # Debug info
            print(f"{champion}: max_val = {max_val}")

            if max_val >= THRESHOLD:
                # Draw bounding box where match was found
                h_i, w_i = gray_icon.shape
                top_left = max_loc
                bottom_right = (top_left[0] + w_i, top_left[1] + h_i)
                cv2.rectangle(detected_frame, top_left, bottom_right, (0, 255, 255), 2)
                cv2.putText(detected_frame, f"{champion} ({max_val:.2f})",
                            (top_left[0], top_left[1]-5),
                            cv2.FONT_HERSHEY_SIMPLEX,
                            0.5, (0, 255, 255), 1)

        cv2.imshow("Debug Detected", detected_frame)
        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

def main():
    parser = argparse.ArgumentParser(description="Minimal debug code for template matching with smaller icons.")
    parser.add_argument("input_video", help="Path to the input .mp4 file (already cropped to minimap).")
    args = parser.parse_args()

    process_video(args.input_video, ICONS_FOLDER)

if __name__ == "__main__":
    main()

links



Index

winters...