前面我們僅僅取了兩個特征維度進行說明。在實際應(yīng)用中,可能存在著更多特征維度需要計算。
下面以手寫數(shù)字識別為例進行簡單的介紹。
假設(shè)我們要讓程序識別圖 20-2 中上方的數(shù)字(當(dāng)然,你一眼就知道是“8”,但是現(xiàn)在要讓計算機識別出來)。識別的方式是,依次計算該數(shù)字圖像(即寫有數(shù)字的圖像)與下方數(shù)字圖像的距離,與哪個數(shù)字圖像的距離最近(此時 k =1),就認(rèn)為它與哪幅圖像最像,從而確定這幅圖像中的數(shù)字是多少。
下面分別從特征值提取和數(shù)字識別兩方面展開介紹。
1. 特征值提取
步驟 1:我們把數(shù)字圖像劃分成很多小塊,如圖 20-3 所示。該圖中每個數(shù)字被分成 5 行 4列,共計 5×4 = 20 個小塊。此時,每個小塊是由很多個像素點構(gòu)成的。當(dāng)然,也可以將每一個像素點理解為一個更小的子塊。
為了敘述上的方便,將這些小塊表示為 B(Bigger),將 B 內(nèi)的像素點,記為 S(Smaller)。
因此,待識別的數(shù)字“8”的圖像可以理解為:
- 由 5 行 4 列,共計 5×4=20 個小塊 B 構(gòu)成。
- 每個小塊 B 內(nèi)其實是由 M×N 個像素(更小塊 S)構(gòu)成的。為了描述上的方便,假設(shè)每個小塊大小為 10×10 =100 個像素。
步驟 2:計算每個小塊 B 內(nèi),有多少個黑色的像素點?;蛘哌@樣說,計算每個小塊 B 內(nèi)有
多少個更小塊 S 是黑色的。
仍以數(shù)字“8”的圖像為例,其第 1 行中:
- 第 1 個小塊 B 共有 0 個像素點(更小塊 S)是黑色的,記為 0。
- 第 2 個小塊 B 共有 28 個像素點(更小塊 S)是黑色的,記為 28。
- 第 3 個小塊 B 共有 10 個像素點(更小塊 S)是黑色的,記為 10。
- 第 4 個小塊 B 共有 0 個像素點(更小塊 S)是黑色的,記為 0。
以此類推,計算出數(shù)字“8”的圖像中每一個小塊 B 中有多少個像素點是黑色的,如圖 20-4 所示。我們觀察后會發(fā)現(xiàn),不同的數(shù)字圖像中每個小塊 B 內(nèi)黑色像素點的數(shù)量是不一樣的。正是這種不同,使我們能用該數(shù)量(每個小塊 B 內(nèi)黑色像素點的個數(shù))作為特征來表示每一個數(shù)字。
步驟 3:有時,為了處理上的方便,我們會把得到的特征值排成一行(寫為數(shù)組形式),如圖 20-5 所示。
當(dāng)然,在 Python 里完全沒有必要這樣做,因為 Python 可以非常方便地直接處理圖 20-5 中上方數(shù)組(array)形式的數(shù)據(jù)。這里為了說明上的方便,仍將其特征值處理為一行數(shù)字的形式。
經(jīng)過上述處理,數(shù)字“8”圖像的特征值變?yōu)橐恍袛?shù)字,如圖 20-6 所示。
步驟 4:與數(shù)字“8”的圖像類似,每個數(shù)字圖像的特征值都可以用一行數(shù)字來表示。從某種意義上來說,這一行數(shù)字類似于我們的身份證號碼,一般來說,具有唯一性。
按照同樣的方式,獲取每個數(shù)字圖像的特征值,如圖 20-7 所示。
2. 數(shù)字識別
數(shù)字識別要做的就是比較待識別圖像與圖像集中的哪個圖像最近。這里,最近指的是二者之間的歐氏距離最短。
本例中為了便于說明和理解進行了簡化,將原來下方的 10 個數(shù)字減少為 2 個(也即將分類從 10 個減少為 2 個)。
假設(shè)要識別的圖像為圖 20-8 中上方的數(shù)字“8”圖像,需要判斷該圖像到底屬于圖 20-8 中下方的數(shù)字“8” 圖像的分類還是數(shù)字“7”圖像的分類。
步驟 1:提取特征值,分別提取待識別圖像的特征值和特征圖像的特征值。
為了說明和理解上的方便,將特征進行簡化,每個數(shù)字圖像只提取 4 個特征值(劃分為 2×2 = 4 個子塊 B),如圖 20-9 所示。此時,提取到的特征值分別為:
- 待識別的數(shù)字“8”圖像:[3, 7, 8, 13]
- 數(shù)字“8”特征圖像:[3, 6, 9, 12]
- 數(shù)字“7”特征圖像:[8, 1, 2, 98]
步驟 2:計算距離。按照 20.1 節(jié)介紹的歐氏距離計算方法,計算待識別圖像與特征圖像之間的距離。
首先,計算待識別的數(shù)字“8”圖像與下方的數(shù)字“8”特征圖像之間的距離,如圖 20-10所示。計算二者之間的距離:
接下來,計算待識別的數(shù)字“8”圖像與數(shù)字“7”特征圖像之間的距離,如圖 20-11 所示。二者之間的距離為:
通過計算可知,待識別的數(shù)字“8”圖像:
- 與數(shù)字“8”特征圖像的距離為根號3=1.732050807568877。
- 與數(shù)字“7”特征圖像的距離為根號7322=85.56868586112562。
步驟 3:識別。
根據(jù)計算的距離,待識別的數(shù)字“8”圖像與數(shù)字“8”特征圖像的距離更近。所以,將待識別的數(shù)字“8”圖像識別為數(shù)字“8”特征圖像所代表的數(shù)字“8”。
上面介紹的是 K 近鄰算法只考慮最近的一個鄰居的情況,相當(dāng)于 K 近鄰中 k =1 的情況。在實際操作中,為了提高可靠性,需要選用大量的特征值。例如,每個數(shù)字都選用不同的形態(tài)的手寫體 100 個,對于 0 ~ 9 這 10
個數(shù)字,共需要 100×10 =1000 幅特征圖像。在識別數(shù)字時, 分別計算待識別的數(shù)字圖像與這些特征圖像之間的距離。這時,可以將 k
調(diào)整為稍大的值,例如 k =11,然后看看其最近的 11 個鄰居分屬于哪些特征圖像。
例如,其中:
- 有 8 個屬于數(shù)字“6”特征圖像。
- 有 2 個屬于數(shù)字“8”特征圖像。
- 有 1 個屬于數(shù)字“9”特征圖像。
通過判斷,當(dāng)前待識別的數(shù)字為數(shù)字“6”特征圖像所代表的數(shù)字“6”。
自定義函數(shù)手寫數(shù)字識別
在本例中,0~9 的每個數(shù)字都有 10 個特征值。例如,數(shù)字“0”的特征值如圖 20-12 所示。
為了便于描述,將所有這些用于判斷分類的圖像稱為特征圖像。
下面分步驟實現(xiàn)手寫數(shù)字的識別。
1. 數(shù)據(jù)初始化
對程序中要用到的數(shù)據(jù)進行初始化。涉及的數(shù)據(jù)主要有路徑信息、圖像大小、特征值數(shù)量、用來存儲所有特征值的數(shù)據(jù)等。
本例中:
- 特征圖像存儲在當(dāng)前路徑的“image_number”文件夾下。
- 用于判斷分類的特征值有 100 個(對應(yīng) 100 幅特征圖像)。
- 特征圖像的行數(shù)(高度)、列數(shù)(寬度)可以通過程序讀取。也可以在圖像上單擊鼠標(biāo)右鍵后通過查找屬性值來獲取。這里采用設(shè)置好的特征圖像集,每個特征圖像都是高240 行、寬 240 列。
根據(jù)上述已知條件,對要用到的數(shù)據(jù)初始化:
s='image_number\\' # 圖像所在的路徑
num=100 # 共有特征值的數(shù)量
row=240 # 特征圖像的行數(shù)
col=240 # 特征圖像的列數(shù)
a=np.zeros((num,row,col)) # a 用來存儲所有特征的值
2. 讀取特征圖像
本步驟將所有的特征圖像讀入到 a 中。共有 10 個數(shù)字,每個數(shù)字有 10 個特征圖像,采用嵌套循環(huán)語句完成讀取。具體代碼如下:
n=0 # n 用來存儲當(dāng)前圖像的編號。
for i in range(0,10):
for j in range(1,11):
a[n,:,:]=cv2.imread(s+str(i)+'\\'+str(i)+'-'+str(j)+'.bmp',0)
n=n+1
3. 提取特征圖像的特征值
在提取特征值時,可以計算每個子塊內(nèi)黑色像素點的個數(shù),也可以計算每個子塊內(nèi)白色像素點的個數(shù)。這里我們選擇計算白色像素點(像素值為 255)的個數(shù)。按照上述思路,圖像映射到特征值的關(guān)系如圖 20-13 所示。
這里需要注意,特征值的行和列的大小都是原圖像的 1/5。所以,在設(shè)計程序時,如果原始圖像內(nèi)位于(row, col)位置的像素點是白色,則要把對應(yīng)特征值內(nèi)位于(row/5, col/5)處的值加 1。
根據(jù)上述分析,編寫代碼如下:
feature=np.zeros((num,round(row/5),round(col/5))) # feature 存儲所有樣本的特征值
#print(feature.shape) # 在必要時查看 feature 的形狀是什么樣子
#print(row) # 在必要時查看 row 的值,有多少個特征值(100 個)
for ni in range(0,num):
for nr in range(0,row):
for nc in range(0,col):
if a[ni,nr,nc]==255:
feature[ni,int(nr/5),int(nc/5)]+=1
f=feature #簡化變量名稱
4. 計算待識別圖像的特征值
讀取待識別圖像,然后計算該圖像的特征值。編寫代碼如下:
o=cv2.imread('image\\test\\9.bmp',0) # 讀取待識別圖像
# 讀取圖像的值
of=np.zeros((round(row/5),round(col/5))) # 用來存儲待識別圖像的特征值
for nr in range(0,row):
for nc in range(0,col):
if o[nr,nc]==255:
of[int(nr/5),int(nc/5)]+=1
5. 計算待識別圖像與特征圖像之間的距離
依次計算待識別圖像與特征圖像之間的距離。編寫代碼如下:
d=np.zeros(100)
for i in range(0,100):
d[i]=np.sum((of-f[i,:,:])*(of-f[i,:,:]))
數(shù)組 d 通過依次計算待識別圖像特征值 of 與數(shù)據(jù)集 f 中各個特征值的歐氏距離得到。數(shù)據(jù)集 f 中依次存儲的是數(shù)字 0~9 的共計 100 個特征圖像的特征值。所以,數(shù)組 d 中的索引號對應(yīng)著各特征圖像的編號。例如,d[mn]表示待識別圖像與數(shù)字“m”的第 n 個特征圖像的距離。數(shù)組 d 的索引與特征圖像之間的對應(yīng)關(guān)系如表 20-2 所示。
如果將索引號整除 10,得到的值正好是其對應(yīng)的特征圖像上的數(shù)字。例如 d[34]對應(yīng)著待識別圖像到數(shù)字“3”的第 4 個特征圖像的歐式距離。而將 34 整除 10,得到 int(34/10) = 3,正好是其對應(yīng)的特征圖像上的數(shù)字。
確定了索引與特征圖像的關(guān)系,下一步可以通過計算索引達到數(shù)字識別的目的。
6. 獲取k個最短距離及其索引
從計算得到的所有距離中,選取 k 個最短距離,并計算出這 k 個最短距離對應(yīng)的索引。具體實現(xiàn)方式是:
- 每次找出最短的距離(最小值)及其索引(下標(biāo)),然后將該最小值替換為最大值。
- 重復(fù)上述過程 k 次,得到 k 個最短距離對應(yīng)的索引。
每次將最小值替換為最大值,是為了確保該最小值在下一次查找最小值的過程中不會再次被找到。
例如,要在數(shù)字序列“11, 6, 3, 9”內(nèi)依次找到從小到大的值。
- 第 1 次找到了最小值“3”,同時將“3”替換為“11”。此時,要查找的序列變?yōu)椤?1, 6,11, 9”。
- 第 2 次查找最小值時,在序列“11, 6, 11, 9”內(nèi)找到的最小值是數(shù)字“6”,同時將“6”替換為最大值“11”,得到序列“11,11,11,9”。
不斷地重復(fù)上述過程,依次在第 3 次找到最小值“9”,在第 4 次找到最小值“11”。當(dāng)然,
在本例中查找的是數(shù)值,具體實現(xiàn)時查找的是索引值。
根據(jù)上述思路,編寫代碼如下:
d=d.tolist()
temp=[]
Inf = max(d)
#print(Inf)
k=7
for i in range(k):
temp.append(d.index(min(d)))
d[d.index(min(d))]=Inf
7. 識別
根據(jù)計算出來的 k 個最小值的索引,結(jié)合表 20-2 就可以確定索引所對應(yīng)的數(shù)字。
具體實現(xiàn)方法是將索引值整除 10,得到對應(yīng)的數(shù)字。
例如,在 k =11 時,得到最小的 11 個值所對應(yīng)的索引依次為:66、60、65、63、68、69、67、78、89、96、32。它們所對應(yīng)的特征圖像如表 20-3 所示。
這說明,當(dāng)前待識別圖像與數(shù)字“6”的第 6 個特征圖像距離最近;接下來,距離最近的第 2 個特征圖像是數(shù)字“6”的第 0 個特征圖像(序號從 0 開始);
以此類推,距離最近的第 11個特征圖像是數(shù)字“3”的第 2 個特征圖像。
上述結(jié)果說明,與待識別圖像距離最近的特征圖像中,有 7 個是數(shù)字“6”的特征圖像。所以,待識別圖像是數(shù)字“6”。
下面討論如何通過程序識別數(shù)字。已知將索引整除 10,就能得到對應(yīng)特征圖像上的數(shù)字,因此對于上述索引整除 10:
(66, 60, 65, 63, 68, 69, 67, 78, 89, 96, 32)整除 10 = (6, 6, 6, 6, 6, 6,
6, 7, 8, 9, 3)
為了敘述上的方便,將上述整除結(jié)果標(biāo)記為 dr,在 dr 中出現(xiàn)次數(shù)最多的數(shù)字,就是識別結(jié)果。對于上例,dr 中“6”的個數(shù)最多,所以識別結(jié)果就是數(shù)字“6”。
這里我們借助索引判斷一組數(shù)字中哪個數(shù)字出現(xiàn)的次數(shù)最多:
- 建立一個數(shù)組 r,讓其元素的初始值都是 0。
- 依次從 dr 中取數(shù)字 n,將數(shù)組 r 索引位置為 n 的值加 1。
例如,從 dr 中取到的第 1 個數(shù)字為“6”,將 r[6]加上 1;從 dr 中取到第 2 個數(shù)字也為“6”,將 r[6]加上 1;以此類推,對于 dr=[6, 6, 6, 6, 6, 6, 6, 7, 8, 9, 3],得到數(shù)組 r 的值為[0, 0, 0, 1, 0, 0, 7, 1, 1, 1]。
在數(shù)組 r 中:
- r[0]=0,表示在 dr 中不存在值為 0 的元素。
- r[3]=1,表示在 dr 中有 1 個“3”。
- r[6]=7,表示在 dr 中有 7 個“6”。
- r[7]=1,表示在 dr 中有 1 個“7”。
根據(jù)上述思路,編寫代碼如下:
temp=[i/10 for i in temp]
# 數(shù)組 r 用來存儲結(jié)果,r[0]表示 K 近鄰中“0”的個數(shù),r[n]表示 K 近鄰中“n”的個數(shù)
r=np.zeros(10)
for i in temp:
r[int(i)]+=1
print('當(dāng)前的數(shù)字可能為:'+str(np.argmax(r)))
上述過程是分步驟的分析結(jié)果,以下是全部源代碼:
import time
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 讀取樣本(特征)圖像的值
start_time = time.time();
s='image_number\\' # 圖像所在路徑
num=100 # 樣本總數(shù)
row=240 # 特征圖像的行數(shù)
col=240 # 特征圖像的列數(shù)
a=np.zeros((num,row,col)) # 存儲所有樣本的數(shù)值
#print(a.shape)
n=0 # 存儲當(dāng)前圖像的編號
for i in range(0,10):
for j in range(1,11):
a[n,:,:]=cv2.imread(s+str(i)+'\\'+str(i)+'-'+str(j)+'.bmp',0)
n=n+1
#提采樣本圖像的特征
feature=np.zeros((num,round(row/5),round(col/5))) # 用來存儲所有樣本的特征值
#print(feature.shape) # 看看特征值的形狀是什么樣子
#print(row) # 看看 row 的值,有多少個特征值(100)
for ni in range(0,num):
for nr in range(0,row):
for nc in range(0,col):
if a[ni,nr,nc]==255:
feature[ni,int(nr/5),int(nc/5)]+=1
f=feature # 簡化變量名稱
#####計算當(dāng)前待識別圖像的特征值
o=cv2.imread('image_number\\test\\5.bmp',0) # 讀取待識別圖像
##讀取圖像值
of=np.zeros((round(row/5),round(col/5))) # 存儲待識別圖像的特征值
for nr in range(0,row):
for nc in range(0,col):
if o[nr,nc]==255:
of[int(nr/5),int(nc/5)]+=1
##計算待識別圖像與樣本圖像的距離
d=np.zeros((num,1)) # 存儲待識別圖像與樣本圖像的距離
for i in range(0,100):
d[i]=np.sum((of-f[i,:,:])*(of-f[i,:,:]))
#print(d)
d=d.tolist()
temp=[]
Inf = max(d)
#print(Inf)
k=7
for i in range(k):
temp.append(d.index(min(d)))
d[d.index(min(d))]=Inf
#print(temp) #看看都被識別為哪些特征值
temp=[i/10 for i in temp]
# 也可以返回去處理為 array,使用函數(shù)處理
#temp=np.array(temp)
#temp=np.trunc(temp/10)
#print(temp)
# 數(shù)組 r 用來存儲結(jié)果,r[0]表示 K 近鄰中“0”的個數(shù),r[n]表示 K 近鄰中“n”的個數(shù)
r=np.zeros(10)
for i in temp:
r[int(i)]+=1
#print(r)
print('當(dāng)前的數(shù)字可能為:'+str(np.argmax(r)))
print('識別所用時間為:'+str(time.time()-start_time)+'秒')
運行結(jié)果:文章來源:http://www.zghlxwxcb.cn/news/detail-652934.html
當(dāng)前的數(shù)字可能為:5
識別所用時間為:4.173201560974121秒
測試圖片下載地址 點擊下載文章來源地址http://www.zghlxwxcb.cn/news/detail-652934.html
到了這里,關(guān)于opencv-進階05 手寫數(shù)字識別原理及示例的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!