Если вы нетерпеливы, прокрутите до конца сообщения репозиторий Github.
Это часть 2 решения Sudoku Solver. Убедитесь, что вы мельком ознакомились с Частью 1. Итак, забегая вперед, до сих пор мы предварительно обрабатывали изображение, то есть берем изображение и выполняем перспективное преобразование кадрирования и деформации. Теперь нам нужно извлечь числа и решить судоку.
B: извлеките каждое число, присутствующее на изображении
Итак, наша следующая задача - извлечь каждое число из изображения, идентифицировать число и сохранить его в 2D-матрице.
Для распознавания цифр мы будем обучать нейронную сеть на наборе данных MNIST, содержащем 60 000 изображений цифр от 0 до 9.
Начнем с импорта всех библиотек.
import numpy import cv2from keras.datasets import mnistfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import Flattenfrom keras.layers.convolutional import Conv2Dfrom keras.layers.convolutional import MaxPooling2Dfrom keras.utils import np_utilsfrom keras import backend as K import matplotlib.pyplot as plt
Мы исправим случайное начальное число для воспроизводимости.
K.set_image_dim_ordering('th') seed = 7numpy.random.seed(seed) (X_train, y_train), (X_test, y_test) = mnist.load_data()
Теперь давайте изменим форму изображений так, чтобы они были сэмплами * пикселями * шириной * высотой, и нормализуем входные данные от 0–255 до 0–1. После этого мы будем горячо кодировать выходные данные.
X_train = X_train.reshape(X_train.shape[0], 1, 28, 28).astype('float32') X_test = X_test.reshape(X_test.shape[0], 1, 28, 28).astype('float32') X_train = X_train / 255 X_test = X_test / 255 y_train = np_utils.to_categorical(y_train) y_test = np_utils.to_categorical(y_test) num_classes = y_test.shape[1]
Далее мы создадим модель для предсказания рукописной цифры.
model = Sequential() model.add(Conv2D(32, (5, 5), input_shape=(1, 28, 28), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Conv2D(16, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.2)) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dense(64, activation='relu')) model.add(Dense(num_classes, activation='softmax'))
После создания модели давайте скомпилируем ее, поместим в набор данных и оценим.
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=200) scores = model.evaluate(X_test, y_test, verbose=0) print("Large CNN Error: %.2f%%" % (100-scores[1]*100))
Теперь мы протестируем созданную выше модель.
test_images = X_test[1:5] test_images = test_images.reshape(test_images.shape[0], 28, 28) print ("Test images shape: {}".format(test_images.shape)) for i, test_image in enumerate(test_images, start=1): org_image = test_image test_image = test_image.reshape(1,1,28,28) prediction = model.predict_classes(test_image, verbose=0) print ("Predicted digit: {}".format(prediction[0])) plt.subplot(220+i) plt.axis('off') plt.title("Predicted digit: {}".format(prediction[0])) plt.imshow(org_image, cmap=plt.get_cmap('gray')) plt.show()
Точность нейронной сети составила 98,314%. Наконец, мы сохраним последовательную модель, чтобы нам не приходилось тренировать ее снова и снова, когда мы хотим ее использовать.
# serialize model to JSON model_json = model.to_json() with open("model.json", "w") as json_file: json_file.write(model_json) # serialize weights to HDF5 model.save_weights("model.h5") print("Saved model to disk")
Чтобы узнать больше о распознавании рукописных цифр https://github.com/aakashjhawar/Handwritten-Digit-Recognition
Нашим следующим шагом будет загрузка предварительно обученной модели и весов.
json_file = open('model.json', 'r') loaded_model_json = json_file.read() json_file.close() loaded_model = model_from_json(loaded_model_json) loaded_model.load_weights("model.h5")
Затем мы изменим размер изображения и разделим его на маленькие изображения 9x9. На каждом маленьком изображении будет цифра от 1 до 9.
sudoku = cv2.resize(sudoku, (450,450)) grid = np.zeros([9,9]) for i in range(9): for j in range(9): image = sudoku[i*50:(i+1)*50,j*50:(j+1)*50] if image.sum() > 25000: grid[i][j] = identify_number(image) else: grid[i][j] = 0 grid = grid.astype(int)
Функция identify_number берет изображение цифры и предсказывает цифру на изображении.
def identify_number(image): image_resize = cv2.resize(image, (28,28)) # For plt.imshow image_resize_2 = image_resize.reshape(1,1,28,28) # For input to model.predict_classes # cv2.imshow('number', image_test_1) loaded_model_pred = loaded_model.predict_classes(image_resize_2 , verbose = 0) return loaded_model_pred[0]
После выполнения вышеуказанных шагов наша сетка судоку будет выглядеть так:
C: вычислить решение судоку с использованием алгоритма поиска с возвратом
Мы будем использовать алгоритм обратного отслеживания, чтобы вычислить решение судоку.
Найдите в сетке запись, которая еще не назначена. Если он найден, для ссылочных параметров row, col будет установлено неназначенное местоположение, и будет возвращено значение true. Если не осталось неназначенных записей, возвращается false. «L» - это переменная списка, переданная из функции resolve_sudoku для отслеживания приращения строк и столбцов.
def find_empty_location(arr,l): for row in range(9): for col in range(9): if(arr[row][col]==0): l[0]=row l[1]=col return True return False
Возвращает логическое значение, которое указывает, соответствует ли какая-либо назначенная запись в указанной строке заданному номеру.
def used_in_row(arr,row,num): for i in range(9): if(arr[row][i] == num): return True return False
Возвращает логическое значение, которое указывает, соответствует ли какая-либо назначенная запись в указанном столбце заданному номеру.
def used_in_col(arr,col,num): for i in range(9): if(arr[i][col] == num): return True return False
Возвращает логическое значение, которое указывает, соответствует ли какая-либо назначенная запись в указанном поле 3x3 заданному номеру.
def used_in_box(arr,row,col,num): for i in range(3): for j in range(3): if(arr[i+row][j+col] == num): return True return False
Проверяет, будет ли допустимо присвоение num заданному (row, col). Возвращает логическое значение, которое указывает, будет ли допустимо присвоение num заданному (строка, столбец) местоположению. Проверьте, не помещено ли «число» в текущую строку, текущий столбец и текущее поле 3x3.
def check_location_is_safe(arr,row,col,num): return not used_in_row(arr,row,num) and not used_in_col(arr,col,num) and not used_in_box(arr,row - row%3,col - col%3,num)
Берет частично заполненную сетку и пытается присвоить значения всем неназначенным местоположениям таким образом, чтобы удовлетворить требованиям для решения судоку (отсутствие дублирования в строках, столбцах и полях). ‘L’ - это переменная списка, которая хранит запись строки и столбца в функции find_empty_location. Назначение значений списка строкам и столбцам, которые мы получили из вышеуказанной функции.
def solve_sudoku(arr): l=[0,0] if(not find_empty_location(arr,l)): return True row=l[0] col=l[1] for num in range(1,10): if(check_location_is_safe(arr,row,col,num)): arr[row][col]=num if(solve_sudoku(arr)): return True # failure, unmake & try again arr[row][col] = 0 return False
Последнее, что нужно - распечатать сетку.
def print_grid(arr): for i in range(9): for j in range(9): print (arr[i][j]) print ('\n')
Теперь давайте сшиваем все функции в главную функцию.
def sudoku_solver(grid): if(solve_sudoku(grid)): print('---') else: print ("No solution exists") grid = grid.astype(int) return grid
Результатом этой функции будет наша решенная судоку.
Заключение
Итак, нам, наконец, удалось решить сетку судоку только на основе изображения. Если вы так долго продержались ... спасибо! Надеюсь, для вас было что-то ценное. Решатель ни в коем случае не надежен; у него все еще есть проблемы с некоторыми изображениями, и он либо не сможет их проанализировать, либо проанализирует их неправильно, что приведет к невозможности их решения (потому что он мог быть проанализирован как недопустимая головоломка). Целью, конечно же, было развитие некоторых новых технологий, и проект был ценным с этой точки зрения. Исходный код доступен на Github. Напишите мне, если сочтете это полезным или у вас возникнут дополнительные вопросы.
Спасибо!