python+tensorflow2.x+opencv搭建真實場景下的手寫數字識別

初步成果

大概的正確率在百分之90左右,但模型訓練過程沒有做任何數據增強,所以模型仍可進一步加強
在這裏插入圖片描述

在這裏插入圖片描述

第一步訓練手寫數字模型

引入需要的庫文件

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras

在這裏numpy和pandas是在對數據處理時用到的庫文件
matplotlib 是python裏非常有名的畫圖庫
tensorflow是我們要訓練模型的主要框架
keras是tf的官方前端,這樣可以減少我們寫的代碼,讓邏輯更加清晰

讀取數據

數據這裏我們先用的官方MNIST數據進行訓練
在tensorflow keras裏直接定義了這個數據集的下載路徑,我們直接調用方法從網站上下載下來就可以

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()#在這裏我們調用這個方法後直接就可以把數據集分成訓練集和測試集,
x_train = x_train.reshape(-1,28,28,1)#在這裏因爲下面要通入模型時需要圖片數據是四維數據,這裏就把數據轉成四維,最後的一是指圖片通道數,因爲我們的圖片本來就是隻有一個通道(RGB有三個通道)所以直接寫成一
x_test = x_test.reshape(-1,28,28,1)
print(x_train.shape)#在這裏讀取各個數據集類型的形狀,查看一下有沒有問題
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)


"""
(60000, 28, 28, 1)
(60000,)
(10000, 28, 28, 1)
(10000,)
"""

這裏看出來訓練集有六萬張圖片(每張圖片28×28×1)
訓練集標籤有60000個每個都是0維(點嘛,一個數據就是零維)
測試集有一萬張圖片
測試集標籤也有一萬個

看一下數據什麼樣

def show_single_image(img_arr):#這裏定義了一個函數用來查看圖片
    img_arr = img_arr.reshape(28,28)#把輸入進來的數據都變成28×28的這樣才能展示數據
    plt.imshow(img_arr,cmap = "binary")#顯示圖片,具體的話可以看我matplotlib的教程
    plt.show()
show_single_image(x_train[0])#這裏我們輸入的訓練集的第一張圖片,可以通過中括號裏的數字看其他不同的數據

數據歸一化

x_train = x_train/255.
x_test = x_test/255.

把數據都變成0-1的範圍這樣方便訓練,提高收斂速度

搭建模型

model = keras.models.Sequential() #先生成一個模型框架
model.add(keras.layers.Conv2D(filters = 128,#在這裏定義了一個卷積核的數量
                              kernel_size = 3,
                              padding = 'same',#這裏選擇模式是same也就是說卷積層不會改變模型大小
                              activation = 'selu',#激活函數選擇selu這是一個自帶歸一化的激活函數
                              input_shape = (28,28,1)#這裏設置輸入數據大小
                              ))
