圖像文本識別的步驟一般為圖像預處理,圖片切割,特征提取、文本分類和圖像文本輸出幾個步驟,我們也可以按這個步驟來識別圖像中的數字。
一、圖像預處理
在圖像預處理中,驗證碼識別還要對圖像進行去燥,文字還原等比較復雜的處理,由於我的圖像沒什么干擾因素,所以直接對其進行二值處理即可。處理結果如下:
因為圖片上的數據灰度值都是75或76,所以只需把灰度值等於75或76的賦為1,其余的為0即可,代碼如下:

def pretreatment(ima): ima=ima.convert('L') #轉化為灰度圖像 im=numpy.array(ima) #轉化為二維數組 for i in range(im.shape[0]):#轉化為二值矩陣 for j in range(im.shape[1]): if im[i,j]==75 or im[i,j]==76: im[i,j]=1 else: im[i,j]=0 return im ima=PIL.Image.open('e://bwtest2//6//6-1.png') #讀入圖像 im=pretreatment(ima) #調用圖像預處理函數 for i in im: print(i)
二、圖片切割
因為最小的識別單位是10以內的個位數字,所以有必要對圖片中的數據進行切割,然后逐個對其識別。切割的過程如下:
切割的過程其實就是對二值矩陣進行分片的過程,只要找到0到1和1到0的過度點就可以判斷出切割點的位置了。
三、特征提取
對圖片進行切割后,得提取出每個圖片的特征,然后才能做后續的處理。圖像的特征很多,本文選取1占所在區域的比例作為圖片特征。首先將圖片分為四部分,如下圖所示:
然后計算左上部分1所占的比例A1、左下部分1所占的比例A2、右上部分1所占的比例A3、右下部分1所占的比例A4和所有1占整副圖的比例A5。這樣,就提取出了此幅圖的特征向量A=[A1,A2,A3,A4,A5]。664那副圖的特征分別為:
可見兩個6的特征極其相似,和4的特征相差有點大。切割圖片並提取特征的代碼如下:

#提取圖片特征 def feature(A): midx=int(A.shape[1]/2)+1 midy=int(A.shape[0]/2)+1 A1=A[0:midy,0:midx].mean() A2=A[midy:A.shape[0],0:midx].mean() A3=A[0:midy,midx:A.shape[1]].mean() A4=A[midy:A.shape[0],midx:A.shape[1]].mean() A5=A.mean() AF=[A1,A2,A3,A4,A5] return AF #切割圖片並返回每個子圖片特征 def incise(im): #豎直切割並返回切割的坐標 a=[];b=[] if any(im[:,0]==1): a.append(0) for i in range(im.shape[1]-1): if all(im[:,i]==0) and any(im[:,i+1]==1): a.append(i+1) elif any(im[:,i]==1) and all(im[:,i+1]==0): b.append(i+1) if any(im[:,im.shape[1]-1]==1): b.append(im.shape[1]) #水平切割並返回分割圖片特征 names=locals();AF=[] for i in range(len(a)): names['na%s' % i]=im[:,range(a[i],b[i])] if any(names['na%s' % i][0,:]==1): c=0 elif any(names['na%s' % i][names['na%s' % i].shape[0]-1,:]==1): d=names['na%s' % i].shape[0]-1 for j in range(names['na%s' % i].shape[0]-1): if all(names['na%s' % i][j,:]==0) and any(names['na%s' % i][j+1,:]==1): c=j+1 elif any(names['na%s' % i][j,:]==1) and all(names['na%s' % i][j+1,:]==0): d=j+1 names['na%s' % i]=names['na%s' % i][range(c,d),:] AF.append(feature(names['na%s' % i])) #提取特征 for j in names['na%s' % i]: print(j) return AF ima=PIL.Image.open('e://bwtest2//test//5.png') im=pretreatment(ima) #圖片預處理 AF=incise(im) #切割圖片並提取特征 for i in AF: print(i)
四、數字分類
文本分類的方法有k最近鄰算法、支持向量機、神經網絡等,本文選用的是最簡單的最近鄰算法。
K最近鄰(k-Nearest Neighbor,KNN)分類算法,是一個理論上比較成熟的方法,也是最簡單的機器學習算法之一。該方法的思路是:如果一個樣本在特征空間中的k個最相似(即特征空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。KNN算法中,所選擇的鄰居都是已經正確分類的對象。相比於其他算法,其時間復雜度和空間復雜度都很高,但具有精度高、對異常值不敏感、無輸入數據假定的優點。
要讓計算機識別東西,首先得告訴它這東西長啥樣,然后它才能正確的識別。所以我把系統中的“0、1、2、3、4、5、6、7、8、9、.”各截了15副圖放到文件夾中,如下圖所示:
其中,‘‘10’’夾中放的是“.”,test文件夾中放的是待識別的數據。KNN算法的具體實現步驟如下:
1)提取1-10文件夾中所有圖片的特征並保存在字典train_set中
2)分割並提取待識別圖片的的特征
3)每個特征都和train_set中的所有特征計算距離
4)對距離進行排序,並選出排名前K個的鍵值
5)統計K個鍵值中每個類別出現的個數,出現個數最多的類別就是最終的分類
6)合並所有子特征的分類結果即是這幅圖片上的數字
664那副圖的KNN分類結果和源程序如下

