AI-анализ видеозаписей спортивных матчей
Профессиональные команды тратят десятки часов в неделю на ручной разбор видео матчей. AI-анализ автоматизирует детекцию игровых ситуаций, трекинг игроков и мяча, разбивку на эпизоды — аналитик вместо просмотра 90 минут видео работает с автоматически нарезанными ключевыми моментами с метриками.
Детекция и трекинг игроков на поле
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())
Автоматическое обнаружение ключевых моментов
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)
Тепловая карта активности игрока
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
Кейс: профессиональный футбольный клуб
Клуб Первой лиги. Задача: автоматический разбор 10–15 матчей в неделю (свои + соперники). Ранее: 1 видеоаналитик, 3–4 часа на матч.
После внедрения:
- Автоматическая нарезка: удары по воротам, стандарты, смены владения — за 8 минут на матч
- Тепловые карты всех игроков, статистика бега (км/матч, спринтов)
- Аналитик тратит 30–45 мин на разбор вместо 3–4 часов
| Метрика | Точность |
|---|---|
| Детекция игроков | 94–97% |
| Трекинг мяча (видимый) | 88–93% |
| Определение команды по цвету | 91–96% |
| Детекция ударов по воротам | 86–92% |
| Тип проекта | Срок |
|---|---|
| Базовый трекинг игроков + тепловые карты | 5–8 недель |
| Полная аналитическая система | 10–16 недель |