model.add(keras.layers.SeparableConv2D(filters = 128,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.MaxPool2D(pool_size = 2))#池化層在這裏我們只規定了池化層核心的大小
model.add(keras.layers.SeparableConv2D(filters = 256,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.SeparableConv2D(filters = 256,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.MaxPool2D(pool_size = 2))
model.add(keras.layers.SeparableConv2D(filters = 512,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.SeparableConv2D(filters = 512,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.MaxPool2D(pool_size = 2))
model.add(keras.layers.Flatten())#在上幾個層次中我們輸出的數據仍然是三維的,在這裏要把數據展平變成一維
model.add(keras.layers.Dense(128,activation = 'selu'))#全連接沒什麼可多說的,具體參照多層感知機模型
model.add(keras.layers.Dense(10,activation = 'softmax'))#softmax將輸入數值變成概率的模式

固化模型

model.compile(optimizer='adam',#求解模型的方法
             loss='sparse_categorical_crossentropy',#損失函數
             metrics=['accuracy'])#評判模型的方法

訓練模型

history = model.fit(#history用來接收訓練過程中的訓練過程參數
					x_train, y_train,# 訓練集的數據和標籤
					epochs = 5,  #訓練5遍
                    validation_data = (x_test,y_test) )#實時展示模型訓練情況

在這裏插入圖片描述

看一下我們的訓練成果

def plot_learning_curves(history):
    pd.DataFrame(history.history).plot(figsize=(8,5))
    plt.grid(True)#畫網格
    plt.gca().set_ylim(0,1.5)
    plt.show()
plot_learning_curves(history)#(輸入訓練時的返回值)

在這裏插入圖片描述

用模型預測一個數據

def predict_data(test_data):#定義一個函數
    pred = model.predict(test_data.reshape(-1,28,28,1))#先把數據轉化成之前我們訓練集數據一樣的通入方式
    return np.argmax(pred)#返回輸出神經元中最大值的索引
show_single_image(x_test[0])
print("模型的預測結果是:",predict_data(x_test[0]))

在這裏插入圖片描述

保存模型

model.save('my_model.h5')

好啦現在我們就有了作爲識別的手寫數字識別分類器的模型

opencv計算機視覺部分

先引入必須的庫文件

import tensorflow as tf
from tensorflow import keras
import cv2
import numpy as np
import matplotlib.pyplot as plt

必要的全局變量

font = cv2.FONT_HERSHEY_SIMPLEX#我們設置寫數字的字體
model =keras.models.load_model('my_model.h5') #讀取之前訓練好的數據模型
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))#
image_path = "z.png"#在這裏寫入圖片路徑

寫一個函數用來查看圖片

def look_image(data):
    plt.figure()#重新顯示一張圖圖片在一個獨立窗口
    data = cv2.cvtColor(data, cv2.COLOR_BGR2RGB)#因爲opencv是bgr模式所以轉換成rgb模式,使用cv的顯示圖片方法也可以,不過有的系統可能不支持爲了兼容性我們採用這種方法,真實部署裏可能壓根不需要顯示圖片
    plt.imshow(data)#顯示圖片

讀取圖片

image = cv2.imread(image_path)#讀取圖片
look_image(image)#顯示圖片

在這裏插入圖片描述

改變圖片大小

可以看到圖片大小是1000多×1000多,這裏數據太大了
我們把它壓縮到250×250
這樣不僅數據量小了,而且小的點狀噪聲也被去除了

image_ =  cv2.resize(image, (250,250), interpolation = cv2.INTER_AREA)#在這裏用了image_接收變量,因爲後面還要用到這個數據
look_image(image_)#顯示圖片

在這裏插入圖片描述

灰度化處理

image = cv2.cvtColor(image_, cv2.COLOR_BGR2GRAY)#灰度化處理

在這裏插入圖片描述
在這裏可能看不出什麼,但圖片通道數變成了1

邊緣檢測

img_w = cv2.Sobel(image,cv2.CV_16S,0,1)#Sobel濾波,邊緣檢測(橫)
img_h = cv2.Sobel(image,cv2.CV_16S,1,0)#Sobel濾波,邊緣檢測(豎)這裏我試過橫豎邊緣一起提取效果不太好
img_w = cv2.convertScaleAbs(img_w)
_, img_w = cv2.threshold(img_w,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)#二值化數據
look_image(img_w)
img_h = cv2.convertScaleAbs(img_h)
_, img_h = cv2.threshold(img_h,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)
look_image(img_h)
image = img_w + img_h#把兩個邊緣檢測結果進行整合,免得出現數字斷頭現象
look_image(image)

在這裏插入圖片描述
在這裏插入圖片描述

閉運算

在前面的圖片裏可以看到數字中間有小的洞洞,這會影響我們之後的模型判斷所以這裏用閉運算去除這些

image = cv2.morphologyEx(image,cv2.MORPH_CLOSE,kernel)#這裏的kernel是之前定義的
look_image(image)

在這裏插入圖片描述

在圖片外圍加上邊框

後面獲取的邊框是緊貼着數字的,但訓練集不是這樣,所以我們要手動擴大邊框,爲了防止數字本就出現在邊緣的情況所以給圖片加個框

#在這裏就是創建幾個numpy數組把它和數據進行合併
temp_data = np.zeros((250,10))
image = np.concatenate((temp_data,image,temp_data),axis = 1)
temp_data = np.zeros((10,270))
image = np.concatenate((temp_data,image,temp_data),axis = 0)
image = cv2.convertScaleAbs(image)
look_image(image)

在這裏插入圖片描述

獲取邊框

contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

最後一步,識別加標註

for _ in contours:#對邊框進行便利
    x,y,w,h = cv2.boundingRect(_)#獲取邊框的座標和長寬
    if w*h  < 100:#定義一下面積,太小的邊框直接捨棄
        continue
    img_model = image[y-10:y+h+10,x-10:x+w+10]#手動擴大邊框並截取
    img_model =  cv2.resize(img_model, (28,28), interpolation = cv2.INTER_AREA)#將截取到的數字改變大小(28×28)因爲我們訓練的時候圖片大小就是28×28
    img_model = img_model/255#數據歸一化
    predict = model.predict(img_model.reshape(-1,28,28,1))#用神經網絡進行判斷
    if np.max(predict) > 0.5:#只顯示置信率百分之50以上的數據
        data_predict = str(np.argmax(predict))#將得到的神經網絡輸出最大的索引轉換成字符串(後面寫必須要字符串形式)
        image_z = cv2.rectangle(image_,(x-10,y-10),(x + w-10,y + h-10),(255,0,0),1)#畫框
        image_z = cv2.putText(image_z,data_predict , (x+10, y+10), font, 0.7, (0, 0, 255), 1)#寫文字
look_image(image_z)#顯示圖片
save = cv2.imwrite( "image_predict.png",image_z)#保存圖片

在這裏插入圖片描述
好到此就全都結束了

全部程序

模型訓練代碼

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import pandas as pd
from tensorflow import keras
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(-1,28,28,1)
x_test = x_test.reshape(-1,28,28,1)
print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)
def show_single_image(img_arr):
    img_arr = img_arr.reshape(28,28)
    plt.imshow(img_arr,cmap = "binary")
    plt.show()
x = 0
show_single_image(x_train[x])
x_train = x_train/255.
x_test = x_test/255.
model = keras.models.Sequential() #先生成一個模型框架
model.add(keras.layers.Conv2D(filters = 128,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              input_shape = (28,28,1)
                              ))
model.add(keras.layers.SeparableConv2D(filters = 128,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.MaxPool2D(pool_size = 2))
model.add(keras.layers.SeparableConv2D(filters = 256,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.SeparableConv2D(filters = 256,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.MaxPool2D(pool_size = 2))
model.add(keras.layers.SeparableConv2D(filters = 512,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.SeparableConv2D(filters = 512,
                              kernel_size = 3,
                              padding = 'same',
                              activation = 'selu',
                              ))
model.add(keras.layers.MaxPool2D(pool_size = 2))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(128,activation = 'selu'))
model.add(keras.layers.Dense(10,activation = 'softmax'))
model.compile(optimizer='adam',#求解模型的方法
             loss='sparse_categorical_crossentropy',
             metrics=['accuracy'])
history = model.fit(x_train, y_train,epochs = 5, #history用來接收訓練過程中的一些參數數值 #訓練的參數#訓練5遍
                    validation_data = (x_test,y_test) )#實時展示模型訓練情況   
def plot_learning_curves(history):
    pd.DataFrame(history.history).plot(figsize=(8,5))
    plt.grid(True)
    plt.gca().set_ylim(0,1.5)
    plt.show()
plot_learning_curves(history)#(輸入訓練時的返回值)
def predict_data(test_data):
    pred = model.predict(test_data.reshape(-1,28,28,1))
    return np.argmax(pred)
show_single_image(x_test[0])
print("模型的預測結果是:",predict_data(x_test[0]))
model.save('my_model.h5')                             

視覺部分代碼

import tensorflow as tf
from tensorflow import keras
import cv2
import numpy as np
import matplotlib.pyplot as plt
font = cv2.FONT_HERSHEY_SIMPLEX
model =keras.models.load_model('my_model.h5') #讀取網絡
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
image_path = "z.png"#在這裏寫入圖片路徑
def look_image(data):
    plt.figure()
    data = cv2.cvtColor(data, cv2.COLOR_BGR2RGB)
    plt.imshow(data)
image = cv2.imread(image_path)#讀取圖片
image_ =  cv2.resize(image, (250,250), interpolation = cv2.INTER_AREA)
image = cv2.cvtColor(image_, cv2.COLOR_BGR2GRAY)#灰度化處理
img_w = cv2.Sobel(image,cv2.CV_16S,0,1)#Sobel濾波,邊緣檢測
img_h = cv2.Sobel(image,cv2.CV_16S,1,0)#Sobel濾波,邊緣檢測
img_w = cv2.convertScaleAbs(img_w)
_, img_w = cv2.threshold(img_w,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)
img_h = cv2.convertScaleAbs(img_h)
_, img_h = cv2.threshold(img_h,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)
image = img_w + img_h
image = cv2.morphologyEx(image,cv2.MORPH_CLOSE,kernel)
temp_data = np.zeros((250,10))
image = np.concatenate((temp_data,image,temp_data),axis = 1)
temp_data = np.zeros((10,270))
image = np.concatenate((temp_data,image,temp_data),axis = 0)
image = cv2.convertScaleAbs(image)
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for _ in contours:
    x,y,w,h = cv2.boundingRect(_)
    if w*h  < 100:
        continue
    img_model = image[y-10:y+h+10,x-10:x+w+10]
    img_model =  cv2.resize(img_model, (28,28), interpolation = cv2.INTER_AREA)
    img_model = img_model/255
    predict = model.predict(img_model.reshape(-1,28,28,1))
    if np.max(predict) > 0.5:
        data_predict = str(np.argmax(predict))
        image_z = cv2.rectangle(image_,(x-10,y-10),(x + w-10,y + h-10),(255,0,0),1)
        image_z = cv2.putText(image_z,data_predict , (x+10, y+10), font, 0.7, (0, 0, 255), 1)
look_image(image_z)
save = cv2.imwrite( "image_predict2.png",image_z)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章