AI-powered sports statistics: automate analytics from video

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 1All 1566 services
AI-powered sports statistics: automate analytics from video
Medium
~2-4 weeks
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1319
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    927
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1161
  • image_logo-advance_0.webp
    B2B Advance company logo design
    621
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    896

How AI helps automate sports statistics?

Manual tagging of sports statistics is a bottleneck for clubs and studios. Each match requires 8–12 hours of video analyst work, and professional system licenses (e.g., Hawk-Eye) cost tens of thousands of dollars. We develop AI systems that, from standard 2–4 camera video, deliver 80–90% accuracy of professional systems at a fraction of the cost — our solutions are multiple times cheaper. Typical manual tagging errors: missed episodes, subjective possession assessment, distance discrepancy up to 15%. AI avoids these issues: it processes every frame, pinpointing coordinates of all players and the ball. Below — technical details: how we compute metrics and build turnkey solutions.

How AI calculates distance and speed?

For distance, speed, and heatmap calculations we use player tracking from YOLOv8 + ByteTrack. The SportsStatisticsEngine class aggregates per-player data in real time.

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      # sprints > 25 km/h
    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 = []

        # Speed thresholds (m/frame → km/h)
        self.sprint_threshold = 25 / 3.6 / fps  # m/frame
        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]

            # Distance and speed
            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 in km/h
                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

            # Heatmap
            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

        # Ball possession
        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]:
        """Closest player to ball = in possession"""
        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')

        # Player within 2m = in possession
        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

How AI determines ball possession?

The _determine_possession method computes the closest player to the ball on each frame. If the distance is under 2 meters, possession is recorded. This is simpler and faster than geometric methods and yields ±3–5% accuracy relative to Hawk-Eye.

Text reports

After numerical metrics, we generate analytical text via an LLM — e.g., Mistral 7B. The prompt is tuned to sports bulletin format. For implementation we use Hugging Face Transformers.

from transformers import pipeline

class MatchReportGenerator:
    def __init__(self):
        # GPT or local LLM for text report generation
        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"""Generate a brief analytical match report.
        Match: {match_info['team_a']} vs {match_info['team_b']}
        Score: {match_info['score']}
        Possession: {stats['possession']}
        Top runners: {self._top_runners(stats['players'])}
        Shots on goal: {sum(p['shots'] for p in stats['players'].values())}

        Write a professional analytical text of 3-4 sentences."""

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

Why AI statistics is more cost-effective than traditional solutions?

An AI system requires 5–10 times lower investment than a Hawk-Eye license and processes data faster than a human. We have delivered over 15 projects for football clubs and studios, and we guarantee at least 80% accuracy on key metrics. If results fall below, we retrain the model at no cost. AI statistics accuracy compared to professional systems:

Metric AI from video Hawk-Eye/Opta
Player distance (error) ±5–8% ±1–2%
Ball possession ±3–5% ±1%
Shot count ±10–15% ±2%
Pass detection ±15–20% ±3%
Maximum speed ±8–12% ±2%

For amateur and semi-professional competitions, this is sufficient. For professional leagues, additional cameras and calibration boost accuracy to acceptable levels.

In one project we deployed a system for a second-tier club: 4 IP cameras 1080p, a server with RTX 4060, training took 2 weeks. After implementation, match analysis time dropped from 10 hours to 30 minutes, and distance accuracy was 87% versus manual tagging. The club saved roughly tens of thousands of dollars per year on video analyst services.

How we guarantee accuracy?

We follow MLOps practices: model versioning via MLflow, A/B testing on real matches, data drift monitoring. Every client receives an accuracy report on their data. Our years of experience and over 15 completed projects let us tackle tasks of any complexity. We guarantee the final accuracy will not fall below the stated level — otherwise we refine the model for free.

Implementation process

  1. Analytics — assess filming conditions, camera count, metric requirements.
  2. Design — choose architecture (YOLO + ByteTrack + LLM), prepare data.
  3. Training and calibration — fine-tune model for the specific stadium, annotate test episodes.
  4. Integration — deploy API, dashboard, configure webhooks.
  5. Testing — compare with manual tagging, optimize p99 latency.
  6. Deployment — install on server or cloud, train operators.

At each stage we use MLOps practices: model versioning via MLflow, A/B testing, data drift monitoring. This allows quick adaptation to changes in filming conditions.

What's included in the project

  • Trained detection and tracking model
  • REST API for real-time statistics
  • Dashboard (metric visualization, heatmaps, reports)
  • Integration with existing infrastructure
  • Operations and API documentation
  • Operator training (2 hours); 3 months technical support

Estimated timelines

Project type Timeline
Basic statistics (distance + possession + heatmap) 4–7 weeks
Full statistical platform (with text reports and API) 8–14 weeks
Adding a new sport 2–4 weeks

Get a consultation for your project — we will assess complexity and accuracy on your data. Order a pilot project and see results in 4 weeks.