#訓練已知圖片的特征 def training(): train_set={} for i in range(11): value=[] for j in range(15): ima=PIL.Image.open('e://bwtest2//'+str(i)+'//'+str(i)+'-'+str(j)+'.png') im=pretreatment(ima) AF=incise(im) value.append(AF[0]) train_set[i]=value #把訓練結果存為永久文件,以備下次使用 output=open('e://bwtest2//train_set.pkl','wb') pickle.dump(train_set,output) output.close() return train_set #計算兩向量的距離 def distance(v1,v2): vector1=numpy.array(v1) vector2=numpy.array(v2) Vector=(vector1-vector2)**2 distance = Vector.sum()**0.5 return distance #用最近鄰算法識別單個數字 def knn(train_set,V,k): key_sort=[11]*k value_sort=[11]*k for key in range(11): for value in train_set[key]: d=distance(V,value) for i in range(k): if d<value_sort[i]: for j in range(k-2,i-1,-1): key_sort[j+1]=key_sort[j] value_sort[j+1]=value_sort[j] key_sort[i]=key value_sort[i]=d break max_key_count=-1 key_set=set(key_sort) for key in key_set: if max_key_count<key_sort.count(key): max_key_count=key_sort.count(key) max_key=key return max_key #合並一副圖片的所有數字 def identification(train_set,AF,k): result='' for i in AF: key=knn(train_set,i,k) if key==10: key='.' result=result+str(key) return float(result) train_set=training() ima=PIL.Image.open('e://bwtest2//test//5'.png') im=pretreatment(ima) #預處理 AF=incise(im) #分割並提取圖片 identification(train_set,AF,7) #knn識別
五、圖片數字輸出
最后,我們需要識別test文件夾中的所有圖片並把識別出的數字輸出到EXCEL文件中。並對數據的狀態做一些簡單的判斷,輸出部分結果如下:
其中,第一列為昨日的備份數據(尚未采集),第二列為今日需識別的數據,第三列為今日數據的狀態。此程序識別正確率達到百分之百,看來用圖像識別的方法完成錄入圖片數據工作不失為一種好的策略啊。本文用的版本為python3.5,完整程序如下:

