AI Sports Match Video Analysis System

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI Sports Match Video Analysis System
Complex
from 1 week to 3 months
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822

AI analysis of sports video recordings

Professional teams spend dozens of hours a week manually analyzing match video. AI analysis automates the detection of game situations, player and ball tracking, and breakdown into episodes. Instead of watching 90 minutes of video, the analyst works with automatically extracted key moments and metrics.

Detection and tracking of players on the field

import cv2
import numpy as np
from ultralytics import YOLO
from collections import defaultdict

class SportsVideoAnalyzer:
    def __init__(self, sport: str, model_path: str):
        # YOLOv8l дообученный на спортивных сценах
        # Классы: player_team_a, player_team_b, referee, ball, goalkeeper
        self.detector = YOLO(model_path)
        self.sport = sport

        # Гомография: пиксели → координаты поля в метрах
        self.homography = None
        self.field_width = 105.0   # футбол
        self.field_height = 68.0

        # Трекинг
        self.player_tracks = {}    # track_id → list of positions
        self.ball_tracks = []

    def set_field_homography(self, frame: np.ndarray):
        """
        Калибровка: находим разметку поля (линии, точки) и вычисляем
        матрицу гомографии для перевода координат.
        Используем детектор разметки или ручную разметку для первого кадра.
        """
        # Реальные координаты угловых точек поля
        field_pts = np.float32([
            [0, 0], [self.field_width, 0],
            [self.field_width, self.field_height],
            [0, self.field_height]
        ])

        # Пиксельные координаты (детектируются или задаются вручную)
        frame_pts = self._detect_field_corners(frame)

        if frame_pts is not None:
            self.homography, _ = cv2.findHomography(
                np.float32(frame_pts), field_pts
            )

    def track_frame(self, frame: np.ndarray) -> dict:
        results = self.detector.track(frame, persist=True, conf=0.45)

        frame_data = {
            'players': [],
            'ball': None,
            'referees': []
        }

        for box in results[0].boxes:
            cls = self.detector.model.names[int(box.cls)]
            bbox = list(map(int, box.xyxy[0]))
            track_id = int(box.id) if box.id is not None else -1
            cx = (bbox[0] + bbox[2]) / 2
            cy = (bbox[1] + bbox[3]) / 2

            # Пересчёт в координаты поля
            field_pos = self._to_field_coords(cx, cy)

            if 'player' in cls:
                player_info = {
                    'track_id': track_id,
                    'team': 'A' if 'team_a' in cls else 'B',
                    'bbox': bbox,
                    'field_pos': field_pos
                }
                frame_data['players'].append(player_info)

                if track_id not in self.player_tracks:
                    self.player_tracks[track_id] = []
                self.player_tracks[track_id].append(field_pos)

            elif 'ball' in cls:
                frame_data['ball'] = {'bbox': bbox, 'field_pos': field_pos}
                self.ball_tracks.append(field_pos)

        return frame_data

    def _to_field_coords(self, px: float, py: float) -> tuple:
        if self.homography is None:
            return (px, py)
        pt = np.float32([[[px, py]]])
        result = cv2.perspectiveTransform(pt, self.homography)
        return tuple(result[0][0].tolist())

Automatic detection of key points

class KeyEventDetector:
    def __init__(self):
        self.ball_speed_history = []
        self.formation_history = []

    def detect_shot_on_goal(self, ball_tracks: list,
                              goal_zone: dict) -> list[dict]:
        """
        Удар по воротам: мяч движется с высокой скоростью в направлении ворот.
        """
        events = []
        for i in range(1, len(ball_tracks)):
            if ball_tracks[i] is None or ball_tracks[i-1] is None:
                continue

            dx = ball_tracks[i][0] - ball_tracks[i-1][0]
            dy = ball_tracks[i][1] - ball_tracks[i-1][1]
            speed = np.sqrt(dx**2 + dy**2)  # м/кадр

            if speed > 3.0:  # быстрое движение мяча
                target_x = ball_tracks[i][0] + dx * 10  # экстраполяция
                target_y = ball_tracks[i][1] + dy * 10

                # Попадает ли траектория в зону ворот?
                if (goal_zone['x1'] <= target_x <= goal_zone['x2'] and
                        goal_zone['y1'] <= target_y <= goal_zone['y2']):
                    events.append({
                        'type': 'shot_on_goal',
                        'frame': i,
                        'ball_speed': speed,
                        'ball_pos': ball_tracks[i]
                    })
        return events

    def detect_pressing(self, team_positions: list,
                          opponent_with_ball: dict) -> float:
        """Индекс прессинга: сколько игроков в радиусе 5м от владельца мяча"""
        if not opponent_with_ball or not team_positions:
            return 0.0

        ball_x, ball_y = opponent_with_ball['field_pos']
        pressing_players = sum(
            1 for p in team_positions
            if np.sqrt((p[0]-ball_x)**2 + (p[1]-ball_y)**2) < 5.0
        )
        return pressing_players / max(len(team_positions), 1)

Player activity heat map

def generate_heatmap(player_track: list,
                      field_w: float = 105, field_h: float = 68,
                      resolution: int = 100) -> np.ndarray:
    heatmap = np.zeros((resolution, int(resolution * field_w / field_h)))

    for pos in player_track:
        if pos is None:
            continue
        px = int(pos[0] / field_w * heatmap.shape[1])
        py = int(pos[1] / field_h * heatmap.shape[0])
        px = np.clip(px, 0, heatmap.shape[1]-1)
        py = np.clip(py, 0, heatmap.shape[0]-1)
        heatmap[py, px] += 1

    heatmap = cv2.GaussianBlur(heatmap.astype(np.float32), (15, 15), 5)
    heatmap /= max(heatmap.max(), 1)
    return heatmap

Case: Professional Football Club

A First League club. Task: automated analysis of 10-15 matches per week (home and opponents). Previously: 1 video analyst, 3-4 hours per match.

After implementation:

  • Automatic cutting: shots on goal, set pieces, changes of possession - in 8 minutes per match
  • Heat maps of all players, running statistics (km/match, sprints)
  • The analyst spends 30-45 minutes on analysis instead of 3-4 hours
Metrics Accuracy
Player detection 94–97%
Ball tracking (visible) 88–93%
Identifying a team by color 91–96%
Shot detection 86–92%
Project type Term
Basic player tracking + heatmaps 5–8 weeks
Complete analytical system 10–16 weeks