Skip to content

Commit 2b9cd4d

Browse files
committed
Refreshing
1 parent 5467fa1 commit 2b9cd4d

24 files changed

+48550
-651
lines changed
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import cv2
2+
import numpy
3+
from matplotlib import pyplot
4+
import sys
5+
import json
6+
import util
7+
8+
9+
"""
10+
Main routine to process the image and extract the sudoku matrix from it
11+
"""
12+
13+
14+
def extract_matrix_from_image(image, x_metrics, y_metrics):
15+
16+
x_coords, y_coords = util.get_cell_boundaries(image)
17+
18+
# Now extract the images of the cells (between the lines), based on the coordinates we just
19+
# calculated. When we have these extracted images, sum the pixel counts along the horizontal
20+
# and vertical axes. This will be used to compare with training data to identify the digits
21+
# in the cells
22+
23+
pyplot.rcParams['figure.dpi'] = 50
24+
bw_threshold = 160
25+
# Make the image monochrome. If the original pixel value > threshold, 1, otherwise 0.
26+
(thresh, monochrome_image) = cv2.threshold(image, bw_threshold, 1, cv2.THRESH_BINARY_INV)
27+
28+
puzzle_matrix = []
29+
for y_coord in y_coords:
30+
new_matrix_row = []
31+
for x_coord in x_coords:
32+
raw_image = monochrome_image[y_coord[0]:y_coord[1], x_coord[0]:x_coord[1]]
33+
image_height, image_width = raw_image.shape
34+
image_sum = raw_image.sum()
35+
image_density = image_sum / (image_width * image_height)
36+
# If the image density (% of black pixels in the image) is less than a certain threshold
37+
# we assume the cell is empty and return 0. This is not a test for 0 % since there can be
38+
# noise in the image. If above the threshold, then determine the number from training data
39+
if image_density < 0.001:
40+
number = 0
41+
else:
42+
# show_image(raw_image,
43+
# title="Y - %s:%s. X - %s:%s" % (y_coord[0], y_coord[1], x_coord[0], x_coord[1]))
44+
cell_image = util.trim(raw_image)
45+
if cell_image is None:
46+
number = 0
47+
else:
48+
number = get_number_from_image(cell_image, x_metrics, y_metrics)
49+
print("Number: %s" % number)
50+
new_matrix_row.append(number)
51+
puzzle_matrix.append(new_matrix_row)
52+
print("\n")
53+
return puzzle_matrix
54+
55+
56+
"""
57+
This routine finds the digit from a cell image based on the training data
58+
"""
59+
60+
61+
def get_number_from_image(cell_image, x_metrics, y_metrics):
62+
# This is summing columns vertically
63+
x_counts = cell_image.sum(axis=0)
64+
# This is summing rows horizontally
65+
y_counts = cell_image.sum(axis=1)
66+
x_metrics_size = len(x_metrics["1"])
67+
y_metrics_size = len(y_metrics["1"])
68+
#normalize the size of the image arrays to match the metrics data
69+
x_histogram = morph_array_to_size(x_counts, x_metrics_size)
70+
y_histogram = morph_array_to_size(y_counts, y_metrics_size)
71+
# Now get percentages
72+
x_sum = sum(x_histogram)
73+
y_sum = sum(y_histogram)
74+
for n in range(x_histogram.size):
75+
x_histogram[n] = x_histogram[n]/x_sum
76+
for n in range(y_histogram.size):
77+
y_histogram[n] = y_histogram[n]/y_sum
78+
# Now calculate difference between x and y pixel distribution for this image and metrics data
79+
# the 1000 is there for readability
80+
x_distance = numpy.zeros((len(x_metrics)+1), dtype=float)
81+
for n in range(0, len(x_metrics)):
82+
x_distance[n] = diff_between_arrays(x_histogram, x_metrics[str(n)]) * 1000
83+
y_distance = numpy.zeros((len(y_metrics)+1), dtype=float)
84+
for n in range(0, len(y_metrics)):
85+
y_distance[n] = diff_between_arrays(y_histogram, y_metrics[str(n)]) * 1000
86+
87+
# x_distance is an array that has the least squares distance between the x-axis histogram
88+
# of this image and the x-axis histograms of the training data [1..9]. y_distance is the
89+
# same thing along the y-axis. The algorithm to determine the digit in the image goes like this:
90+
# We first match using the y-axis histogram. Experiments have shown this is accurate for
91+
# everything except some confusion when a '1' is incorrectly recognized as a '2'. So,
92+
# when we see a '2', perform a second check using the x-axis to see which distance is less,
93+
# '1' or '2'.
94+
#
95+
# Other algorithms attempted:
96+
# - x_distance
97+
# - y_distance
98+
# - x_distance * y_distance
99+
current_y_distance_min = y_distance[0]
100+
number = 0
101+
# print("x histogram: %s" % x_histogram)
102+
# print("y histogram: %s" % y_histogram)
103+
for n in range(0, len(x_metrics)):
104+
print('n: %s\tx_distance: %.2f\ty_distance: %.2f' %
105+
(n, x_distance[n], y_distance[n]))
106+
if y_distance[n] < current_y_distance_min:
107+
current_y_distance_min = y_distance[n]
108+
number = n
109+
if number == 2:
110+
if x_distance[1] < x_distance[2]:
111+
number = 1
112+
113+
return number
114+
115+
"""
116+
The sizes of the images for the matrix cells vary, meaning the length of the image histogram arrays of the
117+
pixel distributions vary in length. This routine changes the length of the image histogram array to
118+
match the length of the training data array. It also strips leading and trailing 0s from the input
119+
array, effectively trimming blank space from around the digit (to match what's in the training data).
120+
121+
Because detecting lines is imperfect, the images can have some noise around the edges, parts of the lines
122+
we weren't able to remove, the trimming starts in the middle and works its way out. Note, this assumes
123+
that the number in the cell is roughly centered (pretty safe for computer generate Sudoku puzzles).
124+
"""
125+
126+
def morph_array_to_size(array, to_size):
127+
from_array = array
128+
from_size = len(from_array)
129+
to_array = numpy.zeros((to_size), dtype=float)
130+
131+
if from_size >= to_size:
132+
# for f in range(from_size):
133+
# to_array[f] = from_array[f]
134+
for f in range(from_size):
135+
starting_to_element = int(to_size / from_size * f)
136+
ending_to_element = int(to_size / from_size * (f + 1))
137+
if starting_to_element == ending_to_element or \
138+
ending_to_element - to_size / from_size * (f + 1) == 0:
139+
to_array[starting_to_element] += from_array[f]
140+
else:
141+
amt1_pct = from_size / to_size * f - int(from_size / to_size * f)
142+
amt2_pct = 1 - amt1_pct
143+
to_array[starting_to_element] += from_array[f] * amt1_pct
144+
to_array[ending_to_element] += from_array[f] * amt2_pct
145+
else:
146+
for t in range(to_size):
147+
starting_from_element = int(from_size / to_size * t)
148+
ending_from_element = int(from_size / to_size * (t + 1))
149+
if starting_from_element == ending_from_element or \
150+
ending_from_element - from_size / to_size * (t + 1) == 0:
151+
to_array[t] += from_array[starting_from_element] * from_size / to_size
152+
else:
153+
amt1_pct = int(from_size / to_size * t) + 1 - from_size / to_size * t
154+
amt2_pct = from_size / to_size * (t + 1) - int(from_size / to_size * (t + 1))
155+
to_array[t] += from_array[starting_from_element] * amt1_pct
156+
to_array[t] += from_array[ending_from_element] * amt2_pct
157+
158+
return to_array
159+
160+
def diff_between_arrays(a1, a2):
161+
diff = 0
162+
for n in range(len(a1)):
163+
diff += (a1[n] - a2[n]) * (a1[n] - a2[n])
164+
return diff
165+
166+
def main():
167+
if len(sys.argv) < 3:
168+
raise Exception("Inputs:\n\tparameter 1: Test image to use for processing.\n\tparameter 2: folder for image metrics json.")
169+
input_image = sys.argv[1]
170+
print("Image: %s" % input_image)
171+
metrics_folder = sys.argv[2]
172+
x_metrics_file = open(metrics_folder + "x.json", "r")
173+
y_metrics_file = open(metrics_folder + "y.json", "r")
174+
x_metrics = json.loads(x_metrics_file.read())
175+
y_metrics = json.loads(y_metrics_file.read())
176+
image = cv2.imread(input_image, cv2.IMREAD_GRAYSCALE)
177+
matrix = extract_matrix_from_image(image, x_metrics, y_metrics)
178+
print(matrix)
179+
180+
if __name__ == '__main__':
181+
main()
-200 KB
Binary file not shown.

Image_metrics_20200912/image.xlsx

287 KB
Binary file not shown.

0 commit comments

Comments
 (0)