歡迎大家關注騰訊雲技術社區-博客園官方主頁,我們將持續在博客園為大家推薦技術精品文章哦~
作者:劉瀟龍
前言
首先需要說明,這里所說的數字識別不是手寫數字識別!
但凡對機器學習有所了解的人,相信看到數字識別的第一反應就是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.整個項目代碼會在下一篇,也就是最終篇之后放出。
相關推薦:
OpenCV玩九宮格數獨(一):九宮格圖片中提取數字
OpenCV檢測篇(一)——貓臉檢測
OpenCV檢測篇(二)——笑臉檢測
此文已由作者授權騰訊雲技術社區發布,轉載請注明文章出處
原文鏈接:https://www.qcloud.com/community/article/901168
獲取更多騰訊海量技術實踐干貨,歡迎大家前往騰訊雲技術社區