700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【人工智能与机器学习】——Keras编程分别实现人脸微笑和口罩数据集的识别模型训练和

【人工智能与机器学习】——Keras编程分别实现人脸微笑和口罩数据集的识别模型训练和

时间:2021-08-04 11:14:52

相关推荐

【人工智能与机器学习】——Keras编程分别实现人脸微笑和口罩数据集的识别模型训练和

机器学习练习目录

一、理解人脸图像特征提取的各种方法的特征1.HOG2.Dlib3.卷积神经网络(CNN)二、卷积神经网络(CNN)笑脸数据集(genki4k)正负样本的划分、模型训练和测试的过程,输出模型训练精度和测试精度,完成对人脸微笑与否的模型训练1.查看运行的环境2.数据集准备3.网络模型4.资料预处理5.开始训练6.使用数据填充7.对人脸微笑与否的模型训练三、卷积神经网络(CNN)对口罩数据集正负样本的划分、模型训练和测试的过程,输出模型训练精度和测试精度,完成对口罩佩戴与否的模型训练1.数据集准备2.网络模型3.资料预处理4.开始训练5.使用数据填充6.对人脸戴口罩与否的模型训练四、完成一个摄像头采集自己人脸、并对表情(笑脸/非笑脸、戴口罩和没戴口罩)的实时分类判读(输出分类文字)的程序1.笑脸/非笑脸实时分类判读(输出分类文字)的程序2.戴口罩和没戴口罩的实时分类判读(输出分类文字)的程序

一、理解人脸图像特征提取的各种方法的特征

1.HOG

介绍:方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。主要思想:在一副图像中,局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。(本质:梯度的统计信息,而梯度主要存在于边缘的地方)。具体的实现方法:首先将图像分成小的连通区域,把它叫细胞单元。然后采集细胞单元中各像素点的梯度的或边缘的方向直方图。最后把这些直方图组合起来就可以构成特征描述器。步骤:灰度图像转换、梯度计算、分网格的梯度方向直方图、快描述子、快描述子归一化、特征数据与检测窗口、匹配方法。优点:① HOG是在图像的局部方格单元上操作,它对图像几何的和光学的形变都能保持很好的不变性,这两种形变只会出现在更大的空间领域上;②在粗的空域抽样、精细的方向抽样以及较强的局部光学归一化等条件下,只要行人大体上能够保持直立的姿势,可以容许行人有一些细微的肢体动作,这些细微的动作可以被忽略而不影响检测效果。
综上所述,HOG特征是特别适合于做图像中的人体检测的。

参考链接:/p/104670289

2.Dlib

介绍:Dlib是一款优秀的跨平台开源的C++工具库,该库使用C++编写,具有优异的性能。Dlib库提供的功能十分丰富,包括线性代数,图像处理,机器学习,网络,最优化算法等众多功能。同时该库也提供了Python接口。核心原理:使用了图像Hog特征来表示人脸,和其他特征提取算子相比,它对图像的几何和光学的形变都能保持很好的不变形。该特征与LBP特征,Harr特征共同作为三种经典的图像特征,该特征提取算子通常和支持向量机(SVM)算法搭配使用,用在物体检测场景。该算法大致思路:
对正样本(即包含人脸的图像)数据集提取Hog特征,得到Hog特征描述子。对负样本(即不包含人脸的图像)数据集提取Hog特征,得到Hog描述子。 其中负样本数据集中的数据量要远远大于正样本数据集中的样本数,负样本图像可以使用不含人脸的图片进行随机裁剪获取。利用支持向量机算法训练正负样本,显然这是一个二分类问题,可以得到训练后的模型。利用该模型进行负样本难例检测,也就是难分样本挖掘(hard-negtive mining),以便提高最终模型的分类能力。具体思路为:对训练集里的负样本不断进行缩放,直至与模板匹配位置,通过模板滑动串口搜索匹配(该过程即多尺度检测过程),如果分类器误检出非人脸区域则截取该部分图像加入到负样本中。集合难例样本重新训练模型,反复如此得到最终分类模型。应用最终训练出的分类器检测人脸图片,对该图片的不同尺寸进行滑动扫描,提取Hog特征,并用分类器分类。如果检测判定为人脸,则将其标定出来,经过一轮滑动扫描后必然会出现同一个人脸被多次标定的情况,这就用NMS完成收尾工作即可。

参考链接:/p/92132280

3.卷积神经网络(CNN)

介绍:卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络,是深度学习的代表算法之一 。卷积神经网络具有表征学习能力,能够按其阶层结构对输入信息进行平移不变分类,因此也被称为“平移不变人工神经网络(SIANN)"。
CNN原理:卷积神经网络(CNN)主要是用于图像识别领域,它指的是一类网络,而不是某一种,其包含很多不同种结构的网络。不同的网络结构通常表现会不一样。所有CNN最终都是把一张图片转化为特征向量。就像上图VGG网络一样,通过多层的卷积,池化,全连接,降低图片维度,最后转化成了一个一维向量。这个向量就包含了图片的特征,当然这个特征不是肉眼上的图片特征,而是针对于神经网络的特征。
CNN的训练过程:
卷积神经网络的前向传播过程
前向传播中的卷积操作前向传播中的池化操作前向传播中的全连接
卷积神经网络的反向传播过程卷积神经网络的权值更新卷积神经网络的训练过程流程图:

不断循环这个过程,最后得到一个稳定的权值和阈值)

CNN的应用:
图片分类相似图搜索对抗样本(比如输入一张猫的图片,人为的修改一点图片数据,肉眼上看还是一只猫,但是你告诉神经网络这是狗。)

参考链接:/p/95158245

二、卷积神经网络(CNN)笑脸数据集(genki4k)正负样本的划分、模型训练和测试的过程,输出模型训练精度和测试精度,完成对人脸微笑与否的模型训练

1.查看运行的环境

import platformimport tensorflowimport kerasprint("Platform: {}".format(platform.platform()))print("Tensorflow version: {}".format(tensorflow.__version__))print("Keras version: {}".format(keras.__version__))

Platform: Windows-7-6.1.7601-SP1Tensorflow version: 1.2.1Keras version: 2.1.2

2.数据集准备

下载图像数据集genki4k.tar,把它解压到相应的目录(我放在了D:\mango目录下), 解压出来看见的原始数据集内容如图所示

手动裁剪过程

其中裁剪之前的图片

对图像进行了裁剪,提取大头照,并把一些不符合的图片进行了剔除,执行完裁剪后的显示效果

由于各种原因没有裁剪剔除的图片

导入需要的包

import keras #导入人工神经网络库import matplotlib.pyplot as plt # matplotlib进行画图import matplotlib.image as mpimg # # mpimg 用于读取图片import numpy as np # numpy操作数组from IPython.display import Image #显示图像import os #os:操作系统接口

划分数据集(在当前写代码的同级目录下会产生一个mangoout的文件夹,包括 train :训练集;validation:验证集;test:测试集。)

# 原目录的路径original_dataset_dir = 'D:\\mango\\files\\cutmango'# 结果路径,存储较小的数据集base_dir = 'mangoout'os.mkdir(base_dir)# 训练目录,验证和测试拆分train_dir = os.path.join(base_dir, 'train')os.mkdir(train_dir)validation_dir = os.path.join(base_dir, 'validation')os.mkdir(validation_dir)test_dir = os.path.join(base_dir, 'test')os.mkdir(test_dir)# 训练微笑图片目录train_smile_dir = os.path.join(train_dir, 'smile')os.mkdir(train_smile_dir)# 训练没微笑图片目录train_unsmile_dir = os.path.join(train_dir, 'unsmile')#s.mkdir(train_dogs_dir)# 验证的微笑图片目录validation_smile_dir = os.path.join(validation_dir, 'smile')os.mkdir(validation_smile_dir)# 验证的没微笑图片目录validation_unsmile_dir = os.path.join(validation_dir, 'unsmile')os.mkdir(validation_unsmile_dir)# 测试的微笑图片目录test_smile_dir = os.path.join(test_dir, 'smile')os.mkdir(test_smile_dir)# 测试的没微笑图片目录test_unsmile_dir = os.path.join(test_dir, 'unsmile')os.mkdir(test_unsmile_dir)

