Python图像识别+KNN求解数独的实现
作者:五岁能抬头s 发布时间:2021-06-11 19:48:23
标签:Python,KNN,求解数独
Python-opencv+KNN求解数独
最近一直在玩数独,突发奇想实现图像识别求解数独,输入到输出平均需要0.5s。
整体思路大概就是识别出图中数字生成list,然后求解。
输入输出demo
数独采用的是微软自带的Microsoft sudoku软件随便截取的图像,如下图所示:
经过程序求解后,得到的结果如下图所示:
程序具体流程
程序整体流程如下图所示:
读入图像后,根据求解轮廓信息找到数字所在位置,以及不包含数字的空白位置,提取数字信息通过KNN识别,识别出数字;无数字信息的在list中置0;生成未求解数独list,之后求解数独,将信息在原图中显示出来。
# -*-coding:utf-8-*-
import os
import cv2 as cv
import numpy as np
import time
####################################################
#寻找数字生成list
def find_dig_(img, train_set):
if img is None:
print("无效的图片!")
os._exit(0)
return
_, thre = cv.threshold(img, 230, 250, cv.THRESH_BINARY_INV)
_, contours, hierarchy = cv.findContours(thre, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
sudoku_list = []
boxes = []
for i in range(len(hierarchy[0])):
if hierarchy[0][i][3] == 0: # 表示父轮廓为 0
boxes.append(hierarchy[0][i])
# 提取数字
nm = []
for j in range(len(boxes)): # 此处len(boxes)=81
if boxes[j][2] != -1:
x, y, w, h = cv.boundingRect(contours[boxes[j][2]])
nm.append([x, y, w, h])
# 在原图中框选各个数字
cropped = img[y:y + h, x:x + w]
im = img_pre(cropped)#预处理
AF = incise(im)#切割数字图像
result = identification(train_set, AF, 7)#knn识别
sudoku_list.insert(0, int(result))#生成list
else:
sudoku_list.insert(0, 0)
if len(sudoku_list) == 81:
sudoku_list= np.array(sudoku_list)
sudoku_list= sudoku_list.reshape((9, 9))
print("old_sudoku -> \n", sudoku_list)
return sudoku_list, contours, hierarchy
else:
print("无效的图片!")
os._exit(0)
######################################################
#KNN算法识别数字
def img_pre(cropped):
# 预处理数字图像
im = np.array(cropped) # 转化为二维数组
for i in range(im.shape[0]): # 转化为二值矩阵
for j in range(im.shape[1]):
# print(im[i, j])
if im[i, j] != 255:
im[i, j] = 1
else:
im[i, j] = 0
return im
# 提取图片特征
def feature(A):
midx = int(A.shape[1] / 2) + 1
midy = int(A.shape[0] / 2) + 1
A1 = A[0:midy, 0:midx].mean()
A2 = A[midy:A.shape[0], 0:midx].mean()
A3 = A[0:midy, midx:A.shape[1]].mean()
A4 = A[midy:A.shape[0], midx:A.shape[1]].mean()
A5 = A.mean()
AF = [A1, A2, A3, A4, A5]
return AF
# 切割图片并返回每个子图片特征
def incise(im):
# 竖直切割并返回切割的坐标
a = [];
b = []
if any(im[:, 0] == 1):
a.append(0)
for i in range(im.shape[1] - 1):
if all(im[:, i] == 0) and any(im[:, i + 1] == 1):
a.append(i + 1)
elif any(im[:, i] == 1) and all(im[:, i + 1] == 0):
b.append(i + 1)
if any(im[:, im.shape[1] - 1] == 1):
b.append(im.shape[1])
# 水平切割并返回分割图片特征
names = locals();
AF = []
for i in range(len(a)):
names['na%s' % i] = im[:, range(a[i], b[i])]
if any(names['na%s' % i][0, :] == 1):
c = 0
else:
for j in range(names['na%s' % i].shape[0]):
if j < names['na%s' % i].shape[0] - 1:
if all(names['na%s' % i][j, :] == 0) and any(names['na%s' % i][j + 1, :] == 1):
c = j
break
else:
c = j
if any(names['na%s' % i][names['na%s' % i].shape[0] - 1, :] == 1):
d = names['na%s' % i].shape[0] - 1
else:
for j in range(names['na%s' % i].shape[0]):
if j < names['na%s' % i].shape[0] - 1:
if any(names['na%s' % i][j, :] == 1) and all(names['na%s' % i][j + 1, :] == 0):
d = j + 1
break
else:
d = j
names['na%s' % i] = names['na%s' % i][range(c, d), :]
AF.append(feature(names['na%s' % i])) # 提取特征
for j in names['na%s' % i]:
pass
return AF
# 训练已知图片的特征
def training():
train_set = {}
for i in range(9):
value = []
for j in range(15):
ima = cv.imread('E:/test_image/knn_test/{}/{}.png'.format(i + 1, j + 1), 0)
im = img_pre(ima)
AF = incise(im)
value.append(AF[0])
train_set[i + 1] = value
return train_set
# 计算两向量的距离
def distance(v1, v2):
vector1 = np.array(v1)
vector2 = np.array(v2)
Vector = (vector1 - vector2) ** 2
distance = Vector.sum() ** 0.5
return distance
# 用最近邻算法识别单个数字
def knn(train_set, V, k):
key_sort = [11] * k
value_sort = [11] * k
for key in range(1, 10):
for value in train_set[key]:
d = distance(V, value)
for i in range(k):
if d < value_sort[i]:
for j in range(k - 2, i - 1, -1):
key_sort[j + 1] = key_sort[j]
value_sort[j + 1] = value_sort[j]
key_sort[i] = key
value_sort[i] = d
break
max_key_count = -1
key_set = set(key_sort)
for key in key_set:
if max_key_count < key_sort.count(key):
max_key_count = key_sort.count(key)
max_key = key
return max_key
# 生成数字
def identification(train_set, AF, k):
result = ''
for i in AF:
key = knn(train_set, i, k)
result = result + str(key)
return result
######################################################
######################################################
#求解数独
def get_next(m, x, y):
# 获得下一个空白格在数独中的坐标。
:param m 数独矩阵
:param x 空白格行数
:param y 空白格列数
"""
for next_y in range(y + 1, 9): # 下一个空白格和当前格在一行的情况
if m[x][next_y] == 0:
return x, next_y
for next_x in range(x + 1, 9): # 下一个空白格和当前格不在一行的情况
for next_y in range(0, 9):
if m[next_x][next_y] == 0:
return next_x, next_y
return -1, -1 # 若不存在下一个空白格,则返回 -1,-1
def value(m, x, y):
# 返回符合"每个横排和竖排以及九宫格内无相同数字"这个条件的有效值。
i, j = x // 3, y // 3
grid = [m[i * 3 + r][j * 3 + c] for r in range(3) for c in range(3)]
v = set([x for x in range(1, 10)]) - set(grid) - set(m[x]) - \
set(list(zip(*m))[y])
return list(v)
def start_pos(m):
# 返回第一个空白格的位置坐标
for x in range(9):
for y in range(9):
if m[x][y] == 0:
return x, y
return False, False # 若数独已完成,则返回 False, False
def try_sudoku(m, x, y):
# 试着填写数独
for v in value(m, x, y):
m[x][y] = v
next_x, next_y = get_next(m, x, y)
if next_y == -1: # 如果无下一个空白格
return True
else:
end = try_sudoku(m, next_x, next_y) # 递归
if end:
return True
m[x][y] = 0 # 在递归的过程中,如果数独没有解开,
# 则回溯到上一个空白格
def sudoku_so(m):
x, y = start_pos(m)
try_sudoku(m, x, y)
print("new_sudoku -> \n", m)
return m
###################################################
# 将结果绘制到原图
def draw_answer(img, contours, hierarchy, new_sudoku_list ):
new_sudoku_list = new_sudoku_list .flatten().tolist()
for i in range(len(contours)):
cnt = contours[i]
if hierarchy[0, i, -1] == 0:
num = new_soduku_list.pop(-1)
if hierarchy[0, i, 2] == -1:
x, y, w, h = cv.boundingRect(cnt)
cv.putText(img, "%d" % num, (x + 19, y + 56), cv.FONT_HERSHEY_SIMPLEX, 1.8, (0, 0, 255), 2) # 填写数字
cv.imwrite("E:/answer.png", img)
if __name__ == '__main__':
t1 = time.time()
train_set = training()
img = cv.imread('E:/test_image/python_test_img/Sudoku.png')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
sudoku_list, contours, hierarchy = find_dig_(img_gray, train_set)
new_sudoku_list = sudoku_so(sudoku_list)
draw_answer(img, contours, hierarchy, new_sudoku_list )
print("time :",time.time()-t1)
PS:
使用KNN算法需要创建训练集,数独中共涉及9个数字,“1,2,3,4,5,6,7,8,9”各15幅图放入文件夹中,如下图所示。
来源:https://blog.csdn.net/m0_37968030/article/details/109615844


猜你喜欢
- 本文实例讲述了Vue插槽原理与用法。分享给大家供大家参考,具体如下:1 插槽内容Vue 实现了一套内容分发的 API,这套 API 基于当前
- 如下所示:logging: config: classpath:spring-logback.xml pattern: console: &
- 在Python中,语法错误可以被Python解释器发现,但逻辑上错误或变量使用错误却不容易发现,如果结果没有符合预期,则需要调试,一个很好的
- 在进行爬虫爬取淘宝商品信息时候,利用selenium来模拟浏览器进行爬取时遇到了这个问题:selenium.common.exception
- Python 输出 "Hello, World!",英文没有问题,但是如果你输出中文字符"你好,世界"
- 今天在做sql Server 2005的实验的时候碰到的问题,问题描述很清楚,怀疑是我以前给计算机修改了名称而导致的.可以用select @
- 数学函数 1.绝对值 S:select abs(-1) value O:select abs(-1) value from dual 2.取
- 数据库服务器主要用于存储、查询、检索企业内部的信息,因此需要搭配专用的数据库系统,对服务器的兼容性、可靠性和稳定性等方面都有很高的要求。下面
- 本文用纯js代码手写一个瀑布流网页效果,初步实现一个基本的瀑布流布局,以及滚动到底部后模拟ajax数据加载新图片功能。缺点:1. 程序不是响
- 直接看实例。例1 重新加载js文件function loadJs(file) {
- 为什么要使用滤波消除图像中的噪声成分叫作图像的平滑化或滤波操作。信号或图像的能量大部分集中在幅度谱的低频和中频段是很常见的,而在较高频段,感
- python中没有swich..case,若要实现一样的功能,又不想用if..elif来实现,可以充分利用字典进行实现主要是想要通过不同的k
- <html> <head> <script language="javasc
- 索引是什么索引是一种特殊的文件,包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引,并指定索引的类型,各类索引有各自的数据结
- 也许自己真的就是有手残的毛病,你说好端端的环境配置好了,自己还在那里瞎鼓捣,我最不想看到的就是在安装一个别的模块的时候,自动卸载了本地的其他
- 昨天有位同事说,他的网页查询过程中发现普通索引和唯一索引的效率是有差别的,普通索引比唯一索引快,今天在我的虚拟机中布置了环境,测试抓图如下:
- 首先来解释一下SpringBoot首页设置的三种方式1.SpringBoot默认首页设置编写一个最简单的html文件 index.html&
- 在学习OpenCV或者其他关于Python技术的时候,我们通常需要准备不同的Python环境,我选择了Anaconda作为我的Python环
- 主要功能在copyFiles()函数里实现,如下:def copyFiles(src, dst): sr
- 本文介绍了python OpenCV学习笔记直方图反向投影的实现,分享给大家,具体如下:官方文档 – https://docs.opencv