Python3实现机器学习经典算法(二)KNN实现简单OCR
一、前言
1、ocr概述
OCR (Optical Character Recognition,光學(xué)字符識別)是指電子設(shè)備(例如掃描儀或數(shù)碼相機(jī))檢查紙上打印的字符,通過檢測暗、亮的模式確定其形狀,然后用字符識別方法將形狀翻譯成計(jì)算機(jī)文字的過程;即,針對印刷體字符,采用光學(xué)的方式將紙質(zhì)文檔中的文字轉(zhuǎn)換成為黑白點(diǎn)陣的圖像文件,并通過識別軟件將圖像中的文字轉(zhuǎn)換成文本格式,供文字處理軟件進(jìn)一步編輯加工的技術(shù)(摘自百度百科:光學(xué)字符識別)。
KNN在OCR的識別過程中能發(fā)揮作用的地方在于將圖像中的文字轉(zhuǎn)換為文本格式,而OCR的其他部分,比如圖像預(yù)處理、二值化等操作將其丟給OpenCV去操作。
2、訓(xùn)練集簡介
由于我們采用的是KNN來轉(zhuǎn)換圖像中的文字為文本格式,需要一個(gè)龐大的手寫字符訓(xùn)練集來支撐我們的算法。這里我使用的是《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》2.3實(shí)例:手寫識別系統(tǒng)中使用的數(shù)據(jù)集,其下載地址為:https://www.manning.com/books/machine-learning-in-action,在Source CodeCh02digits rainingDigits中的兩千多個(gè)手寫字符既是我所使用的訓(xùn)練集。
這個(gè)訓(xùn)練集配合上它所提供的測試集,提供了一個(gè)準(zhǔn)確度非常高的分類器:
訓(xùn)練集是由0~9十個(gè)數(shù)字組成的,每個(gè)數(shù)字有兩百個(gè)左右的訓(xùn)練樣本。所有的訓(xùn)練樣本統(tǒng)一被處理為一個(gè)32*32的0/1矩陣,其中所有值為1的連通區(qū)域構(gòu)成了形象上的數(shù)字,如下所示:
所以,在構(gòu)造我們的測試集的時(shí)候,所有的手寫數(shù)字圖片必須被處理為這樣的格式才能夠使得分類算法正確地進(jìn)行,這也是KNN的局限所在。
二、算法實(shí)現(xiàn)
1、構(gòu)建測試集
上面已經(jīng)提到,要想算法正確地進(jìn)行,測試集的樣式應(yīng)該和訓(xùn)練集相同,也就是說我們要把一張包含有手寫數(shù)字的圖像,轉(zhuǎn)換為一個(gè)32*32的0/1點(diǎn)陣。
測試集使用我自己手寫的10個(gè)數(shù)字:
這里存在一個(gè)非常大的問題:這個(gè)數(shù)據(jù)集的作者是土耳其人,他們書寫數(shù)字的習(xí)慣和我們有諸多不同,比如上面的數(shù)字4和數(shù)字8,下面這樣子的數(shù)字就無法識別:4/8。哈哈,也就是說它連印刷體都無法識別,這是這個(gè)訓(xùn)練集的一大缺陷之一。
1)圖像預(yù)處理
圖像預(yù)處理的過程是一個(gè)數(shù)字圖像處理(DIP)的過程,觀察上面的10個(gè)數(shù)字,可以發(fā)現(xiàn)每張圖像的大小/對比度的差距都非常大,所以圖像預(yù)處理應(yīng)該消除這些差距。
第一步是進(jìn)行圖像的放大/縮小。由于我們很難產(chǎn)生一個(gè)小于32*32像素的手寫數(shù)字圖像,所以這里主要是縮小圖像:
1 import cv2 2 def readImage(imagePath): 3 image = cv2.imread(imagePath,cv2.IMREAD_GRAYSCALE) 4 image = cv2.resize(image,(32,32),interpolation = cv2.INTER_AREA) 5 return image
這里我沒有去實(shí)現(xiàn)圖像重采樣的方法(實(shí)現(xiàn)在后面的博客會寫),而是采用的OpenCV,通過area來確定取樣點(diǎn)的灰度值(推薦用bicubic interpolation,對應(yīng)的插入函數(shù)應(yīng)該是INTER_CUBIC),在讀入圖像的時(shí)候讀入方式位IMRAD_GRAYSCALE,因?yàn)槲覀冃枰氖亲R別手寫字符,灰度圖對比彩色圖能更好的突出重點(diǎn)。
進(jìn)行圖像的縮放是不夠的,因?yàn)橛^察上面的圖片可以發(fā)現(xiàn):拍攝環(huán)境對于對比度的影響非常大,所以我們應(yīng)該突出深色區(qū)域(數(shù)字部分),來保證后面的工作順利進(jìn)行,這里采用的是伽馬變換(也可以采用對數(shù)變換):
1 def imageGamma(image): 2 for i in range(32): 3 for j in range(32): 4 image[i][j]=3*pow(image[i][j],0.8) 5 return image
2)圖像二值化
縮小/放大后的圖像已經(jīng)是一個(gè)32*32的圖像了,下一步則是將非數(shù)字區(qū)域填充0,數(shù)字區(qū)域填充1,這里我采用的是閾值二值化處理:
def imageThreshold(image):
ret,image = cv2.threshold(image,150,255,cv2.THRESH_BINARY)
return image
經(jīng)過二值化處理,數(shù)字部分的灰度值應(yīng)該為0,而非數(shù)字部分的連通區(qū)域的灰度值應(yīng)該為255,如下所示:
3)去噪
圖像去噪的方式有很多種,這里建立使用自適應(yīng)中值濾波器進(jìn)行降噪,因?yàn)槲覀兊膱D像在傳輸過程中可能出現(xiàn)若干的椒鹽噪聲,這個(gè)噪聲在上述的二值化處理中有時(shí)候是非常棘手的。
到目前為止,一副手機(jī)攝像的手寫數(shù)字圖像就可以轉(zhuǎn)換為一個(gè)32*32的二值圖像。
4)生成訓(xùn)練樣本
如何將這個(gè)32*32的二值圖像轉(zhuǎn)換為0/1圖像,這個(gè)處理非常簡單:
1 def imageProcess(image):
2 with open(r'F:UsersyangPycharmProjectsOCR_KNN estDigits6_0.txt','w+') as file:
3 for i in range(32):
4 for j in range(32):
5 if image[i][j] == 255:
6 file.write('0')
7 else:
8 file.write('1')
9 file.writelines('
')
這里我的代碼在掃描這個(gè)圖像的同時(shí),將其保存為一個(gè)訓(xùn)練樣本,命名和訓(xùn)練集的明明要求一樣為N_M.txt,其中N代表這個(gè)訓(xùn)練樣本的實(shí)際分類是什么數(shù)字,M代表這是這個(gè)數(shù)字的第幾個(gè)樣本。這里對圖像進(jìn)行灰度變換已經(jīng)是多此一舉了,我所需要的是0/1矩陣而非一個(gè)0/1圖像,所以在掃描過程中一并生成訓(xùn)練樣本更加省時(shí)直觀。
5)形成訓(xùn)練集
上面的示例只是生成一個(gè)圖像的訓(xùn)練樣本的,而實(shí)際上我們往往需要一次性生成一個(gè)訓(xùn)練集,這就要求這個(gè)圖像預(yù)處理、二值化并且生成0/1矩陣的過程是自動的:
1 from os import listdir
2 def imProcess(imagePath):
3 testDigits = listdir(imagePath)
4 for i in range(len(testDigits)):
5 imageName = testDigits[i]#圖像命名格式為N_M.png,NM含義見4)生成訓(xùn)練樣本
6 #imageClass = int((imageName.split('.')[0]).split('_')[0])#這個(gè)圖像的數(shù)字是多少
7 image = cv2.imread(imageName,cv2.IMREAD_GRAYSCALE)
8 image = cv2.resize(image, (32, 32), interpolation=cv2.INTER_AREA)
9 ret, image = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)
10 with open(r'F:UsersyangPycharmProjectsOCR_KNN estDigits\'+imageName.split('.')[0]+'.txt','w+') as file:
11 for i in range(32):
12 for j in range(32):
13 if image[i][j] == 255:
14 file.write('0')
15 else:
16 file.write('1')
17 file.writelines('
')
這個(gè)函數(shù)將imagePath文件夾中所有的N_M命名的手寫數(shù)字圖像讀取并經(jīng)過預(yù)處理、二值化、最后保存為對應(yīng)的0/1矩陣,命名為N_M.txt,這就構(gòu)成一個(gè)訓(xùn)練集了。
2、構(gòu)建分類器
分類器使用上一節(jié)的分類器(classify):
1 def classify(vector,dataSet,labels,k):
2 distance = sqrt(abs(((tile(vec,(dataSet.shape[0],1)) - dataSet) ** 2).sum(axis = 1))); #計(jì)算距離
3 sortedDistance = distance.argsort()
4 dict={}
5 for i in range(k):
6 label = labels[sortedDistance[i]]
7 if not label in dict:
8 dict[label] = 1
9 else:
10 dict[label]+=1
11 sortedDict = sorted(dict,key = operator.itemgetter(1),reverse = True)
12 return sortedDict[0][0]
13
14 def dict2list(dic:dict):#將字典轉(zhuǎn)換為list類型
15 keys=dic.keys()
16 values=dic.values()
17 lst=[(key, value)for key,value in zip(keys,values)]
18 return lst
distance的計(jì)算和dict2list函數(shù)的詳解在上一節(jié),戳上面的classify既可以跳轉(zhuǎn)過去。
分類器已經(jīng)構(gòu)建完成,下一步是提取每一個(gè)測試樣本,提取訓(xùn)練集,提取label的過程:(這個(gè)過程大部分用的是《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》中的代碼,對于難以理解的代碼在下文中做了解釋:)
1)讀取0/1矩陣文件:
1 def img2vector(filename): 2 returnvec = numpy.zeros((1,1024)) 3 file = open(filename) 4 for i in range(32): 5 line = file.readline() 6 for j in range(32): 7 returnvec[0,32*i+j] = int(line[j]) 8 return returnvec
這里要注意:構(gòu)造一個(gè)32*32的全零矩陣的時(shí)候,應(yīng)該是numpy.zeros((1,1024)),雙層括號!雙層括號!雙層括號!代表構(gòu)造的是一個(gè)二維矩陣!
2)讀取訓(xùn)練集和測試集并求解準(zhǔn)確率:
1 def handWritingClassifyTest():
2 labels=[]
3 trainingFile = listdir(r'F:UsersyangPycharmProjectsOCR_KNN rainingDigits')
4 m = len(trainingFile)
5 trainingMat = numpy.zeros((m,1024))
6 for i in range(m):
7 file = trainingFile[i]
8 filestr = file.strip('.')[0]
9 classnum = int(filestr.strip('_')[0])
10 labels.append(classnum)
11 trainingMat[i,:] = img2vector('trainingDigits/%s' % file)
12 testFileList = listdir(r'F:UsersyangPycharmProjectsOCR_KNN estDigits')
13 error = 0.0
14 testnum = len(testFileList)
15 for i in range(testnum):
16 file_test = testFileList[i]
17 filestr_test = file_test.strip('.')[0]
18 classnum_test = int(filestr_test.strip('_')[0])
19 vector_test = img2vector('testDigits/%s'%file_test)
20 result = classify(vector_test,trainingMat,labels,1)
21 if(result!=classnum_test):error+=1.0
22 print("準(zhǔn)確率:%f"%(1.0-(error/float(testnum))))
代碼其實(shí)沒有很難懂的地方,主要任務(wù)就是讀取文件,通過img2vctor函數(shù)轉(zhuǎn)換為矩陣,還有切割文件名獲取該測試樣本的類別和該訓(xùn)練樣本的類別,通過對比獲得準(zhǔn)確率。
3、使用分類器
現(xiàn)在為止,我們的分類器已經(jīng)構(gòu)建完成,下面就是測試和使用階段:
1)測試《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》中給出的訓(xùn)練集:
2)測試手寫訓(xùn)練集:
emmm果然學(xué)不出來大佬寫字,附上幾張無法識別的0/1數(shù)字矩陣:(0,4,6無法識別的原因是比劃太細(xì)哈哈,8無法識別的原因……太端正了吧)
4、完整代碼:
1 from os import listdir
2 import numpy
3 import operator
4 import cv2
5
6 def imProcess(imagePath):
7 testDigits = listdir(imagePath)
8 for i in range(len(testDigits)):
9 imageName = testDigits[i]#圖像命名格式為N_M.png,NM含義見4)生成訓(xùn)練樣本
10 #imageClass = int((imageName.split('.')[0]).split('_')[0])#這個(gè)圖像的數(shù)字是多少
11 image = cv2.imread(imageName,cv2.IMREAD_GRAYSCALE)
12 image = cv2.resize(image, (32, 32), interpolation=cv2.INTER_AREA)
13 ret, image = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)
14 with open(r'F:UsersyangPycharmProjectsOCR_KNN estDigits\'+imageName.split('.')[0]+'.txt','w+') as file:
15 for i in range(32):
16 for j in range(32):
17 if image[i][j] == 255:
18 file.write('0')
19 else:
20 file.write('1')
21 file.writelines('
')
22
23 def img2vector(filename):
24 returnvec = numpy.zeros((1,1024))
25 file = open(filename)
26 for i in range(32):
27 line = file.readline()
28 for j in range(32):
29 returnvec[0,32*i+j] = int(line[j])
30 return returnvec
31
32 def handWritingClassifyTest():
33 labels=[]
34 trainingFile = listdir(r'F:UsersyangPycharmProjectsOCR_KNN rainingDigits')
35 m = len(trainingFile)
36 trainingMat = numpy.zeros((m,1024))
37 for i in range(m):
38 file = trainingFile[i]
39 filestr = file.strip('.')[0]
40 classnum = int(filestr.strip('_')[0])
41 labels.append(classnum)
42 trainingMat[i,:] = img2vector('trainingDigits/%s' % file)
43 testFileList = listdir(r'F:UsersyangPycharmProjectsOCR_KNN estDigits')
44 error = 0.0
45 testnum = len(testFileList)
46 for i in range(testnum):
47 file_test = testFileList[i]
48 filestr_test = file_test.strip('.')[0]
49 classnum_test = int(filestr_test.strip('_')[0])
50 vector_test = img2vector('testDigits/%s'%file_test)
51 result = classify(vector_test,trainingMat,labels,1)
52 if(result!=classnum_test):error+=1.0
53 print("準(zhǔn)確率:%f"%(1.0-(error/float(testnum))))
54
55 def classify(inX,dataSet,labels,k):
56 size = dataSet.shape[0]
57 distance = (((numpy.tile(inX,(size,1))-dataSet)**2).sum(axis=1))**0.5
58 sortedDistance = distance.argsort()
59 count = {}
60 for i in range(k):
61 label = labels[sortedDistance[i]]
62 count[label]=count.get(label,0)+1
63 sortedcount = sorted(dict2list(count),key=operator.itemgetter(1),reverse=True)
64 return sortedcount[0][0]
65
66 def dict2list(dic:dict):#將字典轉(zhuǎn)換為list類型
67 keys=dic.keys()
68 values=dic.values()
69 lst=[(key, value)for key,value in zip(keys,values)]
70 return lst
71
72 # def imProcess(image):
73 # image = cv2.resize(image, (32, 32), interpolation=cv2.INTER_AREA)
74 # ret, image = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)
75 # cv2.imshow('result',image)
76 # cv2.waitKey(0)
77 # with open(r'F:UsersyangPycharmProjectsOCR_KNN estDigits6_0.txt','w+') as file:
78 # for i in range(32):
79 # for j in range(32):
80 # if image[i][j] == 255:
81 # file.write('0')
82 # else:
83 # file.write('1')
84 # file.writelines('
')
85
86
87
88 # iamge = cv2.imread(r'C:UsersyangDesktop6.png',cv2.IMREAD_GRAYSCALE)
89 # image = imProcess(iamge)
90 imProcess(r'F:UsersyangPycharmProjectsOCR_KNN estDigits')
91 handWritingClassifyTest()
5、github:https://github.com/hahahaha1997/OCR
三、總結(jié)
KNN還是不適合用來做OCR的識別過程的,雖然《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》的作者提到這個(gè)系統(tǒng)是美國的郵件分揀系統(tǒng)實(shí)際運(yùn)行的一個(gè)系統(tǒng),但是它肯定無法高準(zhǔn)確率地識別中國人寫的手寫文字就對了,畢竟中國有些地方的“9”還會寫成“p”的樣子的。這一節(jié)主要是將KNN拓展到實(shí)際運(yùn)用中的,結(jié)合上一節(jié)的理論,KNN的執(zhí)行效率還是太低了,比如這個(gè)系統(tǒng),要識別一個(gè)手寫數(shù)字,它需要和所有的訓(xùn)練樣本做距離計(jì)算,每個(gè)距離計(jì)算又有1024個(gè)(a-b)2,還有運(yùn)行效率特別低下的sqrt(),如果是一個(gè)非常大的測試集,需要的時(shí)間就更加龐大,如果訓(xùn)練集非常龐大,在將0/1矩陣讀入內(nèi)存中的時(shí)候,內(nèi)存開銷是非常巨大的,所以整個(gè)程序可能會非常耗時(shí)費(fèi)力。不過KNN仍舊是一個(gè)精度非常高的算法,并且也是機(jī)器學(xué)習(xí)分類算法中最簡單的算法之一。下一節(jié)將帶來機(jī)器學(xué)習(xí)經(jīng)典算法——ID3決策樹。轉(zhuǎn)載注明出處哦:https://www.cnblogs.com/DawnSwallow/p/9440516.html
總結(jié)
以上是生活随笔為你收集整理的Python3实现机器学习经典算法(二)KNN实现简单OCR的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAP ABAP bcset激活时,关联
- 下一篇: 微信朋友圈怎么发1分钟的视频