Downloads · 30 days
13
28% of all-time downloads
tchauffi/chesssight-corner-heatmap-v1
chesssight-corner-heatmap-v1 is a keypoint detection model from tchauffi. Use it for the keypoint detection task on the model card, and read the license before you ship it in a product. The card lists the license as cc-by-nc-4.0.
Finds the four corners of a chessboard in a photograph. Those four points give the board-plane homography, which is what turns a pile of piece detections into an actual position.
Downloads · 30 days
13
28% of all-time downloads
All-time downloads
46
Public
Parameters
27.8M
111 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors111 MB · 100%
From the Hugging Face model README
Finds the four corners of a chessboard in a photograph. Those four points give the board-plane homography, which is what turns a pile of piece detections into an actual position.
0.128 squares mean corner error on the real ChessReD test split, with usable geometry found on 306 of 306 photographs — trained on synthetic renders only, never on a real photograph.
CC BY-NC 4.0. Commercial use is not permitted.
Trained on tchauffi/chesssight-synthetic-40k, about 60% of which renders the Staunton Chess Set by uppalong (Printables 76438), licensed CC BY-NC 4.0. Whether trained weights are a derivative work of their training data is legally unsettled; this model is labelled NonCommercial because its data is, rather than leaving you to discover the question later.
Needs only torch, timm, torchvision, numpy, pillow and safetensors —
the project it came from is not required.
from huggingface_hub import snapshot_download
from PIL import Image
import sys
path = snapshot_download("tchauffi/chesssight-corner-heatmap-v1")
sys.path.insert(0, path)
from modeling_corner_heatmap import load_corner_model, predict_quad
model, config = load_corner_model(path, device="cuda")
corners = predict_quad(model, Image.open("board.jpg"), config, device="cuda")
# [[x, y], ...] four points, clockwise, or None if fewer than four were found
Then, for the homography:
import cv2, numpy as np
board = np.array([[0, 0], [8, 0], [8, 8], [0, 8]], dtype=np.float32)
homography, _ = cv2.findHomography(board, np.asarray(corners, dtype=np.float32))
One heatmap channel, not four. Four separate channels would name the corners a8/h8/h1/a1, and that naming is not learnable from a board's appearance — rotate the photograph and the image is equally consistent with any assignment. So the model predicts a single "corner-ness" map with four peaks, and ordering is geometric. Which corner is a8 is a separate problem, solvable from square colour parity plus where White's men are (99.0% on ChessReD; see the repo).
Peak suppression, then sub-cell refinement. A 3×3 maximum filter keeps only cells that dominate their neighbourhood, which makes duplicate corners structurally impossible rather than something to filter afterwards — four points containing a duplicate give a degenerate homography rather than an obviously wrong one. A local intensity-weighted mean then places each point between cells, because a whole-cell answer is quantised to 4 input pixels, which a 3072px photograph scales up sevenfold.
Swin, not ViT. A patch-16 ViT's finest feature map is stride 16; upsampling it 4× to reach the heatmap grid discards exactly the localisation this design exists for. Swin's patch-4 stem gives stride 4 natively. Swin-T also removed the failure tail a ResNet-18 backbone had: p90 error fell from 323px to 55px.
ChessReD test split, 306 photographs, against alternatives measured identically:
| corner source | mean | median | p90 |
|---|---|---|---|
| this model | 0.128 sq | 0.127 | 0.158 |
| same model trained without border variation | 0.235 sq | 0.198 | 0.289 |
| RT-DETR corner class | 0.283 sq | 0.126 | 0.203 |
Used as the geometry stage of the full pipeline (with chesssight-rtdetr for pieces), the end-to-end position read is 97.06% of squares and 31.05% of boards exactly right.
topk does not know what a corner is, so
pass min_score to let it answer "no board here" — otherwise a photograph of
a kitchen yields a quad. Peak confidence separates well out of domain
(successes ≈0.5–0.6, failures ≈0.1–0.3), but a threshold fitted on clean data
comes out at zero, because clean data contains no failures to separate.Swin-T pyramid → stride-4 top-down path → one heatmap channel. CenterNet penalty-reduced focal loss (a stride-4 map has ~12,500 cells and four are positive, so plain BCE is 99.97% correct predicting nothing). 25 epochs, batch 32, AdamW at 3e-4 with the backbone at a tenth of that, cosine schedule.
Checkpoint selection is on the real ChessReD validation split, not on renders: synthetic corner error plateaus around 4px while real error is still moving, so selecting on renders past that point picks on noise in the wrong domain.
Code, configs and evaluation: https://github.com/tchauffi/ChessSight