AI Team Tactics and Strategy 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 Team Tactics and Strategy 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-анализ тактики и стратегии командных спортивных игр

Тактический анализ — самая сложная часть спортивной аналитики. Распознать, что команда играет в 4-3-3 или использует высокий прессинг — это не детекция объектов, а паттерн-матчинг на временных последовательностях позиций 11 игроков.

Распознавание схемы расстановки

import numpy as np
from sklearn.cluster import KMeans
from scipy.spatial import ConvexHull
from typing import Optional

class FormationAnalyzer:
    """
    Схема расстановки определяется по медианным позициям игроков
    за период без мяча (когда команда организована).
    """

    KNOWN_FORMATIONS = {
        '4-3-3': [[0.15, 0.5], [0.3, 0.15], [0.3, 0.38], [0.3, 0.62], [0.3, 0.85],
                   [0.55, 0.3], [0.55, 0.5], [0.55, 0.7],
                   [0.75, 0.2], [0.75, 0.5], [0.75, 0.8]],
        '4-4-2': [[0.15, 0.5], [0.3, 0.15], [0.3, 0.38], [0.3, 0.62], [0.3, 0.85],
                   [0.55, 0.2], [0.55, 0.4], [0.55, 0.6], [0.55, 0.8],
                   [0.75, 0.35], [0.75, 0.65]],
        '3-5-2': [[0.15, 0.5], [0.3, 0.25], [0.3, 0.5], [0.3, 0.75],
                   [0.5, 0.1], [0.5, 0.3], [0.5, 0.5], [0.5, 0.7], [0.5, 0.9],
                   [0.75, 0.35], [0.75, 0.65]],
    }

    def __init__(self, field_size: tuple = (105, 68)):
        self.field_w, self.field_h = field_size

    def detect_formation(self, player_positions: list,
                          frames_window: int = 300) -> dict:
        """
        player_positions: list of dicts {player_id, field_pos, team}
        Используем только позиции «без мяча» (команда организована).
        """
        if len(player_positions) < 5:
            return {'formation': 'unknown', 'confidence': 0}

        # Медианная позиция каждого игрока за окно
        positions_by_player = {}
        for record in player_positions[-frames_window:]:
            pid = record['player_id']
            pos = record['field_pos']
            if pos:
                positions_by_player.setdefault(pid, []).append(pos)

        median_positions = []
        for pid, positions in positions_by_player.items():
            if len(positions) > 10:  # минимум данных для оценки
                median_pos = np.median(positions, axis=0)
                median_positions.append(median_pos)

        if len(median_positions) < 8:
            return {'formation': 'unknown', 'confidence': 0}

        # Нормализуем позиции [0..1]
        norm_positions = [[p[0] / self.field_w, p[1] / self.field_h]
                           for p in median_positions[:11]]

        # Сортируем по x (глубина поля)
        norm_positions.sort(key=lambda p: p[0])
        norm_positions = norm_positions[1:]  # убираем вратаря

        # Сравниваем с эталонными схемами
        best_match = 'unknown'
        best_score = float('inf')

        for formation_name, template in self.KNOWN_FORMATIONS.items():
            template_sorted = sorted(template, key=lambda p: p[0])[1:]
            score = self._alignment_score(norm_positions, template_sorted)
            if score < best_score:
                best_score = score
                best_match = formation_name

        confidence = max(0, 1 - best_score / 2)

        return {
            'formation': best_match,
            'confidence': confidence,
            'player_median_positions': norm_positions
        }

    def _alignment_score(self, positions: list, template: list) -> float:
        """Минимальная сумма расстояний между позициями и шаблоном (assignment problem)"""
        from scipy.optimize import linear_sum_assignment

        n = min(len(positions), len(template))
        cost_matrix = np.zeros((n, n))

        for i, pos in enumerate(positions[:n]):
            for j, tmpl in enumerate(template[:n]):
                cost_matrix[i, j] = np.sqrt((pos[0]-tmpl[0])**2 + (pos[1]-tmpl[1])**2)

        row_ind, col_ind = linear_sum_assignment(cost_matrix)
        return float(cost_matrix[row_ind, col_ind].mean())

Детекция прессинга и линий обороны

class TacticalPatternDetector:

    def detect_high_press(self, team_positions: list,
                            opponent_ball_pos: tuple,
                            field_height: float = 68) -> dict:
        """
        Высокий прессинг: большинство игроков на чужой половине поля.
        PPDA (Passes Per Defensive Action) — стандартная метрика прессинга.
        """
        if not team_positions:
            return {'pressing': False}

        # Игроки на чужой половине (x > 52.5 для атаки слева направо)
        half_line = field_height / 2
        players_in_opp_half = sum(
            1 for p in team_positions
            if p.get('field_pos') and p['field_pos'][0] > 52.5
        )

        pressing_intensity = players_in_opp_half / max(len(team_positions), 1)

        # Компактность: ширина и длина блока обороны
        positions = [p['field_pos'] for p in team_positions
                      if p.get('field_pos')]
        if positions:
            xs = [p[0] for p in positions]
            ys = [p[1] for p in positions]
            block_depth = max(xs) - min(xs)
            block_width = max(ys) - min(ys)
        else:
            block_depth = block_width = 0

        return {
            'pressing': pressing_intensity > 0.6,
            'pressing_intensity': pressing_intensity,
            'players_in_opp_half': players_in_opp_half,
            'block_depth_m': block_depth,
            'block_width_m': block_width
        }

    def compute_defensive_line_height(self,
                                       defensive_players: list) -> Optional[float]:
        """Высота линии обороны в метрах от своих ворот"""
        if not defensive_players:
            return None

        positions = [p['field_pos'][0] for p in defensive_players
                      if p.get('field_pos')]
        if not positions:
            return None

        # Линия обороны = медиана глубинной позиции 4 защитников
        return float(np.median(sorted(positions)[:4]))

Кейс: анализ 38 туров чемпионата

Задача аналитического отдела клуба: автоматически строить тактические карты по каждому матчу сезона. Ранее вручную: 6–8 часов на матч.

Система обрабатывала 90-минутные трансляции (видео 1080p@25fps):

  • Детекция расстановки: точность 84% совпадений с экспертной оценкой
  • Время обработки одного матча: 22 минуты (RTX 3090)
  • Автоматически генерировались: тепловые карты по зонам, средние позиции, фазы прессинга с таймкодами
Тактический показатель Точность AI Эталон (эксперт)
Определение схемы расстановки 84%
Детекция высокого прессинга 81%
Линия обороны (ошибка ±м) ±3.2м ±1.5м
Тип проекта Срок
Расстановка + тепловые карты 6–10 недель
Полная тактическая аналитика 12–18 недель