Real-time water polo match analysis using YOLOv8 object detection, ByteTrack multi-object tracking, cap-color team classification, and homography-based field mapping. Extracts player positions, ball trajectories, team possession, and generates tactical heatmaps from broadcast footage.
┌──────────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────┐ ┌───────────┐ ┌────────┐
│ Video Input │───▶│ YOLO │───▶│ ByteTrack │───▶│ Team │───▶│ Homography │───▶│ Analytics │───▶│ Output │
└──────────────┘ │ Detection│ │ Tracking │ │ Classify │ │ Projection │ └───────────┘ └────────┘
└──────────┘ └────────────┘ └──────────┘ └──────────────┘ │
Cap Color Per-team heatmaps
K-Means Possession stats
Annotated video
CSV export
- Ball Tracking — Detect and track the water polo ball across frames with carry-forward interpolation
- Player Detection — Identify all visible players using YOLOv8 with confidence filtering
- Team Classification — Assign players to teams via K-Means clustering on cap HSV colors
- Per-Team Heatmaps — Separate positional heatmaps for each team
- Ball Possession by Team — Proximity-based possession: nearest player within threshold owns the ball
- Zone Possession — Left/right half ball time distribution
- Streamlit Web UI — Upload video, configure parameters, view results interactively
- CSV Export — Frame-by-frame player positions with team labels
git clone https://github.com/yourusername/waterpolo_analytics.git
cd waterpolo_analytics
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python main.py --input match.mp4 --output results/streamlit run app.pyUpload a video, adjust detection confidence, team sampling frames, and possession threshold in the sidebar. View heatmaps and possession stats instantly.
make docker-build
make docker-run INPUT=match.mp4 OUTPUT=results/| File | Description |
|---|---|
annotated.mp4 |
Video with team-colored bounding boxes and ball tracking |
heatmap.png |
Combined player position heatmap |
heatmap_team_0.png |
Team 0 position heatmap |
heatmap_team_1.png |
Team 1 position heatmap |
stats.csv |
Per-frame player positions with team IDs |
Console output includes zone possession (left/right %) and team possession (proximity-based %).
Edit configs/default.yaml:
detection:
confidence: 0.3
model: yolov8n.pt
tracking:
activation_threshold: 0.25
lost_track_buffer: 30
team_classification:
sample_frames: 100 # Frames to sample for cap color fitting
possession_threshold: 3.0 # Meters — max distance for ball possession
field:
length: 30.0
width: 20.0
pixel_corners: # Calibrate to your camera angle
- [100, 200]
- [1800, 200]
- [1800, 900]
- [100, 900]| Component | Library |
|---|---|
| Object Detection | Ultralytics YOLOv8 |
| Multi-Object Tracking | Supervision (ByteTrack) |
| Team Classification | scikit-learn (KMeans) |
| Computer Vision | OpenCV |
| Web UI | Streamlit |
| Numerical Computing | NumPy |
| Visualization | Matplotlib |
| Data Export | Pandas |
waterpolo_analytics/
├── main.py # CLI entry point (2-phase pipeline)
├── app.py # Streamlit web UI
├── src/
│ ├── detector.py # YOLOv8 detection wrapper
│ ├── tracker.py # ByteTrack multi-object tracker
│ ├── team_classifier.py # K-Means cap color team classification
│ └── analytics.py # Homography, heatmaps, possession, export
├── configs/
│ └── default.yaml # Detection/tracking/team parameters
├── Dockerfile
├── Makefile
└── requirements.txt
- Phase 1 (Sampling): The first N frames are processed to extract the dominant HSV color from the top 25% of each player bounding box (the cap region).
- Fitting: K-Means (k=2) clusters these color samples into two groups.
- Phase 2 (Inference): Each player in every subsequent frame is classified into a team by predicting which cluster their cap color belongs to.
This assumes the two teams wear distinguishable cap colors (e.g., white vs. blue), which is standard in competitive water polo.
GPL-3.0