← Back to portfolio
Case Study · 02 / 17
Computer Vision · Full-Stack · 2025

A photo of a board,
turned into notation.

FENgine takes a single photograph of a chess position and returns a FEN string and a replayable PGN — perspective-corrected with OpenCV, read square-by-square by a small CNN. This is how the pipeline is put together, and why the hard part was never the classifier.

Role
Solo build — CV & full-stack
Discipline
Image processing + deep learning
Stack
FastAPI · OpenCV · CNN · React
Status
Open-source project
Fig. 02 · board → FEN
02
Sixty-four squares, thirteen possible pieces, one honest guess per cell.
§ 01 — The problem

Transcribing a board by hand is slow and error-prone.

If you want to analyse a position you saw over the board — in a book, on a friend's set, mid-game — you first have to type it in. That means reading 64 squares, remembering algebraic notation, and not fumbling a rank. It is exactly the kind of mechanical task a camera should be able to do for you.

The goal was narrow and honest: point a phone at a board, get back a FEN string you can paste into any engine, plus a PGN you can replay. No app store, no accounts — just an upload box and a result.

The classifier was easy. The geometry was the project.

§ 02 — Constraints

Three things it had to get right.

§ 03 — Architecture

Four stages, one endpoint.

The whole system is a single POST /api/ocr that accepts an image and returns JSON. Inside, the request passes through four stages: detect the board, warp it flat, classify each cell, then assemble the notation.

01 — Detect
Board find
OpenCV grayscale, blur, adaptive threshold + contour to find the largest quad.
02 — Warp
Perspective
Homography warps the board to a flat 800×800 square, then splits into 64 cells.
03 — Classify
CNN per cell
Each 64×64 cell → one of 13 classes (six pieces × two colours + empty).
04 — Notation
FEN + PGN
Labels map to a1–h8, a python-chess board emits FEN and a PGN game.

The model was trained largely on synthetic data — tens of thousands of board positions rendered programmatically with official piece sprites, then jittered with rotation, brightness, and occlusion so it wouldn't overfit to one clean render.

§ 04 — What broke first

Bad crops poison everything downstream.

The classifier reached strong validation accuracy quickly. The failures in practice came almost entirely from the board-detection step: a missed corner or a slightly-off quadrilateral shears the whole grid, and then every one of the 64 cells is cropped a few pixels wrong. A great piece classifier fed a sheared board produces confident nonsense.

The fix was to treat detection as the first-class problem and give the user the last word — the review screen overlays the detected 8×8 grid so a wrong square is a one-click correction, not a re-shoot.

~97%
Piece-classification validation accuracy
13
Classes per cell — 12 pieces plus empty
64
Cells classified from one warped board
§ 05 — Designing the surface

Upload, review, done.

The React client is three screens on purpose — an upload with instant preview, a review canvas where the detected grid sits over the photo and any square is a dropdown away from correction, and a result view with the FEN in a monospace block and the PGN ready to copy.

The review step is the one that earns trust. It admits, out loud, that the model can be wrong — and makes being wrong cheap.

§ 06 — What I'd do differently

Three things, next time.