Reading an Uno table from a single photo
TL;DR
- Problem
- From one overhead photo of a game, read the center card, whose turn it is and every player’s hand, even when cards overlap, rotate and hide each other.
- What we built
- A lightweight (12M parameter) customized ResNet-18 with CBAM attention, pre-trained through a synthetic data pipeline, then moved to real camera photos with multi-stage fine-tuning.
- Result
- 75% accuracy on a Kaggle dataset.
- Stack
- Python, PyTorch, Torchvision, Albumentations, OpenCV
With few labeled photos and no pretrained weights allowed, the network first learned on tables that never existed.
- 75% ACCURACY · 12M PARAMS
- PyTorch
- Synthetic data
- Sim-to-real
- Attention

Full size diagram (opens in a new tab)
The approach
- Split the table. Each photo is cut into five zones, four hands and the center pile, and every hand is rotated upright.
- Detect by counting. A 56 × 6 output head predicts how many copies (0–5) of each card and turn-token class a zone holds, so real labels never need bounding boxes.
- Model. A customized ResNet-18 trained from scratch, with CBAM channel and spatial attention on its last feature map, inside a 12M-parameter budget.
- Synthetic data. Card cut-outs composited onto real table textures with aggressive augmentations (±180° rotation, perspective distortion, lighting jitter, noise and occlusion holes) so the network can’t memorize textures. Rare turn tokens are oversampled 3×, and nothing is flipped, because mirrored digits don’t exist.
- Multi-stage fine-tuning. Pre-train on synthetic composites, then fine-tune on real table crops at a 10× lower learning rate with early stopping.
What it achieved
- 75% accuracy. Measured on a Kaggle dataset of real table photos.
- Lightweight: 12M parameters. A ResNet-18 trunk with hand-written CBAM attention, trained without pretrained weights.
- 200 synthetic composites per card class. Each has random rotation, perspective, lighting, noise and occlusion, to bridge the sim-to-real gap.
- Two learning rates, two worlds: 1e-3 on synthetic data, then 1e-4 on real photos, starting from the synthetic weights.
Figures
Notes
This was a course competition. The table photos and labels belong to the course, so they are not shown here, and the code stays private.