智能计算课第一周的实验是做基于模板匹配的手写数字识别,光听见就很感兴趣,于是决定认真做做这个实验,本实验基于python3+opencv的python版本,所用到的知识都比较简单,基本上边学边做,技术含量很低。
实现思路:
大致就是这么个思路但其实纠错模块还没写出来,先搁置一下吧,准备先上手第二个实验。
下面按照流程图讲讲详细步骤吧
手写数字window.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80import tkinter
from PIL import Image
import recognition as rec
import numpy as np
import cv2
img = np.zeros((400,400),np.uint8)
app = tkinter.Tk()
app.resizable(0,0)
app.title('数字识别 v1.0')
app['width']=400
app['height']=400
yseno = tkinter.IntVar(value=0)
X = tkinter.IntVar(value=0)
Y = tkinter.IntVar(value=0)
foreColor = '#000000'
backColor = '#FFFFFF'
image = tkinter.PhotoImage()
canvas = tkinter.Canvas(app,bg='white',width=400,height=400)
canvas.create_image(400,400,image=image)
def (event):
yseno.set(1)
X.set(event.x)
Y.set(event.y)
canvas.bind('',onLeftButtonDown)
def onLeftButtonMove(event):
if yseno.get()==0:
return
canvas.create_line(X.get(),Y.get(),event.x,event.y,fill=foreColor,width=10)
X.set(event.x)
Y.set(event.y)
if (event.x-3>=0) and (event.x+3<=400) and (event.y-3>=0) and (event.y+3<=400):
img[event.y-3:event.y+3,event.x-3:event.x+3]=255
canvas.bind('',onLeftButtonMove)
def onLeftButtonUp(event):
# canvas.create_line(X.get(),Y.get(),event.x,event.y,fill=foreColor)
yseno.set(0)
canvas.bind('',onLeftButtonUp)
def clear():
global img
img = img * 0
for item in canvas.find_all():
canvas.delete(item)
# print(item)
buttonClear = tkinter.Button(app,text='清除',command=clear)
buttonClear.place(x=200,y=370,width=200,height=30)
def recognition():
#保存图片...得到img
global img
# img = img.T
num = rec.recognition(img)
print('识别出的数字为:',num)
# cv2.imshow('img',img)
# cv2.waitKey(0)
# cv2.destroyAllWindows
buttonRec = tkinter.Button(app,text='识别',command=recognition)
buttonRec.place(x=0,y=370,width=200,height=30)
canvas.pack(fill=tkinter.BOTH,expand=tkinter.YES)
app.mainloop()
照着图书馆借来的书,边看边敲的代码,所以比较简单,实现的功能也比较简单,运行结果如图:
图片标准化
标准化图片我这里分四步:像素取反,灰度化,去噪以及图片分割。
1.像素取反
1
2
3def negation(img):#图片取反
img = cv2.bitwise_not(img)
return img
其实就一句话,也就是用opencv自带的方法 cv2.bitwise_not方法就OK了
2.灰度化(+二值化)
1
2
3
4
5def graying(img):#二值化图像
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh=cv2.threshold(img,250,255,cv2.THRESH_BINARY)
img = thresh
return thresh
同样使用opencv函数,得到的是黑底(0)白字(255)。
3.去噪
1
2
3
4
5def denoise(img):
kernel = np.ones((5,5),np.uint8)
img = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
img = cv2.blur(img,(10,10))
return img
用到中值滤波和低通滤波,原意是为了去掉离群点,但经过老师点拨,发现效果其实并不好我去掉的只是数字周围的毛刺点,并不是离群点,如下图
就没办法得到理想的效果,最新的想法是利用能量分布去除离群点(不是很懂,找时间研究研究),好像马氏距离也行?嗯,先放放···
4.图片分割
原图是这样的
代码如下:
1
2
3
4
5
6
7
8def splitPicture(img):
img = standard(img)
binary,contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
x,y,w,h = cv2.boundingRect(contours[i])
# print([x,y,w,h])
img = img[y:y+h,x:x+w]
return img
cv2.findContours函数是opencv的轮廓检测函数,其中返回值coutours是轮廓点集,再利用cv2.boundingRect函数,得出最小矩形边框,x,y,w,h分别是该矩形左上角坐标及矩形宽高,然后用numpy索引就可以 高效分割图片啦(之所以强调 高效,是因为最开始我用自己的双重for循环做出来的东西跑一趟要30多秒,而现在只要不到0.3秒,所以好好利用库函数总是没错的!)
与模板库对比
模板匹配,顾名思义就是与模板对比,以某种方式得出两张图片的相似程度,从而判断出手写数字是多少。我的第一版用的是欧氏距离匹配,简单来说,两点欧式距离
$d=sqrt{(x1-x2)^2+(y1-y2)^2}$
而对于图像来说,我们可以先算出它的特征值,然后求特征值之间的距离,就可以得到相似程度的距离值。
首先,求特征值。为了减少计算量,我把每张图片分成$5*5$即每张图分为25小块,每一小块的特征值用该区域白色(255)像素点个数除以该块总像素数代表,每张图片有25个特征值,对比两张图片就是依次比较它们的特征值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def getEigenvalue(img):#传入图片,返回一个特征值列表
img = splitPicture(img)
h,w = img.shape
h0 = int(h/5)
w0 = int(w/5)
# print([h0,w0])
# count = 0
# imgList = []#25张小图片
ratioList = []#小图片白色像素特征值列表
for i in range(5):
for j in range(5):
# imgList.append(img[i+i*h0:i+(i+1)*h0,j+j*w0:j+(j+1)*w0])
ratioList.append(getRatio(img[i+i*h0:i+(i+1)*h0,j+j*w0:j+(j+1)*w0]))
# for k in range(len(imgList)):
# ratioList.append(getRatio(imgList[k]))
# print(ratioList)
return ratioList
然后,得到”距离“。
1
2
3
4
5
6
7
8def getDistance(img1,img2):
list1 = getEigenvalue(img1)
list2 = getEigenvalue(img2)
distance = 0
for i in range(25):
distance = distance + (list1[i]-list2[i])**2
return int(math.sqrt(distance))
#return int(distance)
到这里,思路已经很清晰了,将所要识别的数字图像依次和图片库中的图片对比求“距离”,取“距离值”最小的数字代表的值即为所要识别的数字的值。
剩下的就是依次对比的部分了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43def getImgNameList():#获取图片库每张图片的路径及其所代表的数字
wolk_path = os.path.split(os.path.realpath(__file__))[0]
# print(os.listdir(wolk_path))
#rootList = os.listdir(wolk_path)
source_path = os.path.join(wolk_path,'source photo')
#print(source_path)
imgdir_path_list=[]
# img_path_list=[]
namemap={}
for i in range(10):
imgdir_path = os.path.join(source_path,str(i))#source下0,1...等目录
#print(imgdir_path)
imgdir_path_list.append(imgdir_path)
# print(os.listdir(imgdir_path_list[0]))
for j in range(10):
dir = len(os.listdir(imgdir_path_list[j]))
#print(os.listdir(imgdir_path_list[j]))
for k in range(dir):
img_path = os.path.join(imgdir_path_list[j],(os.listdir(imgdir_path_list[j]))[k])
# img_path_list.append(img_path)
#print(img_path)
namemap[img_path]=j
# return img_path_list,namemap
return namemap
def recognition(img1):#识别
# img1 = cv2.imread(img1)
# img_path_list,namemap = getImgNameList()
namemap = getImgNameList()
# print(namemap.keys()[0])
similarty = []
try:
# for i in range(len(img_path_list)):
# img2 = cv2.imread(img_path_list[i])
#print(img_path_list[i])
for key in namemap.keys():
# print(key)
img2 = cv2.imread(key)
# cv2.imshow('img',img2)
# cv2.waitKey(0)
# print(key)
similarty.append(getDistance(img1,img2))
# print(similarty[i],'..................')
最后,纠错模块就是人工指出数字是几,然后将图片保存到相应文件夹,因为图片的命名没啥思路,就先偷个懒先放着了,然后就是,想把识别结果也用窗口显示出来,也先搁着了。st=>start: 开始
e=>end: 结束
io1=>inputoutput: 手写数字
op1=>operation: 图片标准化
op2=>operation: 与模板库对比
cond=>condition: 人工鉴别
op3=>operation: 纠错并加入模板库
io2=>inputoutput: 识别正确
io4=>inputoutput: 无法识别
end=>end: 结束
st->io1->op1->op2->cond
cond(yes)->io2->end
cond(no,left)->op3->end{"scale":1,"line-width":2,"line-length":50,"text-margin":10,"font-size":12}