分配数据集,可以使用人为划分和代码划分进行一次检查,计算每个分组中有多少张照片(训练/验证/测试)

print('total training smile images:', len(os.listdir(train_smile_dir)))

total training smile images: 1000

print('total testing smile images:', len(os.listdir(test_smile_dir)))

total testing smile images: 300

print('total training unsmile images:', len(os.listdir(train_unsmile_dir)))

total training unsmile images: 1000

print('total validation smile images:', len(os.listdir(validation_smile_dir)))

total validation smile images: 300

print('total testing unsmile images:', len(os.listdir(test_unsmile_dir)))

total testing unsmile images: 300

print('total validation unsmile images:', len(os.listdir(validation_unsmile_dir)))

total validation unsmile images: 300

有2000个训练图像,然后是600个验证图像,600个测试图像,其中每个分类都有相同数量的样本,是一个平衡的二元分类问题,意味着分类准确度将是合适的度量标准。

3.网络模型

卷积网络(convnets)将是一组交替的Conv2D(具有relu激活)和MaxPooling2D层。从大小150x150(有点任意选择)的输入开始,我们最终得到了尺寸为7x7的Flatten层之前的特征图。

注意特征图的深度在网络中逐渐增加(从32到128),而特征图的大小正在减少(从148x148到7x7)。这是一个你将在几乎所有的卷积网络(convnets)结构中会看到的模式。

由于我们正在处理二元分类问题,所以我们用一个神经元(一个大小为1的密集层(Dense))和一个sigmoid激活函数来结束网络。该神经元将会被用来查看图像归属于那一类或另一类的概率。

创建模型

from keras import layersfrom keras import modelsmodel = models.Sequential()model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(64, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Flatten())model.add(layers.Dense(512, activation='relu'))model.add(layers.Dense(1, activation='sigmoid'))

看特征图的尺寸如何随着每个连续的图层而改变,打印网络结构

model.summary()

_________________________________________________________________Layer (type) Output Shape Param # =================================================================conv2d_5 (Conv2D) (None, 148, 148, 32)896 _________________________________________________________________max_pooling2d_5 (MaxPooling2 (None, 74, 74, 32) 0 _________________________________________________________________conv2d_6 (Conv2D) (None, 72, 72, 64) 18496_________________________________________________________________max_pooling2d_6 (MaxPooling2 (None, 36, 36, 64) 0 _________________________________________________________________conv2d_7 (Conv2D) (None, 34, 34, 128) 73856_________________________________________________________________max_pooling2d_7 (MaxPooling2 (None, 17, 17, 128) 0 _________________________________________________________________conv2d_8 (Conv2D) (None, 15, 15, 128) 147584 _________________________________________________________________max_pooling2d_8 (MaxPooling2 (None, 7, 7, 128) 0 _________________________________________________________________flatten_2 (Flatten)(None, 6272) 0 _________________________________________________________________dense_3 (Dense) (None, 512)3211776 _________________________________________________________________dense_4 (Dense) (None, 1) 513 =================================================================Total params: 3,453,121Trainable params: 3,453,121Non-trainable params: 0_________________________________________________________________

在编译步骤里,使用RMSprop优化器。由于用一个单一的神经元(Sigmoid的激活函数)结束了网络,将使用二进制交叉熵(binary crossentropy)作为损失函数

from keras import pile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])

4.资料预处理

网络的预处理步骤:读入图像
将JPEG内容解码为RGB网格的像素
将其转换为浮点张量
将像素值(0和255之间)重新缩放到[0,1]间隔
数据应该先被格式化成适当的预处理浮点张量,然后才能输入到神经网络中

from keras.preprocessing.image import ImageDataGenerator# 所有的图像将重新进行归一化处理Rescaled by 1./255train_datagen = ImageDataGenerator(rescale=1./255)validation_datagen=ImageDataGenerator(rescale=1./255)test_datagen = ImageDataGenerator(rescale=1./255)# 直接从目录读取图像数据train_generator = train_datagen.flow_from_directory(# 训练图像的目录train_dir,# 所有图像大小会被转换成150x150target_size=(150, 150),# 每次产生20个图像的批次batch_size=20,# 由于这是一个二元分类问题,y的label值也会被转换成二元的标签class_mode='binary')# 直接从目录读取图像数据validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(150, 150),batch_size=20,class_mode='binary')test_generator = test_datagen.flow_from_directory(test_dir,target_size=(150, 150),batch_size=20,class_mode='binary')

Found 2000 images belonging to 2 classes.Found 600 images belonging to 2 classes.Found 600 images belonging to 2 classes.

图像张量生成器(generator)的输出,它产生150x150 RGB图像(形状"(20,150,150,3)")和二进制标签(形状"(20,)")的批次张量。20是每个批次中的样品数(批次大小)

for data_batch, labels_batch in train_generator:print('data batch shape:', data_batch.shape)print('labels batch shape:', labels_batch)break

data batch shape: (20, 150, 150, 3)labels batch shape: [ 1. 1. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0.0. 0.]

输出结果的0(笑脸)和1(非笑脸)

train_generator.class_indices

{'smile': 0, 'unsmile': 1}

5.开始训练

其中epochs值越大,花费的时间就越久、训练的精度更高,我电脑性能不好,运行了很久… …

history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=10,validation_data=validation_generator,validation_steps=50)

Epoch 1/10100/100 [==============================] - 227s 2s/step - loss: 0.6776 - acc: 0.5740 - val_loss: 0.6745 - val_acc: 0.5660Epoch 2/10100/100 [==============================] - 230s 2s/step - loss: 0.6422 - acc: 0.6520 - val_loss: 0.7091 - val_acc: 0.5290Epoch 3/10100/100 [==============================] - 222s 2s/step - loss: 0.5889 - acc: 0.7020 - val_loss: 0.5711 - val_acc: 0.7530Epoch 4/10100/100 [==============================] - 192s 2s/step - loss: 0.5251 - acc: 0.7575 - val_loss: 0.5592 - val_acc: 0.7330Epoch 5/10100/100 [==============================] - 191s 2s/step - loss: 0.4854 - acc: 0.7825 - val_loss: 0.5250 - val_acc: 0.7550Epoch 6/10100/100 [==============================] - 184s 2s/step - loss: 0.4503 - acc: 0.8015 - val_loss: 0.5111 - val_acc: 0.7980Epoch 7/10100/100 [==============================] - 183s 2s/step - loss: 0.4111 - acc: 0.8255 - val_loss: 0.5376 - val_acc: 0.7500Epoch 8/10100/100 [==============================] - 189s 2s/step - loss: 0.3748 - acc: 0.8380 - val_loss: 0.4906 - val_acc: 0.7850Epoch 9/10100/100 [==============================] - 188s 2s/step - loss: 0.3493 - acc: 0.8590 - val_loss: 0.4397 - val_acc: 0.8170Epoch 10/10100/100 [==============================] - 186s 2s/step - loss: 0.3177 - acc: 0.8670 - val_loss: 0.4265 - val_acc: 0.8400

训练完成后把模型保存下来

model.save('mangoout/smileAndUnsmile_1.h5')

使用图表来绘制在训练过程中模型对训练和验证数据的损失(loss)和准确性(accuracy)数据

import matplotlib.pyplot as pltacc = history.history['acc']val_acc = history.history['val_acc']loss = history.history['loss']val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training acc')plt.plot(epochs, val_acc, 'b', label='Validation acc')plt.title('Training and validation accuracy')plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')plt.plot(epochs, val_loss, 'b', label='Validation loss')plt.title('Training and validation loss')plt.legend()plt.show()

可能由于我们提前对图片进行了裁剪筛选操作,过度拟合这里不是太能体现出来,训练精确度随着时间线性增长,验证精度也在增长。验证损失和训练损失都在线性上保持下降直到接近0。

💥 为了进一步优化,下面会引入一个新的、特定于电脑视觉影像,并在使用深度学习模型处理图像时几乎普遍使用的技巧:数据扩充(data augmentation)

6.使用数据填充

