Python + OpenCV + DeepLearning 解数独问题【二、数字的识别】

该博客介绍了使用深度学习解决数独问题的三个步骤:数独图像提取、数字识别模型训练与预测。首先,通过MNIST数据集训练卷积神经网络模型,然后使用额外数据集对模型进行进一步训练提升准确率。最后,将数独图像切分并识别数字,模型在本地数据集上的识别准确率超过99%。
摘要由CSDN通过智能技术生成

整个解数独问题可以大致分为3个部分:

  1. 从图片中提取出完整的数独
  2. 从数独中提取出数字并传入神经网络进行预测【本文的部分
  3. 解出数独

【环境】

  • Python:3.8.5
  • OpenCV:4.5.1
  • Keras:2.4.3
     

【第二部分】数字的识别:

这部分流程比较简单清晰,步骤如下:

  1. 使用 MNIST 数据集训练模型并保存
  2. 读取模型,使用本地数据集继续训练
  3. 切分数独图片,传入模型进行预测

【一】使用 MNIST 数据集训练模型

这部分相当于深度学习的 "Hello world" ,整体比较简单,所以直接贴出代码,每一行都有注释:

整体分为 4 部分:

  1. 构建训练、测试数据集
  2. 构建模型
  3. 把数据喂入模型进行训练
  4. 保存模型

import numpy
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Flatten
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.utils import np_utils
from keras import backend as K


# 设置图片数据格式为 "channels_last",即 NHWC
K.set_image_data_format('channels_last')

# 固定随机种子,使具有可重复性
seed = 7
numpy.random.seed(seed)

(X_train, Y_train), (X_test, Y_test) = mnist.load_data()

# 把数据集变为 NHWC 格式
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1).astype('float32')

