在計算機視覺領域,數字識別是一個經典問題,廣泛應用于郵政編碼識別、車牌識別等場景。本文將介紹如何使用 OpenCV 進行圖像處理,并結合 KNN(K 近鄰)算法實現數字識別,同時對比 OpenCV 內置 KNN 與 scikit-learn 庫中 KNN 的實現差異。
準備工作
首先,我們需要導入所需的庫:
import numpy as np
import cv2
from sklearn.neighbors import KNeighborsClassifier
其中,numpy
用于數值計算,cv2
(OpenCV)用于圖像處理,KNeighborsClassifier
則是 scikit-learn 庫中的 KNN 分類器。
圖像讀取與預處理
我們需要讀取兩張圖像:一張包含大量數字樣本的訓練圖像,另一張作為測試圖像。
# 讀取訓練圖像和測試圖像
img = cv2.imread(r'D:\pythonProject11\class\aa.png')
c_img = cv2.imread(r'D:\pythonProject11\class\ccc.png')# 將彩色圖像轉換為灰度圖像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
c_gray = cv2.cvtColor(c_img, cv2.COLOR_BGR2GRAY)
彩色圖像包含 RGB 三個通道,轉換為灰度圖像可以減少計算量,同時保留圖像的主要特征。cv2.cvtColor
函數用于顏色空間轉換,COLOR_BGR2GRAY
參數表示從 BGR 格式(OpenCV 默認的彩色圖像格式)轉換為灰度格式。
數據準備
圖像分割
我們假設訓練圖像是一個包含 50 行 100 列數字的網格圖像,每個數字占據一個 20×20 像素的區域。我們需要將這個大圖像分割成多個小圖像,每個小圖像對應一個數字樣本。
python
運行
# 先垂直分割成50行,再對每行水平分割成100列
cell = np.array([np.hsplit(row, 100) for row in np.split(gray, 50)])
這里使用了np.split
和np.hsplit
兩個函數:
np.split(gray, 50)
將灰度圖像垂直分割成 50 個等高度的子數組np.hsplit(row, 100)
將每個子數組水平分割成 100 個等寬度的子數組
最終得到的cell
是一個形狀為 (50, 100, 20, 20) 的數組,表示 50 行 100 列,每個元素是 20×20 像素的數字圖像。
訓練集和測試集劃分
我們將前 50 列作為訓練集,后 50 列作為測試集:
python
運行
# 劃分訓練集和測試集
train_ma = cell[:, :50] # 前50列作為訓練集
test_ma = cell[:, 50:100] # 后50列作為測試集# 轉換為二維數組(樣本數×特征數)
train_ma = train_ma.reshape(-1, 400).astype(np.float32) # 50×50=2500個樣本,每個樣本20×20=400個特征
test_ma = test_ma.reshape(-1, 400).astype(np.float32)
reshape(-1, 400)
將每個 20×20 的圖像轉換為一個長度為 400 的一維數組,便于作為機器學習算法的輸入。-1
表示自動計算該維度的大小,這里計算結果為 2500(50×50)。
標簽生成
我們需要為每個樣本生成對應的標簽(即該樣本對應的數字)。假設圖像中的數字是按 0-9 的順序重復排列的:
python
運行
# 生成標簽
kernel = np.arange(10) # 生成0-9的數字
train_la = np.repeat(kernel, 250)[:, np.newaxis] # 每個數字重復250次,形成2500個標簽
test_la = np.repeat(kernel, 250)[:, np.newaxis]
np.repeat(kernel, 250)
將 0-9 每個數字重復 250 次,得到一個長度為 2500 的數組,與我們的樣本數量一致。[:, np.newaxis]
將一維數組轉換為二維列向量,以滿足 OpenCV 中 KNN 算法對標簽格式的要求。
測試圖像預處理
我們需要對測試圖像進行同樣的預處理:
python
運行
# 預處理測試圖像
c_test = np.array(c_gray).reshape(-1, 400).astype(np.float32)
使用 OpenCV 的 KNN 進行識別
OpenCV 庫中內置了 KNN 算法的實現:
python
運行
# 創建并訓練OpenCV的KNN模型
knn = cv2.ml.KNearest_create()
knn.train(train_ma, cv2.ml.ROW_SAMPLE, train_la) # ROW_SAMPLE表示每行是一個樣本# 預測
ret, results, neigh, dist = knn.findNearest(c_test, 4) # 尋找4個最近鄰
print("OpenCV KNN預測結果:", results)
cv2.ml.KNearest_create()
創建一個 KNN 模型實例,train
方法用于訓練模型,findNearest
方法用于預測。findNearest
的第二個參數表示要尋找的最近鄰數量 K。
使用 scikit-learn 的 KNN 進行識別
我們也可以使用 scikit-learn 庫中的 KNN 實現:
python
運行
# 為scikit-learn準備標簽(一維數組)
train_la = np.repeat(kernel, 250)
test_la = np.repeat(kernel, 250)# 創建并訓練scikit-learn的KNN模型
knnl = KNeighborsClassifier(n_neighbors=5) # K=5
knnl.fit(train_ma, train_la)# 評估模型準確率
a = knnl.score(test_ma, test_la)
print("模型準確率:", a)# 預測
b = knnl.predict(c_test)
print("scikit-learn KNN預測結果:", b)
scikit-learn 的 KNN 使用起來更加簡潔,KNeighborsClassifier
的n_neighbors
參數指定 K 值,fit
方法用于訓練,score
方法用于評估模型準確率,predict
方法用于預測。
總代碼
import numpy as np
import cv2
from sklearn.neighbors import KNeighborsClassifier
img = cv2.imread(r'D:\pythonProject11\class\aa.png')
c_img = cv2.imread(r'D:\pythonProject11\class\ccc.png')
# c_img = cv2.imread('img_1.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#轉化為灰度圖像
c_gray = cv2.cvtColor(c_img,cv2.COLOR_BGR2GRAY)
c_test = np.array(c_gray).reshape(-1,400).astype(np.float32)cell =np.array([np.hsplit(row,100) for row innp.split(gray,50)])
train_ma = cell[:,:50]
test_ma = cell[:,50:100]
train_ma = train_ma.reshape(-1,400).astype(np.float32)
test_ma = test_ma.reshape(-1,400).astype(np.float32)
kernel = np.arange(10)
train_la = np.repeat(kernel,250)[:,np.newaxis]#cv2的方法
test_la = np.repeat(kernel,250)[:,np.newaxis]
knn = cv2.ml.KNearest_create()
knn.train(train_ma,cv2.ml.ROW_SAMPLE,train_la)#cv2.mL.ROW_SAMPLE:這是一個標志,告訴0pencv訓練數據是按行組織的,即每一行是一個樣本。
ret, results, neigh, dist = knn.findNearest(c_test,4)
print(results)
train_la = np.repeat(kernel,250)#sklearn的方法
test_la = np.repeat(kernel,250)
knnl = KNeighborsClassifier(n_neighbors=5)
knnl.fit(train_ma,train_la)
a=knnl.score(test_ma,test_la)
b=knnl.predict(c_test)
print(a,b)
兩種實現的對比
接口設計:OpenCV 的 KNN 接口更偏向于計算機視覺領域的使用習慣,而 scikit-learn 的接口則更符合機器學習的通用范式。
輸入格式:OpenCV 的 KNN 要求標簽是列向量,而 scikit-learn 的 KNN 要求標簽是一維數組。
功能:scikit-learn 的 KNN 提供了更多的評估方法和參數設置,而 OpenCV 的 KNN 則更輕量,與圖像處理功能結合更緊密。
結果:在相同的 K 值設置下,兩種實現的預測結果應該是相似的,但可能會因為具體實現細節的不同而略有差異。
總結
本文介紹了如何使用 OpenCV 進行圖像預處理,以及如何使用 KNN 算法實現數字識別。通過對比 OpenCV 和 scikit-learn 中 KNN 的實現,我們可以看到不同庫在接口設計和使用方式上的差異。
在實際應用中,我們可以根據具體需求選擇合適的庫和算法。如果需要處理圖像并進行簡單的分類,OpenCV 的 KNN 可能是一個不錯的選擇;如果需要更復雜的機器學習功能和更全面的評估方法,scikit-learn 則更為適合。
此外,KNN 算法雖然簡單易懂,但在處理大規模數據集時效率較低。在實際應用中,我們可能需要考慮使用更高效的算法,如 SVM、神經網絡等,以獲得更好的性能。