数据增加采用从现有训练样本生成更多训练数据的方法,通过产生可信的图像的多个随机变换来"增加"样本。目标是在训练的时候,我们的模型永远不会再看到完全相同的画面两次。这有助于模型学习到数据的更多方面,并更好地推广。
在keras中,可以通过配置对ImageDataGenerator实例读取的图像执行多个随机变换来完成

datagen = ImageDataGenerator(rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,fill_mode='nearest')

这里列出一些可用的选项(更多选项,可以参考keras文档),快速看一下这些参数:

rotation_range 是以度(0-180)为单位的值,它是随机旋转图片的范围

width_shift 和 height_shift 是范围(占总宽度或高度的一小部分),用于纵向或横向随机转换图片

shear_range 用于随机剪切变换

zoom_range 用于随机放大图片内容

horizontal_flip 用于在没有水平不对称假设(例如真实世界图片)的情况下水平地随机翻转一半图像

fill_mode 是用于填充新创建的像素的策略,可以在旋转或宽/高移位后显示

看增强后的图像

import matplotlib.pyplot as plt# 图像预处理功能模块from keras.preprocessing import image# 取得训练数据集中微笑的图片列表fnames = [os.path.join(train_smile_dir, fname) for fname in os.listdir(train_smile_dir)]img_path = fnames[3] # 取一个图像img = image.load_img(img_path, target_size=(150, 150)) # 读入图像并进行大小处理x = image.img_to_array(img) # 转换成Numpy array 并且shape(150,150,3)x = x.reshape((1,) + x.shape) # 重新Reshape成(1,150,150,3)以便输入到模型中# 通过flow()方法将会随机产生新的图像,它会无线循环,所以我们需要在某个时候“断开”循环i = 0for batch in datagen.flow(x, batch_size=1):plt.figure(i)imgplot = plt.imshow(image.array_to_img(batch[0]))i += 1if i % 4 == 0:breakplt.show()

如果我们使用这种数据增强配置来训练一个新的网络,我们的网络将永远不会看到相同重复的输入。
然而,它看到的输入仍然是相互关联的,因为它们来自少量的原始图像 - 我们不能产生新的信息,我们只能重新混合现有的信息。因此,这可能不足以完全摆脱过度拟合(overfitting)。为了进一步克服过度拟合(overfitting),我们还将在密集连接(densely-connected)的分类器之前添加一个Dropout层。

model = models.Sequential()model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(64, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Flatten())model.add(layers.Dropout(0.5))model.add(layers.Dense(512, activation='relu'))model.add(layers.Dense(1, activation='sigmoid'))pile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])

使用数据填充(data augmentation)和dropout来训练我们的网络

#归一化处理train_datagen = ImageDataGenerator(rescale=1./255,rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,)# 注意,验证数据不应该扩充test_datagen = ImageDataGenerator(rescale=1./255)train_generator = train_datagen.flow_from_directory(# 这是图像资料的目录train_dir,# 所有的图像大小会被转换成150x150target_size=(150, 150),batch_size=32,# 由于这是一个二元分类问题,y的label值也会被转换成二元的标签class_mode='binary')validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(150, 150),batch_size=32,class_mode='binary')history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=60, validation_data=validation_generator,validation_steps=50)

