环境:Pycharm + python3.7 + opencv
目录
1. 准备工作:
1.1 添加相关库
numpy
sklearn
keras 2.3.1
opencv-python
matplotlib 3.5.1
tensorflow 2.0.0
2. 主要代码
2.1 导入相关库
import numpy as np
import cv2
import os
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from keras.preprocessing.image import ImageDataGenerator
from keras.utils.np_utils import to_categorical
from keras.models import Sequential
from keras.layers.convolutional import Conv2D,MaxPooling2D
from keras.layers import Dropout,Flatten,Dense
from keras.optimizers import Adam
import pickle
2.2 导入数据图片及其标签,并检测是否导入成功
##################################
path = 'myData'
##################################
images = [] # 存放图片
classNo = [] # classNumber:存放类别号(图片标签)
myList = os.listdir(path)
print("Total No of Classes Detected", len(myList)) # 查看文件夹总数(类别总数:有10类)
noOfClass = len(myList) # numberOfClass:类别总数
print("Importing Classes ......")
for x in range(0, noOfClass): # 遍历每个类别的文件夹
myPicList = os.listdir(path+"/"+str(x)) # 于 当前类别的文件夹 的列表
for y in myPicList: # 遍历 当前类别文件夹 中的img
curImg = cv2.imread(path+"/"+str(x)+"/"+str(y))
curImg = cv2.resize(curImg, (32, 32))
images.append(curImg) # 存入当前图片
classNo.append(x) # 存入当前图片的标签
print(x, end=" ") # 打印出当前类别
print("\n")
print(len(images)) # 检查共导入了多少张image
print(len(classNo)) # 检查共导入了多少个image标签
输出:
2.3 将数据及标签转换成numpy数组
images = np.array(images)
classNo = np.array(classNo)
print(images.shape) # 由此可看出image经转换后是 32*32且3通道
print(classNo.shape)
2.4 拆分数据集(训练集,测试集,验证集)
# 将数据分割成训练集和测试集(将20%的数据用作测试集)
X_train,X_test,Y_train,Y_test = train_test_split(images, classNo,
test_size= testRatio)
print(X_train.shape) # 打印拆分后训练集的shape
print(X_test.shape) # 打印拆分后测试集的shape
# 将数据分割成训练集和测试集(将20%的数据用作测试集)
X_train,X_test,Y_train,Y_test = train_test_split(images, classNo,
test_size= testRatio)
# 将分割后的测试数据进一步分割出20%用作验证集
X_train,X_validation,Y_train,Y_validation = train_test_split(X_train, Y_train,
test_size= valRatio)
print(X_train.shape) # 打印拆分后训练集的shape
print(X_test.shape) # 打印拆分后测试集的shape
print(X_validation.shape) # 打印拆分后验证集的shape
如图:
第一行:原来数据集的shape
第二行:拆分后训练集的shape
第三行:拆分后测试集的shape
第四行:拆分后验证集的shape
2.5 绘制柱状图
绘制各标签所拥有图数的柱状图
# print(np.where(Y_train==0)) # 返回类别为0的数据 的所有索引
# print(len(np.where(Y_train==0)[0])) # 返回类别为0的数据 的索引总数总长度(多少个)
numOfSamples = [] # 存放类别索引总长(样本总数)
for x in range(0, noOfClass):
# print(len(np.where(Y_train == x)[0])) # 返回当前类别数据 的索引总数总长度(多少个)
numOfSamples.append(len(np.where(Y_train == x)[0])) # 添加当前类别的样本数
print(numOfSamples)
plt.figure(figsize=(10, 5)) # 生成图框
plt.bar(range(0,noOfClass), numOfSamples) # 绘制柱状图(画条条)
plt.title("No of Image for each Class (各标签的图数)")
plt.xlabel("Class ID (标签)")
plt.ylabel("Number of Image (图数)")
plt.show()
2.6 对图片进行预处理
########### 对图片进行 预处理 ##################################
def preProcessing(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.equalizeHist(img) # 直方图均衡化处理(图像增强,提升图像质量)
img = img/255 # 将值控制在0到1之间
return img
img = preProcessing(X_train[25])
img = cv2.resize(img,(300,300))
cv2.imshow("PreProcessing", img)
cv2.waitKey(0)
2.6.1 将数据依次进行预处理:
print(X_train[25].shape)
# 将训练集的图依次进行预处理
X_train = np.array(list(map(preProcessing, X_train)))
img = X_train[25]
img = cv2.resize(img,(300,300))
cv2.imshow("PreProcessing", img)
cv2.waitKey(0)
print(X_train[25].shape)
X_train = np.array(list(map(preProcessing, X_train))) # 将训练集的图依次进行预处理
X_test = np.array(list(map(preProcessing, X_test))) # 将测试集的图依次进行预处理
X_validation = np.array(list(map(preProcessing, X_validation))) # 将验证集的图依次进行预处理
2.6.2 重塑数据,给其增加一个深度:
print(X_train.shape) # 重塑前的shape
# 重塑数据,给其增加一个深度
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)
print(X_train.shape) # 重塑后的shape
# 重塑数据,给其增加一个深度
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], X_test.shape[2], 1)
X_validation = X_validation.reshape(X_validation.shape[0], X_validation.shape[1], X_validation.shape[2], 1)
2.6.3 为定义模型做准备(图像生成器)
# 图像生成器:对每批次的训练图片适时进行数据增强(可增强模型的泛化能力)
dataGen = ImageDataGenerator(width_shift_range=0.1, # 随机水平位移
height_shift_range=0.1, # 随机垂直位移
zoom_range=0.2, # 随机缩放
shear_range=0.1, # 随机剪裁
rotation_range=10) # 随机旋转角度
dataGen.fit(X_train) # 使用实时数据增益的批数据 对模型进行拟合
# 将标签向量转换为0 1的矩阵类型表示
Y_train = to_categorical(Y_train, noOfClass)
Y_test = to_categorical(Y_test, noOfClass)
Y_validation = to_categorical(Y_validation, noOfClass)
2.7 定义模型
def myModel():
# 基于LetNet模型
noOfFilters = 60 # number Of Filters:过滤器数量
sizeOfFilter1 = (5,5) # 过滤器1(较大那个)
sizeOfFilter2 = (3,3) # 过滤器2(较小那个)
sizeOfPool = (2,2) # 池化窗口
noOfNode = 500 # 节点数
model = Sequential()
model.add((Conv2D(noOfFilters, sizeOfFilter1,
input_shape=(imageDimensions[0],
imageDimensions[1], 1),
activation='relu')))
model.add((Conv2D(noOfFilters, sizeOfFilter1, activation='relu')))
model.add(MaxPooling2D(pool_size=sizeOfPool))
model.add((Conv2D(noOfFilters//2, sizeOfFilter2, activation='relu'))) # 过滤器减半
model.add((Conv2D(noOfFilters//2, sizeOfFilter2, activation='relu')))
model.add(MaxPooling2D(pool_size=sizeOfPool))
model.add(Dropout(0.5)) # 随机失活
model.add(Flatten()) # 将得到的特征映射“压平”
model.add(Dense(noOfNode, activation='relu')) # 全连接层
model.add(Dropout(0.5))
model.add(Dense(noOfClass, activation='softmax'))
# 为模型设置优化器optimizer,损失函数loss,准确性评价函数metrics
model.compile(Adam(lr=0.001), loss='categorical_crossentropy',
metrics=['accuracy']) # lr=0.001为学习率
return model
model = myModel()
print(model.summary()) #打印模型的网络结构
2.8 训练模型
2.8.1 将数据送入模型进行训练
########### 将数据送入模型进行训练 ##################################
batchSizeVal = 50 # 批量大小,即每批用50个样本(每经过50个样本更新一次权重)
epochsVal = 10 # 迭代10次
stepsPerEpochVal = 2000 # 每轮的步数,即每次迭代执行的批次
# steps_per_epoch:为训练数据总数/批量大小
# fit_generator():
# 接受批量数据,执行反向传播,并更新模型中的权重。
# 重复该过程直到迭代结束
# dataGen.flow():
# 对输入数据(图像,标签)打乱后,依次取batch_size的图片并逐一进行变换。取完后再循环
history = model.fit_generator(dataGen.flow(X_train,Y_train,
batch_size=batchSizeVal),
steps_per_epoch=stepsPerEpochVal,
epochs=epochsVal,
validation_data=(X_validation,Y_validation),
shuffle=1) # 打乱(洗牌)
2.8.2 可视化loss和准确率精度的变化:
# 可视化loss的变化
plt.figure(1)
plt.plot(history.history['loss']) # 画图
plt.plot(history.history['val_loss'])
plt.legend(['training','validation']) # 绘制图例
plt.title('Loss')
plt.xlabel('epoch')
# 可视化准确率精度的变化
plt.figure(2)
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['training','validation']) # 绘制图例
plt.title('Accuracy')
plt.xlabel('epoch')
plt.show()
2.8.3 对模型进行评估并查看测试分数和精度:
# 对模型进行评估
score = model.evaluate(X_test, Y_test, verbose=0) # verbose=0不在标准输出流 输出日志信息
print('Test Score = ', score[0]) # 打印测试的分数
print('Test Accuracy = ', score[1]) # 打印测试的准确率
如图:测试的准确率达99.85%
2.8 保存模型文件以便下次使用
########### 保存模型文件以便下次使用 ##################################
pickle_out = open("model_trained.p", "wb")
pickle.dump(model, pickle_out) # 序列化对象,将对象model保存到文件pickle_out中
pickle_out.close()
2.9 应用模型
加载模型,调用摄像头并进行预处理
##################################
width = 640
height = 480
##################################
cap = cv2.VideoCapture(0)
# 把宽高改成 640*480
cap.set(3, width)
cap.set(4,height)
pickle_in = open("model_trained.p", "rb")
model = pickle.load(pickle_in) # 加载数据
########### 对图片预处理 ##################################
def preProcessing(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.equalizeHist(img) # 直方图均衡化处理(图像增强,提升图像质量)
img = img/255 # 将值控制在0到1之间
return img
while True:
success, imgOriginal = cap.read()
img = np.asarray(imgOriginal)
img = cv2.resize(img, (320,320))
img = preProcessing(img)
cv2.imshow("Processed Image", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
2.10 使用模型进行预测
# Predict 预测
classIndex = int(model.predict_classes(img)) # 返回预测出的类别的 索引
# print(classIndex) # 打印预测出的标签索引
predictions = model.predict(img) # 用模型来实时预测图像的标签
print(predictions) # 打印预测出的 标签概率值列表
probVal = np.amax(predictions) # 找出概率数组predictions中的最大可能性
预测出的 标签概率值列表 :
# Predict 预测
classIndex = int(model.predict_classes(img)) # 返回预测出的类别的 索引
# print(classIndex) # 打印预测出的标签索引
predictions = model.predict(img) # 用模型来实时预测图像的标签
# print(predictions) # 打印预测出的 标签概率值列表
probVal = np.amax(predictions) # 找出概率数组predictions中的最大可能性
print(probVal) # 打印预测出的 最大概率值
预测出的 最大概率值:
预测出的标签及其概率值:
print(classIndex, probVal) # 打印预测出的标签及其概率值
若概率值>0.7,则再摄像头获取的视频窗口左上角绘制预测出的标签及其概率值:
# 若概率值>0.7,则再摄像头获取的视频窗口左上角绘制预测出的标签及其概率值
if probVal > threshold:
cv2.putText(imgOriginal, str(classIndex)+" "+str(probVal),
(50,50), cv2.FONT_HERSHEY_COMPLEX, 1, (0,0,255), 1)
cv2.imshow("Original Image", imgOriginal)
3 总结
OCR_CNN_Training.py
import numpy as np
import cv2
import os
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from keras.preprocessing.image import ImageDataGenerator
from keras.utils.np_utils import to_categorical
from keras.models import Sequential
from keras.layers.convolutional import Conv2D,MaxPooling2D
from keras.layers import Dropout,Flatten,Dense
from keras.optimizers import Adam
import pickle
##################################
path = 'myData'
testRatio = 0.2 # 测试集比率
valRatio = 0.2 # 验证集比率
imageDimensions = (32,32,3) # 图像维度
batchSizeVal = 50 # 批量大小,即每批用50个样本(每经过50个样本更新一次权重)
epochsVal = 10 # 迭代10次
stepsPerEpochVal = 2000 # 每轮的步数为2000,即每次迭代执行的批次
# steps_per_epoch:为训练数据总数/批量大小
##################################
images = [] # 存放图片
classNo = [] # classNumber:存放类别号(图片标签)
myList = os.listdir(path)
print("Total No of Classes Detected", len(myList)) # 查看文件夹总数(类别总数:有10类)
noOfClass = len(myList) # numberOfClass:类别总数
print("Importing Classes ......")
for x in range(0, noOfClass): # 遍历每个类别的文件夹
myPicList = os.listdir(path+"/"+str(x)) # 于 当前类别的文件夹 的列表
for y in myPicList: # 遍历 当前类别文件夹 中的img
curImg = cv2.imread(path+"/"+str(x)+"/"+str(y))
curImg = cv2.resize(curImg, (imageDimensions[0], imageDimensions[1]))
images.append(curImg) # 存入当前图片
classNo.append(x) # 存入当前图片的标签
print(x, end=" ") # 打印出当前类别
print(" ")
# print("\n")
# print(len(images)) # 检查共导入了多少张image
# print(len(classNo)) # 检查共导入了多少个image标签
images = np.array(images)
classNo = np.array(classNo)
print(images.shape) # 由此可看出image经转换后是 32*32且3通道
# print(classNo.shape)
########### Spliting the Data 拆分数据集(训练集,测试集,验证集) ############
# 将数据分割成训练集和测试集(将20%的数据用作测试集)
X_train,X_test,Y_train,Y_test = train_test_split(images, classNo,
test_size= testRatio)
# 将分割后的测试数据进一步分割出20%用作验证集
X_train,X_validation,Y_train,Y_validation = train_test_split(X_train, Y_train,
test_size= valRatio)
print(X_train.shape) # 打印拆分后训练集的shape
print(X_test.shape) # 打印拆分后测试集的shape
print(X_validation.shape) # 打印拆分后验证集的shape
# print(np.where(Y_train==0)) # 返回类别为0的数据 的所有索引
# print(len(np.where(Y_train==0)[0])) # 返回类别为0的数据 的索引总数总长度(多少个)
numOfSamples = [] # 存放类别索引总长(样本总数)
for x in range(0, noOfClass):
# print(len(np.where(Y_train == x)[0])) # 返回当前类别数据 的索引总数总长度(多少个)
numOfSamples.append(len(np.where(Y_train == x)[0])) # 添加当前类别的样本数
print(numOfSamples)
plt.figure(figsize=(10, 5)) # 生成图框
plt.bar(range(0,noOfClass), numOfSamples) # 绘制柱状图(画条条)
plt.title("No of Image for each Class (各标签的图数)")
plt.xlabel("Class ID (标签)")
plt.ylabel("Number of Image (图数)")
plt.show()
########### 对图片进行 预处理 ##################################
def preProcessing(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.equalizeHist(img) # 直方图均衡化处理(图像增强,提升图像质量)
img = img/255 # 将值控制在0到1之间
return img
# img = preProcessing(X_train[25])
# img = cv2.resize(img,(300,300))
# cv2.imshow("PreProcessing", img)
# cv2.waitKey(0)
# print(X_train[25].shape)
X_train = np.array(list(map(preProcessing, X_train))) # 将训练集的图依次进行预处理
X_test = np.array(list(map(preProcessing, X_test))) # 将测试集的图依次进行预处理
X_validation = np.array(list(map(preProcessing, X_validation))) # 将验证集的图依次进行预处理
# img = X_train[25]
# img = cv2.resize(img,(300,300))
# cv2.imshow("PreProcessing", img)
# cv2.waitKey(0)
# print(X_train[25].shape)
# print(X_train.shape) # 重塑前的shape
# 重塑数据,给其增加一个深度
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], X_test.shape[2], 1)
X_validation = X_validation.reshape(X_validation.shape[0], X_validation.shape[1], X_validation.shape[2], 1)
# print(X_train.shape) # 重塑后的shape
# 图像生成器:对每批次的训练图片适时进行数据增强(可增强模型的泛化能力)
dataGen = ImageDataGenerator(width_shift_range=0.1, # 随机水平位移
height_shift_range=0.1, # 随机垂直位移
zoom_range=0.2, # 随机缩放
shear_range=0.1, # 随机剪裁
rotation_range=10) # 随机旋转角度
dataGen.fit(X_train) # 使用实时数据增益的批数据 对模型进行拟合
# 将标签向量转换为0 1的矩阵类型表示
Y_train = to_categorical(Y_train, noOfClass)
Y_test = to_categorical(Y_test, noOfClass)
Y_validation = to_categorical(Y_validation, noOfClass)
def myModel():
# 基于LetNet模型
noOfFilters = 60 # number Of Filters:过滤器数量
sizeOfFilter1 = (5,5) # 过滤器1(较大那个)
sizeOfFilter2 = (3,3) # 过滤器2(较小那个)
sizeOfPool = (2,2) # 池化窗口
noOfNode = 500 # 节点数
model = Sequential()
model.add((Conv2D(noOfFilters, sizeOfFilter1,
input_shape=(imageDimensions[0],
imageDimensions[1], 1),
activation='relu')))
model.add((Conv2D(noOfFilters, sizeOfFilter1, activation='relu')))
model.add(MaxPooling2D(pool_size=sizeOfPool))
model.add((Conv2D(noOfFilters//2, sizeOfFilter2, activation='relu'))) # 过滤器减半
model.add((Conv2D(noOfFilters//2, sizeOfFilter2, activation='relu')))
model.add(MaxPooling2D(pool_size=sizeOfPool))
model.add(Dropout(0.5)) # 随机失活
model.add(Flatten()) # 将得到的特征映射“压平”
model.add(Dense(noOfNode, activation='relu')) # 全连接层
model.add(Dropout(0.5))
model.add(Dense(noOfClass, activation='softmax'))
# 为模型设置优化器optimizer,损失函数loss,准确性评价函数metrics
model.compile(Adam(lr=0.001), loss='categorical_crossentropy',
metrics=['accuracy']) # lr=0.001为学习率
return model
model = myModel()
print(model.summary()) #打印模型的网络结构
########### 将数据送入模型进行训练 ##################################
# fit_generator():
# 接受批量数据,执行反向传播,并更新模型中的权重。
# 重复该过程直到迭代结束
# dataGen.flow():
# 对输入数据(图像,标签)打乱后,依次取batch_size的图片并逐一进行变换。取完后再循环
history = model.fit_generator(dataGen.flow(X_train,Y_train,
batch_size=batchSizeVal),
steps_per_epoch=stepsPerEpochVal,
epochs=epochsVal,
validation_data=(X_validation,Y_validation),
shuffle=1) # 打乱(洗牌)
# 可视化loss的变化
plt.figure(1)
plt.plot(history.history['loss']) # 画图
plt.plot(history.history['val_loss'])
plt.legend(['training','validation']) # 绘制图例
plt.title('Loss')
plt.xlabel('epoch')
plt.ylabel('loss')
# 可视化准确率精度的变化
plt.figure(2)
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['training','validation']) # 绘制图例
plt.title('Accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
# 对模型进行评估
score = model.evaluate(X_test, Y_test, verbose=0) # verbose=0不在标准输出流 输出日志信息
print('Test Score = ', score[0]) # 打印测试的分数
print('Test Accuracy = ', score[1]) # 打印测试的准确率
########### 保存模型文件以便下次使用 ##################################
pickle_out = open("model_trained.p", "wb")
pickle.dump(model, pickle_out) # 序列化对象,将对象model保存到文件pickle_out中
pickle_out.close()
OCR_CNN_Test.py
import numpy as np
import cv2
import pickle
##################################
width = 640
height = 480
threshold = 0.7 # 阈值(判定预测的标签是否通过)
##################################
cap = cv2.VideoCapture(0)
# 把宽高改成 640*480
cap.set(3, width)
cap.set(4,height)
pickle_in = open("model_trained.p", "rb")
model = pickle.load(pickle_in) # 加载数据
########### 对图片预处理 ##################################
def preProcessing(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.equalizeHist(img) # 直方图均衡化处理(图像增强,提升图像质量)
img = img/255 # 将值控制在0到1之间
return img
while True:
success, imgOriginal = cap.read()
img = np.asarray(imgOriginal)
img = cv2.resize(img, (32,32))
img = preProcessing(img)
# cv2.imshow("Processed Image", img)
img = img.reshape(1, 32, 32, 1)
# Predict 预测
classIndex = int(model.predict_classes(img)) # 返回预测出的类别的 索引
# print(classIndex) # 打印预测出的标签索引
predictions = model.predict(img) # 用模型来实时预测图像的标签
# print(predictions) # 打印预测出的 标签概率值列表
probVal = np.amax(predictions) # 找出概率数组predictions中的最大可能性
# print(probVal) # 打印预测出的 最大可能性
print(classIndex, probVal) # 打印预测出的标签及其概率值
# 若概率值>0.7,则再摄像头获取的视频窗口左上角绘制预测出的标签及其概率值
if probVal > threshold:
cv2.putText(imgOriginal, str(classIndex)+" "+str(probVal),
(50,50), cv2.FONT_HERSHEY_COMPLEX, 1, (0,0,255), 1)
cv2.imshow("Original Image", imgOriginal)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
实现:在tansorflow开发环境下利用CNN来训练网络,从而对数字图像进行分类,使其测试集准确率达到99.85%。然后调用电脑摄像头进行实时检测,并在视频窗口中实时绘制预测出的数字标签及其概率值。
15参考及数据集出处:https://gitee.com/zzhzwh/Opencv-project/tree/main