OpenCV 玩九宮格數獨(二):knn 數字識別
前言
首先需要說明,這裡所說的數字識別不是手寫數字識別!
但凡對機器學習有所瞭解的人,相信看到數字識別的第一反應就是MNIST。MNIST是可以進行數字識別,但是那是手寫數字。我們現在要做的是要識別從九宮格圖片中提取出來的印刷體的數字。手寫數字集訓練出來的模型用來識別印刷體數字,顯然不太專業。而且手寫體跟印刷體相差不小,我們最看重的正確率問題不能保證。
本文從零開始做一遍數字識別,展示了數字識別的完整流程。從收集資料開始,到資料預處理,再到訓練KNN,最後進行數字識別。
我們一步一步來說。
資料收集
為了便於處理,我百度找到了10張下面這樣按照1-9-0順序排列的圖片,作為我們的初始資料集。
有的圖片可能本來除數字區域外,周圍空白部分比較多。為了便於處理,首先用windows自帶的畫圖軟體把圖片裁剪成上面這樣只包含數字區域的樣子。
這十張資料集基本涵蓋了印刷數字體的不同樣式、字型,而且顏色、背景甚至漸變方式都各不相同。
資料處理
顯然,我們第一步要做的就是上一節的內容,那就是把圖片中的數字分別提取出來。
訓練knn,還有其他任何有監督的機器學習模型,不光要有樣本資料,還要有知道每一個樣本對應的標籤。這也是為什麼我要選擇上面這樣按順序排列的數字圖片。
提取數字之後,我們可以對每一個數字的位置進行排序,然後根據位置資訊可以知道每一個數字是幾。標籤也就由此生成了。
這一部分的內容可以分兩部分來說,第一部分就是提取數字,第二部分是提取數字之後的資料預處理。
1.提取數字
提取數字的處理流程與上一篇內容差不多:
1.遍歷資料夾下的原始數字圖片;
2.對每一張圖片進行輪廓提取操作,只提取外圍輪廓(參考上一節講解);
img_path = gb.glob("numbers\\*") k = 0 labels = [] samples = [] for path in img_path: img = cv2.imread(path) gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray,(5,5),0) thresh = cv2.adaptiveThreshold(blur,255,1,1,11,2) image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
3.求輪廓外包矩形,並根據矩形大小資訊篩選出所有的數字輪廓;
4.然後根據位置資訊對數字框排序,顯然第一排依次是12345,第二排依次是67890;
height,width = img.shape[:2]
w = width/5
rect_list = []
list1 = []
list2 = []
for cnt in contours:
#if cv2.contourArea(cnt)>100:
[x,y,w,h] = cv2.boundingRect(cnt)
if w>30 and h > (height/4):
if y < (height/2):
list1.append([x,y,w,h])
else:
list2.append([x,y,w,h])
list1_sorted = sorted(list1,key = lambda t : t[0])
list2_sorted = sorted(list2,key = lambda t : t[0])
5.提取出每一個數字所在的矩形框,作為ROI取出。
for i in range(5):
[x1,y1,w1,h1] = list1_sorted[i]
[x2,y2,w2,h2] = list2_sorted[i]
number_roi1 = gray[y1:y1+h1, x1:x1+w1] #Cut the frame to size
number_roi2 = gray[y2:y2+h2, x2:x2+w2] #Cut the frame to size
資料預處理
為了加快訓練速度,我們不用原圖作為輸入,而是對每一個數字原圖做一定的處理。此處可選方案很多,提取特徵有很多經典特徵可選,也可以是自己設計的特徵。
這裡我用比較簡單的方法,把每一張數字圖片ROI轉換為二值影象。大致流程是這樣的:
1.把每一張ROI大小統一變換為40 x 20。
2.閾值分割。
resized_roi1=cv2.resize(number_roi1,(20,40))
thresh1 = cv2.adaptiveThreshold(resized_roi1,255,1,1,11,2)
resized_roi2=cv2.resize(number_roi2,(20,40))
thresh2 = cv2.adaptiveThreshold(resized_roi2,255,1,1,11,2)
3.把二值影象轉換為0-1二值影象。
4.把處理完的數字圖片儲存到對應數字的資料夾中。(此為中間過程,可註釋掉)
number_path1 = "number\\%s\\%d" % (str(i+1),k) + '.jpg'
j = i+6
if j ==10:
j = 0
number_path2 = "number\\%s\\%d" % (str(j),k) + '.jpg'
k+=1
normalized_roi1 = thresh1/255.
normalized_roi2 = thresh2/255.
cv2.imwrite(number_path1,thresh1)
cv2.imwrite(number_path2,thresh2)
處理完之後儲存的資料夾如下:
每一個資料夾裡面類似這樣,可以看到背景有黑有白,數字也是有黑有白:
5.把處理完的二值影象展開成一行。
6.最後把展開成的一行行樣本儲存起來作為訓練用的資料。
7.對應的,把數字標籤按照數字的儲存順序對應儲存成訓練用的資料。
sample1 = normalized_roi1.reshape((1,800))
samples.append(sample1[0])
labels.append(float(i+1))
sample2 = normalized_roi2.reshape((1,800))
samples.append(sample2[0])
labels.append(float(j))
import numpy as np
samples = np.array(samples,np.float32)
labels = np.array(labels,np.float32)
labels = labels.reshape((labels.size,1))
np.save('samples.npy',samples)
np.save('label.npy',labels)
訓練kNN識別數字
這裡用opencv自帶的knn演算法實現。我同時嘗試了opencv自帶的神經網路和SVM,發現還是kNN的效果最好。有興趣的可以自己去嘗試一下。也可能是我引數沒調好。
這裡的流程是:
1.載入上面儲存的樣本和標籤資料;
2.分別用80個作為訓練資料,20個作為測試資料;
3.用opencv自帶的knn訓練模型;
4.用訓練好的模型識別測試資料中的數字;
5.輸出預測值和實際標籤值。
import numpy as np
import cv2
samples = np.load('samples.npy')
labels = np.load('label.npy')
k = 80
train_label = labels[:k]
train_input = samples[:k]
test_input = samples[k:]
test_label = labels[k:]
model = cv2.ml.KNearest_create()
model.train(train_input,cv2.ml.ROW_SAMPLE,train_label)
retval, results, neigh_resp, dists = model.findNearest(test_input, 1)
string = results.ravel()
print(test_label.reshape(1,len(test_label))[0])
print(string)
下面是輸出結果:
可以看到,預測值和實際值簡直一模一樣!
注意
1.opencv中的knn只能訓練模型,不能儲存和載入模型。所以只能用的時候訓練,訓練好直接用。
2.此次訓練樣本只有不到一百,暫時只能保證對於本系列文章自帶的九宮格圖片進行完美的數字識別。其他圖片的數字識別準確率不敢保證。如果想要得到更好的效果,請按照機器學習的方法進行優化,或進行更好的資料與處理,或加大資料集等。
3.整個專案程式碼會在下一篇,也就是最終篇之後放出。