Found 2000 images belonging to 2 classes.Found 600 images belonging to 2 classes.Epoch 1/60100/100 [==============================] - 377s 4s/step - loss: 0.6905 - acc: 0.5312 - val_loss: 0.6815 - val_acc: 0.5587Epoch 2/60100/100 [==============================] - 303s 3s/step - loss: 0.6849 - acc: 0.5550 - val_loss: 0.6768 - val_acc: 0.5852Epoch 3/60100/100 [==============================] - 314s 3s/step - loss: 0.6790 - acc: 0.5866 - val_loss: 0.6748 - val_acc: 0.5777Epoch 4/60100/100 [==============================] - 312s 3s/step - loss: 0.6774 - acc: 0.5806 - val_loss: 0.6722 - val_acc: 0.5821Epoch 5/60100/100 [==============================] - 303s 3s/step - loss: 0.6696 - acc: 0.5869 - val_loss: 0.6734 - val_acc: 0.5909Epoch 6/60100/100 [==============================] - 300s 3s/step - loss: 0.6665 - acc: 0.5956 - val_loss: 0.6679 - val_acc: 0.6029Epoch 7/60100/100 [==============================] - 309s 3s/step - loss: 0.6719 - acc: 0.5853 - val_loss: 0.6836 - val_acc: 0.5814Epoch 8/60100/100 [==============================] - 299s 3s/step - loss: 0.6628 - acc: 0.6031 - val_loss: 0.7166 - val_acc: 0.5600Epoch 9/60100/100 [==============================] - 310s 3s/step - loss: 0.6676 - acc: 0.6009 - val_loss: 0.6723 - val_acc: 0.6048Epoch 10/60100/100 [==============================] - 309s 3s/step - loss: 0.6629 - acc: 0.6100 - val_loss: 0.6546 - val_acc: 0.6288Epoch 11/60100/100 [==============================] - 300s 3s/step - loss: 0.6558 - acc: 0.6163 - val_loss: 0.6744 - val_acc: 0.6073Epoch 12/60100/100 [==============================] - 300s 3s/step - loss: 0.6575 - acc: 0.6216 - val_loss: 0.6425 - val_acc: 0.6572Epoch 13/60100/100 [==============================] - 307s 3s/step - loss: 0.6541 - acc: 0.6294 - val_loss: 0.7095 - val_acc: 0.5960Epoch 14/60100/100 [==============================] - 303s 3s/step - loss: 0.6429 - acc: 0.6400 - val_loss: 0.6381 - val_acc: 0.6414Epoch 15/60100/100 [==============================] - 310s 3s/step - loss: 0.6427 - acc: 0.6300 - val_loss: 0.6297 - val_acc: 0.6723Epoch 16/60100/100 [==============================] - 308s 3s/step - loss: 0.6404 - acc: 0.6369 - val_loss: 0.6254 - val_acc: 0.6667Epoch 17/60100/100 [==============================] - 301s 3s/step - loss: 0.6367 - acc: 0.6500 - val_loss: 0.6145 - val_acc: 0.6408Epoch 18/60100/100 [==============================] - 301s 3s/step - loss: 0.6246 - acc: 0.6450 - val_loss: 0.5991 - val_acc: 0.6831Epoch 19/60100/100 [==============================] - 307s 3s/step - loss: 0.6230 - acc: 0.6625 - val_loss: 0.5956 - val_acc: 0.7052Epoch 20/60100/100 [==============================] - 303s 3s/step - loss: 0.6062 - acc: 0.6725 - val_loss: 0.5812 - val_acc: 0.6951Epoch 21/60100/100 [==============================] - 301s 3s/step - loss: 0.6094 - acc: 0.6647 - val_loss: 0.5640 - val_acc: 0.7033Epoch 22/60100/100 [==============================] - 303s 3s/step - loss: 0.6089 - acc: 0.6794 - val_loss: 0.5698 - val_acc: 0.6774Epoch 23/60100/100 [==============================] - 305s 3s/step - loss: 0.5846 - acc: 0.6869 - val_loss: 0.5458 - val_acc: 0.7311Epoch 24/60100/100 [==============================] - 300s 3s/step - loss: 0.5782 - acc: 0.7022 - val_loss: 0.5380 - val_acc: 0.7551Epoch 25/60100/100 [==============================] - 303s 3s/step - loss: 0.5676 - acc: 0.7112 - val_loss: 0.5731 - val_acc: 0.7109Epoch 26/60100/100 [==============================] - 305s 3s/step - loss: 0.5711 - acc: 0.7134 - val_loss: 0.5303 - val_acc: 0.7121Epoch 27/60100/100 [==============================] - 315s 3s/step - loss: 0.5565 - acc: 0.7166 - val_loss: 0.4785 - val_acc: 0.7942Epoch 28/60100/100 [==============================] - 307s 3s/step - loss: 0.5462 - acc: 0.7269 - val_loss: 0.5904 - val_acc: 0.7052Epoch 29/60100/100 [==============================] - 309s 3s/step - loss: 0.5367 - acc: 0.7309 - val_loss: 0.4601 - val_acc: 0.7847Epoch 30/60100/100 [==============================] - 304s 3s/step - loss: 0.5283 - acc: 0.7359 - val_loss: 0.5043 - val_acc: 0.7532Epoch 31/60100/100 [==============================] - 302s 3s/step - loss: 0.5202 - acc: 0.7472 - val_loss: 0.5292 - val_acc: 0.7424Epoch 32/60100/100 [==============================] - 308s 3s/step - loss: 0.5147 - acc: 0.7531 - val_loss: 0.5043 - val_acc: 0.7860Epoch 33/60100/100 [==============================] - 305s 3s/step - loss: 0.5100 - acc: 0.7434 - val_loss: 0.4506 - val_acc: 0.7955Epoch 34/60100/100 [==============================] - 303s 3s/step - loss: 0.5067 - acc: 0.7628 - val_loss: 0.4423 - val_acc: 0.7803Epoch 35/60100/100 [==============================] - 303s 3s/step - loss: 0.4937 - acc: 0.7591 - val_loss: 0.4281 - val_acc: 0.8037Epoch 36/60100/100 [==============================] - 300s 3s/step - loss: 0.4903 - acc: 0.7619 - val_loss: 0.4191 - val_acc: 0.8125Epoch 37/60100/100 [==============================] - 299s 3s/step - loss: 0.4704 - acc: 0.7769 - val_loss: 0.4266 - val_acc: 0.8213Epoch 38/60100/100 [==============================] - 307s 3s/step - loss: 0.4811 - acc: 0.7778 - val_loss: 0.4196 - val_acc: 0.8239Epoch 39/60100/100 [==============================] - 312s 3s/step - loss: 0.4722 - acc: 0.7753 - val_loss: 0.4366 - val_acc: 0.7992Epoch 40/60100/100 [==============================] - 301s 3s/step - loss: 0.4694 - acc: 0.7797 - val_loss: 0.4597 - val_acc: 0.7879Epoch 41/60100/100 [==============================] - 304s 3s/step - loss: 0.4658 - acc: 0.7866 - val_loss: 0.4021 - val_acc: 0.8239Epoch 42/60100/100 [==============================] - 303s 3s/step - loss: 0.4700 - acc: 0.7859 - val_loss: 0.4271 - val_acc: 0.8100Epoch 43/60100/100 [==============================] - 300s 3s/step - loss: 0.4591 - acc: 0.7850 - val_loss: 0.4687 - val_acc: 0.7898Epoch 44/60100/100 [==============================] - 308s 3s/step - loss: 0.4592 - acc: 0.7847 - val_loss: 0.4136 - val_acc: 0.8169Epoch 45/60100/100 [==============================] - 311s 3s/step - loss: 0.4449 - acc: 0.8031 - val_loss: 0.4427 - val_acc: 0.7784Epoch 46/60100/100 [==============================] - 299s 3s/step - loss: 0.4505 - acc: 0.7897 - val_loss: 0.4030 - val_acc: 0.8220Epoch 47/60100/100 [==============================] - 306s 3s/step - loss: 0.4515 - acc: 0.7978 - val_loss: 0.4324 - val_acc: 0.7948Epoch 48/60100/100 [==============================] - 303s 3s/step - loss: 0.4485 - acc: 0.8025 - val_loss: 0.4979 - val_acc: 0.7544Epoch 49/60100/100 [==============================] - 303s 3s/step - loss: 0.4310 - acc: 0.8137 - val_loss: 0.4203 - val_acc: 0.8100Epoch 50/60100/100 [==============================] - 309s 3s/step - loss: 0.4327 - acc: 0.8044 - val_loss: 0.4064 - val_acc: 0.8194Epoch 51/60100/100 [==============================] - 313s 3s/step - loss: 0.4308 - acc: 0.8091 - val_loss: 0.4266 - val_acc: 0.7891Epoch 52/60100/100 [==============================] - 307s 3s/step - loss: 0.4321 - acc: 0.7997 - val_loss: 0.4612 - val_acc: 0.8169Epoch 53/60100/100 [==============================] - 303s 3s/step - loss: 0.4316 - acc: 0.8081 - val_loss: 0.4235 - val_acc: 0.7942Epoch 54/60100/100 [==============================] - 302s 3s/step - loss: 0.4226 - acc: 0.8135 - val_loss: 0.4083 - val_acc: 0.8302Epoch 55/60100/100 [==============================] - 300s 3s/step - loss: 0.4240 - acc: 0.8062 - val_loss: 0.3863 - val_acc: 0.8340Epoch 56/60100/100 [==============================] - 308s 3s/step - loss: 0.4139 - acc: 0.8184 - val_loss: 0.4100 - val_acc: 0.8232Epoch 57/60100/100 [==============================] - 303s 3s/step - loss: 0.4149 - acc: 0.8159 - val_loss: 0.3911 - val_acc: 0.8239Epoch 58/60100/100 [==============================] - 305s 3s/step - loss: 0.4171 - acc: 0.8113 - val_loss: 0.3851 - val_acc: 0.8321Epoch 59/60100/100 [==============================] - 304s 3s/step - loss: 0.4221 - acc: 0.8141 - val_loss: 0.4243 - val_acc: 0.7891Epoch 60/60100/100 [==============================] - 303s 3s/step - loss: 0.4019 - acc: 0.8262 - val_loss: 0.3919 - val_acc: 0.8371

再次查看0或1含义

train_generator.class_indices

{'smile': 0, 'unsmile': 1}

保存模型,将在convnet可视化里使用它

#保存模型model.save('mangoout/smileAndUnsmile_2.h5')

保存模型文件夹显示效果

绘制数据增强后的训练集与验证集的精确度与损失度的图形,看一遍结果

acc = history.history['acc']val_acc = history.history['val_acc']loss = history.history['loss']val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training acc')plt.plot(epochs, val_acc, 'b', label='Validation acc')plt.title('Training and validation accuracy')plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')plt.plot(epochs, val_loss, 'b', label='Validation loss')plt.title('Training and validation loss')plt.legend()plt.show()

由于数据增加(data augmentation)和dropout使用,不再有过度拟合(overfitting)的问题;训练曲线相当密切地跟随着验证曲线。训练精度和验证精度经过60个循环接近85%。通过进一步利用正规化技术,及调整网络参数(例如每个卷积层的滤波器数量或网络层数),可以获得更好的准确度。

7.对人脸微笑与否的模型训练

判断的第一张图片(D:/mango/mangotest.jpg路径下)这就是训练集中的某张图片

# 单张图片进行判断 是笑脸还是非笑脸from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as np#加载模型model = load_model('mangoout/smileAndUnsmile_2.h5')#本地图片路径img_path='D:/mango/mangotest.jpg'img = image.load_img(img_path, target_size=(150, 150))img_tensor = image.img_to_array(img)/255.0img_tensor = np.expand_dims(img_tensor, axis=0)prediction =model.predict(img_tensor) print(prediction)if prediction[0][0]>0.5:result='非笑脸'else:result='笑脸'print(result)

[[ 0.00120554]]笑脸

可以看见,判断为笑脸是正确的。

判断的第二张图片(D:/mango/mangotest2.jpg路径下)
这是我假期无聊时用美图拍的,哈哈… … ☺️

# 单张图片进行判断 是笑脸还是非笑脸from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as np#加载模型model = load_model('mangoout/smileAndUnsmile_2.h5')#本地图片路径img_path='D:/mango/mangotest2.jpg'img = image.load_img(img_path, target_size=(150, 150))img_tensor = image.img_to_array(img)/255.0img_tensor = np.expand_dims(img_tensor, axis=0)prediction =model.predict(img_tensor) print(prediction)if prediction[0][0]>0.5:result='非笑脸'else:result='笑脸'print(result)

[[ 0.69130015]]非笑脸

可以看见,判断为非笑脸是正确的。
判断的第三张图片(D:/mango/mengmengsmile.jpg路径下)她自己拍的,顺便被我拿来做作业,哈哈… …

