Lab · Computer vision · MIT

Read the film.
Call the play.

football-cv turns one play of film into a position label for all 22 players, then reads the pre-snap look to predict run or pass and which way the run goes.

The labelled frame is real pipeline output. The play-call card uses made-up example values.

~70%position accuracy, all players ~77%on defense 22players labelled per play
Part 1 · Reading the film

Who is where, and what they are.

Detect and track everyone, split the teams, calibrate to field yards, find the line and the snap, and classify every player.

Part 2 · Calling the play

What the offense is likely to do.

Turn the alignment at the snap into pre-snap features, add the game situation, and predict run or pass with a model trained on your own snaps.

Part 1 · Reading the film

Boxes are easy.
Football is the hard part.

Most player tracking stops at boxes. Turning them into football means knowing which body is a defensive lineman and which is a safety, who is on offense, and where the ball is. That takes camera calibration to field yards, a jersey-colour team split, snap detection, and filtering out everyone who isn't in the play: officials, benches, the chain crew.

film→detect→track→team colours→stitch→calibrate→field yards→line + snap→positions

Detect and track

YOLO finds every person in each frame; a multi-object tracker turns them into stable ids, and a stitch step merges fragments of one player back into one track.

Split the teams

Jersey colours are clustered into team A, team B and officials, so the officials drop out and offense and defense are known.

Map to the field

Mark at least four field points on one frame and every track is projected to real yards. Other plays from the same game reuse that calibration by feature matching.

Line, snap, positions

It finds the line of scrimmage and the snap, then classifies each player from their field-relative alignment into plain position groups.

DefenseRead
DLon the line of scrimmage
LBoff the line, inside
CBpressed wide, near the boundary
Sdeep, toward the middle
OffenseRead
OLon the line
WRsplit wide
QBdeep, centered
RBin the backfield

The labels are standard position groups, never team- or playbook-specific names. It is a toolkit: a one-call Python API, a football-cv CLI, and a zero-build browser viewer for calibration and overlays. Every stage writes its own files per play, so runs are inspectable and resumable.

Part 2 · Calling the play

From the pre-snap look
to run or pass.

An optional add-on. Once a play is classified, football-cv reads the offense's alignment at the snap into one row of features. You add the game situation and your own tags, and an XGBoost model trained on your snaps predicts run or pass, and for runs, left or right.

classified play→pre-snap features+your situation→snaps.csv→XGBoost→p(pass), run side
Read from the film

The look

  • Formation as receivers outside the tackles per side, like 3x1 or 2x2
  • Strength: the side with more players outside the tackles
  • Backfield: how many running backs
  • QB set: gun or under center
  • Box count: defenders near the ball
  • Pre-snap motion, the hash and field position
Only you can add

The situation

  • Down and distance, usually the strongest features
  • Quarter, the previous play and its gain
  • The game and its date, to split by game
  • The labels to learn: run or pass, and run direction

Every column but the game and the label is optional, so you can start with whatever you already tag.

Split by game, never by snap

Games in date order: the latest two are the test set, the one before them is for early stopping, the rest train. A snap never sees its own game in training.

Beat the baselines

Every result sits next to "always the most common play" and a down-and-distance pass-rate table, roughly what a coach does by hand. Not beating the table means the model adds nothing.

When it's sure, is it right?

It reports how often the model is at least 70% or 80% one way, and how often those confident calls are right. That is the number a play-caller actually uses.

Synthetic example season, not real football

The tool working, on made-up data

The repo ships a generated season with invented tendencies (618 snaps, 10 games), so you can see the whole loop run. Test set: the last 2 games, 126 snaps. These numbers say nothing about how well it predicts any real team; measure on your own snaps.

Model67.5%
Always pass57.1%
Down × distance table50.8%

Also on the synthetic set: the model was at least 70% sure on 42.1% of test snaps and right on 77.4% of those. Run direction: 61.1% against 59.3% for always picking the more common side, close enough to call a tie on 54 runs.

Film features inherit every error upstream. They lean on the offense labels, so backfield and QB set are the noisiest; a tight end lands in OL or WR, so formations are generic rather than playbook names; and motion needs the motion man tracked through the last two seconds before the snap.

Evaluation

Results.

Position labels are scored against 21 hand-labelled plays, 162 players. The labels are private; the evaluation tool and the numbers are not.

~70% overall

Across every player, offense and defense, after the learned offense model.

~77% on defense

The strength: full alignment and role logic for DL, LB, CB and S.

~65% without the line

Leaving out OL and DL, the easiest groups, so the headline isn't flattered by them.

DefensePrecisionRecallF1n
CB1.000.890.9418
DL0.870.870.8723
LB0.830.690.7535
S0.680.710.7021
OffenseOLQBRBWRAll
Geometric rules82%12%47%63%57%
Learned (random forest)82%50%36%69%61%

The offense backfield is the hardest part: a QB and an RB look almost the same in field-relative alignment, and the geometric rules found the quarterback once in eight. A small random forest on alignment features, scored leave-one-play-out so no play leaks into its own test, gets him half the time. It is used only for offense, where it helps; an all-position model tied the defense logic and did worse on QB.

Quick start

Run it on your own film.

Python 3.11+. Detection (torch) and the play-call model (XGBoost) are optional extras, so the core install stays light.

# install
pip install -e .              # pipeline
pip install -e ".[detect]"    # + YOLO
pip install -e ".[playcall]"  # + XGBoost

# read the film
football-cv ingest myplay --video play.mp4 --data ./data
football-cv all    myplay --data ./data
# call the play
football-cv playcall features play1 play2 \
    --data ./data --out snaps.csv
# add down, dist, game, run_pass… then
football-cv playcall train   --data snaps.csv --out model/
football-cv playcall predict --model model/ --data next.csv

As a library

footballcv.analyze(video, calibration, snap) returns a position group for every track id.

The viewer

A browser tool to scrub to a frame, click field points, mark the snap and see the labels drawn over the film.

License

MIT. Use it, fork it, build on it. Issues and pull requests are welcome.

Free to read, run
and build on.