Recreate famous artworks as mosaics made entirely of Pokémon TCG cards.
Pokesaic transforms any image into a mosaic composed entirely of Pokémon Trading Card Game cards. Each tile of the mosaic is replaced by the Pokémon card whose dominant color best matches the original region, using perceptually accurate color matching in the CIELAB color space and a KD-Tree for fast nearest-neighbor search.
The concept was originally inspired by a video from Newbie Indie Game Dev on YouTube. The main goal of this project was to reproduce the idea from scratch and drastically improve generation speed — from ~2 hours in the original video down to ~4 minutes on my side.
The pipeline follows four main stages:
All Pokémon TCG card images are downloaded, and for each card a dominant color is computed in the CIELAB color space. A 10% margin is cropped from each card before computing the mean color to avoid border artifacts.
A scipy KD-Tree is built from the LAB color vectors of all cards (~10,000+). This spatial index enables O(log N) nearest-neighbor lookups instead of brute-force comparisons.
The input image is divided into a grid of tiles (177×250 px each — standard Pokémon card ratio). For each tile:
- The average LAB color is computed
- The KD-Tree is queried to find the closest matching Pokémon card
Each matched card is placed at its corresponding grid position to reconstruct the final mosaic image.
Why CIELAB? Unlike RGB, the CIELAB color space is perceptually uniform — Euclidean distances between LAB vectors closely approximate how humans perceive color differences. This produces significantly more visually accurate mosaics.
Pokesaic/
├── src/
│ ├── KDTree_generator.py # KD-Tree construction & serialization
│ ├── pokesaic_generator.py # Mosaic generation engine
│ └── models/
│ ├── pokemon_card.py # Card model (download, LAB color extraction)
│ └── pokemon_series.py # Series grouping model
├── scripts/
│ ├── cache_colors.py # Step 1: Enrich card data with LAB colors
│ ├── generate_KDTree.py # Step 2: Build & save the KD-Tree index
│ └── generate_pokesaic.py # Step 3: Generate a mosaic from an image
├── data/
│ ├── json/
│ │ ├── pokemon_cards.json # Raw card metadata
│ │ └── pokemon_cards_labs.json # Card metadata + LAB colors
│ ├── kdtree/
│ │ └── pokemon_kdtree.pkl # Serialized KD-Tree index
│ ├── images/ # Cached card images
│ ├── input/ # Source images (e.g. La Joconde)
│ └── output/ # Generated mosaics
├── worflow.png # Visual workflow diagram
├── LICENSE
└── README.md
- Python 3.10+
# Clone the repository
git clone https://github.com/lucienlaumont/Pokesaic.git
cd Pokesaic
# Create a virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# Install dependencies
pip install numpy scipy scikit-image Pillow requests tqdmThe project runs in three sequential steps:
Downloads all Pokémon card images and computes their dominant LAB color.
python scripts/cache_colors.pyThis step produces
data/json/pokemon_cards_labs.jsonand caches images indata/images/.
Constructs the spatial index from the enriched card data.
python scripts/generate_KDTree.pyThis step produces
data/kdtree/pokemon_kdtree.pkl.
Place your source image in data/input/, then run:
python scripts/generate_pokesaic.pyYou can adjust the scale parameter in the script to control mosaic resolution:
| Scale | Grid density | Detail level |
|---|---|---|
| 1 | Low | Abstract / artistic |
| 10 | Medium | Recognizable |
| 20 | High | Detailed reproduction |
Higher scale values produce finer mosaics with more cards but take longer to generate.
| Metric | Value |
|---|---|
| Generation time | ~4 minutes (scale=20) |
| Original benchmark | ~2 hours (source video) |
| Speedup | ~30× faster |
Key optimizations:
- Vectorized LAB conversion — NumPy batch operations instead of per-pixel loops
- Batched KD-Tree queries — all tiles queried in a single call
- Multi-threaded tile processing —
ThreadPoolExecutorfor parallel I/O - Image caching — card images loaded once and reused
| Library | Role |
|---|---|
| NumPy | Vectorized array operations |
| SciPy | KD-Tree spatial indexing |
| scikit-image | RGB → CIELAB color conversion |
| Pillow | Image loading, resizing & assembly |
| requests | Card image downloading |
| tqdm | Progress bars |
- Inspiration: Newbie Indie Game Dev — original concept video on YouTube
- Card data: Images sourced from the Pokémon TCG database via Pokécardex
This project is licensed under the MIT License — see the LICENSE file for details.