# 数据归一化 from 0-255 to 0-1
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()
# 2个卷积层
model.add(Conv2D(32, (5, 5), input_shape=(28, 28, 1), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(16, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
# Dropout层
model.add(Dropout(0.2))
# Flatten层
model.add(Flatten())
# 3个Dense层
model.add(Dense(128, activation='relu'))
model.add(Dense(64, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))

# 编译模型,指定损失函数为 categorical_crossentropy,优化器为 adam,模型评估标准为 accuracy
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# 模型训练,传入训练集,验证集,指定 epochs 和 batch_size
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("CNN Error: %.2f%%" % (100 - scores[1] * 100))

# - - - - - - - 保存模型 - - - - - - - -

# 把模型保存到 JSON 文件中
model_json = model.to_json()
with open("model.json", "w") as json_file:
    json_file.write(model_json)

# 把权重参数保存到 HDF5文件中
model.save_weights("model.h5")

print("Saved model to disk")

【二】读取模型,使用本地数据集继续训练

因为只使用 MNIST 数据集进行训练得到的模型,用来预测提取的数独数字图片准确率不高,所以我又从网上下载了额外的数据集来继续训练,提高准确率。 (也可能是因为我的模型或者参数问题,大家有兴趣的话可以自己调试)。

整体分为 4 部分:

  1. 处理本地数据,并构建训练、测试数据集
  2. 读取模型
  3. 把数据喂入模型继续训练
  4. 保存模型
from sklearn.model_selection import train_test_split
from imutils import paths
import random
import cv2
import os
import numpy as np
from keras.models import model_from_json
from keras.utils import np_utils


print("loading images")
data = []
labels = []

# 列出路径下的所有文件名并存入 list 列表,以便 for 循环时使用
imagePaths = sorted(list(paths.list_images(".\\digits\\")))

# 固定随机种子,使具有可重复性
random.seed(42)

# 洗牌
random.shuffle(imagePaths)

# 循环遍历所有图片
for imagePath in imagePaths:
    img = cv2.imread(imagePath, 0)

    # 图片大小预处理
    img_resize = cv2.resize(img, (28, 28))
    img_resize = img_resize.reshape(28, 28, 1)

    # 保存图片数据
    data.append(img_resize)

    # 从图像路径中提取分类标签并存储到分类标签数组中
    label = imagePath.split(os.path.sep)[-2]
    labels.append(label)

# 数据归一化
data = np.array(data, dtype="float") / 255.0

# 标签转化为array数组
labels = np.array(labels)

# 构建训练和测试数据集
(X_train, X_test, Y_train, Y_test) = train_test_split(data, labels,
                                                      test_size=0.25, random_state=42)

# 将标签转化为二值序列
Y_train = np_utils.to_categorical(Y_train)
Y_test = np_utils.to_categorical(Y_test)

# 获取类别数量
num_classes = Y_test.shape[1]


# 读取预训练模型
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")

print("Loaded saved model from disk.")

# 编译模型,指定损失函数为 categorical_crossentropy,优化器为 adam,模型评估标准为 accuracy
loaded_model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# 模型训练,传入训练集,验证集,指定 epochs 和 batch_size
loaded_model.fit(X_train, Y_train, validation_data=(X_test, Y_test), epochs=10, batch_size=32)

# - - - - - - - 保存模型 - - - - - - - -

# 把模型保存到 JSON 文件中
model_json = loaded_model.to_json()
with open("model.json", "w") as json_file:
    json_file.write(model_json)

# 把权重参数保存到 HDF5文件中
loaded_model.save_weights("model.h5")

print("Saved model to disk")

【三】切分数独图片,传入模型进行预测

这里有一个注意的点是,把数独图片进行 9 * 9 切分后,因为方格中有数字时的像素总值总是比没有数字时的像素总值大很多,所以可以直接计算该方格的像素总值,如果大于某个阈值,则认为有数字,否则认为没有数字。结果如下:

     

from SudokuExtractor import extract_sudoku
import cv2
import numpy as np
from keras.models import model_from_json
import sys
import os

# 读取训练模型
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")

print("Loaded saved model from disk.")


# 识别数字图片
def identify_number(img):
    # 图片大小处理
    img_show = cv2.resize(img, (28, 28))
    img_resize = img_show.reshape(1, 28, 28, 1)
    # 把图片输入模型进行预测
    loaded_model_pred = np.argmax(loaded_model.predict(img_resize), axis=-1)
    # 返回预测值
    return loaded_model_pred[0]


# 提取数字图片
def extract_number(sudoku):
    # 把图片大小转为450 * 450,然后以50 * 50切分为81张图片进行识别
    sudoku = cv2.resize(sudoku, (450, 450))
    grid = np.zeros([9, 9])
    for i in range(9):
        for j in range(9):
            img = sudoku[i * 50:(i + 1) * 50, j * 50:(j + 1) * 50]
            '''
            如果像素总值大于 100000,那么认为图片中有数字;
            100000的由来和图片大小450 * 450有关,是经过本地测试得到的;
            有数字的图片像素总值总是大于 100000,无数字的图片像素总值总是小于 100000;
            '''
            if img.sum() > 100000:
                grid[i][j] = identify_number(img)
            else:
                grid[i][j] = 0
    return grid.astype(int)


def output(a):
    sys.stdout.write(str(a))


# 打印数独
def display_sudoku(sudoku):
    output("--------+----------+---------\n")
    for i in range(9):
        for j in range(9):
            cell = sudoku[i][j]
            if cell == 0 or isinstance(cell, set):
                output('.')
            else:
                output(cell)
            if (j + 1) % 3 == 0 and j < 8:
                output(' |')

            if j != 8:
                output('  ')
        output('\n')
        if (i + 1) % 3 == 0 and i < 8:
            output("--------+----------+---------\n")
    output("--------+----------+---------\n")
    output("\n")


# 读图
filePath = ".\\imgs\\"
files = os.listdir(filePath)
for file in files:
    path = filePath + file
    image = extract_sudoku(path)
    result = extract_number(image)
    display_sudoku(result)

【第二部分总结】

这部分主要是识别 1-9 数字,整体准确率在 99% 以上,一般来说只要第一部分能够较好地提取出数独,那么第二部分就能准确识别出数字。当然很难保证该模型对所有数字图片都能准确识别。

【源码】

【GitHub链接】:GitHub - ITACHI142857/SudokuSolving: sudoku solver using python\opencv\deep learning

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值