【圖像處理】數(shù)獨(dú)識圖

工具
系統(tǒng):Windows 10
Python版本:Version 3.6
OpenCV版本:Version 2.4.9
圖片來源:截取自ubuntu上的sudoku數(shù)獨(dú)游戲

具體實(shí)現(xiàn)

本文將用python語言,結(jié)合OpenCV視覺庫來解決來識別數(shù)獨(dú)游戲上的數(shù)字塞关。

sudoku

上面的數(shù)獨(dú)可以用如下的矩陣表示:
[[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]]

要做的工作就是從圖像中識別出這個(gè)矩陣(空白空格用0表示)。具體的步驟如下:
1.讀取圖片驶睦,并獲取灰度圖
2.反色處理匿醒,使得數(shù)字的顏色為白色
3.裁剪圖形,裁去多余的邊界
4.分割圖形廉羔,將原圖分成9*9=81個(gè)更小的圖形
5.識別圖形,將識別的數(shù)字填入numArray

圖像識別部分的功能我用一個(gè)名字叫ImageProcessor的類來實(shí)現(xiàn)憋他,具體代碼如下展示。

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

class ImageProcessor:
    __originalImage=0   #原圖
    __grayScaleImage=0  #灰度圖
    __binaryImage=0     #二值圖
    __invBinaryImage=0  #顏色反轉(zhuǎn)之后的二值圖
    __theCutImage=0     #裁剪后的圖像
    __tightSize=[]      #裁剪后的圖像的尺寸竹挡,寬度和高度
    __cutImage=0
    __imageList=[]      #81個(gè)小格子

    numArray=full([9,9],0,dtype=uint8)  #9*9的全0矩陣,用來放數(shù)獨(dú)識別的矩陣

    __template=[]       #模板

    # 類初始化函數(shù)
    def __init__(self,path):
        self.__loadTemplate()   #加載圖形模板
        self.__originalImage=imread(path)   #載入圖像
        self.__grayScaleImage=cvtColor(self.__originalImage,COLOR_BGR2GRAY,)    #獲得灰度圖像
        self.__thresh()     #閾值處理梯码,獲得二值圖像
        self.__inverseColor()   #顏色反轉(zhuǎn)處理
        self.__getTightSize()   #獲得緊尺寸
        self.__getCutImage()    #裁剪圖形
        self.__splitBoards()    #劃分格子,將裁剪后的圖形分成81個(gè)小圖形
        self.__fill()           #往空矩陣?yán)锩嫣顢?shù)字
        self.__resultDisplay()  #結(jié)果顯示

    #閾值處理
    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])

    # 顏色反轉(zhuǎn)
    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]

        #細(xì)化一番
        # 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("識別結(jié)果:")
        print(self.numArray)
        print('\n')

上面代碼里用到了一個(gè)函數(shù)叫Xihua的函數(shù)框往,其功能是最圖形進(jìn)行細(xì)化,其來自于一個(gè)叫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()

接下來...
建立一個(gè)ImageProcessor對象,看看識別結(jié)果

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

由于在ImageProcessor類的init函數(shù)里面我加入了__resultDisplay()函數(shù)闹司,即對識別結(jié)果進(jìn)行了顯示。運(yùn)行程序游桩,結(jié)果表明為:
[[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]]
可以發(fā)現(xiàn)識別結(jié)果是正確的。

我發(fā)現(xiàn)用這樣的識別方法借卧,效率是比較低的,待優(yōu)化

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铐刘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子镰吵,更是在濱河造成了極大的恐慌,老刑警劉巖疤祭,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戏售,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)草穆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锋喜,“玉大人,你說我怎么就攤上這事跑芳。” “怎么了直颅?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵功偿,是天一觀的道長。 經(jīng)常有香客問我械荷,道長,這世上最難降的妖魔是什么吨瞎? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任痹兜,我火速辦了婚禮字旭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遗淳。我一直安慰自己,他們只是感情好心傀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脂男,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宰翅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天潘飘,我揣著相機(jī)與錄音,去河邊找鬼掉缺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眶明,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搜囱,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼蜀肘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扮宠?” 一聲冷哼從身側(cè)響起西乖,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤获雕,失蹤者是張志新(化名)和其女友劉穎薄腻,沒想到半個(gè)月后庵楷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尽纽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年蜓斧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挎春。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豆拨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出施禾,到底是詐尸還是另有隱情,我是刑警寧澤弥搞,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站攀例,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粤铭。R本人自食惡果不足惜挖胃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梆惯。 院中可真熱鬧酱鸭,春花似錦、人聲如沸垛吗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怯屉。三九已至扁誓,卻和暖如春防泵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝗敢。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留足删,地道東北人寿谴。 一個(gè)月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像失受,于是被迫代替她去往敵國和親讶泰。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 這一段時(shí)間拂到,發(fā)生了太多的事情痪署。家里,工作里兄旬,都有不如愿的事情發(fā)生狼犯。 想想人到中年,真是有壓力领铐!加上很多的不可預(yù)知隨...
    箜溪曉閱讀 351評論 2 5
  • 人與人之間雖說不能用利益?zhèn)z字,但有時(shí)候金錢的社會(huì)里永遠(yuǎn)離不開錢幻碱。循環(huán)往復(fù)褥傍,就形成目前社會(huì)的腐敗,在2012摔桦,...
    過去那是昨天閱讀 420評論 0 0