Thesis Project by Rishabh Vats LinkedIn
- The Problem with Standard xG
- The xG Vision Solution
- Results
- Computer Vision Pipeline
- GNN Tactical Analysis
- Streamlit Dashboard
- Project Structure
- Getting Started
- Usage
- Model Weights
- Tech Stack
- Data Sources
- References
- License
- Author
Expected Goals (xG) is a probabilistic metric that quantifies the quality of a scoring opportunity in football. A value of 0.0 means no chance of scoring, while 1.0 represents a near-certain goal. Every modern football analytics platform (StatsBomb, Opta, FBref, Understat) relies on xG to evaluate players, teams, and tactical approaches.
However, standard xG models are fundamentally limited. They rely almost exclusively on spatial features -- the distance and angle from goal, the body part used, and whether the shot is a penalty. These models treat every player identically and ignore the dynamic context surrounding each shot.
| What Standard xG Sees | What It Misses |
|---|---|
| Distance to goal | Goalkeeper position (on line? off line? out of position?) |
| Angle to goal | Defensive pressure intensity (how many blockers? how close?) |
| Body part (head/foot) | Build-up sequence context (counter-attack? set piece? solo run?) |
| Is it a penalty? | Game state (is the team chasing? protecting a lead?) |
| Is it a big chance? | Player-specific finishing skill |
Research from KU Leuven (Davis & Robberechts, 2024) quantified this problem: standard xG underestimates Messi's finishing ability by 17%. Elite finishers consistently "outperform" their xG -- but this is not variance or luck. It is measurable skill that standard models cannot capture.
-
Goalkeeper positioning is ignored. A 1v1 where the goalkeeper is 5 meters off the goal line receives the same xG as one where the goalkeeper is perfectly positioned on the line. In reality, the first scenario is a near-certain goal.
-
Defensive pressure is underweighted. A shot with 4 defenders blocking the shooting lane and a nearest defender 1 meter away gets roughly the same xG as a shot with a clear path to goal, as long as the distance and angle are similar.
-
All players are treated identically. Erling Haaland's one-on-one receives the same xG as a center-back's one-on-one, despite Haaland converting these at approximately 80% and the average player at around 45%.
xG Vision addresses all three limitations through a three-layer prediction system:
+-------------------------------------------------------------+
| THREE-LAYER ARCHITECTURE |
+-------------------------------------------------------------+
| |
| LAYER 1: Context-Aware Base xG |
| ----------------------------------- |
| 54 engineered features across 5 categories: |
| - Spatial features (distance, angle, centrality) |
| - GK positioning (off line? out of position?) |
| - Defensive pressure (blockers, nearest defender) |
| - Sequence context (counter-attack? fast break?) |
| - Game state (minute, score differential) |
| |
| | |
| v |
| |
| LAYER 2: Player Skill Modifier |
| ---------------------------------- |
| Shot-type specific Bayesian calibration: |
| - Long range proficiency (Szoboszlai: 2.0x) |
| - 1v1 finishing (Haaland: 1.45x) |
| - Free kick conversion (Ward-Prowse: 2.0x) |
| - Tight angle shooting (Salah: 1.26x) |
| |
| | |
| v |
| |
| LAYER 3: Final xG Vision |
| -------------------------- |
| xg_vision = base_xg x player_modifier |
| Clamped to [0.01, 0.99] with confidence weighting |
| |
+-------------------------------------------------------------+
The first layer extracts 54 engineered features organized into 5 categories. This is implemented in src/analytics/features.py using the XGFeatureExtractor class.
| Category | Count | Key Features | What It Captures |
|---|---|---|---|
| Spatial | 13 | Distance to goal, angle to goal, centrality, zone classification, distance-angle interaction | Shot location geometry and interaction effects between distance and angle |
| Defensive | 10 | GK off-line score, blockers in shooting triangle, pressure gradient, nearest defender distance, open goal factor | How exposed the goal is and how much defensive interference exists |
| Shot Context | 14 | Body part encoding (4 types), technique (8 types), assist type (7 types), first-time shot flag, big chance flag | The biomechanics and situation leading to the shot |
| Game State | 11 | Match minute, goal difference, home/away, extra time, chasing game factor, protecting lead interaction | Psychological and tactical context of the match |
| Sequence | 7 | Counter-attack flag, fast break flag, buildup speed, sequence length and duration, distance covered | How the attacking move developed before the shot |
The defensive features represent the largest innovation. The GK off-line score quantifies how far the goalkeeper is from optimal positioning (0 = on the line, 1 = completely out of position). The blockers in shooting triangle counts how many defenders occupy the geometric triangle between the ball and both goalposts. The pressure gradient captures how quickly defensive pressure builds as the attacker approaches goal.
The second layer applies player-specific skill adjustments using Bayesian hierarchical modeling, implemented in src/analytics/player_profiler.py.
8 Shot Types Tracked:
| Shot Type | Definition | Example Specialist |
|---|---|---|
| Long Range | > 20m from goal | Szoboszlai (2.0x modifier) |
| One-on-One | Only GK to beat | Haaland (1.45x modifier) |
| Header | Headed attempts | Van Dijk |
| Free Kick | Direct free kicks | Ward-Prowse (2.0x modifier) |
| Penalty | Penalty kicks | Penalty specialists |
| Tight Angle | < 20 degree angle to goal | Salah (1.26x modifier) |
| Big Chance | High xG opportunity | Elite finishers |
| Regular | All other shots | General shooters |
Bayesian Shrinkage with Position Priors:
Not all players have enough shot data for reliable estimates. The system uses hierarchical Bayesian shrinkage with 6 position-based priors (striker, attacking midfielder, winger, midfielder, defender, goalkeeper):
- High sample count (50+ shots of that type): Player's own observed conversion rate dominates the estimate.
- Medium sample count (10-50 shots): Blended between the player's rate and the positional average.
- Low sample count (< 10 shots): Heavily shrunk toward the position group's average for that shot type.
- New player (0 shots): Uses position baseline (modifier = 1.0).
The modifier is clamped to [0.5, 2.0] to prevent extreme values from dominating predictions.
The final prediction combines both layers:
xg_vision = base_xg * player_modifier
The result is clamped to [0.01, 0.99] to maintain valid probability bounds.
| Scenario | Standard xG | xG Vision | Difference | Key Driver |
|---|---|---|---|---|
| Counter-attack, GK out of position (Haaland) | 0.17 | 0.99 | +486% | GK 5m off goal line + 0 blockers + Haaland 1v1 skill (1.45x) |
| Long range shot (Szoboszlai) | 0.01 | 0.06 | +512% | Long-range specialist modifier (2.0x) applied to base xG |
| Crowded box, high defensive pressure | 0.10 | 0.06 | -40% | 4 defenders blocking + pressure score 0.9 + first-time cross |
| Tight angle 1v1 (Salah) | 0.03 | 0.27 | +829% | Tight-angle skill (1.26x) + solo run counter-attack context |
Key Insight: Standard xG systematically underestimates the quality of elite chances (where skill and context create near-certain goals) AND overestimates the quality of blocked or pressured chances (where defensive context makes scoring far harder). xG Vision corrects in both directions.
The project includes a full real-time computer vision pipeline for analyzing match footage. This enables xG Vision to be applied to broadcast video rather than requiring manual event data.
Video Frame
|
v
Detection (RT-DETR or YOLOv8)
|
v
Multi-Object Tracking (ByteTrack via supervision)
|
v
Team Classification (SigLIP Embeddings + K-Means Clustering)
|
v
Homography Transformation (Pixel -> Pitch Coordinates)
|
v
xG Calculation (Standard + xG Vision with Player Multipliers)
|
v
GNN Tactical Analysis (optional)
|
v
Annotated Frame with Overlays
The system supports two detection backends:
-
RT-DETR (Real-Time Detection Transformer): Transformer-based architecture from
rtdetr-l.pt. Higher accuracy in crowded scenes due to attention mechanisms. Integrated via Ultralytics and optionally through a Roboflow Inference Server atlocalhost:9001. -
YOLOv8 (You Only Look Once v8): CNN-based architecture from
yolov8n.pt. Faster inference, suitable for real-time processing. Detects 4 classes: player, ball, goalkeeper, referee.
- ByteTrack (via
supervisionlibrary) maintains consistent player IDs across frames, handling occlusions and re-appearances. - Team classification uses SigLIP (Sigmoid Language-Image Pre-training) embeddings with UMAP dimensionality reduction and K-Means clustering to distinguish home vs. away players by jersey color. Manual color seeding is supported for known team colors.
- Goalkeeper assignment uses a nearest-team-centroid heuristic based on the Roboflow Football AI approach.
The HomographyTransformer class (src/cv_integration.py) maps pixel coordinates from broadcast footage to real-world pitch coordinates in meters using cv2.findHomography(). The standard pitch dimensions are 105m x 68m. This enables the analytics engine to compute real distances and angles from detected player positions.
The processed video output includes:
- Corner-style bounding boxes around detected players (color-coded by team)
- A persistent xG/pG banner near the ball during shot situations
- A cumulative header bar showing home/away xG and pG totals
- EMA (Exponential Moving Average) ball position fallback when detection fails (8-frame lookback)
- Shot cooldown timer (150 frames) to prevent duplicate shot events
- Player brilliance multipliers loaded from
data/player_multipliers.json
The pG_vision/gnn_tactical.py module implements a Graph Neural Network for real-time tactical scoring during video analysis.
Each video frame generates a player interaction graph:
- Nodes: Each detected player becomes a node with 6 features:
[x, y, team_encoding, distance_to_ball, distance_to_goal, is_defender] - Edges: Players within a configurable radius (default: 200 pixels) are connected, representing spatial interaction potential.
When PyTorch Geometric is available, the system uses a 2-layer Graph Convolutional Network:
- Input: 6 node features
- Hidden: 32 channels
- Output: 3 tactical scores (sigmoid-activated, scaled to 0-100)
When PyTorch Geometric is not installed, the system falls back to geometry-based heuristic scoring that approximates the GCN's behavior.
| Score | Measures | High Value Means |
|---|---|---|
| Defensive Shape | Formation organization and compactness | Well-organized defensive line, minimal gaps |
| Pressing Intensity | Rate of space closure by defenders toward ball | Aggressive high press, defenders closing quickly |
| Passing Lane Quality | Number and quality of open passing options | Multiple passing outlets available to ball carrier |
All scores are smoothed with EMA (alpha=0.3) for frame-to-frame stability.
The pG_vision/app.py file provides an interactive web dashboard for video analysis.
- Video Upload: Accepts MP4, AVI, MOV, and MKV formats for processing
- Detection Engine Selection: Toggle between RT-DETR (higher accuracy) and YOLOv8 (faster)
- GNN Tactical Toggle: Enable or disable tactical analysis overlay
- Team Color Customization: Color picker controls for manual home/away team color seeding
- Frame Skip Configuration: Adjustable frame skip interval (1-5) for performance tuning
- Real-Time Metrics: Live cumulative xG and pG tracking with tactical score progress bars
- GPU Detection: Automatic CUDA detection with FP16 half-precision support
- Processed Video Download: One-click download of annotated output video
The dashboard uses a Manchester United-inspired dark theme with red accent colors (#c70000), gradient backgrounds, custom metric cards, and hidden Streamlit branding for a clean presentation.
xg-vision/
|-- src/
| |-- analytics/ # Core xG analytics engine
| | |-- __init__.py # Module exports
| | |-- features.py # 54 engineered features across 5 categories
| | |-- player_profiler.py # Bayesian player skill calibration (8 shot types)
| | |-- xg_models.py # Standard xG & three-layer xG Vision models
| | +-- demo.py # Analytics demonstration script
| |-- cv_integration.py # Base CV pipeline (YOLOv8 + homography + xG overlay)
| |-- cv_advanced.py # Advanced CV (K-Means team colors, ByteTrack Re-ID)
| +-- keypoint_tracker.py # YOLO-Pose pitch keypoint detection
|
|-- pG_vision/ # Posterior Goals Vision (Transformer-based pipeline)
| |-- __init__.py
| |-- app.py # Streamlit dashboard
| |-- processor.py # RT-DETR video processor with GNN overlay
| |-- transformer_tracker.py # Roboflow + RT-DETR + SigLIP team classification
| +-- gnn_tactical.py # Graph Neural Network tactical analysis
|
|-- scripts/
| +-- train_pitch_keypoints.py # YOLO-Pose training script for pitch keypoints
|
|-- data/
| |-- player_multipliers.json # Per-player finishing multipliers (City vs Brentford 2024/25)
| |-- City_Brent.mp4 # Manchester City vs Brentford match footage
| |-- Barca_Joan.mp4 # FC Barcelona match footage
| +-- football_ai_nb.txt # Reference notes
|
|-- tests/
| |-- test_advanced_pipeline.py # Advanced CV pipeline tests
| |-- test_pg_vision.py # pG Vision integration tests
| |-- generate_synthetic_video.py # Synthetic match video generator for testing
| +-- synthetic_match.mp4 # Generated test video
|
|-- outputs/ # Processed videos with xG overlays
| |-- figures/ # Generated charts and visualizations
| +-- [processed match videos]
|
|-- models/ # Directory for trained model weights
|-- generate_thesis.py # ReportLab-based thesis PDF generator
|-- requirements.txt # Python dependencies
+-- README.md
- Python 3.10 or higher
- pip package manager
- GPU with CUDA support (recommended for video processing, not required)
# Clone the repository
git clone https://github.com/vatsinaname/xg-vision.git
cd xg-vision
# Create and activate virtual environment
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # Linux / macOS
# Install dependencies
pip install -r requirements.txt# For full GNN tactical analysis (PyTorch Geometric)
pip install torch-geometric
# For SigLIP-based team classification
pip install sports
# For Roboflow cloud inference (alternative to local detection)
pip install inferenceRun the analytics demonstration to see the three-layer model in action with example scenarios:
python src/analytics/demo.pyThis runs three demonstrations:
- Feature extraction showcase with a sample shot
- Player skill profiling with Bayesian calibration
- Side-by-side comparison of Standard xG vs. xG Vision across key scenarios
Launch the interactive dashboard for video upload and processing:
streamlit run pG_vision/app.pyOpen http://localhost:8501 in your browser. Upload a match video, configure detection settings, and process the footage with real-time xG overlay.
Process a video file directly via the processor module:
from pG_vision.processor import PGVideoProcessor
processor = PGVideoProcessor(model_type="rtdetr")
processor.load_video("data/City_Brent.mp4", "outputs/result.mp4")
# Process frames in a loop with processor.draw_overlay()Generate the full academic thesis document:
python generate_thesis.pyPre-trained model weights are not included in this repository due to file size. The Ultralytics library will automatically download them on first use, or you can download manually:
| Model | Size | Purpose | Auto-Download |
|---|---|---|---|
yolov8n.pt |
6.5 MB | CNN-based player/ball detection (faster) | Yes (via ultralytics) |
rtdetr-l.pt |
66.5 MB | Transformer-based detection (more accurate) | Yes (via ultralytics) |
Place the downloaded weights in the project root directory.
| Component | Technology | Purpose |
|---|---|---|
| Core ML | XGBoost, scikit-learn | xG model training, evaluation, and calibration |
| Explainability | SHAP | Feature importance and model interpretability |
| Object Detection | YOLOv8, RT-DETR (Ultralytics) | Real-time player, ball, and goalkeeper detection |
| Object Tracking | ByteTrack (supervision) | Multi-object tracking with ID persistence |
| Team Classification | SigLIP + UMAP + K-Means | Jersey color-based team assignment |
| Tactical Analysis | PyTorch Geometric (GCNConv) | Graph Neural Network for formation analysis |
| Homography | OpenCV | Pixel-to-pitch coordinate transformation |
| Dashboard | Streamlit | Interactive web UI for video processing |
| Pitch Visualization | mplsoccer | Football pitch plots and shot maps |
| Charting | Matplotlib, Seaborn, Plotly | Static and interactive data visualizations |
| Football Data | statsbombpy | Open football event data from StatsBomb |
| Video I/O | OpenCV, MoviePy | Video reading, processing, and composition |
| Thesis Generation | ReportLab | Programmatic PDF document creation |
| Environment | python-dotenv | Environment variable management |
- StatsBomb Open Data via
statsbombpy-- Historical shot event data with freeze frames, used for model development and validation. - Player Multipliers (
data/player_multipliers.json) -- Pre-computed finishing skill multipliers for Manchester City vs. Brentford (2024/25 season), including players like Haaland (1.38x), De Bruyne (1.22x), Foden (1.18x), Toney (1.30x), and Mbeumo (1.28x). - Match Footage -- Sample broadcast videos for demonstration. The system accepts any standard football broadcast footage in MP4/AVI/MOV/MKV format.
- Davis, J. & Robberechts, P. (2024). Biases in Expected Goals Models. KU Leuven.
- StatsBomb (2025). Upgrading Expected Goals. StatsBomb R&D.
- Rahimian, P. et al. (2025). Automated Explanation of Machine Learning xG Models. Research Paper.
- Cavus, M. & Biecek, P. (2022). Explainable Expected Goal Models for Performance Analysis in Football Analytics. IEEE DSAA.
- Roboflow (2025). Track Football Players with Computer Vision. Technical Blog.
- StatsBomb (2024). AI and Homography Estimation in Football. Technical Report.
MIT License -- feel free to use and build upon this work.
Rishabh Vats Analyst, AI Pod at Slice | Former Integrations Engineer at Robylon AI