# 单张图片进行判断 是笑脸还是非笑脸from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as np#加载模型model = load_model('mangoout/smileAndUnsmile_1.h5')#本地图片路径img_path='D:/mango/mengmengsmile.jpg'img = image.load_img(img_path, target_size=(150, 150))img_tensor = image.img_to_array(img)/255.0img_tensor = np.expand_dims(img_tensor, axis=0)prediction =model.predict(img_tensor) print(prediction)if prediction[0][0]>0.5:result='非笑脸'else:result='笑脸'print(result)

[[ 0.03113164]]笑脸

可以看见,判断为笑脸是正确的(但是有一些图片中人脸部分在整张图片的占比不是在特别多的话,它判断出来的准确度就比较小,甚至出现判断错误的情况,但是一般大头照判断出来的准确度还是很高的。)

三、卷积神经网络(CNN)对口罩数据集正负样本的划分、模型训练和测试的过程,输出模型训练精度和测试精度,完成对口罩佩戴与否的模型训练

1.数据集准备

下载口罩数据集,把它解压到相应的目录(我放在了D:\mangomask目录下)

解压后原始有口罩的数据集显示效果

解压后原始没有口罩的数据集显示效果

将正样本(有口罩)数据集重命名为连续序列,以便后面调整

#coding:utf-8import ospath = "D:/mangomask/mask/have_mask" # 人脸口罩数据集正样本的路径filelist = os.listdir(path)count=1000 #开始文件名1000.jpgfor file in filelist: Olddir=os.path.join(path,file) if os.path.isdir(Olddir): continuefilename=os.path.splitext(file)[0] filetype=os.path.splitext(file)[1]Newdir=os.path.join(path,str(count)+filetype) os.rename(Olddir,Newdir)count+=1

对数据集重命名后,人脸正样本(有口罩)数据集显示效果如下

将负样本(没有口罩)数据集重命名为连续序列,以便后面调整

#coding:utf-8import ospath = "D:/mangomask/mask/no_mask" # 人脸口罩数据集负样本的路径filelist = os.listdir(path)count=10000 #开始文件名1000.jpgfor file in filelist: Olddir=os.path.join(path,file) if os.path.isdir(Olddir): continuefilename=os.path.splitext(file)[0] filetype=os.path.splitext(file)[1]Newdir=os.path.join(path,str(count)+filetype) os.rename(Olddir,Newdir)count+=1

对数据集重命名后,人脸负样本(没有口罩)数据集显示效果如下

正负样本数据集像素处理

正样本(有口罩)数据集的像素设置为 20x20,模型训练精度更高;

负样本(没有口罩)数据集像素设置不低于50x50,加快模型训练的速度。

1.调整正样本(有口罩)像素

import pandas as pdimport cv2for n in range(1000,1606):#代表正数据集中开始和结束照片的数字path='D:/mangomask/mask/have_mask/'+str(n)+'.jpg'# 读取图片img = cv2.imread(path)img=cv2.resize(img,(20,20)) #修改样本像素为20x20cv2.imwrite('D:/mangomask/mask/have_mask/' + str(n) + '.jpg', img)n += 1

调整正样本(有口罩)像素后,数据集图像显示效果

2.调整负样本(没有口罩)像素

#修改负样本像素import pandas as pdimport cv2for n in range(10000,11790):#代表负样本数据集中开始和结束照片的数字path='D:/mangomask/mask/no_mask/'+str(n)+'.jpg'# 读取图片img = cv2.imread(path)img=cv2.resize(img,(80,80)) #修改样本像素为60x60cv2.imwrite('D:/mangomask/mask/no_mask/' + str(n) + '.jpg', img)n += 1

调整负样本(没有口罩)像素后,数据集图像显示效果

划分数据集(在当前写代码的同级目录下会产生一个maskout的文件夹,包括 train :训练集;validation:验证集;test:测试集。)

original_dataset_dir = 'D://mangomask//mask' # 原始数据集的路径base_dir = 'maskout' # 存放图像数据集的目录os.mkdir(base_dir)train_dir = os.path.join(base_dir, 'train') # 训练图像的目录os.mkdir(train_dir)validation_dir = os.path.join(base_dir, 'validation') # 验证图像的目录os.mkdir(validation_dir)test_dir = os.path.join(base_dir, 'test') # 测试图像的目录os.mkdir(test_dir)train_havemask_dir = os.path.join(train_dir, 'have_mask') # 有口罩的图片的训练资料的目录os.mkdir(train_havemask_dir)train_nomask_dir = os.path.join(train_dir, 'no_mask') # 没有口罩的图片的训练资料的目录os.mkdir(train_nomask_dir)validation_havemask_dir = os.path.join(validation_dir, 'have_mask') # 有口罩的图片的验证集目录os.mkdir(validation_havemask_dir)validation_nomask_dir = os.path.join(validation_dir, 'no_mask')# 没有口罩的图片的验证集目录os.mkdir(validation_nomask_dir)test_havemask_dir = os.path.join(test_dir, 'have_mask') # 有口罩的图片的测试数据集目录os.mkdir(test_havemask_dir)test_nomask_dir = os.path.join(test_dir, 'no_mask') # 没有口罩的图片的测试数据集目录os.mkdir(test_nomask_dir)

分配数据集,可以使用人为划分和代码划分进行一次检查,计算每个分组中有多少张照片(训练/验证/测试)

print('total training havemask images:', len(os.listdir(train_havemask_dir)))

total training havemask images: 300

print('total testing havemask images:', len(os.listdir(test_havemask_dir)))

total testing havemask images: 150

print('total training nomask images:', len(os.listdir(train_nomask_dir)))

total training nomask images: 300

print('total validation havemask images:', len(os.listdir(validation_havemask_dir)))

total validation havemask images: 150

print('total testing nomask images:', len(os.listdir(test_nomask_dir)))

total testing nomask images: 150

print('total validation nomask images:', len(os.listdir(validation_nomask_dir)))

total validation nomask images: 150

有600个训练图像,然后是300个验证图像,300个测试图像,其中每个分类都有相同数量的样本,是一个平衡的二元分类问题,意味着分类准确度将是合适的度量标准。

2.网络模型

卷积网络(convnets)将是一组交替的Conv2D(具有relu激活)和MaxPooling2D层。从大小150x150(有点任意选择)的输入开始,我们最终得到了尺寸为7x7的Flatten层之前的特征图。

注意特征图的深度在网络中逐渐增加(从32到128),而特征图的大小正在减少(从148x148到7x7)。这是一个你将在几乎所有的卷积网络(convnets)结构中会看到的模式。

由于我们正在处理二元分类问题,所以我们用一个神经元(一个大小为1的密集层(Dense))和一个sigmoid激活函数来结束网络。该神经元将会被用来查看图像归属于那一类或另一类的概率。

创建模型

#创建模型from keras import layersfrom keras import modelsmodel = models.Sequential()model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(64, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Flatten())model.add(layers.Dense(512, activation='relu'))model.add(layers.Dense(1, activation='sigmoid'))

看特征图的尺寸如何随着每个连续的图层而改变,打印网络结构

model.summary()

_________________________________________________________________Layer (type) Output Shape Param # =================================================================conv2d_13 (Conv2D) (None, 148, 148, 32)896 _________________________________________________________________max_pooling2d_13 (MaxPooling (None, 74, 74, 32) 0 _________________________________________________________________conv2d_14 (Conv2D) (None, 72, 72, 64) 18496_________________________________________________________________max_pooling2d_14 (MaxPooling (None, 36, 36, 64) 0 _________________________________________________________________conv2d_15 (Conv2D) (None, 34, 34, 128) 73856_________________________________________________________________max_pooling2d_15 (MaxPooling (None, 17, 17, 128) 0 _________________________________________________________________conv2d_16 (Conv2D) (None, 15, 15, 128) 147584 _________________________________________________________________max_pooling2d_16 (MaxPooling (None, 7, 7, 128) 0 _________________________________________________________________flatten_4 (Flatten)(None, 6272) 0 _________________________________________________________________dense_7 (Dense) (None, 512)3211776 _________________________________________________________________dense_8 (Dense) (None, 1) 513 =================================================================Total params: 3,453,121Trainable params: 3,453,121Non-trainable params: 0_________________________________________________________________

