Python图像识别+KNN求解数独的实现


Posted in Python onNovember 13, 2020

Python-opencv+KNN求解数独

最近一直在玩数独,突发奇想实现图像识别求解数独,输入到输出平均需要0.5s。

整体思路大概就是识别出图中数字生成list,然后求解。

输入输出demo

数独采用的是微软自带的Microsoft sudoku软件随便截取的图像,如下图所示:

Python图像识别+KNN求解数独的实现

经过程序求解后,得到的结果如下图所示:

Python图像识别+KNN求解数独的实现

程序具体流程

程序整体流程如下图所示:

Python图像识别+KNN求解数独的实现

读入图像后,根据求解轮廓信息找到数字所在位置,以及不包含数字的空白位置,提取数字信息通过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幅图放入文件夹中,如下图所示。

Python图像识别+KNN求解数独的实现

到此这篇关于Python图像识别+KNN求解数独的实现的文章就介绍到这了,更多相关Python KNN求解数独内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python不带重复的全排列代码
Aug 13 Python
Python中的类与对象之描述符详解
Mar 27 Python
python根据出生日期获得年龄的方法
Mar 31 Python
详解duck typing鸭子类型程序设计与Python的实现示例
Jun 03 Python
Python设计模式之中介模式简单示例
Jan 09 Python
python实现决策树分类(2)
Aug 30 Python
Python绘制并保存指定大小图像的方法
Jan 10 Python
Python设计模式之抽象工厂模式原理与用法详解
Jan 15 Python
基于python历史天气采集的分析
Feb 14 Python
Python HTMLTestRunner测试报告view按钮失效解决方案
May 25 Python
scrapy结合selenium解析动态页面的实现
Sep 28 Python
利用python+ffmpeg合并B站视频及格式转换的实例代码
Nov 24 Python
Django正则URL匹配实现流程解析
Nov 13 #Python
Django框架请求生命周期实现原理
Nov 13 #Python
python在地图上画比例的实例详解
Nov 13 #Python
python语言实现贪吃蛇游戏
Nov 13 #Python
Python使用struct处理二进制(pack和unpack用法)
Nov 12 #Python
python切割图片的示例
Nov 12 #Python
教你使用Sublime text3搭建Python开发环境及常用插件安装另分享Sublime text3最新激活注册码
Nov 12 #Python
You might like
php mysql_real_escape_string函数用法与实例教程
2013/09/30 PHP
PHP对表单提交特殊字符的过滤和处理方法汇总
2014/02/18 PHP
PHP中feof()函数实例测试
2014/08/23 PHP
php将csv文件导入到mysql数据库的方法
2014/12/24 PHP
php实现通用的信用卡验证类
2015/03/24 PHP
示例详解Laravel重置密码代码重构
2016/08/10 PHP
一个非常实用的php文件上传类
2017/07/04 PHP
js 中{},[]中括号,大括号使用详解
2011/05/12 Javascript
javascrpt绑定事件之匿名函数无法解除绑定问题
2012/12/06 Javascript
使用jQuery实现的网页版的个人简历(可换肤)
2013/04/19 Javascript
js在IE与firefox的差异集锦
2014/11/11 Javascript
浅谈AngularJS中ng-class的使用方法
2016/11/11 Javascript
javascript实现一个网页加载进度loading
2017/01/04 Javascript
JavaScript实现正则去除a标签并保留内容的方法【测试可用】
2018/07/18 Javascript
Angularjs实现数组随机排序的方法
2018/10/02 Javascript
[01:07:17]EG vs Optic Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
Python中使用动态变量名的方法
2014/05/06 Python
python求列表交集的方法汇总
2014/11/10 Python
Python如何快速实现分布式任务
2017/07/06 Python
python操作redis方法总结
2018/06/06 Python
python字典排序的方法
2019/10/12 Python
如何基于Python实现自动扫雷
2020/01/06 Python
Python中包的用法及安装
2020/02/11 Python
借助Paramiko通过Python实现linux远程登陆及sftp的操作
2020/03/16 Python
日本网路线上商品代购服务:转送JAPAN
2016/08/05 全球购物
中国宠物用品商城:E宠商城
2016/08/27 全球购物
活动总结报告怎么写
2014/07/03 职场文书
运动员获奖感言
2014/08/15 职场文书
2014流动人口计划生育工作总结
2014/12/20 职场文书
2015年安全生产目标责任书
2015/01/29 职场文书
商务代表岗位职责
2015/02/15 职场文书
2015年度房地产工作总结
2015/04/09 职场文书
2015年化工厂工作总结
2015/05/04 职场文书
新娘婚礼答谢词
2015/09/29 职场文书
python for循环赋值问题
2021/06/03 Python
VMware虚拟机安装 Windows Server 2022的详细图文教程
2022/09/23 Servers