iT邦幫忙

2022 iThome 鐵人賽

1

這篇教學會使用 Keras 搭配 NumPy 訓練手寫數字模型,再搭配 OpenCV KNN 演算方法 ( cv2.ml.KNearest_load ),即時辨識出手寫的阿拉伯數字。

原文參考:辨識手寫數字

因為程式中的 OpenCV 會需要使用鏡頭或 GPU,所以請使用本機環境 ( 參考:使用 Python 虛擬環境 ) 或使用 Anaconda Jupyter 進行實作 ( 參考:使用 Anaconda ) ,並安裝 OpenCV 函式庫 ( 參考:OpenCV 函式庫 )。

Python 教學 - 辨識手寫數字

什麼是 KNN 演算法?

KNN 演算法的全名為 K Nearest Neighbor,也稱為 K-近鄰演算法,是機器學習中的一種演算法,意思是尋找 k 個最接近「某分類」的鄰居,透過這些鄰居來投票,以多數決定這個分類代表什麼,舉例來說,小明住的地方有 100 個鄰居,小明生了一個小孩不知道要叫什麼名字,於是一一詢問鄰居的意見,最後以多數鄰居的意見作為小孩的命名。

參考:K-近鄰演算法

以下圖為例,測試樣本 ( 綠色圓形 ) 在 k=3 ( 實線圓圈 ) 的狀態下,會被分配給紅色三角形 ( 因為紅色比較多 ),如果 k=5 ( 虛線圓圈 ) 的狀態下,會被分配給藍色正方形,何選擇一個最佳的 K 值取決於資料內容。一般情況下,在分類時較大的 K 值能夠減小雜訊的影響,但會使類別之間的界限變得模糊。

Python 教學 - 辨識手寫數字

安裝 Keras

Keras 是一個開放原始碼,可以進行神經網路學習的 Python 函式庫,在 2017 年,Google TensorFlow 的核心庫加入支援 Keras 的功能,爾後 Keras 常常會和 TensorFlow 互相搭配使用,許多功能也都必須建構在 TensorFlow 的基礎上運作。

參考「Jupyter 安裝 Tensorflow」文章,安裝 TensorFlow 後,就會一併安裝 Keras,簡單步驟說明如下 ( 如果已經安裝完成可略過此部分 ):

  • 建立名為 tensorflow 的虛擬環境

    conda create --name tensorflow python=3.9
    
  • 啟動虛擬環境

    conda activate tensorflow
    
  • 安裝 Jupyter

    conda install jupyter notebook
    
  • 安裝 tensorflow

    pip install tensorflow==2.5
    
  • 安裝 OpenCV 和 OpenCV 進階套件

    pip install opencv-python
    pip install opencv_contrib_python
    
  • 開啟 Anaconda,進入 tensorflow 虛擬環境,啟動 Jupyter

    Python 教學 - 辨識手寫數字

訓練手寫數字模型

訓練手寫數字模使用 keras 內建的「MNIST 手寫字符數據集」進行訓練,數據集內分成「訓練集」和「測試集」,訓練集有 60,000 張 28x28 像素灰度圖像,作為深度學習與訓練模型使用,測試集內有 10,000 同規格圖像,作為測試訓練模型使用,訓練後可以辨識手寫數字 0~9,下圖為訓練集的其中一部份手寫數字影像。

參考:MNIST 手寫字符數據集

Python 教學 - 辨識手寫數字

下方的程式碼執行後會進行模型訓練,訓練後會將模型儲存為 mnist_knn.xml ( 檔案大小約 200~250 MB ),儲存後會使用測試集進行測試,測試過程需要耗費幾分鐘的時間 ( 範例的測試結果準確度為 96.88% ),如果不想測試也可移除該部分程式碼,直接訓練與儲存模型。

import cv2
import numpy as np
from keras.datasets import mnist
from keras import utils

(x_train, y_train), (x_test, y_test) = mnist.load_data()  # 載入訓練集

# 訓練集資料
x_train = x_train.reshape(x_train.shape[0],-1)  # 轉換資料形狀
x_train = x_train.astype('float32')/255         # 轉換資料型別
y_train = y_train.astype(np.float32)

# 測試集資料
x_test = x_test.reshape(x_test.shape[0],-1)     # 轉換資料形狀
x_test = x_test.astype('float32')/255           # 轉換資料型別
y_test = y_test.astype(np.float32)

knn=cv2.ml.KNearest_create()                    # 建立 KNN 訓練方法
knn.setDefaultK(5)                              # 參數設定
knn.setIsClassifier(True)