在编译步骤里,使用RMSprop优化器。由于用一个单一的神经元(Sigmoid的激活函数)结束了网络,将使用二进制交叉熵(binary crossentropy)作为损失函数

from keras import pile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])

3.资料预处理

网络的预处理步骤:读入图像
将JPEG内容解码为RGB网格的像素
将其转换为浮点张量
将像素值(0和255之间)重新缩放到[0,1]间隔
数据应该先被格式化成适当的预处理浮点张量,然后才能输入到神经网络中

from keras.preprocessing.image import ImageDataGenerator# 所有的图像将重新进行归一化处理Rescaled by 1./255train_datagen = ImageDataGenerator(rescale=1./255)validation_datagen=ImageDataGenerator(rescale=1./255)test_datagen = ImageDataGenerator(rescale=1./255)# 直接从目录读取图像数据train_generator = train_datagen.flow_from_directory(# 训练图像的目录train_dir,# 所有图像大小会被转换成150x150target_size=(150, 150),# 每次产生20个图像的批次batch_size=20,# 由于这是一个二元分类问题,y的label值也会被转换成二元的标签class_mode='binary')# 直接从目录读取图像数据validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(150, 150),batch_size=20,class_mode='binary')test_generator = test_datagen.flow_from_directory(test_dir,target_size=(150, 150),batch_size=20,class_mode='binary')

Found 600 images belonging to 2 classes.Found 300 images belonging to 2 classes.Found 300 images belonging to 2 classes.

图像张量生成器(generator)的输出,它产生150x150 RGB图像(形状"(20,150,150,3)")和二进制标签(形状"(20,)")的批次张量。20是每个批次中的样品数(批次大小)

for data_batch, labels_batch in train_generator:print('data batch shape:', data_batch.shape)print('labels batch shape:', labels_batch)break

data batch shape: (20, 150, 150, 3)labels batch shape: [ 1. 1. 1. 0. 1. 1. 0. 0. 0. 0. 1. 0. 0. 1. 1. 0. 1. 0.0. 0.]

4.开始训练

这里取epochs=10,其中epochs值越大,花费的时间就越久、训练的精度更高,我电脑性能不好,运行了很久… …

#花费时间长history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=10,validation_data=validation_generator,validation_steps=50)

Epoch 1/10100/100 [==============================] - 218s 2s/step - loss: 0.2563 - acc: 0.8990 - val_loss: 0.1740 - val_acc: 0.9400Epoch 2/10100/100 [==============================] - 189s 2s/step - loss: 0.0862 - acc: 0.9700 - val_loss: 0.1294 - val_acc: 0.9540Epoch 3/10100/100 [==============================] - 190s 2s/step - loss: 0.0548 - acc: 0.9820 - val_loss: 0.1033 - val_acc: 0.9680Epoch 4/10100/100 [==============================] - 186s 2s/step - loss: 0.0325 - acc: 0.9880 - val_loss: 0.1132 - val_acc: 0.9620Epoch 5/10100/100 [==============================] - 192s 2s/step - loss: 0.0238 - acc: 0.9925 - val_loss: 0.0922 - val_acc: 0.9800Epoch 6/10100/100 [==============================] - 191s 2s/step - loss: 0.0132 - acc: 0.9965 - val_loss: 0.0950 - val_acc: 0.9710Epoch 7/10100/100 [==============================] - 189s 2s/step - loss: 0.0061 - acc: 0.9980 - val_loss: 0.1093 - val_acc: 0.9710Epoch 8/10100/100 [==============================] - 188s 2s/step - loss: 0.0025 - acc: 0.9995 - val_loss: 0.1305 - val_acc: 0.9690Epoch 9/10100/100 [==============================] - 185s 2s/step - loss: 0.0080 - acc: 0.9980 - val_loss: 0.1067 - val_acc: 0.9770Epoch 10/10100/100 [==============================] - 189s 2s/step - loss: 6.6883e-04 - acc: 1.0000 - val_loss: 0.1032 - val_acc: 0.9780

训练完成后把模型保存下来

model.save('maskout/maskAndNomask_1.h5')

使用图表来绘制在训练过程中模型对训练和验证数据的损失(loss)和准确性(accuracy)数据

import matplotlib.pyplot as plt# import h5pyacc = history.history['acc']val_acc = history.history['val_acc']loss = history.history['loss']val_loss = history.history['val_loss']epochs = range(len(acc))# print('acc:',acc)# print('val_acc:',val_acc)# print('loss:',loss)# print('val_loss:',val_loss)plt.plot(epochs, acc, 'bo', label='Training acc')plt.plot(epochs, val_acc, 'b', label='Validation acc')plt.title('Training and validation accuracy')plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')plt.plot(epochs, val_loss, 'b', label='Validation loss')plt.title('Training and validation loss')plt.legend()plt.show()

上面图标显示了过度拟合(overfitting)的特征。我们的训练精确度随着时间线性增长,直到接近100%,然而我们的验证精度却停在96%~97%。我们的验证损失在第4个循环(epochs)之后达到最小值,然后停顿,而训练损失在线性上保持下降直到接近0。

5.使用数据填充

数据增加采用从现有训练样本生成更多训练数据的方法,通过产生可信的图像的多个随机变换来"增加"样本。目标是在训练的时候,我们的模型永远不会再看到完全相同的画面两次。这有助于模型学习到数据的更多方面,并更好地推广。
在keras中,可以通过配置对ImageDataGenerator实例读取的图像执行多个随机变换来完成

datagen = ImageDataGenerator(rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,fill_mode='nearest')

这里列出一些可用的选项(更多选项,可以参考keras文档),快速看一下这些参数:

rotation_range 是以度(0-180)为单位的值,它是随机旋转图片的范围

width_shift 和 height_shift 是范围(占总宽度或高度的一小部分),用于纵向或横向随机转换图片

shear_range 用于随机剪切变换

zoom_range 用于随机放大图片内容

horizontal_flip 用于在没有水平不对称假设(例如真实世界图片)的情况下水平地随机翻转一半图像

fill_mode 是用于填充新创建的像素的策略,可以在旋转或宽/高移位后显示

看增强后的图像

import matplotlib.pyplot as pltfrom keras.preprocessing import imagefnames = [os.path.join(train_havemask_dir, fname) for fname in os.listdir(train_havemask_dir)]img_path = fnames[3]img = image.load_img(img_path, target_size=(150, 150))x = image.img_to_array(img)x = x.reshape((1,) + x.shape)i = 0for batch in datagen.flow(x, batch_size=1):plt.figure(i)imgplot = plt.imshow(image.array_to_img(batch[0]))i += 1if i % 4 == 0:breakplt.show()

如果我们使用这种数据增强配置来训练一个新的网络,我们的网络将永远不会看到相同重复的输入。
然而,它看到的输入仍然是相互关联的,因为它们来自少量的原始图像 - 我们不能产生新的信息,我们只能重新混合现有的信息。因此,这可能不足以完全摆脱过度拟合(overfitting)。为了进一步克服过度拟合(overfitting),我们还将在密集连接(densely-connected)的分类器之前添加一个Dropout层。

model = models.Sequential()model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(64, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(128, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Flatten())model.add(layers.Dropout(0.5))model.add(layers.Dense(512, activation='relu'))model.add(layers.Dense(1, activation='sigmoid'))pile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])

使用数据填充(data augmentation)和dropout来训练我们的网络

#归一化处理train_datagen = ImageDataGenerator(rescale=1./255,rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,)# Note that the validation data should not be augmented!test_datagen = ImageDataGenerator(rescale=1./255)train_generator = train_datagen.flow_from_directory(# This is the target directorytrain_dir,# All images will be resized to 150x150target_size=(150, 150),batch_size=32,# Since we use binary_crossentropy loss, we need binary labelsclass_mode='binary')validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(150, 150),batch_size=32,class_mode='binary')history = model.fit_generator(train_generator,steps_per_epoch=100,epochs=60, validation_data=validation_generator,validation_steps=50)

