{"id":23168003,"url":"https://github.com/zamb0/sudoku-computer-vision","last_synced_at":"2025-04-04T22:21:26.121Z","repository":{"id":256780191,"uuid":"856394201","full_name":"zamb0/Sudoku-Computer-Vision","owner":"zamb0","description":"Number extraction from sudoku games using computer vision preprocessing","archived":false,"fork":false,"pushed_at":"2024-09-12T14:23:13.000Z","size":149,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-10T06:45:11.737Z","etag":null,"topics":["computer-vision"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zamb0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-12T14:04:43.000Z","updated_at":"2024-09-12T14:25:57.000Z","dependencies_parsed_at":"2024-09-13T02:44:32.200Z","dependency_job_id":"655b8926-b248-4743-bfdf-9303ba8e73f5","html_url":"https://github.com/zamb0/Sudoku-Computer-Vision","commit_stats":null,"previous_names":["zamb0/sudoku-computer-vision"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zamb0%2FSudoku-Computer-Vision","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zamb0%2FSudoku-Computer-Vision/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zamb0%2FSudoku-Computer-Vision/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zamb0%2FSudoku-Computer-Vision/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zamb0","download_url":"https://codeload.github.com/zamb0/Sudoku-Computer-Vision/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247256766,"owners_count":20909357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["computer-vision"],"created_at":"2024-12-18T02:37:25.244Z","updated_at":"2025-04-04T22:21:26.104Z","avatar_url":"https://github.com/zamb0.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Computer Vision Project: Sudoku\n## University of Pavia - 2023/24\n### Fabio Zamboni\n### Federico Pietro Bernardini\n\n\u003c/div\u003e\n\n# Sudoku number extraction\n\nIn this notebook we will use computer vision techniques to extract the numbers from a sudoku grid. We will use the following steps:\n\n\n0. Generate a Sudoku Puzzles\n1. Load the image   \n2. Grid detection\n3. Gray scale conversion\n4. Blurring\n5. Thresholding\n6. Erosion\n7. Contour detection\n8. Number extraction (using PyTesseract)\n\n\n\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"sudoku.png\" style=\"padding: 40px;\" /\u003e\n\u003cimg src=\"sudoku_recognized.png\" style=\"padding: 40px;\" /\u003e\n\n\u003c/div\u003e\n\n\n```python\nimport cv2\nimport pytesseract as tess\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom sudoku import Sudoku\nfrom PIL import Image, ImageDraw, ImageFont\n```\n\n# Generate a Sudoku Puzzles\n\n\n```python\ngame = Sudoku(3).solve()\n\n# Convert the board to a numpy array\nsudoku = np.array(game.board)\n\n# Remove some numbers from the solution to create a puzzle\nfor _ in range(71):\n    while True:\n        i, j = np.random.randint(0, 9, size=2)\n        if sudoku[i, j] != 0:\n            sudoku[i, j] = 0\n            break\n\n# Create a new image with a white background\nimg = Image.new('RGB', (450, 450), color = (255, 250, 0))\n\n# Create a drawing object\nd = ImageDraw.Draw(img)\n\n# Draw a grid\nfor i in range(10):\n    if i % 3 == 0:\n        d.line([(i*50, 0), (i*50, 450)], fill=(0,0,0), width=2)\n        d.line([(0, i*50), (450, i*50)], fill=(0,0,0), width=2)\n    else:\n        d.line([(i*50, 0), (i*50, 450)], fill=(0,0,0), width=1)\n        d.line([(0, i*50), (450, i*50)], fill=(0,0,0), width=1)\n\n# Load a font\nfnt = ImageFont.truetype('~/Library/Fonts/174b2ec3d1b18323f2021ce3fdda6028.ttf', 35)\n\n# Draw the numbers\nfor i in range(9):\n    for j in range(9):\n        if sudoku[i, j] != 0:\n            d.text((j*50+15, i*50+5), str(sudoku[i, j]), font=fnt, fill=(255,0,0))\n\n# Save the image\nimg.save('sudoku.png')\nplt.imshow(img)\nplt.axis('off')\nplt.title('Random Sudoku')\nplt.show()\n```\n\n\n    \n![png](sudoku_files/sudoku_5_0.png)\n    \n\n\n# Load the image\n\n\n```python\n# Load the image\nimg = cv2.imread('sudoku.png')\n\n# Show the image\nplt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\nplt.axis('off')\nplt.title('Loaded Sudoku')\nplt.show()\n\n```\n\n\n    \n![png](sudoku_files/sudoku_7_0.png)\n    \n\n\n# Grid detection\n\nWe will divide the image into 81 cells, each containing a number of the sudoku grid.\n\n\n```python\n# Cut the image into 81 cells\ncells = [np.hsplit(row, 9) for row in np.vsplit(img, 9)]\nprint('Number of cells:', len(cells)*len(cells[0]))\n\n# Print all cells\nfor i in range(9):\n    for j in range(9):\n        plt.subplot(9, 9, i*9+j+1)\n        plt.imshow(cv2.cvtColor(cells[i][j], cv2.COLOR_BGR2RGB))\n        plt.axis('off')\nplt.show()\n```\n\n    Number of cells: 81\n\n\n\n    \n![png](sudoku_files/sudoku_9_1.png)\n    \n\n\n# Gray scale conversion\n\nWe will convert the cells image to a gray scale cells image.\n\n\n```python\n# Convert the cells to grayscale\ngray_cells = np.zeros((9, 9), dtype=np.ndarray)\n\nfor i in range(9):\n    for j in range(9):\n        gray_cells[i][j] = cv2.cvtColor(cells[i][j], cv2.COLOR_BGR2GRAY)\n\n# Print all cells\nfor i in range(9):\n    for j in range(9):\n        plt.subplot(9, 9, i*9+j+1)\n        plt.imshow(cv2.cvtColor(gray_cells[i][j], cv2.COLOR_BGR2RGB))\n        plt.axis('off')\nplt.show()\n```\n\n\n    \n![png](sudoku_files/sudoku_11_0.png)\n    \n\n\n# Blurring\n\nWe will apply a Gaussian blur to the cells image.\n\n\n```python\n# Apply gaussian blur\nblur_cells = np.zeros((9, 9), dtype=np.ndarray)\n\nfor i in range(9):\n    for j in range(9):\n        blur_cells[i][j] = cv2.GaussianBlur(gray_cells[i][j], (3, 3), 1)\n\n# print all cells\nfor i in range(9):\n    for j in range(9):\n        plt.subplot(9, 9, i*9+j+1)\n        plt.imshow(cv2.cvtColor(blur_cells[i][j], cv2.COLOR_BGR2RGB))\n        plt.axis('off')\n```\n\n\n    \n![png](sudoku_files/sudoku_13_0.png)\n    \n\n\n# Thresholding\n\nWe will apply a threshold to the cells image.\n\n\n```python\n# apply threshold\nthresh_cells = np.zeros((9, 9), dtype=np.ndarray)\n\nfor i in range(9):\n    for j in range(9):\n        thresh_cells[i][j] = cv2.threshold(blur_cells[i][j], 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]\n\n# print all cells\nfor i in range(9):\n    for j in range(9):\n        plt.subplot(9, 9, i*9+j+1)\n        plt.imshow(cv2.cvtColor(thresh_cells[i][j], cv2.COLOR_BGR2RGB))\n        plt.axis('off')\nplt.show()\n\n```\n\n\n    \n![png](sudoku_files/sudoku_15_0.png)\n    \n\n\n## Erosion\n\n\n```python\n# apply dilation on the inverted image\n\nerosion_cells = np.zeros((9, 9), dtype=np.ndarray)\ninverted_cells = np.zeros((9, 9), dtype=np.ndarray)\n\nfor i in range(9):\n    for j in range(9):\n        kernel = np.ones((2, 2), np.uint8)\n        erosion_cells[i][j] = cv2.erode(thresh_cells[i][j], kernel, iterations=2)\n\n\n# print all cells\nfor i in range(9):\n    for j in range(9):\n        plt.subplot(9, 9, i*9+j+1)\n        plt.imshow(cv2.cvtColor(erosion_cells[i][j], cv2.COLOR_BGR2RGB))\n        plt.axis('off')\nplt.show()\n```\n\n\n    \n![png](sudoku_files/sudoku_17_0.png)\n    \n\n\n# Contour detection\n\nWe will detect the contours of the numbers in the thresholded cells image using the dilated image.\n\n\n```python\n# find contours\ncontours_cells = np.zeros((9, 9), dtype=np.ndarray)\nbiggest_contours_cells = np.zeros((9, 9), dtype=np.ndarray)\ndigit_cells = np.zeros((9, 9), dtype=np.ndarray)\n\ncells_copy = thresh_cells.copy()\n\nfor i in range(9):\n    for j in range(9):\n        contours_cells[i][j], _ = cv2.findContours(erosion_cells[i][j], cv2.CONTOURS_MATCH_I1, cv2.CHAIN_APPROX_NONE)\n        #contours_cells[i][j], _ = cv2.findContours(dilated_cells[i][j], cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n\nfor i in range(9):\n    for j in range(9):\n        if len(contours_cells[i][j]) \u003e 0:\n            biggest_contours_cells[i][j] = max(contours_cells[i][j], key=cv2.contourArea)\n            x, y, w, h = cv2.boundingRect(biggest_contours_cells[i][j])\n            #digit_cells[i][j] = cells_copy[i][j][int(y*0.6):y+int(h*1.4), int(x*0.6):x+int(w*1.4)]\n            digit_cells[i][j] = cells_copy[i][j][y:y+h, x:x+w]\n\nfor i in range(9):\n    for j in range(9):\n        plt.subplot(9, 9, i*9+j+1)\n        plt.imshow(cv2.cvtColor(digit_cells[i][j], cv2.COLOR_BGR2RGB))\n        plt.axis('off')\nplt.show()\n```\n\n\n    \n![png](sudoku_files/sudoku_19_0.png)\n    \n\n\n# Number extraction (using PyTesseract)\n\n\n```python\n# pytesseract\ntess_cells = np.zeros((9, 9), dtype=np.ndarray)\n\nfor i in range(9):\n    for j in range(9):\n        tess_cells[i][j] = tess.image_to_string(cv2.cvtColor(digit_cells[i][j], cv2.COLOR_BGR2RGB), config='--psm 10 --oem 3 -c tessedit_char_whitelist=123456789')\n\nprint('Tesseract output:')\nprint(tess_cells)\n\n```\n\n    Tesseract output:\n    [['' '' '8\\n' '' '' '' '' '' '']\n     ['' '' '' '' '' '' '' '' '']\n     ['' '' '' '' '' '' '' '' '']\n     ['' '' '5\\n' '' '' '4\\n' '' '' '']\n     ['6\\n' '' '' '' '' '' '' '1\\n' '']\n     ['' '' '' '' '' '' '' '' '']\n     ['4\\n' '9\\n' '' '' '' '' '' '' '']\n     ['' '5\\n' '' '' '' '7\\n' '' '' '1\\n']\n     ['' '' '' '' '' '' '' '' '']]\n\n\n## Final Image\n\n\n```python\nimg_rc = Image.fromarray(cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB), 'RGB')\n\n# Create a drawing object\nd = ImageDraw.Draw(img_rc)\n\n# Load a font\nfnt = ImageFont.truetype('~/Library/Fonts/174b2ec3d1b18323f2021ce3fdda6028.ttf', 20)\nfor i in range(9):\n    for j in range(9):\n        if tess_cells[i][j] != '':\n            x, y, w, h = cv2.boundingRect(biggest_contours_cells[i][j])\n            d.rectangle([(j*50+x, i*50+y), (j*50+x+w, i*50+y+h)], outline=(0,255,0), width=2)\n            d.text((j*50+35, i*50+25), tess_cells[i][j], font=fnt, fill=(0,255,0))\n\n# Save the image\nimg_rc.save('sudoku_recognized.png')\nplt.imshow(img_rc)\nplt.axis('off')\nplt.title('Recognized Sudoku')\nplt.show()\n\n\n\n\n```\n\n\n    \n![png](sudoku_files/sudoku_23_0.png)\n    \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzamb0%2Fsudoku-computer-vision","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzamb0%2Fsudoku-computer-vision","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzamb0%2Fsudoku-computer-vision/lists"}