print('training...')
knn.train(x_train, cv2.ml.ROW_SAMPLE, y_train)  # 開始訓練
knn.save('mnist_knn.xml')                       # 儲存訓練模型
print('ok')

print('testing...')
test_pre = knn.predict(x_test)                  # 讀取測試集並進行辨識
test_ret = test_pre[1]
test_ret = test_ret.reshape(-1,)
test_sum = (test_ret == y_test)
acc = test_sum.mean()                           # 得到準確率
print(acc)

根據模型,辨識手寫數字

已經訓練好 xml 模型檔後,就可以開始進行辨識,下方的程式碼會先取出一個正方形的區域,將這個區域的像素做二值化黑白的轉換 ( 因為手寫字通常是白底黑字,要轉換成黑底白字 ),轉換後將尺寸縮小到 28x28 進行辨識,就能得到手寫字的辨識結果,同時也會將辨識的影像顯示在原本影像的右上角。

import cv2
import numpy as np

cap = cv2.VideoCapture(0)                     # 啟用攝影鏡頭
print('loading...')
knn = cv2.ml.KNearest_load('mnist_knn.xml')   # 載入模型
print('start...')
if not cap.isOpened():
    print("Cannot open camera")
    exit()
while True:
    ret, img = cap.read()
    if not ret:
        print("Cannot receive frame")
        break
    img = cv2.resize(img,(540,300))          # 改變影像尺寸,加快處理效率
    x, y, w, h = 400, 200, 60, 60            # 定義擷取數字的區域位置和大小
    img_num = img.copy()                     # 複製一個影像作為辨識使用
    img_num = img_num[y:y+h, x:x+w]          # 擷取辨識的區域

    img_num = cv2.cvtColor(img_num, cv2.COLOR_BGR2GRAY)    # 顏色轉成灰階
    # 針對白色文字,做二值化黑白轉換,轉成黑底白字
    ret, img_num = cv2.threshold(img_num, 127, 255, cv2.THRESH_BINARY_INV)
    output = cv2.cvtColor(img_num, cv2.COLOR_GRAY2BGR)     # 顏色轉成彩色
    img[0:60, 480:540] = output                            # 將轉換後的影像顯示在畫面右上角

    img_num = cv2.resize(img_num,(28,28))   # 縮小成 28x28,和訓練模型對照
    img_num = img_num.astype(np.float32)    # 轉換格式
    img_num = img_num.reshape(-1,)          # 打散成一維陣列資料,轉換成辨識使用的格式
    img_num = img_num.reshape(1,-1)
    img_num = img_num/255
    img_pre = knn.predict(img_num)          # 進行辨識
    num = str(int(img_pre[1][0][0]))        # 取得辨識結果

    text = num                              # 印出的文字內容
    org = (x,y-20)                          # 印出的文字位置
    fontFace = cv2.FONT_HERSHEY_SIMPLEX     # 印出的文字字體
    fontScale = 2                           # 印出的文字大小
    color = (0,0,255)                       # 印出的文字顏色
    thickness = 2                           # 印出的文字邊框粗細
    lineType = cv2.LINE_AA                  # 印出的文字邊框樣式
    cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType) # 印出文字

    cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),3)  # 標記辨識的區域
    cv2.imshow('oxxostudio', img)
    if cv2.waitKey(50) == ord('q'):
        break     # 按下 q 鍵停止
cap.release()
cv2.destroyAllWindows()

Python 教學 - 辨識手寫數字

更多 Python 教學

大家好,我是 OXXO,是個即將邁入中年的斜槓青年,我已經寫了超過 400 篇 Python 的教學,有興趣可以參考下方連結呦~ ^_^


上一篇
( Day 30 ) 辨識是否戴口罩
下一篇
( Day 32 ) 「Python x AI 影像辨識好好玩」完賽心得~
系列文
Python x AI 影像辨識好好玩32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
neil410912021
iT邦新手 4 級 ‧ 2023-03-09 00:33:49

不好意思想問一下如果想要改變紅色區域辨識的大小,他的數值要怎麼改

0
BOYA
iT邦新手 5 級 ‧ 2023-08-09 12:26:14

您好,
我想詢問有關辨識問題,
因為大多的數字跟背景顏色其實都會相差蠻大的,
那如果假設我的數字是白色,
但背景是有點偏淺藍快接近白色的狀態的話,
會建議哪種方式比較好呢?

我要留言

立即登入留言