Found 600 images belonging to 2 classes.Found 300 images belonging to 2 classes.Epoch 1/60100/100 [==============================] - 351s 4s/step - loss: 0.4850 - acc: 0.7632 - val_loss: 0.2380 - val_acc: 0.8900Epoch 2/60100/100 [==============================] - 323s 3s/step - loss: 0.3041 - acc: 0.8703 - val_loss: 0.2513 - val_acc: 0.8833Epoch 3/60100/100 [==============================] - 322s 3s/step - loss: 0.2864 - acc: 0.8725 - val_loss: 0.2486 - val_acc: 0.8867Epoch 4/60100/100 [==============================] - 316s 3s/step - loss: 0.2490 - acc: 0.8964 - val_loss: 0.1243 - val_acc: 0.9533Epoch 5/60100/100 [==============================] - 308s 3s/step - loss: 0.2303 - acc: 0.9056 - val_loss: 0.1830 - val_acc: 0.9200Epoch 6/60100/100 [==============================] - 306s 3s/step - loss: 0.2098 - acc: 0.9152 - val_loss: 0.1101 - val_acc: 0.9633Epoch 7/60100/100 [==============================] - 312s 3s/step - loss: 0.1905 - acc: 0.9200 - val_loss: 0.1417 - val_acc: 0.9367Epoch 8/60100/100 [==============================] - 310s 3s/step - loss: 0.1771 - acc: 0.9272 - val_loss: 0.1021 - val_acc: 0.9700Epoch 9/60100/100 [==============================] - 302s 3s/step - loss: 0.1710 - acc: 0.9284 - val_loss: 0.1220 - val_acc: 0.9467Epoch 10/60100/100 [==============================] - 321s 3s/step - loss: 0.1618 - acc: 0.9375 - val_loss: 0.0920 - val_acc: 0.9667Epoch 11/60100/100 [==============================] - 308s 3s/step - loss: 0.1458 - acc: 0.9420 - val_loss: 0. - val_acc: 0.9167Epoch 12/60100/100 [==============================] - 303s 3s/step - loss: 0.1411 - acc: 0.9456 - val_loss: 0.0829 - val_acc: 0.9700Epoch 13/60100/100 [==============================] - 304s 3s/step - loss: 0.1190 - acc: 0.9537 - val_loss: 0.0932 - val_acc: 0.9667Epoch 14/60100/100 [==============================] - 307s 3s/step - loss: 0.1163 - acc: 0.9569 - val_loss: 0.1085 - val_acc: 0.9567Epoch 15/60100/100 [==============================] - 306s 3s/step - loss: 0.1006 - acc: 0.9629 - val_loss: 0.0715 - val_acc: 0.9767Epoch 16/60100/100 [==============================] - 312s 3s/step - loss: 0.0960 - acc: 0.9667 - val_loss: 0.0588 - val_acc: 0.9767Epoch 17/60100/100 [==============================] - 308s 3s/step - loss: 0.0806 - acc: 0.9676 - val_loss: 0.0535 - val_acc: 0.9800Epoch 18/60100/100 [==============================] - 305s 3s/step - loss: 0.0778 - acc: 0.9711 - val_loss: 0.2239 - val_acc: 0.9300Epoch 19/60100/100 [==============================] - 307s 3s/step - loss: 0.0761 - acc: 0.9713 - val_loss: 0.0575 - val_acc: 0.9767Epoch 20/60100/100 [==============================] - 308s 3s/step - loss: 0.0507 - acc: 0.9816 - val_loss: 0.0926 - val_acc: 0.9667Epoch 21/60100/100 [==============================] - 306s 3s/step - loss: 0.0635 - acc: 0.9799 - val_loss: 0.0470 - val_acc: 0.9833Epoch 22/60100/100 [==============================] - 319s 3s/step - loss: 0.0701 - acc: 0.9750 - val_loss: 0.0437 - val_acc: 0.9867Epoch 23/60100/100 [==============================] - 315s 3s/step - loss: 0.0493 - acc: 0.9849 - val_loss: 0.0408 - val_acc: 0.9900Epoch 24/60100/100 [==============================] - 309s 3s/step - loss: 0.0513 - acc: 0.9824 - val_loss: 0.0449 - val_acc: 0.9767Epoch 25/60100/100 [==============================] - 304s 3s/step - loss: 0.0580 - acc: 0.9816 - val_loss: 0.0330 - val_acc: 0.9900Epoch 26/60100/100 [==============================] - 312s 3s/step - loss: 0.0434 - acc: 0.9884 - val_loss: 0.0357 - val_acc: 0.9833Epoch 27/60100/100 [==============================] - 302s 3s/step - loss: 0.0707 - acc: 0.9785 - val_loss: 0.0214 - val_acc: 0.9933Epoch 28/60100/100 [==============================] - 311s 3s/step - loss: 0.0431 - acc: 0.9869 - val_loss: 0.0306 - val_acc: 0.9900Epoch 29/60100/100 [==============================] - 305s 3s/step - loss: 0.0424 - acc: 0.9859 - val_loss: 0.0278 - val_acc: 0.9900Epoch 30/60100/100 [==============================] - 305s 3s/step - loss: 0.0240 - acc: 0.9934 - val_loss: 0.0233 - val_acc: 0.9933Epoch 31/60100/100 [==============================] - 335s 3s/step - loss: 0.0515 - acc: 0.9853 - val_loss: 0.0268 - val_acc: 0.9867Epoch 32/60100/100 [==============================] - 326s 3s/step - loss: 0.0515 - acc: 0.9884 - val_loss: 0.0222 - val_acc: 0.9933Epoch 33/60100/100 [==============================] - 320s 3s/step - loss: 0.0273 - acc: 0.9927 - val_loss: 0.0281 - val_acc: 0.9900Epoch 34/60100/100 [==============================] - 310s 3s/step - loss: 0.0411 - acc: 0.9909 - val_loss: 0.0282 - val_acc: 0.9900Epoch 35/60100/100 [==============================] - 306s 3s/step - loss: 0.0204 - acc: 0.9950 - val_loss: 0.0165 - val_acc: 0.9933Epoch 36/60100/100 [==============================] - 306s 3s/step - loss: 0.0623 - acc: 0.9842 - val_loss: 0.0268 - val_acc: 0.9900Epoch 37/60100/100 [==============================] - 304s 3s/step - loss: 0.0325 - acc: 0.9908 - val_loss: 0.0152 - val_acc: 0.9933Epoch 38/60100/100 [==============================] - 305s 3s/step - loss: 0.0178 - acc: 0.9933 - val_loss: 0.0117 - val_acc: 0.9967Epoch 39/60100/100 [==============================] - 309s 3s/step - loss: 0.0507 - acc: 0.9884 - val_loss: 0.0164 - val_acc: 0.9933Epoch 40/60100/100 [==============================] - 305s 3s/step - loss: 0.0398 - acc: 0.9919 - val_loss: 0.0236 - val_acc: 0.9933Epoch 41/60100/100 [==============================] - 300s 3s/step - loss: 0.0243 - acc: 0.9909 - val_loss: 0.0176 - val_acc: 0.9933Epoch 42/60100/100 [==============================] - 307s 3s/step - loss: 0.0419 - acc: 0.9922 - val_loss: 0.0145 - val_acc: 0.9933Epoch 43/60100/100 [==============================] - 302s 3s/step - loss: 0.0451 - acc: 0.9928 - val_loss: 0.0155 - val_acc: 0.9933Epoch 44/60100/100 [==============================] - 304s 3s/step - loss: 0.0640 - acc: 0.9893 - val_loss: 0.2175 - val_acc: 0.9333Epoch 45/60100/100 [==============================] - 314s 3s/step - loss: 0.0285 - acc: 0.9934 - val_loss: 0.0092 - val_acc: 0.9967Epoch 46/60100/100 [==============================] - 309s 3s/step - loss: 0.0279 - acc: 0.9937 - val_loss: 0.0116 - val_acc: 0.9933Epoch 47/60100/100 [==============================] - 305s 3s/step - loss: 0.0258 - acc: 0.9925 - val_loss: 0.0157 - val_acc: 0.9900Epoch 48/60100/100 [==============================] - 307s 3s/step - loss: 0.0319 - acc: 0.9906 - val_loss: 0.0142 - val_acc: 0.9933Epoch 49/60100/100 [==============================] - 305s 3s/step - loss: 0.0562 - acc: 0.9884 - val_loss: 0.0228 - val_acc: 0.9933Epoch 50/60100/100 [==============================] - 305s 3s/step - loss: 0.0370 - acc: 0.9931 - val_loss: 0.0230 - val_acc: 0.9867Epoch 51/60100/100 [==============================] - 309s 3s/step - loss: 0.0047 - acc: 0.9984 - val_loss: 0.0147 - val_acc: 0.9933Epoch 52/60100/100 [==============================] - 306s 3s/step - loss: 0.0237 - acc: 0.9941 - val_loss: 0.0161 - val_acc: 0.9900Epoch 53/60100/100 [==============================] - 301s 3s/step - loss: 0.0278 - acc: 0.9950 - val_loss: 0.0202 - val_acc: 0.9933Epoch 54/60100/100 [==============================] - 309s 3s/step - loss: 0.0266 - acc: 0.9945 - val_loss: 0.0267 - val_acc: 0.9933Epoch 55/60100/100 [==============================] - 302s 3s/step - loss: 0.0264 - acc: 0.9941 - val_loss: 0.0231 - val_acc: 0.9967Epoch 56/60100/100 [==============================] - 304s 3s/step - loss: 0.0132 - acc: 0.9959 - val_loss: 0.0177 - val_acc: 0.9933Epoch 57/60100/100 [==============================] - 326s 3s/step - loss: 0.0773 - acc: 0.9891 - val_loss: 0.0893 - val_acc: 0.9733Epoch 58/60100/100 [==============================] - 311s 3s/step - loss: 0.0049 - acc: 0.9984 - val_loss: 0.0277 - val_acc: 0.9933Epoch 59/60100/100 [==============================] - 308s 3s/step - loss: 0.0791 - acc: 0.9906 - val_loss: 0.0314 - val_acc: 0.9867Epoch 60/60100/100 [==============================] - 307s 3s/step - loss: 0.0133 - acc: 0.9956 - val_loss: 0.0186 - val_acc: 0.9933

