You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In this section, we integrate all previous components into a complete Sudoku processing pipeline. Starting from a full Sudoku image, we detect and rectify the grid, split it into individual cells, recognize digits using the ONNX model, and finally solve the puzzle using a deterministic solver. By the end of this step, you will have an end-to-end system that takes a photograph of a Sudoku puzzle and produces a solved board, along with visual outputs for debugging and validation.
12
12
13
+
## Context
13
14
So far, we have:
14
15
1. Generated a synthetic, well-labeled Sudoku digit dataset,
15
16
2. Trained a lightweight CNN (DigitNet) to recognize digits and blanks,
@@ -19,9 +20,9 @@ So far, we have:
19
20
At this point, the digit recognizer is reliable in isolation. The remaining challenge is connecting vision with reasoning: extracting the Sudoku grid from an image, mapping each cell to a digit, and applying a solver. This section bridges that gap.
20
21
21
22
## Overview of the pipeline
22
-
To implement the Sudoku processor, create a new file sudoku_processor.py and modify it as follows:
23
+
To implement the Sudoku processor, create the file (sudoku_processor.py) and paste the implementation below:
Overlays ONLY the filled-in digits (where original board has 0).
224
225
"""
@@ -340,116 +341,19 @@ The first task is to locate the Sudoku grid in the image. We convert the image t
340
341
341
342
Once the four corners are identified, we compute a perspective transform and warp the grid into a square image. This rectified representation removes camera tilt and perspective distortion, allowing all subsequent steps to assume a fixed geometry.
342
343
343
-
```Python
344
-
defdetect_and_warp_board(self, bgr: np.ndarray):
345
-
"""
346
-
Finds the largest Sudoku-like quadrilateral and warps it to a square.
347
-
Returns warped_board, homography, quad_points.
348
-
"""
349
-
gray = cv.cvtColor(bgr, cv.COLOR_BGR2GRAY)
350
-
blur = cv.GaussianBlur(gray, (5, 5), 0)
351
-
352
-
# Strong binary image helps contour finding (works well for printed grids)
We order the four corners consistently (top-left → top-right → bottom-right → bottom-left) before computing the perspective transform.
389
345
390
346
## Splitting the grid into cells
391
347
After rectification, the grid is divided evenly into a 9×9 array. Each cell is cropped based on its row and column index. At this stage, every cell corresponds to one Sudoku position and is ready for preprocessing and classification.
392
348
393
-
```Python
394
-
defsplit_cells(self, warped_bgr: np.ndarray):
395
-
"""
396
-
Splits a rectified square board into 81 cell images.
397
-
Returns list of (r, c, cell_bgr).
398
-
"""
399
-
cells = []
400
-
step =self.warp_size //9
401
-
for r inrange(9):
402
-
for c inrange(9):
403
-
y0, y1 = r * step, (r +1) * step
404
-
x0, x1 = c * step, (c +1) * step
405
-
cell = warped_bgr[y0:y1, x0:x1].copy()
406
-
cells.append((r, c, cell))
407
-
return cells
408
-
```
409
-
410
349
Each cell undergoes light preprocessing before inference:
411
350
* Conversion to grayscale,
412
351
* Cropping of a small margin to suppress grid lines,
413
352
* Adaptive thresholding and morphological cleanup to isolate printed digits,
414
353
* Resizing to the model’s input size (28×28),
415
354
* Normalization to match the training distribution.
416
355
417
-
```Python
418
-
defpreprocess_cell(self, cell_bgr: np.ndarray):
419
-
"""
420
-
Produces a 28x28 float32 tensor in the same normalization as training:
421
-
grayscale -> [0,1] -> normalize to [-1,1] via (x-0.5)/0.5
422
-
Also tries to suppress grid lines / borders by cropping margins.
Cells with very little foreground content are treated as blank candidates, reducing false digit detections in empty cells.
356
+
We crop a margin to suppress grid lines, because grid strokes can dominate the digit pixels and cause systematic misclassification. Cells with very little foreground content are treated as blank candidates, reducing false digit detections in empty cells.
453
357
454
358
## Batched ONNX inference
455
359
All 81 cell tensors are stacked into a single batch and passed to ONNX Runtime in one call. Because the model was exported with a dynamic batch dimension, this batched inference is efficient and mirrors how the model will be used in production.
@@ -460,100 +364,21 @@ The result is a 9×9 board where:
460
364
* 0 represents a blank cell,
461
365
* 1–9 represent recognized digits.
462
366
463
-
```Python
464
-
defrecognize_board(self, cells):
465
-
"""
466
-
Runs batched ONNX inference on 81 cells and returns:
467
-
board[9][9] with 0 for blank
468
-
conf[9][9] with max softmax probability
469
-
"""
470
-
xs = []
471
-
coords = []
472
-
for r, c, cell in cells:
473
-
coords.append((r, c))
474
-
xs.append(self.preprocess_cell(cell))
475
-
476
-
X = np.concatenate(xs, axis=0).astype(np.float32) # [81,1,28,28]
conf_grid = [[0.0for _ inrange(9)] for _ inrange(9)]
484
-
for i, (r, c) inenumerate(coords):
485
-
p =int(pred[i])
486
-
cf =float(conf[i])
487
-
488
-
# Optional safety: low-confidence => blank
489
-
if cf <self.blank_conf_threshold:
490
-
p =self.blank_class
491
-
492
-
board[r][c] = p
493
-
conf_grid[r][c] = cf
494
-
495
-
return board, conf_grid
496
-
```
497
-
498
367
## Solving the Sudoku
499
368
With the recognized board constructed, we apply a classic backtracking Sudoku solver. This solver deterministically fills empty cells while respecting Sudoku constraints (row, column, and 3×3 block rules).
500
369
501
370
If the solver succeeds, we obtain a complete solution. If it fails, the failure usually indicates one or more recognition errors, which can be diagnosed using the intermediate visual outputs.
502
371
503
-
```Python
504
-
defsolve_sudoku(board):
505
-
pos = find_empty(board)
506
-
if pos isNone:
507
-
returnTrue
508
-
r, c = pos
509
-
for v inrange(1, 10):
510
-
if valid(board, r, c, v):
511
-
board[r][c] = v
512
-
if solve_sudoku(board):
513
-
returnTrue
514
-
board[r][c] =0
515
-
returnFalse
516
-
517
-
518
-
deffind_empty(board):
519
-
for r inrange(9):
520
-
for c inrange(9):
521
-
if board[r][c] ==0:
522
-
return (r, c)
523
-
returnNone
524
-
525
-
526
-
defvalid(board, r, c, v):
527
-
# row
528
-
for j inrange(9):
529
-
if board[r][j] == v:
530
-
returnFalse
531
-
# col
532
-
for i inrange(9):
533
-
if board[i][c] == v:
534
-
returnFalse
535
-
# box
536
-
br, bc =3* (r //3), 3* (c //3)
537
-
for i inrange(br, br +3):
538
-
for j inrange(bc, bc +3):
539
-
if board[i][j] == v:
540
-
returnFalse
541
-
returnTrue
542
-
```
543
-
544
372
## Visualization and outputs
545
-
To aid debugging and demonstration, the processor produces several visual artifacts:
546
-
1. The rectified (warped) Sudoku grid,
547
-
2. A clean rendering of the recognized board,
548
-
3. A clean rendering of the solved board,
549
-
4. An overlay of the solved digits on top of the original image.
550
-
551
-
These outputs make it easy to understand how each stage behaves and are particularly useful when testing on real camera images.
552
-
373
+
The processor saves several artifacts to help debugging and demonstration:
374
+
-`artifacts/warped.png` – rectified top-down view of the Sudoku grid.
375
+
-`artifacts/overlay_solution.png` – solution digits overlaid onto the original image (if solved).
376
+
- (Optional) `artifacts/recognized_board.png`, `artifacts/solved_board.png`, `artifacts/boards_side_by_side.png` – clean board renderings if you enabled those helpers.
553
377
554
378
## Running the processor
555
379
A small driver script (05_RunSudokuProcessor.py) demonstrates how to use the SudokuProcessor:
0 commit comments