AI-система автоматической генерации спортивной статистики

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
AI-система автоматической генерации спортивной статистики
Средняя
~2-4 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    854
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

Автоматическая генерация спортивной статистики с помощью AI

Статистика матча — количество ударов, владение мячом, пробег игроков, пасовые сети — традиционно требует ручной разметки или дорогостоящих оптических систем типа Hawk-Eye. AI по видео с обычных камер даёт 80–90% точности профессиональных систем за долю стоимости.

Ключевые метрики и методы расчёта

import numpy as np
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class PlayerStats:
    player_id: int
    team: str
    distance_m: float = 0.0
    sprint_count: int = 0      # спринты > 25 км/ч
    max_speed_kmh: float = 0.0
    touches: int = 0
    shots: int = 0
    passes_attempted: int = 0
    passes_completed: int = 0
    heatmap: np.ndarray = field(default_factory=lambda: np.zeros((68, 105)))

class SportsStatisticsEngine:
    def __init__(self, fps: float, sport: str = 'football'):
        self.fps = fps
        self.sport = sport
        self.player_stats: dict[int, PlayerStats] = {}
        self.possession_log = []  # (frame_idx, team_with_ball)
        self.event_log = []

        # Пороги скорости (м/с на кадр → км/ч)
        self.sprint_threshold = 25 / 3.6 / fps  # м/кадр
        self.jogging_threshold = 11 / 3.6 / fps

    def update(self, frame_idx: int, tracked_players: list,
                ball_pos: Optional[tuple]):
        for player in tracked_players:
            pid = player['track_id']
            team = player.get('team', 'unknown')
            pos = player.get('field_pos')

            if pid not in self.player_stats:
                self.player_stats[pid] = PlayerStats(
                    player_id=pid, team=team
                )

            stats = self.player_stats[pid]

            # Расстояние и скорость
            if hasattr(stats, '_prev_pos') and stats._prev_pos and pos:
                dist = np.sqrt((pos[0]-stats._prev_pos[0])**2 +
                                (pos[1]-stats._prev_pos[1])**2)
                stats.distance_m += dist

                # Скорость в км/ч
                speed_ms = dist * fps
                speed_kmh = speed_ms * 3.6

                if speed_kmh > stats.max_speed_kmh:
                    stats.max_speed_kmh = speed_kmh

                if speed_kmh > 25:
                    stats.sprint_count += 1

            stats._prev_pos = pos

            # Тепловая карта
            if pos:
                hm_x = int(np.clip(pos[0], 0, 104))
                hm_y = int(np.clip(pos[1], 0, 67))
                stats.heatmap[hm_y, hm_x] += 1

        # Владение мячом
        if ball_pos:
            possessing_team = self._determine_possession(
                ball_pos, tracked_players
            )
            self.possession_log.append((frame_idx, possessing_team))

    def _determine_possession(self, ball_pos: tuple,
                                players: list) -> Optional[str]:
        """Ближайший игрок к мячу = владеет мячом"""
        if not players or not ball_pos:
            return None

        min_dist = float('inf')
        possessing_team = None

        for player in players:
            pos = player.get('field_pos')
            if pos is None:
                continue
            dist = np.sqrt((ball_pos[0]-pos[0])**2 + (ball_pos[1]-pos[1])**2)
            if dist < min_dist:
                min_dist = dist
                possessing_team = player.get('team')

        # Игрок ближе 2м = владеет
        return possessing_team if min_dist < 2.0 else None

    def compute_possession_stats(self) -> dict:
        total = len(self.possession_log)
        if total == 0:
            return {'team_a': 50.0, 'team_b': 50.0}

        counts = defaultdict(int)
        for _, team in self.possession_log:
            if team:
                counts[team] += 1

        contested = total - sum(counts.values())
        return {
            team: round(count / total * 100, 1)
            for team, count in counts.items()
        }

    def generate_match_report(self) -> dict:
        report = {
            'possession': self.compute_possession_stats(),
            'players': {}
        }

        for pid, stats in self.player_stats.items():
            report['players'][pid] = {
                'team': stats.team,
                'distance_km': round(stats.distance_m / 1000, 2),
                'sprint_count': stats.sprint_count,
                'max_speed_kmh': round(stats.max_speed_kmh, 1),
                'shots': stats.shots,
                'passes_attempted': stats.passes_attempted,
                'pass_accuracy': (stats.passes_completed /
                                   max(stats.passes_attempted, 1) * 100)
            }

        return report

Автоматическая генерация текстовых отчётов

from transformers import pipeline

class MatchReportGenerator:
    def __init__(self):
        # GPT или локальная LLM для генерации текстового отчёта
        self.generator = pipeline('text-generation',
                                   model='mistralai/Mistral-7B-Instruct-v0.2',
                                   device=0)

    def generate_narrative(self, stats: dict, match_info: dict) -> str:
        prompt = f"""Сгенерируй краткий аналитический отчёт матча.
        Матч: {match_info['team_a']} vs {match_info['team_b']}
        Счёт: {match_info['score']}
        Владение: {stats['possession']}
        Топ по пробегу: {self._top_runners(stats['players'])}
        Удары по воротам: {sum(p['shots'] for p in stats['players'].values())}

        Напиши профессиональный аналитический текст на 3-4 предложения."""

        result = self.generator(prompt, max_new_tokens=200,
                                 temperature=0.7, do_sample=True)
        return result[0]['generated_text'].split(prompt)[-1].strip()

Точность автоматической статистики vs. профессиональные системы

Метрика AI по видео Hawk-Eye/Opta
Пробег игрока (ошибка) ±5–8% ±1–2%
Владение мячом ±3–5% ±1%
Количество ударов ±10–15% ±2%
Определение паса ±15–20% ±3%
Максимальная скорость ±8–12% ±2%

Для любительских и полупрофессиональных соревнований — вполне достаточно. Для профессиональных лиг — дополнительные камеры и калибровка повышают точность до приемлемого уровня.

Тип проекта Срок
Базовая статистика (пробег + владение) 4–7 недель
Полная статистическая платформа 8–14 недель