In [2]:
import logging
import math
import ast
class SudokuMatrix(object):
"""
Build monochrome matrix of the sudoku board.
You can add new scans to the matrix and then you can normalize the
results. Each scan is an arc of measurements.
"""
# The radius of the circle covered by the scanning device.
k_scanner_radius_mm = 48
# A map from coordinates to a list of measurements for that point in space.
measurements = {}
def add_scan(self, cx, cy, degrees, measures):
"""cx, cy center of the measuring device
degrees the size of the arc measured
measures the samples gathered while measuring (we assume these are
uniformly distributed along the movement arc).
"""
if len(measures) < 1:
logging.exception("No measurements provided")
for i in range(0, len(measures)):
deg_moved = degrees * i / len(measures)
angle = math.radians(degrees / 2 - deg_moved)
a = self.k_scanner_radius_mm * math.cos(angle)
b = self.k_scanner_radius_mm * math.sin(angle)
px = cx - b
py = cy + a
# x axis sampling is twice as much as y axis
p = (int(math.floor(px * 2)), int(math.floor(py)))
if p not in self.measurements:
self.measurements[p] = []
self.measurements[p].append(measures[i])
print(px, py, measures[i])
print('Scan added')
def to_str(self):
return str(self.measurements)
def dump_to_file(self, filename):
with open(filename, 'w') as f:
f.write(str(self.measurements))
f.close()
def load_from_file(self, filename):
fstr = ""
try:
with open(filename, 'r') as f:
fstr = f.read()
f.close()
self.measurements = ast.literal_eval(fstr)
except Exception as e:
print('Unable to load file:')
print(str(e))
In [325]:
import numpy as np
import skimage.filter
from skimage.morphology import disk
from skimage.filter import rank
class SudokuBoardImage(object):
"""
Represents a grayscale image of a sudoku board constructed from a SudokuMatrix.
"""
def __init__(self, file_name):
self.load_scan(file_name)
self.image = self.build_image(self.m)
def load_scan(self, file_name):
self.m = SudokuMatrix()
self.m.load_from_file('/home/ch/ev3dev/share/ev3-repo/' + file_name)
def build_image(self, m):
"""
Convert the measurements in a SudokuMatrix into a numpy array.
"""
a = m.measurements
aav = dict()
minx = 1 << 20
maxx = - (1 << 20)
miny = 1 << 20
maxy = - (1 << 20)
for k, v in a.iteritems():
aav[k] = sum(v)/len(v)
minx = min(minx, k[0])
maxx = max(maxx, k[0])
miny = min(miny, k[1])
maxy = max(maxy, k[1])
arr = []
for i in range(minx, maxx):
arr.append([])
for j in range(miny, maxy):
arr[i - minx].append(aav.get((i, j), 0))
# We need to rotate the array and trim the top and bottom.
return np.rot90(np.array(arr)[:,15:-10])
def edges(self):
thresh = rank.otsu(self.image.astype(np.uint8), disk(5))
return self.image < thresh * 0.7
@property
def img(self):
return self.image
def dedupe_digit_rectangles(self, coords, size):
"""
Uses a greedy approach to remove all overlapping digit rectangles
Assumes the coordinates are sorted by row and then by col
"""
dc = []
for (i, j) in coords:
intersects = False
for (x, y) in dc:
if abs(y - j) < size and abs(x - i) < size:
intersects = True
break
if intersects:
continue
dc.append((i, j))
return dc
def digit_coordinates(self):
"""
Return a set of coordinates that are the most likely lower right corners of a digit
sum_h and sum_v not used for now.
"""
k_size = 16
k_perim_thr = .04
k_area_thr = .08
edges = self.edges()
sum_h = np.zeros(edges.shape)
sum_v = np.zeros(edges.shape)
sum_all = np.zeros(edges.shape)
for (i, j), value in np.ndenumerate(edges):
# Compute the cumulative sums
if i == 0 and j == 0:
sum_h[i, j] = sum_v[i, j] = sum_all[i, j] = edges[i, j]
elif i == 0:
sum_h[i, j] = sum_all[i, j] = sum_h[i, j - 1] + edges[i, j]
elif j == 0:
sum_v[i, j] = sum_all[i, j] = sum_v[i - 1, j] + edges[i, j]
else:
sum_h[i, j] = sum_h[i, j - 1] + edges[i, j]
sum_v[i, j] = sum_v[i - 1, j] + edges[i, j]
sum_all[i, j] = edges[i, j] + sum_all[i - 1, j] + sum_all[i, j- 1] - sum_all[i - 1, j - 1]
def sq_sum(m, i, j, size):
# Computes the sum of a subsquare in a matrix with the end in i, j
# m should be a matrix of cumulative sums.
return (m[i, j] -
(m[i, j - size] if j - size >= 0 else 0) -
(m[i - size, j] if i - size >= 0 else 0) +
(m[i - size, j - size] if i - size >= 0 and j - size >= 0 else 0))
i = j = k_size - 1
corners = []
# We know digits don't overlap so when we find a digit we can skip
# some rows and columns.
found = False
while i < edges.shape[0] and j < edges.shape[1]:
perim_and_inside = sq_sum(sum_all, i, j, k_size)
inside = sq_sum(sum_all, i - 1, j - 1, k_size - 2)
perim = perim_and_inside - inside
if perim < (k_size * 4 - 4) * k_perim_thr:
if inside > (k_size * k_size) * k_area_thr:
corners.append((i, j))
# j += k_size - 1
found = True
j += 1
if j >= edges.shape[1]:
j = k_size - 1
i += 1 #(1 if not found else k_size)
return self.dedupe_digit_rectangles(corners, k_size)
def digit_coordinates_img(self):
coord = self.digit_coordinates()
res = np.zeros(self.image.shape)
for (i, j) in coord:
for k in xrange(10):
res[i, j - k] = res[i - k, j] = 1
return res
def center_of_mass(matrix):
total = 0.0
mx = 0.0
my = 0.0
for i in xrange(0, len(matrix)):
for j in xrange(0, len(matrix[i])):
total += matrix[i][j]
mx += matrix[i][j] * i
my += matrix[i][j] * j
return [mx / total, my / total]
def distance(p1, p2):
return math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]))
def center_of_mass_filter(image):
k_kernel_size = 8
res = np.zeros(image.shape)
for i in xrange(k_kernel_size, len(image) - k_kernel_size + 1):
for j in xrange(k_kernel_size, len(image[i]) - k_kernel_size + 1):
cm = center_of_mass(image[i - k_kernel_size: i + k_kernel_size, j - k_kernel_size: j + k_kernel_size])
d1 = abs(cm[0] - k_kernel_size)
d2 = abs(cm[1] - k_kernel_size)
res[i][j] = 100 if d1 < 0.2 and d2 < 0.2 else 0
return res
In [326]:
left = SudokuBoardImage('slim_left.txt')
center = SudokuBoardImage('slim_center.txt')
right = SudokuBoardImage('slim_right.txt')
In [116]:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
In [117]:
plt.subplot(1, 3, 1)
plt.imshow(left.img, cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 3, 2)
plt.imshow(center.img, cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 3, 3)
plt.imshow(right.img, cmap=plt.cm.gray, interpolation='none')
plt.show()
In [179]:
#plt.imshow(right.edges(), cmap=plt.cm.gray, interpolation='none')
im = center.img
#im = skimage.filter.sobel(im.astype(np.float64))
thresh = skimage.filter.threshold_otsu(im.astype(np.float64))
plt.imshow(im < thresh * 0.6, cmap=plt.cm.gray, interpolation='none')
# plt.imshow(im, cmap=plt.cm.gray, interpolation='none')
Out[179]:
In [264]:
from skimage import data
from skimage.morphology import disk
from skimage.filter import threshold_otsu, rank
from skimage.util import img_as_ubyte
img = center.img.astype(np.uint8)
#img = enhance_contrast(bilat, disk(3))
local_otsu = rank.otsu(img, disk(5))
plt.imshow(center.img <= local_otsu * 0.7, cmap=plt.cm.gray, interpolation='none')
Out[264]:
In [250]:
from skimage.filter.rank import mean_bilateral, enhance_contrast
bilat = mean_bilateral(center.img.astype(np.uint16), disk(16), s0=8, s1=8)
bilat = enhance_contrast(bilat, disk(3))
plt.imshow(bilat, cmap=plt.cm.gray, interpolation='none')
Out[250]:
In [328]:
def pc(l):
plt.imshow(edges[l[0] - 15:l[0], l[1] - 15:l[1]], cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 2, 1)
plt.imshow(center.edges(), cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 2, 2)
plt.imshow(center.digit_coordinates_img(), cmap=plt.cm.gray, interpolation='none')
Out[328]:
In [317]:
len(dedupe_digit_rectangles(right.digit_coordinates(), 16))
Out[317]:
In [232]:
# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# License: BSD 3 clause
# Standard scientific Python imports
import matplotlib.pyplot as plt
# Import datasets, classifiers and performance metrics
from sklearn import datasets, svm, metrics
from sklearn.ensemble import GradientBoostingClassifier
# The digits dataset
digits = datasets.load_digits()
# The data that we are interested in is made of 8x8 images of digits, let's
# have a look at the first 3 images, stored in the `images` attribute of the
# dataset. If we were working from image files, we could load them using
# pylab.imread. Note that each image must have the same size. For these
# images, we know which digit they represent: it is given in the 'target' of
# the dataset.
def dou(matrix):
dr = []
for row in matrix:
q = []
for j in row:
q += [j, j]
dr += [q, q]
return dr
images_and_labels = list(zip(digits.images, digits.target))
for index, (image, label) in enumerate(images_and_labels[:4]):
plt.subplot(2, 4, index + 1)
plt.axis('off')
plt.imshow(dou(image), cmap=plt.cm.gray_r, interpolation='none')
plt.title('Training: %i' % label)
# To apply a classifier on this data, we need to flatten the image, to
# turn the data in a (samples, feature) matrix:
n_samples = len(digits.images)
# Resize the images
big_images = np.asarray([skf.threshold_adaptive(np.asarray(dou(im)), 16).astype(np.int32) for im in digits.images])
data = big_images.reshape((n_samples, -1))
# Create a classifier: a support vector classifier
classifier = svm.SVC(gamma=0.001)
# We learn the digits on the first half of the digits
classifier.fit(data[:n_samples / 2], digits.target[:n_samples / 2])
# Now predict the value of the digit on the second half:
expected = digits.target[n_samples / 2:]
predicted = classifier.predict(data[n_samples / 2:])
print("Classification report for classifier %s:\n%s\n"
% (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))
images_and_predictions = list(zip(digits.images[n_samples / 2:], predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:4]):
plt.subplot(2, 4, index + 5)
plt.axis('off')
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('Prediction: %i' % prediction)
plt.show()
In [170]:
print np.max(digits.images)
seven_normalized = (3 - ((seven - np.min(seven)) * 4 / (np.max(seven) - np.min(seven)))) * 4
print classifier.predict(np.asarray(dou(digits.images[17])).flatten())
print classifier.predict(seven_normalized.astype(np.float64).flatten())
In [171]:
seven_normalized
Out[171]:
In [168]:
np.asarray(dou(digits.images[17])).astype(np.int32)
Out[168]:
In [172]:
for index, (image, label) in enumerate(images_and_labels[16:20]):
plt.subplot(2, 4, index + 1)
plt.axis('off')
plt.imshow(dou(seven_normalized), cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('Training: %i' % label)
In [251]:
from sklearn.ensemble import GradientBoostingClassifier
# BDT
classifier_BDT = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0, max_depth=12, random_state=3)
# We learn the digits on the first half of the digits
classifier_BDT.fit(data[:n_samples / 2], digits.target[:n_samples / 2])
# Now predict the value of the digit on the second half:
expected = digits.target[n_samples / 2:]
predicted = classifier_BDT.predict(data[n_samples / 2:])
print("Classification report for classifier %s:\n%s\n"
% (classifier_BDT, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))
In [253]:
print np.max(digits.images)
seven_normalized = (3 - ((seven - np.min(seven)) * 4 / (np.max(seven) - np.min(seven)))) * 4
seven_t = 1 - skf.threshold_adaptive(seven.astype(np.float64), 16)
six_t = 1 - skf.threshold_adaptive(six.astype(np.float64), 16)
print classifier_BDT.predict(np.asarray(dou(digits.images[17])).flatten())
print classifier_BDT.predict(seven_t.flatten())
In [82]:
mo = one - np.min(one)
len((mo.astype(np.float64) / np.max(mo) * 2 - 1).flatten())
Out[82]:
In [5]:
import os
Out[5]:
In [59]:
#### Detect where digits are.
np.max(arr_right)
Out[59]:
In [39]:
for (i, j) in zip(range(2) * range(3)):
print i, j
In [59]:
In [ ]: