【图像处理】数独识图

工具
系统: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<count:
                count=count0
                whichOne=i
            count0=0

        if whichOne==0:
            return 0
        elif whichOne==1:
            return 1
        elif whichOne==2:
            return 2
        elif whichOne==3:
            return 3
        elif whichOne==4:
            return 4
        elif whichOne==5:
            return 5
        elif whichOne==6:
            return 6
        elif whichOne==7:
            return 7
        elif whichOne==8:
            return 8
        elif whichOne==9:
            return 9


    def __loadTemplate(self):
        self.__template.append(imread('TEMPLATE\\_0.tif', 0))
        self.__template.append(imread('TEMPLATE\\_1.tif', 0))
        self.__template.append(imread('TEMPLATE\\_2.tif', 0))
        self.__template.append(imread('TEMPLATE\\_3.tif', 0))
        self.__template.append(imread('TEMPLATE\\_4.tif', 0))
        self.__template.append(imread('TEMPLATE\\_5.tif', 0))
        self.__template.append(imread('TEMPLATE\\_6.tif', 0))
        self.__template.append(imread('TEMPLATE\\_7.tif', 0))
        self.__template.append(imread('TEMPLATE\\_8.tif', 0))
        self.__template.append(imread('TEMPLATE\\_9.tif', 0))

    def __resultDisplay(self):
        print("识别结果:")
        print(self.numArray)
        print('\n')

上面代码里用到了一个函数叫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<j<w-1 else 1
                if image[i,j] == 0  and M != 0:
                    a = [0]*9
                    for k in range(3):
                        for l in range(3):
                            if -1<(i-1+k)<h and -1<(j-1+l)<w and image[i-1+k,j-1+l]==255:
                                a[k*3+l] = 1
                    sumN = a[0]*1+a[1]*2+a[2]*4+a[3]*8+a[5]*16+a[6]*32+a[7]*64+a[8]*128
                    image[i,j] = array[sumN]*255
                    if array[sumN] == 1:
                        NEXT = 0
    return image

def HThin(image):
    h = image.shape[0]
    w = image.shape[1]
    NEXT = 1
    for j in range(w):
        for i in range(h):
            if NEXT == 0:
                NEXT = 1
            else:
                M = image[i-1,j]+image[i,j]+image[i+1,j] if 0<i<h-1 else 1
                if image[i,j] == 0 and M != 0:
                    a = [0]*9
                    for k in range(3):
                        for l in range(3):
                            if -1<(i-1+k)<h and -1<(j-1+l)<w and image[i-1+k,j-1+l]==255:
                                a[k*3+l] = 1
                    sumN = a[0]*1+a[1]*2+a[2]*4+a[3]*8+a[5]*16+a[6]*32+a[7]*64+a[8]*128
                    image[i,j] = array[sumN]*255
                    if array[sumN] == 1:
                        NEXT = 0
    return image

def Xihua(image,num=10):
    iThin=image
    iThin=full([image.shape[0], image.shape[0]], 255, uint8) - image
    for i in range(num):
        VThin(iThin)
        HThin(iThin)
    iThin=full([image.shape[0],image.shape[1]],255,uint8)-iThin
    return iThin

def saveTemplate():
    path0 = 'TEMPLATE\\'
    path1 = 'TEMPLATE\\_'
    for i in range(0, 2):
        tmp = cv2.imread(path0 + str(i) + '.tif', 0)
        tst = Xihua(tmp)
        cv2.imwrite(path1 + str(i) + '.tif', tst)

#saveTemplate()

接下来...
建立一个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]]
可以发现识别结果是正确的。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 这一段时间,发生了太多的事情。家里,工作里,都有不如愿的事情发生。 想想人到中年,真是有压力!加上很多的不可预知随...
    箜溪晓阅读 349评论 2 5
  • 人与人之间虽说不能用利益俩字,但有时候金钱的社会里永远离不开钱。循环往复,就形成目前社会的腐败,在2012,...
    过去那是昨天阅读 415评论 0 0