【图像处理】数独识图

工具
系统:Windows 10
Python版本:Version 3.6
OpenCV版本:Version 2.4.9
图片来源:截取自ubuntu上的sudoku数独游戏

具体实现

本文将用python语言,结合OpenCV视觉库来解决来识别数独游戏上的数字。

sudoku

上面的数独可以用如下的矩阵表示:
[[0 0 0 7 0 0 4 1 0]
[0 0 3 0 2 0 0 0 6]
[1 0 7 4 0 0 5 2 3]
[4 0 1 6 0 0 0 8 0]
[0 2 9 0 7 0 6 3 0]
[0 7 0 0 0 4 2 0 1]
[7 5 2 0 0 6 3 0 9]
[3 0 0 0 4 0 1 0 0]
[0 1 4 0 0 3 0 0 0]]

要做的工作就是从图像中识别出这个矩阵(空白空格用0表示)。具体的步骤如下:
1.读取图片,并获取灰度图
2.反色处理,使得数字的颜色为白色
3.裁剪图形,裁去多余的边界
4.分割图形,将原图分成9*9=81个更小的图形
5.识别图形,将识别的数字填入numArray

图像识别部分的功能我用一个名字叫ImageProcessor的类来实现,具体代码如下展示。

from cv2 import *
from numpy import *
from thining import *

class ImageProcessor:
    __originalImage=0   #原图
    __grayScaleImage=0  #灰度图
    __binaryImage=0     #二值图
    __invBinaryImage=0  #颜色反转之后的二值图
    __theCutImage=0     #裁剪后的图像
    __tightSize=[]      #裁剪后的图像的尺寸,宽度和高度
    __cutImage=0
    __imageList=[]      #81个小格子

    numArray=full([9,9],0,dtype=uint8)  #9*9的全0矩阵,用来放数独识别的矩阵

    __template=[]       #模板

    # 类初始化函数
    def __init__(self,path):
        self.__loadTemplate()   #加载图形模板
        self.__originalImage=imread(path)   #载入图像
        self.__grayScaleImage=cvtColor(self.__originalImage,COLOR_BGR2GRAY,)    #获得灰度图像
        self.__thresh()     #阈值处理,获得二值图像
        self.__inverseColor()   #颜色反转处理
        self.__getTightSize()   #获得紧尺寸
        self.__getCutImage()    #裁剪图形
        self.__splitBoards()    #划分格子,将裁剪后的图形分成81个小图形
        self.__fill()           #往空矩阵里面填数字
        self.__resultDisplay()  #结果显示

    #阈值处理
    def __thresh(self):
        ret,self.__binaryImage=threshold(self.__grayScaleImage,20,255,THRESH_BINARY)

    #获取紧尺寸
    def __getTightSize(self):
        ox=-1
        oy=-1
        height=0
        width=0
        op_confirm=0
        rows=self.__invBinaryImage.shape[0]
        cols=self.__invBinaryImage.shape[1]

        # the following loop help find x0 and y0
        for i in range(0,rows-1):
            for j in range(0,cols-1):
               if op_confirm==0 and self.__invBinaryImage[i,j]:
                   op_confirm=1
                   ox = j
                   oy = i
            if 1==op_confirm:
                break
        # the following loop help find width and height

        ep_confirm=0
        ex=-1
        ey=-1
        for i in range(rows-1,0,-1):
            for j in range(cols-1,0,-1):
                if ep_confirm==0 and self.__invBinaryImage[i,j]:
                    ep_confirm=1
                    ex=j
                    ey=i
            if 1==ep_confirm:
                break

        width=ex-ox
        height=ey-oy


        # finally assign value to rect
        if op_confirm==1 and ep_confirm==1:
            self.__tightSize=(ox,oy,height,width)
        else:
            raise RuntimeError("fail to find tight size of a image")

    def showValue(self):
        print(self.__tightSize)
        print(self.__invBinaryImage[0:3,0:3])

    #
    def showImage(self):
        imshow("original",self.__originalImage)
        imshow("gray",self.__grayScaleImage)
        imshow("binary",self.__binaryImage)
        imshow("invImage",self.__invBinaryImage)
        imshow("cut", self.__theCutImage)
        # print(self.__grayScaleImage[0:10,0:10])

    # 颜色反转
    def __inverseColor(self):
        tmp=full([self.__binaryImage.shape[0],self.__binaryImage.shape[1]],255,dtype=uint8)
        self.__invBinaryImage=tmp-self.__binaryImage

    #裁剪图片
    def __getCutImage(self):
        self.__theCutImage=self.__invBinaryImage[self.__tightSize[1]:self.__tightSize[3],\
                           self.__tightSize[0]:self.__tightSize[2]]
    #保存图片
    def saveImage(self):
        imwrite('D:\original.jpg',self.__originalImage)
        imwrite('D:\gray.tif', self.__grayScaleImage)
        imwrite(r'D:\inv.tif',self.__invBinaryImage)
        imwrite('D:\cut.tif',self.__theCutImage)

    # 将图像分成81份
    def __splitBoards(self):
        # height of each grid
        gh=self.__theCutImage.shape[0]/9

        # width of each grid
        gw=self.__theCutImage.shape[1]/9

        path="D:\\"

        for i in range(9):
            for j in range(9):
                tmp=self.__theCutImage[int(i*gh):int((i+1)*gh),int(j*gw):\
                    int((j+1)*gw)]
                self.__imageList.append(tmp)

        # normalize those grids as 54pixels*pixels
        self.__normalize()

        # need to filter the boarder away
        for k in range(81):
            for i in range(54):
                for j in range(54):
                    if 10< i<54-10 and 10< j<54-10:
                        self.__imageList[k][i,j]=self.__imageList[k][i,j]*1
                    else:
                        self.__imageList[k][i, j] = self.__imageList[k][i, j] * 0

        # test
        # save the imageList altered
        for i in range(81):
            imwrite(path+str(i)+'.tif',self.__imageList[i])

        # print("filling process done")

    def __normalize(self):
        for i in range(len(self.__imageList)):
            tmp=cv2.resize(self.__imageList[i],dsize=(54,54))
            ret,tmp=threshold(tmp,50,255,THRESH_BINARY)
            self.__imageList[i]=tmp


    # this function is used to recognize number in the original image
    # and fill the __numArray
    def __fill(self):
        rowsOfNumArray=9
        colsOfNumArray=9
        for i in range(rowsOfNumArray):
            for j in range(colsOfNumArray):
                self.numArray[i,j]=self.__recognize(self.__imageList[i*9+j])


    def __recognize(self,img):
        dst=0
        count=0
        count0=0
        whichOne=0
        rows=img.shape[0]
        cols=img.shape[1]

        #细化一番
        # img=Xihua(img)


        for i in range(len(self.__template)):
            for x in range(1,rows):
                for y in range(1,cols):
                    count0 = count0 + (int(self.__template[i][x, y]) - int(img[x, y])) * \
                                      (int(self.__template[i][x, y]) - int(img[x, y]))
            if i==0:
                count=count0


            if count0

