工具
系统: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]]
可以发现识别结果是正确的。
我发现用这样的识别方法,效率是比较低的,待优化
网友评论