用手写数据集验证 ANN 与 CNN 模型的泛化能力
作者:互联网
目录
收集手写数据集
获取数据
首先设置 10x10 的空表格,收集 100 份手写数据集,然后使用机器扫描表格,保存在文件夹 figures 。扫描后的表格数据如下所示:
去除表格
预处理
首先进行灰度化处理,再进行二值化处理(选择 blockSize = 99 ,可以去除噪声),代码如下:
gray=cv2.cvtColor(img_kuang,cv2.COLOR_BGR2GRAY) #灰度化
# 二值化
binary=cv2.adaptiveThreshold(~gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,99,-10)
结果:
识别表格
先识别横线,再识别竖线,代码如下:
rows,cols=binary.shape
scale=25
# 识别横线
kernel=cv2.getStructuringElement(cv2.MORPH_RECT(cols//scale,2))
eroded=cv2.erode(binary,kernel,iterations=2)
dilatedrow=cv2.dilate(eroded,kernel,iterations=20)
# 识别竖线
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(2,rows//scale))
eroded=cv2.erode(binary,kernel,iterations=2)
dilatedcol=cv2.dilate(eroded,kernel,iterations=20)
识别横线结果为:
识别竖线结果为:
最后将识别横线与竖线的结果相加,识别出完整的表格:
merge=cv2.add(dilatedrow,dilatedcol)
结果:
去除表格
最后去除表格,因为图 1 表格为黑色,图 2 表格为白色时,所以将图 1 表格对应的位置修改为白色(适当放大了区域),代码如下:
img1=~binary
for i in range(merge.shape[0]):
for j in range(merge.shape[1]):
if merge[i,j]!=0:
img1[i-10:i+10,j-10:j+10]=255
去除表格后的结果为:
可以发现,图中数字附近会有一些小黑点,因此用中值滤波器去噪,代码如下:
img2=cv2.medianBlur(img1,7)
结果为:
图片分割
提取每张图片中所有数字的轮廓,代码如下:
binary=cv2.adaptiveThreshold(~img2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,99,-10)
contours,hierarchy=cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
x,y,w,h=cv2.boundingRect(contours[i])
if h>10 and w>10:
imgi=img2[y-20:y+h+20,x-20:x+w+20]
# 裁剪
caijian=cv2.resize(imgi,(28,28))
cv2.imwrite('test_db/'+str(index)+str(i).rjust(3,'0')+'.png',caijian)
其中可能存在部分轮廓提取错误,如下图所示:
图片处理
图片分类
先创建名字分别为 0-9 的十个文件夹,代码如下:
for i in range(10):
if not os.path.exists('test_db/'+str(i)):
os.mkdir('test_db/'+str(i))
再手动将对应的图片放入文件夹中。
删除不规范的图片
图片分割后只存在少数不规范数据,所以只进行手动删除。
对处理后的图片进行命名
图片名的第一个数字是图片对应的类别,后面的数字是图片在文件夹中的位置,例如:文件夹 1 中的第 23 张图片命名为: 123.png 。
files=os.listdir('test_db')
for i in files:
pics=os.listdir('test_db/'+i)
for pic in range(len(pics)):
os.rename('test_db/'+i+'/'+pics[pic],'test_db/'+i+'/'+i+str(pic)+'.png')
数据调整
因为图片分割出来的宽度太小,标签为 1 的图片中的数字 1 被加粗。因此需要要将图片中的数字 1 变细,代码如下:
if not os.path.exists('test_db/1_'):
os.mkdir('test_db/1_')
pics=os.listdir('test_db/1')
for pic in pics:
img1=cv2.imread('test_db/1/'+pic)
kernel=np.ones((2,2))
img2=cv2.dilate(img1,kernel,iterations=1)
cv2.imwrite('test_db/1_/'+pic,img2)
变细前后对比如下:
至此,图片处理已完毕。完整代码如下:
# -*- coding: utf-8 -*-
"""
Created on Wed Mar 17 16:45:34 2021
@author: chenzhuohui
"""
import os
import cv2
import shutil
import numpy as np
# 创建文件夹
if not os.path.exists('figures'):
os.mkdir('figures')
if not os.path.exists('test_db'):
os.mkdir('test_db')
# 读取文件夹内所有文件
pics=os.listdir('figures')
index=0
for pic in pics:
try:
img=cv2.imread('figures/'+pic)
## 识别表格
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #灰度化
# 二值化
binary=cv2.adaptiveThreshold(~gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,99,-10)
# 识别横线
rows,cols=binary.shape
scale=25
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(cols//scale,2))
eroded=cv2.erode(binary,kernel,iterations=2)
dilatedrow=cv2.dilate(eroded,kernel,iterations=20)
# 识别竖线
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(2,rows//scale))
eroded=cv2.erode(binary,kernel,iterations=2)
dilatedcol=cv2.dilate(eroded,kernel,iterations=20)
# 识别表格
merge=cv2.add(dilatedrow,dilatedcol)
## 去除表格
img1=~binary
for i in range(merge.shape[0]):
for j in range(merge.shape[1]):
if merge[i,j]!=0:
img1[i-10:i+10,j-10:j+10]=255
# 中值滤波器
img2=cv2.medianBlur(img1,7)
cv2.imwrite(str(index)+'.png',img2)
## 图片分割(100张)
# 提取轮廓
binary=cv2.adaptiveThreshold(~img2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,99,-10)
contours,hierarchy=cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
x,y,w,h=cv2.boundingRect(contours[i])
if h>10 and w>10:
imgi=img2[y-20:y+h+20,x-20:x+w+20]
# 裁剪
caijian=cv2.resize(imgi,(28,28))
cv2.imwrite('test_db/'+str(index)+str(i).rjust(3,'0')+'.png',caijian)
index+=1
except:
print(pic)
## 删除不规范的图片,并且手动分类,放入名为0~9的十个文件夹
'''
# 创建分类文件夹
for i in range(10):
if not os.path.exists('test_db/'+str(i)):
os.mkdir('test_db/'+str(i))
# 命名
files=os.listdir('test_db')
for i in files:
pics=os.listdir('test_db/'+i)
for pic in range(len(pics)):
os.rename('test_db/'+i+'/'+pics[pic],'test_db/'+i+'/'+i+str(pic)+'.png')
# 对1加细
if not os.path.exists('test_db/1_'):
os.mkdir('test_db/1_')
pics=os.listdir('test_db/1')
for pic in pics:
img1=cv2.imread('test_db/1/'+pic)
kernel=np.ones((2,2))
img2=cv2.dilate(img1,kernel,iterations=1)
cv2.imwrite('test_db/1_/'+pic,img2)
# 删除文件夹:1
pics=os.listdir('test_db/1')
for pic in pics:
os.remove('test_db/1/'+pic)
os.rmdir('test_db/1')
# 将图片移动到test_db文件夹
files=os.listdir('test_db')
for i in files:
pics=os.listdir('test_db/'+i)
for pic in pics:
shutil.move('test_db/'+i+'/'+pic,'test_db/'+pic)
# 删除文件夹:0,1_,2~9
files=os.listdir('test_db')
files=list(filter(lambda i:not i.endswith('.png'),files))
list(map(lambda i:os.rmdir('test_db/'+i),files))
'''
验证 ANN 模型的泛化能力
使用 TensorFlow 自带手写数据 MNIST 作为 ANN 的模型的训练集,将准备好的手写数据集作为测试集。对模型进行训练,得出测试集得分为 0.71 。又用 MNIST 数据进行训练测试,得出测试集得分为 0.98 。可以看出 ANN 模型泛化能力较好。
完整代码如下:
# -*- coding: utf-8 -*-
"""++++++++++++++++++++++++++++++++++++++++++++++++++
@ Deep Learning Using Python:Recognize MNIST by ANN
@ My First Neural Network
@ 人工神经网络ANN(或 MLP)识别手写数字
+++++++++++++++++++++++++++++++++++++++++++++++++++++"""
#导入所需的模块
import time
# import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
"""-------------------
第一块积木:数据块
----------------------"""
#加载手写数据MNIST (查看路径 mnist.__path__)
mnist = tf.keras.datasets.mnist
(x_train_image,y_train_label),\
(x_test_image,y_test_label)= mnist.load_data()
##### 自己数据集
import os
import cv2
import numpy as np
file=os.listdir('test_db')
imgMatrix=[]
y_test_label=[]
for i in file:
img=cv2.imread('test_db/'+i)
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #灰度化
imgMatrix.append(img_gray)
y_test_label.append(int(i[0]))
x_test_image=np.array(imgMatrix)
x_test_image=255-x_test_image
y_test_label=np.array(y_test_label)
#####
type(x_train_image) #numpy.ndarray
x_train_image.shape #(60000, 28, 28)
y_train_label.shape #(60000,)
x_train_image.size
# @@@ 数据处理
#转化(reshape)为一维向量,其长度为784,并设为Float数。
x_Train =x_train_image.reshape(60000, 784).astype('float32')
x_Test = x_test_image.reshape(x_test_image.shape[0], 784).astype('float32')
type(x_Train)
x_Train.shape
#将训练集和测试集数据归一化
x_Train_normalize = x_Train / 255
x_Test_normalize = x_Test / 255
#Label的值是0~9,必须以One-Hot Encoding(一位有效编码)的形式转换。
y_Train_OneHot = tf.keras.utils.to_categorical(y_train_label)
y_Test_OneHot = tf.keras.utils.to_categorical(y_test_label)
type(y_Train_OneHot)
y_Train_OneHot.shape
"""--------------------
第二块积木:网络层
@ 建立人工神经网络ANN模型(也称多层感机MLP)
-----------------------"""
# 建立tf.keras 模型称 “model”,后续用model.add()的方法
# 将ANN神经网络的各个层加入到模型中
model = tf.keras.Sequential()
"""定义输入层与隐藏层
input_dim=784,设置输入层的神经元为28x28=784个
units=512,设置隐藏层神经元为为512个
kernel_initializer='normal',初始化权重wights是正态分布随机数
activation='relu',定义激活函数为relu: (x小于0,y=0;x大于等于0,y=x)
"""
model.add(tf.keras.layers.Dense(units=1024,
input_dim=784,
kernel_initializer='normal',
activation='relu'))
model.add(tf.keras.layers.Dense(units=128,
kernel_initializer='normal',
activation='relu'))
model.add(tf.keras.layers. Dropout(0.5))
"""定义输出层:10个神经元
kernel_initializer='normal',初始化权重wights是正态分布随机数与bias
activation='softmax',定义激活函数为softmax
"""
model.add(tf.keras.layers.Dense(units=10,
kernel_initializer='normal',
activation='softmax'))
#查看模型摘要
print(model.summary()) #Total params: 936,330
"""---------------------
第三块积木:训练模型
-------------------------"""
"""下面开始训练模型
用model.compile方法对训练模型进行设置:
损失函数(目标函数)loss='categorical_crossentropy'(对数损失)
优化器optimizers:optimizer='adam'
adam:Adam: A Method for Stochastic Optimization
https://arxiv.org/abs/1412.6980v8
详情见论文:A Method for Stochastic Optimization.pdf
性能评估:metrics=['accuracy'] """
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
"""
x=x_Train_normalize,输入训练参数features
y=y_Train_OneHot,输出label值
validation_split=0.2,二八开训练与验证数据比例 48000:12000
epochs=10,设置训练周期,共10次训练周期
batch_size=200,每一批次200项数据(大约240个批次)
verbose=2,显示训练过程
"""
t1=time.time() #查看运行时间功能 CPU Time taken: 18.3
train_history = model.fit(x=x_Train_normalize,
y=y_Train_OneHot,validation_split=0.2,
epochs=10, batch_size=200,verbose=2)
t2=time.time()
ANN512fit = float(t2-t1)
print("Time taken: {} seconds".format(ANN512fit))
"""---------------------
第四块积木:评估模型
-------------------------"""
#可视化准确率执行结果和误差执行结果
def show_train_history(train_history,train,validation):
plt.plot(train_history.history[train])
plt.plot(train_history.history[validation])
plt.title('Train History')
plt.ylabel(train)
plt.xlabel('Epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
show_train_history(train_history,'accuracy','val_accuracy') #准确率执行结果
show_train_history(train_history,'loss','val_loss') #误差执行结果
""" 以测试数据(10000个),来评估模型的准确率:accuracy= 0.9789 """
scores = model.evaluate(x_Test_normalize, y_Test_OneHot)
print('accuracy=',scores[1])
"""---------------------
第五块积木:模型的预测
-------------------------"""
#以测试数据(10000个),来进行预测
prediction=model.predict_classes(x_Test)
prediction
def plot_images_labels_prediction(images,labels,prediction,
idx,num=10):
fig = plt.gcf()
fig.set_size_inches(12, 14)
if num>25: num=25
for i in range(0, num):
ax=plt.subplot(5,5, 1+i)
ax.imshow(images[idx], cmap='binary')
title= "label=" +str(labels[idx])
if len(prediction)>0:
title+=",predict="+str(prediction[idx])
ax.set_title(title,fontsize=10)
ax.set_xticks([]);ax.set_yticks([])
idx+=1
plt.show()
plot_images_labels_prediction(x_test_image,y_test_label, prediction,idx=0)
"""显示混淆矩阵confusion matrix,也称为误差矩阵。
对角线是预测正确的数字,非对角线表示标签预测错误
"""
pd.crosstab(y_test_label,prediction,
rownames=['label'],colnames=['predict'])
""" 查看真实值与预测值"""
df = pd.DataFrame({'label':y_test_label, 'predict':prediction})
df[:10]
""" 查看真实值是“5”,但与预测值是“3”的数据 """
df[(df.label==5)&(df.predict==3)]
""" 查看第340项结果,真实值是“5”,但与预测值是“3”。"""
plot_images_labels_prediction(x_test_image,y_test_label
,prediction,idx=340,num=1)
""" 查看第1289项结果,真实值是“5”,但与预测值是“3”。"""
plot_images_labels_prediction(x_test_image,y_test_label
,prediction,idx=4271,num=1)
验证 CNN 模型的泛化能力
使用 TensorFlow 自带手写数据 MNIST 作为 CNN 的模型的训练集,将准备好的手写数据集作为测试集。对模型进行训练,得出测试集得分为 0.88 。又用 MNIST 数据进行训练测试,得出测试集得分为 0.99 。可以看出ANN模型泛化能力较好。
完整代码如下:
# -*- coding: utf-8 -*-
"""===========================================
@ Deep Learning Using Python
@ Recognize MNIST by CNN(卷积神经网络)
@ TensorFlow 2.0 版本
@ keras 看作 TensorFlow 的一个子模块
==============================================="""
#导入所需的模块
import time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#导入TensorFlow模块,keras作为tensorflow的子模块。tf.keras
import tensorflow as tf
""" @@@ 第一块积木: 数据与数据描述
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""
#加载手写数据
mnist = tf.keras.datasets.mnist
(x_Train, y_Train), (x_Test, y_Test) = mnist.load_data()
type(x_Train)
x_Train.shape
##### 自己数据集
import os
import cv2
file=os.listdir('test_db')
imgMatrix=[]
y_Test=[]
for i in file:
img=cv2.imread('test_db/'+i)
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #灰度化
imgMatrix.append(img_gray)
y_Test.append(int(i[0]))
x_Test=np.array(imgMatrix)
x_Test=255-x_Test
y_Test=np.array(y_Test)
#####
# 统计多维数组所有元素出现次数(方法1)
#对整数数组中各个元素出现的次数进行统计,它要求数组中所有元素都是非负的。
#np.bincount([2, 2, 2, 0, 0, 1, 2, 1, 0, 2])
np.bincount(y_Test)
# 统计多维数组所有元素出现次数(方法2)
# 使用pandas顶级函数pd.value_counts,value_counts是一个顶级pandas方法,
# 可用于任何数组或序列:
#pd.value_counts(y_train_label, sort=False)
pd.value_counts(y_Test, sort=False)
#画出测试集的0~9柱形图
rects = plt.bar(range(len(np.bincount(y_Test))), np.bincount(y_Test),fc='b')
for rect in rects:
height = rect.get_height()
plt.text(rect.get_x() + rect.get_width() / 2, height, str(height), ha='center', va='bottom')
plt.show()
#将featrues(数字图像特征)转换为四维矩阵。60000x28x28x1
x_Train4D=x_Train.reshape(x_Train.shape[0],28,28,1).astype('float32')
x_Test4D=x_Test.reshape(x_Test.shape[0],28,28,1).astype('float32')
x_Train4D.shape # (60000, 28, 28, 1)
#标准化featyres数据与标签One-Hot Encoding转换
x_Train4D_normalize = x_Train4D / 255
x_Test4D_normalize = x_Test4D / 255
# 将1维的标签空间映射到10维空间
y_TrainOneHot = tf.keras.utils.to_categorical(y_Train)
y_TestOneHot = tf.keras.utils.to_categorical(y_Test)
""" @@@ 第二块积木: 卷积神经网络
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""
#建立卷积神经网络模型
# 建立tf.keras.的模型称 “model”,后续用model.add()的方法
# 将卷积神经网络的各个层加入到模型中
model = tf.keras.Sequential()
""" 建立卷积层1++++++++++++++++++
filters=16: 16个滤镜
kernel_size=(5,5): 每个滤镜5x5大小
padding='same':卷积运算所产生的卷积图像大小不变
input_shape=(28,28,1):图像的形状为28x28,图像是单色灰度图像,所以用1表示,
如果图像是彩色图像RGB,所以用3表示,
activation='relu':激活函数 (x小于0,y=0;x大于等于0,y=x)
"""
model.add(tf.keras.layers.Conv2D(filters=16,
kernel_size=(5,5),
padding='same',
input_shape=(28,28,1),
activation='relu'))
""" 建立迟化层1
参数pool_size=(2, 2),执行第1次缩减采样,将16个28x28的图像缩小为16个14x14的图像
"""
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
""" 建立卷积层2+++++++++++++++++++++
执行第2次卷积运算,将原来的16个滤镜变为36个滤镜,input_shape=14x14的36个图像
"""
model.add(tf.keras.layers.Conv2D(filters=36,
kernel_size=(5,5),
padding='same',
activation='relu'))
""" 建立迟化层2
参数pool_size=(2, 2),执行第2次缩减采样,将36个14x14的图像缩小为36个7x7的图像
"""
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
# 加入Dropout功能避免过度拟合
model.add(tf.keras.layers.Dropout(0.25))
""" 建立平坦层++++++++++++++++++++++
36*7*7 = 1764个神经元
"""
model.add(tf.keras.layers.Flatten())
""" 建立隐藏层 """
model.add(tf.keras.layers.Dense(128, activation='relu'))
#加入Dropout功能避免过度拟合
model.add(tf.keras.layers.Dropout(0.5))
""" 建立输出层 """
model.add(tf.keras.layers.Dense(10,activation='softmax'))
#查看模型摘要+++++++++++++++++++++++++++++++++++++++++++
print(model.summary())
""" @@@ 第三块积木: 模型训练
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""
# 选择优化器
model.compile(loss='categorical_crossentropy',
optimizer='adam',metrics=['accuracy'])
# 开始执行10个训练周期 epochs=10,需要大约6分钟左右
# epochs=30,需要大约1085
# epochs=60,需要大约2140
t1=time.time()
train_history=model.fit(x=x_Train4D_normalize,
y=y_TrainOneHot,validation_split=0.2,
epochs=10, batch_size=300,verbose=2)
t2=time.time()
CNNfit = float(t2-t1)
print("Time taken: {} seconds".format(CNNfit))
""" @@@ 第四块积木: 模型评估
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""
#可视化准确率与误差
import matplotlib.pyplot as plt
def show_train_history(train_acc,test_acc):
plt.plot(train_history.history[train_acc])
plt.plot(train_history.history[test_acc])
plt.title('Train History')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
# 训练与验证曲线
show_train_history('accuracy','val_accuracy') #准确率执行结果
show_train_history('loss','val_loss')
# 评估模型测试集的准确率 99.09% (epochs=10)
# 准确率 99.40% (epochs=20)
# 准确率 99.43% (epochs=60)
# 自己数据集
# 评估模型测试集的准确率 14.49% (epochs=60)
scores = model.evaluate(x_Test4D_normalize , y_TestOneHot)
scores[1]
""" @@@ 第五块积木: 模型预测
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"""
#预测结果
prediction=model.predict_classes(x_Test4D_normalize)
prediction[:10]
#查看预测结果
def plot_images_labels_prediction(images,labels,prediction,idx,num=10):
fig = plt.gcf()
fig.set_size_inches(12, 14)
if num>25: num=25
for i in range(0, num):
ax=plt.subplot(5,5, 1+i)
ax.imshow(images[idx], cmap='binary')
ax.set_title("label=" +str(labels[idx])+
",predict="+str(prediction[idx])
,fontsize=10)
ax.set_xticks([]);ax.set_yticks([])
idx+=1
plt.show()
plot_images_labels_prediction(x_Test,y_Test,prediction,idx=0)
# 模糊矩阵结果
#confusion matrix
import pandas as pd
pd.crosstab(y_Test,prediction,
rownames=['label'],colnames=['predict'])
# 查看预测错误项
df = pd.DataFrame({'label':y_Test, 'predict':prediction})
df[(df.label==5)&(df.predict==3)]
plot_images_labels_prediction(x_Test,y_Test
,prediction,idx=1393,num=1)
plot_images_labels_prediction(x_Test,y_Test
,prediction,idx=2597,num=1)
plot_images_labels_prediction(x_Test,y_Test
,prediction,idx=5937,num=1)
# 画彩色模糊矩阵
import itertools
from sklearn.metrics import confusion_matrix
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, cm[i, j],
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
# compute the confusion matrix
confusion_mtx = confusion_matrix(y_Test, prediction)
# 彩色模糊矩阵画图
# plot the confusion matrix
plot_confusion_matrix(confusion_mtx, classes = range(10))
标签:10,plt,泛化,db,ANN,cv2,test,Test,CNN 来源: https://blog.csdn.net/weixin_45745219/article/details/115037337