查看0与1代表含义(0代表有口罩、1代表没有口罩)

train_generator.class_indices

{'have_mask': 0, 'no_mask': 1}

保存训练模型

#保存模型model.save('maskout/maskAndNomask_2.h5')

保存模型后的文件夹显示效果

绘制数据增强后的训练集与验证集的精确度与损失度的图形,看一遍结果

acc = history.history['acc']val_acc = history.history['val_acc']loss = history.history['loss']val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training acc')plt.plot(epochs, val_acc, 'b', label='Validation acc')plt.title('Training and validation accuracy')plt.legend()plt.figure()plt.plot(epochs, loss, 'bo', label='Training loss')plt.plot(epochs, val_loss, 'b', label='Validation loss')plt.title('Training and validation loss')plt.legend()plt.show()

由于数据增加(data augmentation)和dropout使用,不再有过度拟合(overfitting)的问题。
训练曲线相当密切地跟随着验证曲线。训练精度和验证精度经过60个循环无限接近100%。验证损失和训练损失在线性上保持下降直到接近0。
通过进一步利用正规化技术,及调整网络参数(例如每个卷积层的滤波器数量或网络层数),可以获得更好的准确度。

6.对人脸戴口罩与否的模型训练

判断的第一张图片(D:/mango/nana.jpg路径下)我喜欢的明星 Nana

from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as npmodel = load_model('maskout/maskAndNomask_1.h5')img_path='D:/mango/nana.jpg'img = image.load_img(img_path, target_size=(150, 150))#print(img.size)img_tensor = image.img_to_array(img)/255.0img_tensor = np.expand_dims(img_tensor, axis=0)prediction =model.predict(img_tensor) print(prediction)if prediction[0][0]>0.5:result='未戴口罩'else:result='戴口罩'print(result)

[[ 0.0132275]]戴口罩

判断的第二张图片(D:/mango/mengmeng.jpg路径下)(这也是她自己拍的,哈哈… …)

# 单张图片进行判断 是否戴口罩from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as npmodel = load_model('maskout/maskAndNomask_2.h5')img_path='D:/mango/mengmeng.jpg'img = image.load_img(img_path, target_size=(150, 150))#print(img.size)img_tensor = image.img_to_array(img)/255.0img_tensor = np.expand_dims(img_tensor, axis=0)prediction =model.predict(img_tensor) print(prediction)if prediction[0][0]>0.5:result='未戴口罩'else:result='戴口罩'print(result)

[[ 0.99999881]]未戴口罩

可以看见,判断图片是否戴口罩准确度还是很高的,但是还是有一定得误差。综上所述,图片中人脸越清晰越容易判别正确的精度就越高。

四、完成一个摄像头采集自己人脸、并对表情(笑脸/非笑脸、戴口罩和没戴口罩)的实时分类判读(输出分类文字)的程序

1.笑脸/非笑脸实时分类判读(输出分类文字)的程序

#检测视频或者摄像头中的人脸import cv2from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as npimport dlibfrom PIL import Imagemodel = load_model('mangoout/smileAndUnsmile_2.h5')detector = dlib.get_frontal_face_detector()video=cv2.VideoCapture(0)font = cv2.FONT_HERSHEY_SIMPLEXdef rec(img):gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)dets=detector(gray,1)if dets is not None:for face in dets:left=face.left()top=face.top()right=face.right()bottom=face.bottom()cv2.rectangle(img,(left,top),(right,bottom),(0,255,0),2)img1=cv2.resize(img[top:bottom,left:right],dsize=(150,150))img1=cv2.cvtColor(img1,cv2.COLOR_BGR2RGB)img1 = np.array(img1)/255.img_tensor = img1.reshape(-1,150,150,3)prediction =model.predict(img_tensor) if prediction[0][0]>0.5:result='unsmile'else:result='smile'cv2.putText(img, result, (left,top), font, 2, (0, 255, 0), 2, cv2.LINE_AA)cv2.imshow('Video', img)while video.isOpened():res, img_rd = video.read()if not res:breakrec(img_rd)if cv2.waitKey(1) & 0xFF == ord('q'):breakvideo.release()cv2.destroyAllWindows()

识别自己的图片运行效果:没有微笑
微笑

2.戴口罩和没戴口罩的实时分类判读(输出分类文字)的程序

import cv2from keras.preprocessing import imagefrom keras.models import load_modelimport numpy as npimport dlibfrom PIL import Imagemodel = load_model('maskout/maskAndNomask_2.h5')detector = dlib.get_frontal_face_detector()# video=cv2.VideoCapture('media/video.mp4')# video=cv2.VideoCapture('data/face_recognition.mp4')video=cv2.VideoCapture(0)font = cv2.FONT_HERSHEY_SIMPLEXdef rec(img):gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)dets=detector(gray,1)if dets is not None:for face in dets:left=face.left()top=face.top()right=face.right()bottom=face.bottom()cv2.rectangle(img,(left,top),(right,bottom),(0,255,255),2)def mask(img):img1=cv2.resize(img,dsize=(150,150))img1=cv2.cvtColor(img1,cv2.COLOR_BGR2RGB)img1 = np.array(img1)/255.img_tensor = img1.reshape(-1,150,150,3)prediction =model.predict(img_tensor) if prediction[0][0]>0.5:result='no-mask'else:result='have-mask'cv2.putText(img, result, (100,200), font, 2, (255, 255, 100), 2, cv2.LINE_AA)cv2.imshow('Video', img)while video.isOpened():res, img_rd = video.read()if not res:break#将视频每一帧传入两个函数,分别用于圈出人脸与判断是否带口罩rec(img_rd)mask(img_rd)#q关闭窗口if cv2.waitKey(1) & 0xFF == ord('q'):breakvideo.release()cv2.destroyAllWindows()

运行结果:没有戴口罩的效果
戴口罩的效果

【人工智能与机器学习】——Keras编程分别实现人脸微笑和口罩数据集的识别模型训练和测试(卷积神经网络CNN) + 实时分类微笑和口罩识别检测

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。