footyviz

Open-source match tracking for amateur football: every player tracked, team heatmaps, possession and distance from a single Veo or YouTube camera

Below: 1 min of a Cape Town 5-a-side, 1799 frames, processed in 56 s on one GPU. Source on GitHub

Dusk slice

11:07 to 12:07 of hhaWMwLlugE, 1799 frames at 1280x720

Possession

Grey
46%
Pink
22%
Unresolved
31%

Proxy: in each frame with a ball detection (YOLO COCO 'sports ball'), the team of the nearest player (foot point) within 3 player-heights owns the ball; frames without a ball inherit the last owner for up to 1 s, otherwise count as unresolved. Ball nearest a goalkeeper/other-kit player counts as unresolved. Ball detected in 76% of frames.

Distance covered

Grey
416 m
Pink
199 m

No homography. Player foot-point tracks in image pixels, camera pan removed by global phase correlation, smoothed (9-frame mean), steps faster than 0.5 frame-widths/s dropped. Metres = pixels / (frame width / 25 m), i.e. assumes the visible pitch width is 25 m and ignores zoom and perspective. Expect ±50%.

Heatmaps

Grey
Less time More time
Pink
Less time More time

Positions are in the camera's view, not on the pitch: the camera pans to follow play, so left and right are edges of the frame, not ends of the pitch.

Roster

  • Best crop of A-1 A-1
  • Best crop of A-5 A-5
  • Best crop of A-4 A-4
  • Best crop of A-2 A-2
  • Best crop of A-6 A-6
  • Best crop of A-3 A-3
  • Best crop of A-11 A-11
  • Best crop of A-7 A-7
  • Best crop of A-8 A-8
  • Best crop of A-10 A-10
  • Best crop of A-9 A-9
  • Best crop of B-2 B-2
  • Best crop of B-1 B-1
  • Best crop of B-4 B-4
  • Best crop of B-3 B-3
  • Best crop of B-5 B-5
  • Best crop of O-1 O-1
  • Best crop of O-2 O-2
  • Best crop of O-5 O-5
  • Best crop of O-4 O-4
  • Best crop of O-3 O-3

Identity is capped by the footage: this pipeline resolved 21 stable identities; on the reference night slice it found ~23 stable vs ~14–16 true players. Team-level stats do not depend on perfect identity.

Pipeline footyviz 0.2.0; detector yolo11m.pt (ultralytics 8.4.174); tracker ByteTrack (supervision 0.30.9); re-ID BoT-SORT + OSNet x1_0 msmt17 (boxmot 16.0.11); vision-LLM off. Run time 56 s for 1 min of footage.

Dusk slice on its own page

Night slice

Floodlights, near-identical kits and players in hoodies. Team stats hold up; player identity is where the pipeline hits the limit of the footage.

0:00 to 1:00 of hhaWMwLlugE, 1800 frames at 1280x720

Possession

Grey
37%
Pink
26%
Unresolved
37%

Proxy: in each frame with a ball detection (YOLO COCO 'sports ball'), the team of the nearest player (foot point) within 3 player-heights owns the ball; frames without a ball inherit the last owner for up to 1 s, otherwise count as unresolved. Ball nearest a goalkeeper/other-kit player counts as unresolved. Ball detected in 63% of frames.

Distance covered

Grey
378 m
Pink
189 m

No homography. Player foot-point tracks in image pixels, camera pan removed by global phase correlation, smoothed (9-frame mean), steps faster than 0.5 frame-widths/s dropped. Metres = pixels / (frame width / 25 m), i.e. assumes the visible pitch width is 25 m and ignores zoom and perspective. Expect ±50%.

Heatmaps

Grey
Less time More time
Pink
Less time More time

Positions are in the camera's view, not on the pitch: the camera pans to follow play, so left and right are edges of the frame, not ends of the pitch.

Roster

  • Best crop of A-1 A-1
  • Best crop of A-5 A-5
  • Best crop of A-7 A-7
  • Best crop of A-2 A-2
  • Best crop of A-3 A-3
  • Best crop of A-8 A-8
  • Best crop of A-6 A-6
  • Best crop of A-4 A-4
  • Best crop of B-4 B-4
  • Best crop of B-3 B-3
  • Best crop of B-1 B-1
  • Best crop of B-6 B-6
  • Best crop of B-9 B-9
  • Best crop of B-7 B-7
  • Best crop of B-13 B-13
  • Best crop of B-8 B-8
  • Best crop of B-12 B-12
  • Best crop of B-2 B-2
  • Best crop of B-10 B-10
  • Best crop of B-5 B-5
  • Best crop of B-11 B-11
  • Best crop of O-1 O-1
  • Best crop of O-3 O-3
  • Best crop of O-5 O-5
  • Best crop of O-6 O-6
  • Best crop of O-2 O-2
  • Best crop of O-4 O-4

Identity is capped by the footage: this pipeline resolved 27 stable identities; on the reference night slice it found ~23 stable vs ~14–16 true players. Team-level stats do not depend on perfect identity.

Pipeline footyviz 0.2.0; detector yolo11m.pt (ultralytics 8.4.174); tracker ByteTrack (supervision 0.30.9); re-ID BoT-SORT + OSNet x1_0 msmt17 (boxmot 16.0.11); vision-LLM off. Run time 56 s for 1 min of footage.

Night slice on its own page

Run it on your match

git clone https://github.com/dan-slater/footyviz && cd footyviz
curl -LsSf https://astral.sh/uv/install.sh | sh        # skip if you already have uv
uv venv --python 3.12 .venv
uv pip install -e .
.venv/bin/python -c "import torch; print('GPU' if torch.cuda.is_available() else 'CPU only')"
.venv/bin/python -m fv.job --url "https://www.youtube.com/watch?v=hhaWMwLlugE" --start-s 667 --end-s 727 --out out/my_match
.venv/bin/python -m json.tool out/my_match/stats.json | head -60

Swap in your own match URL and a window of up to 180 s. An NVIDIA GPU with CUDA is strongly recommended; --device cpu works but is slow. The optional vision-LLM roster review needs your own Gemini or OpenAI key in .env (GEMINI_API_KEY or OPENAI_API_KEY); it is off by default.

github.com/dan-slater/footyviz

How it works

  1. 01Acquire
  2. 02Detect
  3. 03Track
  4. 04Team gate
  5. 05Crops
  6. 06Jersey OCR
  7. 07Participant
  8. 08Descriptor
  9. 09Grouping
  10. 10Constraints
  11. 11Roster

Team stats do not depend on who is who. Player identities are approximate: in poor light one player can show up twice in the roster, and each result prints how far off it is.

Open source: github.com/dan-slater/footyviz