上面代码里用到了一个函数叫Xihua的函数,其功能是最图形进行细化,其来自于一个叫thining的py文件,下面贴出它的代码:

import cv2
from numpy import *
array=[0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\
0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\
1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,\
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\
1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,1,\
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\
0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\
0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,\
1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,\
1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,\
1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,0,\
1,1,0,0,1,1,1,0,1,1,0,0,1,0,0,0]

def VThin(image):
    h = image.shape[0]
    w = image.shape[1]
    NEXT = 1
    for i in range(h):
        for j in range(w):
            if NEXT == 0:
                NEXT = 1
            else:
                M = image[i,j-1]+image[i,j]+image[i,j+1] if 0

接下来...
建立一个ImageProcessor对象,看看识别结果

from ImageProcessor import *
from solution import *
i=ImageProcessor('original.jpg')

由于在ImageProcessor类的init函数里面我加入了__resultDisplay()函数,即对识别结果进行了显示。运行程序,结果表明为:
[[0 0 0 7 0 0 4 1 0]
[0 0 3 0 2 0 0 0 6]
[1 0 7 4 0 0 5 2 3]
[4 0 1 6 0 0 0 8 0]
[0 2 9 0 7 0 6 3 0]
[0 7 0 0 0 4 2 0 1]
[7 5 2 0 0 6 3 0 9]
[3 0 0 0 4 0 1 0 0]
[0 1 4 0 0 3 0 0 0]]
可以发现识别结果是正确的。

我发现用这样的识别方法,效率是比较低的,待优化

你可能感兴趣的:(【图像处理】数独识图)