Produits
Sudoku OCR
Une photo de grille en entrée, la solution réincrustée en sortie
- Rôle
- Conception et développement
- Contexte
- Projet personnel déployé
- Stack
- Python, OpenCV, TensorFlow / Keras, Streamlit, pytest
Hébergée sur Streamlit Community Cloud : le premier chargement peut prendre jusqu'à une minute

Vue d'ensemble
Application de vision par ordinateur qui détecte une grille de sudoku dans une photo, la redresse avec OpenCV, lit les chiffres avec un CNN Keras entraîné sur mon propre jeu de données (repli sur Tesseract), résout la grille par backtracking et réincruste la solution sur l'image. Interface Streamlit déployée pour vérifier et corriger la lecture case par case, signaler les conflits et télécharger le résultat, avec tests de bout en bout sur des grilles réelles.
Points clés
- 01Détection et redressement de la grille avec OpenCV
- 02CNN Keras entraîné sur un jeu de données maison, repli sur Tesseract
- 03Résolution par backtracking et réincrustation sur la photo
- 04Correction case par case et détection des conflits dans Streamlit
- 05Tests de bout en bout sur des grilles réelles
Architecture
Sous le capot
Une application Python qui trouve une grille de sudoku dans une photo ou une capture d'écran, lit ses chiffres et la résout. OpenCV détecte et redresse la grille, un petit réseau de neurones Keras lit chaque case, un solveur par backtracking termine la grille, puis la solution est redessinée sur l'image d'origine. Deux points d'entrée partagent le même pipeline : une ligne de commande, et une interface Streamlit où l'utilisateur corrige la lecture avant la résolution.
- de précision sur 624 chiffres de test
- 98,1%de précision sur 624 chiffres de test
- de précision sur les chiffres acceptés (confiance ≥ 0,6)
- 99,84%de précision sur les chiffres acceptés (confiance ≥ 0,6)
- paramètres dans le CNN, 3 convolutions
- 94 505paramètres dans le CNN, 3 convolutions
- chiffres imprimés étiquetés pour l'entraînement
- 4 157chiffres imprimés étiquetés pour l'entraînement
- étapes de l'image à la grille lue
- 10étapes de l'image à la grille lue
- tests, dont les grilles d'exemple de bout en bout
- 89tests, dont les grilles d'exemple de bout en bout
Qui parle à qui
Points d'entrée
CLI · sudoku-ocr
--image --out --config --backend --weights
Interface · sudoku-ocr-ui
Streamlit : import, correction, solution, téléchargement
Scripts
Entraînement du CNN, validation, expériences OCR
read_grid() en cache · render_solution()
Pipeline
config.py
DEFAULTS ← YAML ← options, clé inconnue refusée
pipeline.read_grid()
Image → GridReading : grille lue, cases, homographie inverse
render_solution()
Solution → image d'origine
Modules spécialisés
Modules
detect · geometry
Contour de la grille, homographie vers 450 × 450
cells
Lignes, 81 cases, case vide, extraction du chiffre
ocr/cnn · ocr/tesseract
CNNOCR et son contrat .meta.json · repli Tesseract
solver · overlay
Backtracking MRV, conflits · dessin ramené par Minv
Lit le modèle et la configuration versionnés
Fichiers du dépôt
models/sudoku_cnn.keras
Modèle versionné + .meta.json (classes, échelle, polarité)
configs/default.yaml
Backend OCR, seuil de confiance, rendu
data/assets
4 157 chiffres étiquetés pour l'entraînement
data/samples
6 grilles d'exemple, claires et sombres
Principes de conception
01
Deux portes, un seul pipeline
Une seule fonction read_grid fait la détection, le découpage et l'OCR. La CLI comme l'interface l'appellent et reçoivent un GridReading.
02
Toujours de l'encre foncée
Une grille en mode sombre est lue sur l'image inversée. On garde le candidat dont la grille redressée a un fond clair.
03
Le modèle porte son contrat
sudoku_cnn.meta.json décrit les classes, l'échelle des pixels et la polarité. L'entrée du réseau est préparée d'après ce fichier, pas d'après des constantes.
04
Une configuration stricte
Toute clé absente des valeurs par défaut est refusée : une faute de frappe dans un YAML lève une erreur au lieu d'être ignorée.
05
L'humain corrige, la machine résout
L'interface ne corrige rien en silence. Elle montre les conflits et le nombre de solutions, et ne résout qu'une grille à solution unique.
06
Ni serveur ni base
Aucun état persistant. L'interface met le modèle en cache et garde les 16 dernières lectures.
Pipeline · de l'image à la grille lue
- 01imread · decode_imageImagePhoto ou capture, en BGR
- 02bitwise_notDeux candidatsL'image et son inverse, pour le mode sombre
- 03find_sudoku_quadDétectionCLAHE, seuil adaptatif, 20 plus grands contours notés par grid_score
- 04four_point_transformRedressementCarré de 450 × 450, homographie inverse gardée
- 05grid_scoreChoixFond clair d'abord, puis meilleur score
- 06detect_grid_linesLignesProjections, 10 + 10 lignes, repli sur une grille régulière
- 07split_cells_by_lines81 casesDécoupe avec une marge d'1/40 de case
- 08is_blank_cellCase vide ?Contraste du centre < 40 : pas d'OCR
- 09extract_digitExtractionComposante la plus centrale, ramenée en 28 × 28
- 10predict_digitCNN9 classes, confiance < 0,6 → case laissée vide
↳ La CLI répare seule
- Retire les chiffres en conflit
- Remplit les cases à candidat unique
- Résout, et relit les cases vides avec Tesseract en cas d'échec
↳ L'interface demande
- Tableau éditable : l'utilisateur corrige la lecture
- Conflits en rouge, nombre de solutions compté jusqu'à 2
- Résolution seulement si la solution est unique, puis dessin sur l'original
Mieux vaut une case vide, que l'utilisateur remplit, qu'un faux chiffre qui rend la grille insoluble : c'est tout le rôle du seuil de confiance. Si l'extraction échoue sur une case non vide, trois binarisations font voter le CNN.
Réseau de neurones · 94 505 paramètres
| Couche | Sortie | Paramètres |
|---|---|---|
| Entrée | 28 × 28 × 1 | 0 |
| Conv 3×3 · BN · ReLU | 28 × 28 × 32 | 288 + 128 |
| MaxPool 2×2 | 14 × 14 × 32 | 0 |
| Conv 3×3 · BN · ReLU | 14 × 14 × 64 | 18 432 + 256 |
| MaxPool 2×2 | 7 × 7 × 64 | 0 |
| Conv 3×3 · BN · ReLU | 7 × 7 × 128 | 73 728 + 512 |
| GlobalAveragePooling · Dropout 0,3 | 128 | 0 |
| Dense · softmax | 9 (chiffres 1 à 9) | 1 161 |
Pas de classe « vide » : les cases vides sont écartées avant le réseau. BatchNorm à momentum 0,9, car avec la valeur par défaut de Keras les statistiques ne convergeaient pas et le modèle s'effondrait en inférence.
Entraînement · test sur 624 images
| Mesure | Avant | Actuel |
|---|---|---|
| Précision | 96,3 % | 98,1 % |
| Couverture (confiance ≥ 0,6) | 91,3 % | 97,9 % |
| Précision des chiffres acceptés | — | 99,84 % |
| Faux chiffres acceptés | 4 | 1 |
4 157 chiffres imprimés, 15 % mis de côté pour le test. Même prétraitement que l'app : chaque image passe par l'extraction de l'interface. Adam, lots de 64, arrêt anticipé, environ 2 minutes sur processeur.
Résolution
| Fonction | Rôle |
|---|---|
| solve | Backtracking qui commence par la case la plus contrainte (MRV) et abandonne dès qu'une case n'a plus de candidat |
| count_solutions | Compte les solutions sans modifier la grille, en s'arrêtant à 2 |
| find_conflicts | Cases répétées dans leur ligne, colonne ou boîte |
Tests · 89 au total
| Fichier | Couvre |
|---|---|
| test_end_to_end | Les 6 grilles d'exemple, claires et sombres, avec le vrai modèle |
| test_ui | Fonctions de l'interface et page Streamlit via AppTest |
| test_config | Fusion, clés inconnues, valeurs invalides |
| test_solver | Résolution, conflits, unicité |
| test_cells · test_detect | Cases vides, fonds grisés, grille encadrée |
| test_cnn_ocr | Contrat du modèle, polarité, seuil de confiance |
Limites connues
| Sujet | Constat |
|---|---|
| Grilles 9 × 9 seulement | Le découpage et le solveur supposent des boîtes de 3 × 3 ; un mini sudoku 6 × 6 est mal lu. |
| 81 appels au réseau | Une case à la fois, environ 28 ms chacune : 2 à 3 s par grille. |
| Pas de classe « pas un chiffre » | Une tache qui passe les filtres est classée de 1 à 9 ; seul le seuil de confiance l'arrête. |
| Mémoire sur le cloud | Environ 756 Mo une fois TensorFlow chargé, pour une limite d'environ 1 Go. |
| Pas de vraies photos dans les tests | Les exemples sont des captures : perspective, flou et éclairage inégal ne sont pas couverts. |
CI/CD et environnements
dev
Travail quotidien, tests en local
main
Avance rapide depuis dev
Streamlit Cloud
Redéploie à chaque push sur main
Pas d'intégration continue : les 89 tests tournent en local avec make test (environ 90 s), make test-fast saute ceux qui chargent le modèle. Le modèle est versionné dans le dépôt, OpenCV est en version headless car le serveur n'a pas de libGL.
Stack complète
| Techno | Version | Rôle |
|---|---|---|
| Python | 3.10–3.13 | Langage, plafonné par TensorFlow |
| opencv-python-headless · numpy | — | Seuils, contours, homographies, dessin |
| TensorFlow (Keras) | ≥ 2.12 | Chargement et inférence du CNN |
| pyyaml | — | Lecture des configurations |
| Techno | Version | Rôle |
|---|---|---|
| Streamlit (+ pandas) | ≥ 1.40 | Interface web, tableau éditable |
| pytesseract | — | Backend OCR alternatif, repli de la CLI |
| scikit-learn | — | Découpage stratifié pour l'entraînement |
| pytest | — | Tests, marqueur e2e |
| Techno | Version | Rôle |
|---|---|---|
| uv · Makefile | — | Environnement Python 3.12, commandes test, train, run, ui |
| setuptools · pyproject.toml | — | Commandes sudoku-ocr et sudoku-ocr-ui |
| Streamlit Community Cloud | — | Hébergement de sudoku-ocr.streamlit.app |
Relevé dans le code, branche dev, v0.1.0, 20 octobre 2026
Captures
Survolez ou parcourez au clavier pour agrandir une capture




Projet suivant QRTO
Quarto →