SudokuSolver / utils.py
LTPhat's picture
code
1f1fc6b
import cv2
import numpy as np
import operator
import torch
def find_corners(polygon, limit_func, compare_func):
"""
Input: Rectangle puzzle extract from contours
Output: One of four cornet point depend on limit_func, compare_func
# limit_fn is the min or max function
# compare_fn is the np.add or np.subtract function
Note: (0,0) point is at the top-left
top-left: (x+y) min
top-right: (x-y) max
bot-left: (x-y) min
bot-right: (x+y) max
"""
index, _ = limit_func(enumerate([compare_func(ptr[0][0], ptr[0][1]) for ptr in polygon]), key = operator.itemgetter(1))
return polygon[index][0][0], polygon[index][0][1]
def draw_circle_at_corners(original, ptr):
"""
Helper function to draw circle at corners
"""
cv2.circle(original, ptr, 5, (0,255,0), cv2.FILLED)
def grid_line_helper(img, shape_location, length = 10):
"""
Helper function to fine vertical, horizontal line
Find horizontal line: shape_location = 0
Find vertical line: shape_location = 1
"""
clone_img = img.copy()
row_or_col = clone_img.shape[shape_location]
# Find the distance between lines
size = row_or_col // length
# Morphological transfromation to find line
# Define morphology kernel
if shape_location == 0:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size,1))
else:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,size))
clone_img = cv2.erode(clone_img, kernel)
clone_img = cv2.dilate(clone_img, kernel)
return clone_img
def draw_line(img, lines):
"""
Draw all lines in lines got from cv2.HoughLine()
"""
clone_img = img.copy()
# lines list from cv2.HoughLine() is 3d array
# Convert to 2d array
lines = np.squeeze(lines)
for rho, theta in lines:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * a)
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * a)
#Draw line every loop
cv2.line(clone_img, (x1,y1), (x2,y2), (255,255,255), 4)
return clone_img
def clean_square_helper(img):
"""
Clean noises in every square splited
Input: One of 81 squares
Output: Cleaned square and boolean var which so that there is number in it
"""
if np.isclose(img, 0).sum() / (img.shape[0] * img.shape[1]) >= 0.96:
return np.zeros_like(img), False
# if there is very little white in the region around the center, this means we got an edge accidently
height, width = img.shape
mid = width // 2
if np.isclose(img[:, int(mid - width * 0.38):int(mid + width * 0.38)], 0).sum() / (2 * width * 0.38 * height) >= 0.98:
return np.zeros_like(img), False
# center image
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
x, y, w, h = cv2.boundingRect(contours[0])
start_x = (width - w) // 2
start_y = (height - h) // 2
new_img = np.zeros_like(img)
new_img[start_y:start_y + h, start_x:start_x + w] = img[y:y + h, x:x + w]
return new_img, True
def resize_square(clean_square_list):
"""
Resize clean squares into 28x28 in order to feed to classifier
"""
resized_list = []
for img in clean_square_list:
resized = cv2.resize(img, (28,28), interpolation=cv2.INTER_AREA)
resized_list.append(resized)
return resized_list
def resize_square32(clean_square_list):
"""
Resize clean squares into 32x32 in order to feed to tf classifier
"""
resized_list = []
for img in clean_square_list:
resized = cv2.resize(img, (32,32), interpolation=cv2.INTER_AREA)
resized_list.append(resized)
return resized_list
def classify_one_digit(model, resize_square, org_image):
"""
Determine whether each square has number by counting number of (not black) pixel and compare to threshold value
Using classifier to predict number in square
- Return 0 if the square is blank
- Return predict digit if the square has number
"""
threshold = 0
if (org_image.shape[0] > 600 or org_image.shape[1] > 600) or (org_image.shape[1] > 600 or org_image.shape[2] > 600):
threshold = 40
else:
threshold = 60
# Determine blank square
if (resize_square != resize_square.min()).sum() < threshold:
return str(0)
model.eval()
# Convert to shape (1,1,28,28) to be compatible with dataloader for evaluation
iin = torch.Tensor(resize_square).unsqueeze(0).unsqueeze(0)
with torch.no_grad():
out = model(iin)
# Get index of predict digit
_, index = torch.max(out, 1)
pred_digit = index.item()
return str(pred_digit)
def normalize(resized_list):
"""
Scale pixel value for recognition
"""
return [img/255 for img in resized_list]
def convert_str_to_board(string, step = 9):
"""
Convert recognized string into 2D array for sudoku solving
"""
board = []
for i in range(0, len(string), step):
board.append([int(char) for char in string[i:i+step]])
return np.array(board)