import numpy import PIL.Image import pickle import xlwt #圖片預處理 def pretreatment(ima): ima=ima.convert('L') #轉化為灰度圖像 im=numpy.array(ima) #轉化為二維數組 for i in range(im.shape[0]):#轉化為二值矩陣 for j in range(im.shape[1]): if im[i,j]==75 or im[i,j]==76: im[i,j]=1 else: im[i,j]=0 return im #提取圖片特征 def feature(A): midx=int(A.shape[1]/2)+1 midy=int(A.shape[0]/2)+1 A1=A[0:midy,0:midx].mean() A2=A[midy:A.shape[0],0:midx].mean() A3=A[0:midy,midx:A.shape[1]].mean() A4=A[midy:A.shape[0],midx:A.shape[1]].mean() A5=A.mean() AF=[A1,A2,A3,A4,A5] return AF #切割圖片並返回每個子圖片特征 def incise(im): #豎直切割並返回切割的坐標 a=[];b=[] if any(im[:,0]==1):#避免截圖沒截好的情況 a.append(0) for i in range(im.shape[1]-1): if all(im[:,i]==0) and any(im[:,i+1]==1): a.append(i+1) elif any(im[:,i]==1) and all(im[:,i+1]==0): b.append(i+1) if any(im[:,im.shape[1]-1]==1): b.append(im.shape[1]) #水平切割並返回分割圖片特征 names=locals() #初始化分割后子圖片的動態變量名 AF=[] #初始化子圖片的特征列表 for i in range(len(a)): names['na%s' % i]=im[:,range(a[i],b[i])] if any(names['na%s' % i][0,:]==1): c=0 elif any(names['na%s' % i][names['na%s' % i].shape[0]-1,:]==1): d=names['na%s' % i].shape[0]-1 for j in range(names['na%s' % i].shape[0]-1): if all(names['na%s' % i][j,:]==0) and any(names['na%s' % i][j+1,:]==1): c=j+1 elif any(names['na%s' % i][j,:]==1) and all(names['na%s' % i][j+1,:]==0): d=j+1 names['na%s' % i]=names['na%s' % i][range(c,d),:] AF.append(feature(names['na%s' % i])) for j in names['na%s' % i]: print(j) return AF #訓練已知圖片的特征 def training(): train_set={} for i in range(11): value=[] for j in range(15): ima=PIL.Image.open('e://bwtest2//'+str(i)+'//'+str(i)+'-'+str(j)+'.png') im=pretreatment(ima) AF=incise(im) #切割並提取特征 value.append(AF[0]) train_set[i]=value #把訓練結果存為永久文件,以備下次使用 output=open('e://bwtest2//train_set.pkl','wb') pickle.dump(train_set,output) output.close() return train_set #計算兩向量的距離 def distance(v1,v2): vector1=numpy.array(v1) vector2=numpy.array(v2) Vector=(vector1-vector2)**2 distance = Vector.sum()**0.5 return distance #用最近鄰算法識別單個數字 def knn(train_set,V,k): key_sort=[11]*k value_sort=[11]*k for key in range(11): for value in train_set[key]: d=distance(V,value) for i in range(k):#從小到大排序 if d<value_sort[i]: for j in range(k-2,i-1,-1): key_sort[j+1]=key_sort[j] value_sort[j+1]=value_sort[j] key_sort[i]=key value_sort[i]=d break max_key_count=-1 key_set=set(key_sort) for key in key_set: #統計每個類別出現的次數 if max_key_count<key_sort.count(key): max_key_count=key_sort.count(key) max_key=key return max_key #合並一副圖片的所有數字 def identification(train_set,AF,k): result='' for i in AF: key=knn(train_set,i,k) if key==10: key='.' result=result+str(key) return float(result) #識別文件夾中的所有圖片上的數據並輸出到EXCELL中 def main(k,trained=None,backup=None): # k:knn算法的的k,即取訓練集中最相鄰前k個樣本進行判別 #trained:trained=0則程序會開始訓練出訓練集,否則會用已保存的訓練集 #backup:backup=0則程序令昨日數據均為0,並備份今日數據到昨日數據和昨日測試數據 # backup=1則程序會令昨日數據為昨日測試數據,並備份今日數據到昨日測試數據 # backup等於其他時則程序會令昨日數據為昨日數據,並備份今日數據到昨日數據 if trained==0: train_set=training() else: pkl_file=open('e://bwtest2//train_set.pkl','rb') train_set=pickle.load(pkl_file) pkl_file.close() if backup==0: yestoday_data=[0]*32 elif backup==1: pkl_file=open('e://bwtest2//yestoday_data_test.pkl','rb') yestoday_data=pickle.load(pkl_file) pkl_file.close() else: pkl_file=open('e://bwtest2//yestoday_data.pkl','rb') yestoday_data=pickle.load(pkl_file) pkl_file.close() backups=[] workbook = xlwt.Workbook('e://bwtest2//bwtest2.xls') sheet = workbook.add_sheet("record and contrast") #設置EXCEL字體為綠色 font = xlwt.Font() font.colour_index = 3 style = xlwt.XFStyle() style.font = font #識別所有圖片的數字並輸出到EXCELL中 for i in range(1,33): ima=PIL.Image.open('e://bwtest2//test//'+str(i)+'.png') im=pretreatment(ima) AF=incise(im) result=identification(train_set,AF,k) backups.append(result) sheet.write(i-1, 0, yestoday_data[i-1]) if result==yestoday_data[i-1]: sheet.write(i-1, 1, result,style) sheet.write(i-1, 2, '正常') else: sheet.write(i-1, 1, result) sheet.write(i-1, 2, '待定') workbook.save('e://bwtest2//bwtest2.xls') if backup==0: output=open('e://bwtest2//yestoday_data_test.pkl','wb') pickle.dump(backups,output) output.close() output=open('e://bwtest2//yestoday_data.pkl','wb') pickle.dump(backups,output) output.close() elif backup==1:#/yestoday_data_test用來測試之用 output=open('e://bwtest2//yestoday_data_test.pkl','wb') pickle.dump(backups,output) output.close() else: output=open('e://bwtest2//yestoday_data.pkl','wb') pickle.dump(backups,output) output.close() main(4,0,0) #表示:4近鄰,還沒有訓練集備份,還沒有昨日數據備份