@[TOC](详解深度学习之经典网络:AlexNet(2012) 并利用该网络架构实现人脸识别**)
近来闲来无事,翻出了搁置已久的轻薄版电脑,望着积满灰尘的显示屏,觉得有愧于老师的尊尊教导,心中叹息的声音久久不能平复。 古人云:盛年不重来,一日难再晨。及时宜自勉,岁月不待人。
古人亦云:亡羊补牢,犹未为晚。我辈岂是蓬蒿人?非也。余遂拍案而起,壮士当攻城略地,建功立业,岂可荒戏于杯酒佳肴之间。是日,闻鸡起舞,秉烛夜读,卧薪尝胆,终打通任督二脉,习得这通天彻地之技能----人脸识别。古有周易占卜,今有Al换脸。
好了,不装逼了,再装逼就要被打死了。
今天的主题是使用Python3,keras等搭建AlexNet(2012)
最终的运行结果如下
本文讲解如何使用Tensorflow搭建一个属于自己的神经网络,并如何进行图像的采集,图像的预处理,神经网络的搭建,以及该模型的训练,参数的计算。并实现一个可以鉴别动态视频流中的人脸的应用,并通过该过程让没有任何机器学习基础的读者了解机器学习使用的场景和流程,包括如何加载模型、如何准备输入数据、如何解析推理结果。
本文默认已搭建好Tensorflow,keras,OpenCV等环境,若没搭建则可以参照其他的博客,此处不再啰嗦。
一,数据的采集
首先进行的是人脸的识别
OpenCV的Haar级联分类器可以通过对比分析相邻图像区域的特征来判断给定图像或子图像与已知对象是否匹配,从而给图像进行分类。提取图像的细节对产生稳定可靠的分类结果很有用。这些提取的结果被称为特征。特征的数量通常应该比像素数少得多。两个图像的相似程度可以通过它们的特征向量的距离来计算。
OpenCV的Haar级联分类器具有尺度不变型(通过循环缩放图像再进行特征比对来实现),即它在尺度缩放上具有鲁棒性。但是,它不具备旋转不变形。例如,Haar级联分离器认为倒置的人脸图像和正立的人脸图像不一样,且认为侧面的人脸图像和正面的人脸图像也不一样。在OpenCV的源代码的副本中会有一个文件夹 \sources\data\haarcascades,但我的是在(D:\Program Files\TenserFlow\pkgs\libopencv-3.4.2-h20b85fd_0\Library\etc\haarcascades)。该文件夹包含了所有OpenCV的人脸检测的XML文件,这些文件可用于检测静止图像、视频和摄像头所得到的图像中的人脸。
这个东西的作用主要是实现对人脸识别的功能,在安装中还有其他的功能,我也一并列在下面:
人脸检测器(默认):haarcascade_frontalface_default.xml
人脸检测器(快速Harr):haarcascade_frontalface_alt2.xml
人脸检测器(侧视):haarcascade_profileface.xml
眼部检测器(左眼):haarcascade_lefteye_2splits.xml
眼部检测器(右眼):haarcascade_righteye_2splits.xml
嘴部检测器:haarcascade_mcs_mouth.xml
鼻子检测器:haarcascade_mcs_nose.xml
身体检测器:haarcascade_fullbody.xml
人脸检测器(快速LBP):lbpcascade_frontalface.xml
另外,如果我们想构建自己的分类器,比如识别火焰、汽车,数,花等,我们依然可以使用OpenCV训练构建。
假设我们已将上述文件夹都拷贝到了项目文件夹中。下面的例子我们来检测静止图像中人脸,视频帧流中人脸检测的方法也大致一样
import cv2
import numpy as np
from matplotlib import pyplot as plt
def detect(filename):
img = cv2.imread(filename)
gray =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#检测正脸
front_face_cascade = cv2.CascadeClassifier('./cascades/haarcascade_frontalface_default.xml')
faces0 = front_face_cascade.detectMultiScale(gray, 1.022, 5)
print("共检测到%d张人的正脸" %len(faces0))
#检测侧脸
profile_face_cascade = cv2.CascadeClassifier('./cascades/haarcascade_profileface.xml')
faces1 = profile_face_cascade.detectMultiScale(gray, 1.2, 6)
print("共检测到%d张人的侧脸" %len(faces1))
for (x, y, w, h) in faces0:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255), 1) #画红色矩形框标记正脸
for (x, y, w, h) in faces1:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 1) #画绿色矩形框标记侧脸
return img
img = detect("physicists.jpg")
plt.subplot(1,1,1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title("OpenCV 人脸检测",fontSize =16, color="b")
plt.show()
附上detectMultiScale函数的参数说明:
detectMultiScale(...) method of cv2.CascadeClassifier instance
detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]]) -> objects .
@brief Detects objects of different sizes in the input image. The detected objects are returned as a list .
of rectangles. . . @param image Matrix of the type CV_8U containing an image where objects are detected. .
@param objects Vector of rectangles where each rectangle contains the detected object, the .
rectangles may be partially outside the original image. .
@param scaleFactor Parameter specifying how much the image size is reduced at each image scale. .
@param minNeighbors Parameter specifying how many neighbors each candidate rectangle should have . to retain it. .
@param flags Parameter with the same meaning for an old cascade as in the function . cvHaarDetectObjects. It is not used for a new cascade. . @param minSize Minimum possible object size. Objects smaller than that are ignored. .
@param maxSize Maximum possible object size. Objects larger than that are ignored. If `maxSize == minSize` model is evaluated on single scale. . . The function is parallelized with the TBB library. . .
@note . - (Python) A face detection example using cascade classifiers can be found at .
opencv_source_code/samples/python/facedetect.py
scaleFactor是每次迭代的缩放比例,越小(比1大)越可能检测到更多的人脸,但更可能重复。
minNeighbors 是每个人脸矩形保留尽量数目的最小值,整数。越小越可能检测到更多的人脸。
minSize 和maxSize 可以加入尺寸过滤。
原始图像:
检测的结果如下:(图中不看镜头的那位大牛是发表“泡利不相容”原理的泡利,被检测出了侧脸)
这只是识别静态图片中的人脸,接下来是识别动态视频流中的人脸,只需把图片的来源转化为视频流中的每一帧即可。
import cv2
import sys
from PIL import Image
def CatchUsbVideo(window_name, camera_idx):
cv2.namedWindow(window_name)
#视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
cap = cv2.VideoCapture(camera_idx)
#告诉OpenCV使用人脸识别分类器
classfier = cv2.CascadeClassifier("./cascades/haarcascade_frontalface_default.xml")
#识别出人脸后要画的边框的颜色,RGB格式
color = (0, 255, 0)
while cap.isOpened():
ok, frame = cap.read() #读取一帧数据
if not ok:
break
#将当前帧转换成灰度图像
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
if len(faceRects) > 0: #大于0则检测到人脸
for faceRect in faceRects: #单独框出每一张人脸
x, y, w, h = faceRect
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
#显示图像
cv2.imshow(window_name, frame)
c = cv2.waitKey(10)
if c & 0xFF == ord('q'):
break
#释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
if len(sys.argv) != 1:
print("Usage:%s camera_id\r\n" % (sys.argv[0]))
else:
CatchUsbVideo("识别人脸区域", 0)
关于具体每个步骤的含义,初学者可自行百度。
这只是可以在视频流中识别到了人脸,接下来我们需要将这些人脸以图片的格式保存起来
import cv2
import sys
from PIL import Image
def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
cv2.namedWindow(window_name)
cap = cv2.VideoCapture(camera_idx)
classfier = cv2.CascadeClassifier("./cascades/haarcascade_frontalface_default.xml")
color = (0, 255, 0)
num = 0
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
if len(faceRects) > 0: #大于0则检测到人脸
for faceRect in faceRects: #单独框出每一张人脸
x, y, w, h = faceRect
#将当前帧保存为图片
img_name = '%s/%d.jpg'%(path_name, num)
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
cv2.imwrite(img_name, image)
num += 1
if num > (catch_pic_num): #如果超过指定最大保存数量退出循环
break
#画出矩形框
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
#显示当前捕捉到了多少人脸图片了,这样站在那里被拍摄时心里有个数,不用两眼一抹黑傻等着
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame,'num:%d' % (num),(x + 30, y + 30), font, 1, (255,0,255),4)
#超过指定最大保存数量结束程序
if num > (catch_pic_num): break
#显示图像
cv2.imshow(window_name, frame)
c = cv2.waitKey(10)
if c & 0xFF == ord('q'):
break
#释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
if len(sys.argv) != 1:
print("Usage:%s camera_id face_num_max path_name\r\n" % (sys.argv[0]))
else:
CatchPICFromVideo("截取人脸", 0, 1000, 'C:\\Users\\DELL\\Desktop\\zhengfei\\DL\\data')
这段代码我们只是在前面代码的基础上增加脸部图像存储功能,比较简单。
def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
在函数定义中,几个参数,分别是窗口名字,摄像头系列号,捕捉照片数量,以及存储路径。根据自己需要进行修改,咱们这里为了精度高一点,选择捕捉1000张脸部照片。在你捕捉的时候由于精度的问题,会捕捉许多非脸部的照片,这时候需要你将不是脸部的照片清洗掉,使数据更加准确。另外,我们还需要捕捉另一个人的图片来提高模型的准确度。然后存储到另一个文件夹下,注意,一个人的照片存储到一个文件夹下,不可弄混。截图完成,就像下图这样。
到此为止,我们已经做好了图像的采集的操作。共2000张脸部图像。
二,数据的预处理
import os
import sys
import numpy as np
import cv2
IMAGE_SIZE = 64
#按照指定图像大小调整尺寸
def resize_image(image, height = IMAGE_SIZE, width = IMAGE_SIZE):
top, bottom, left, right = (0, 0, 0, 0)
#获取图像尺寸
h, w, _ = image.shape
#对于长宽不相等的图片,找到最长的一边
longest_edge = max(h, w)
#计算短边需要增加多上像素宽度使其与长边等长
if h < longest_edge:
dh = longest_edge - h
top = dh // 2
bottom = dh - top
elif w < longest_edge:
dw = longest_edge - w
left = dw // 2
right = dw - left
else:
pass
#RGB颜色
BLACK = [0, 0, 0]
#给图像增加边界,是图片长、宽等长,cv2.BORDER_CONSTANT指定边界颜色由value指定
constant = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value = BLACK)
#调整图像大小并返回
return cv2.resize(constant, (height, width))
#读取训练数据
images = []
labels = []
def read_path(path_name):
for dir_item in os.listdir(path_name):
#从初始路径开始叠加,合并成可识别的操作路径
full_path = os.path.abspath(os.path.join(path_name, dir_item))
if os.path.isdir(full_path): #如果是文件夹,继续递归调用
read_path(full_path)
else: #文件
if dir_item.endswith('.jpg'):
image = cv2.imread(full_path)
image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)
#放开这个代码,可以看到resize_image()函数的实际调用效果
#cv2.imwrite('1.jpg', image)
images.append(image)
labels.append(path_name)
return images,labels
#从指定路径读取训练数据
def load_dataset(path_name):
images,labels = read_path(path_name)
#将输入的所有图片转成四维数组,尺寸为(图片数量*IMAGE_SIZE*IMAGE_SIZE*3)
#我和闺女两个人共1200张图片,IMAGE_SIZE为64,故对我来说尺寸为1200 * 64 * 64 * 3
#图片为64 * 64像素,一个像素3个颜色值(RGB)
images = np.array(images)
print(images.shape)
#标注数据,'liziqiang'文件夹下都是我的脸部图像,全部指定为0,另外一个文件夹下是同学的,全部指定为1
labels = np.array([0 if label.endswith('liziqiang') else 1 for label in labels])
return images, labels
if __name__ == '__main__':
if len(sys.argv) != 1:
print("Usage:%s path_name\r\n" % (sys.argv[0]))
else:
images, labels = load_dataset("C:\\Users\\Administrator\\Desktop\\Python\\data")
稍微解释一下resize_image()函数。这个函数的功能是判断图片是不是正方形,如果不是则增加短边的长度使之变成正方形。这样再调用cv2.resize()函数就可以实现等比例缩放了。因为我们指定缩放的比例就是64 x 64,只有缩放之前图像为正方形才能确保图像不失真。例如:
大概是这么个意思。
将你捕捉到的照片放在俩个不同的文件夹里,我在这里一块放在了data文件夹里。
三,模型的搭建
AlexNet是2012年ImageNet竞赛冠军获得者Hinton和他的学生Alex Krizhevsky设计的。也是在那年之后,更多的更深的神经网络被提出,比如优秀的vgg,GoogLeNet。 这对于传统的机器学习分类算法而言,已经相当的出色。——百度百科
2012年,Alex Krizhevsky、Ilya Sutskever在多伦多大学Geoff Hinton的实验室设计出了一个深层的卷积神经网络AlexNet,夺得了2012年ImageNet LSVRC的冠军,且准确率远超第二名(top5错误率为15.3%,第二名为26.2%),引起了很大的轰动。AlexNet可以说是具有历史意义的一个网络结构,在此之前,深度学习已经沉寂了很长时间,自2012年AlexNet诞生之后,后面的ImageNet冠军都是用卷积神经网络(CNN)来做的,并且层次越来越深,使得CNN成为在图像识别分类的核心算法模型,带来了深度学习的大爆发。
AlexNetba包含了5个卷积层和3个全连接层,模型示意图:
1.1 96个1010的卷积层
输入数据类型(None,64,64,1)
输出数据类型(None,55,55,96) (55=(64-10)/1+1)
param=1010396+96=28896
1.2 ReLU激活函数
1.3 最大池化(3*3,s=2)
输入数据类型(None,55,55,96)
输出数据类型(None,27,27,96) (55=(55-3)/2+1)
2.1 256个55的卷积层
输入数据类型(None,27,27,96)
输出数据类型(None,27,27,256)
param=5596256+256=614656
2.2 ReLU激活函数
2.3 最大池化(3*3,s=2)
输入数据类型(None,27,27,256)
输出数据类型(None,13,13,256) (13=(27-3)/2+1)
3.1 384个33的卷积层
输入数据类型(None,13,13,256)
输出数据类型(None,13,13,384)
param=33256384+384=885120
3.2 ReLU激活函数
4.1 384个33的卷积层
输入数据类型(None,13,13,384)
输出数据类型(None,13,13,384)
param=33384384+384=1327488
4.2 ReLU激活函数
5.1 256个33的卷积层
输入数据类型(None,13,13,384)
输出数据类型(None,13,13,256)
param=33384256+256=884992
5.2 ReLU激活函数
5.3 最大池化(3*3,s=2)
输入数据类型(None,13,13,256)
输出数据类型(None,6,6,256) (6=(13-3)/2+1)
6.1 扁平化 Flatten层
输出数据类型(None,9216)66256=9216
7.1全连接 Dense(4096)
输出数据类型(None,4096)
param=9216*4096=37748736
7.2ReLU激活函数
7.3DropOut(0.5)
8.1全连接 Dense(4096)
输出数据类型(None,4096)
param=4096*4096+4096=16781312
8.2ReLU激活函数
8.3DropOut(0.5)
9.1全连接 Dense(2)
9.2softmax激活函数
各层的参数如下:
Layer (type) Output Shape Param #
conv2d_31 (Conv2D) (None, 55, 55, 96) 28896
_________________________________________________________________
activation_47 (Activation) (None, 55, 55, 96) 0
_________________________________________________________________
max_pooling2d_19 (MaxPooling (None, 27, 27, 96) 0
_________________________________________________________________
conv2d_32 (Conv2D) (None, 27, 27, 256) 614656
_________________________________________________________________
activation_48 (Activation) (None, 27, 27, 256) 0
_________________________________________________________________
max_pooling2d_20 (MaxPooling (None, 13, 13, 256) 0
_________________________________________________________________
conv2d_33 (Conv2D) (None, 13, 13, 384) 885120
_________________________________________________________________
activation_49 (Activation) (None, 13, 13, 384) 0
_________________________________________________________________
conv2d_34 (Conv2D) (None, 13, 13, 384) 1327488
_________________________________________________________________
activation_50 (Activation) (None, 13, 13, 384) 0
_________________________________________________________________
conv2d_35 (Conv2D) (None, 13, 13, 256) 884992
_________________________________________________________________
activation_51 (Activation) (None, 13, 13, 256) 0
_________________________________________________________________
max_pooling2d_21 (MaxPooling (None, 6, 6, 256) 0
_________________________________________________________________
flatten_5 (Flatten) (None, 9216) 0
_________________________________________________________________
dense_17 (Dense) (None, 4096) 37752832
_________________________________________________________________
activation_52 (Activation) (None, 4096) 0
_________________________________________________________________
dropout_15 (Dropout) (None, 4096) 0
_________________________________________________________________
dense_18 (Dense) (None, 4096) 16781312
_________________________________________________________________
activation_53 (Activation) (None, 4096) 0
_________________________________________________________________
dropout_16 (Dropout) (None, 4096) 0
_________________________________________________________________
dense_19 (Dense) (None, 2) 8194
____________________________________________________
Total params: 58,283,490
parameter的计算
卷积层的parameter: 记卷积核的宽为kwk_wkw,卷积核的高为khk_hkh,输入这一层的通道数是cinc_{in}cin,这一层输出的通道数是c_out,则这个卷积层的参数量是(kw∗kh∗cin)∗cout+cout(k_w * k_h * c_{in}) * c_{out} +c_{out}(kw∗kh∗cin)∗cout+cout。其中,(kw∗kh∗cin)∗cout(k_w * k_h * c_{in}) * c_{out}(kw∗kh∗cin)∗cout是权重的数量,最后加的coutc_outcout是偏置的数量。可以看出,卷积层的参数量只与卷积核的大小和输入输出的通道数有关。
全连接层的parameter: 因为全连接层中,会把输入先拉伸成一个flat的一维向量,原本feature map中的每个像素都变成一个结点,然后这一层的每个结点都会融合前面所有结点的信息,然后加上偏置。假设这一层的输入拉伸成flat向量之后结点数为ninn_{in}nin,这一层的输出结点数为noutn_{out}nout,则这个全连接层的参数量是(nin∗nout)+nout(n_{in} * n_{out}) + n_{out}(nin∗nout)+nout。可以看出,全连接层参数量只与输入输出结点数有关。
代码如下:
import random
import numpy as np
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K
from loat_data import load_dataset, resize_image, IMAGE_SIZE
class Dataset:
def __init__(self, path_name):
#训练集
self.train_images = None
self.train_labels = None
#验证集
self.valid_images = None
self.valid_labels = None
#测试集
self.test_images = None
self.test_labels = None
#数据集加载路径
self.path_name = path_name
#当前库采用的维度顺序
self.input_shape = None
#加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作
def load(self, img_rows = IMAGE_SIZE, img_cols = IMAGE_SIZE,
img_channels = 3, nb_classes = 2):
#加载数据集到内存
images, labels = load_dataset(self.path_name)
train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size = 0.3, random_state = random.randint(0, 100))
_, test_images, _, test_labels = train_test_split(images, labels, test_size = 0.5, random_state = random.randint(0, 100))
#当前的维度顺序如果为'th',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels
#这部分代码就是根据keras库要求的维度顺序重组训练数据集
if K.image_dim_ordering() == 'th':
train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
self.input_shape = (img_channels, img_rows, img_cols)
else:
train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
self.input_shape = (img_rows, img_cols, img_channels)
#输出训练集、验证集、测试集的数量
print(train_images.shape[0], 'train samples')
print(valid_images.shape[0], 'valid samples')
print(test_images.shape[0], 'test samples')
#我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将
#类别标签进行one-hot编码使其向量化,在这里我们的类别只有两种,经过转化后标签数据变为二维
train_labels = np_utils.to_categorical(train_labels, nb_classes)
valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
test_labels = np_utils.to_categorical(test_labels, nb_classes)
#像素数据浮点化以便归一化
train_images = train_images.astype('float32')
valid_images = valid_images.astype('float32')
test_images = test_images.astype('float32')
#将其归一化,图像的各像素值归一化到0~1区间
train_images /= 255
valid_images /= 255
test_images /= 255
self.train_images = train_images
self.valid_images = valid_images
self.test_images = test_images
self.train_labels = train_labels
self.valid_labels = valid_labels
self.test_labels = test_labels
#CNN网络模型类
class Model:
def __init__(self):
self.model = None
#建立模型
def build_model(self, dataset, nb_classes = 2):
#构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
self.model = Sequential()
#以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
self.model.add(Convolution2D(96, 10, 10, input_shape = dataset.input_shape))
self.model.add(Activation('relu'))
self.model.add(MaxPooling2D(pool_size=(3, 3),strides=2))
self.model.add(Convolution2D(256, 5, 5,border_mode='same'))
self.model.add(Activation('relu'))
self.model.add(MaxPooling2D(pool_size=(3, 3),strides=2
self.model.add(Convolution2D(384, 3, 3,border_mode='same'))
self.model.add(Activation('relu'))
self.model.add(Convolution2D(384, 3, 3,border_mode='same'))
self.model.add(Activation('relu'))
self.model.add(Convolution2D(256, 3, 3,border_mode='same'))
self.model.add(Activation('relu'))
self.model.add(MaxPooling2D(pool_size=(3, 3),strides=2))
self.model.add(Flatten())
self.model.add(Dense(4096))
self.model.add(Activation('relu'))
self.model.add(Dropout(0.5))
self.model.add(Dense(4096))
self.model.add(Activation('relu'))
self.model.add(Dropout(0.5))
self.model.add(Dense(nb_classes)) #17 Dense层
self.model.add(Activation('softmax')) #18 分类层,输出最终结果
#输出模型概况
self.model.summary()
#训练模型
def train(self, dataset, batch_size = 20, nb_epoch = 4, data_augmentation = True):
sgd = SGD(lr = 0.01, decay = 1e-6,
momentum = 0.9, nesterov = True) #采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
self.model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy']) #完成实际的模型配置工作
#不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的
#训练数据,有意识的提升训练数据规模,增加模型训练量
if not data_augmentation:
self.model.fit(dataset.train_images,
dataset.train_labels,
batch_size = batch_size,
nb_epoch = nb_epoch,
validation_data = (dataset.valid_images, dataset.valid_labels),
shuffle = True)
#使用实时数据提升
else:
#定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一
#次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器
datagen = ImageDataGenerator(
featurewise_center = False, #是否使输入数据去中心化(均值为0),
samplewise_center = False, #是否使输入数据的每个样本均值为0
featurewise_std_normalization = False, #是否数据标准化(输入数据除以数据集的标准差)
samplewise_std_normalization = False, #是否将每个样本数据除以自身的标准差
zca_whitening = False, #是否对输入数据施以ZCA白化
rotation_range = 20, #数据提升时图片随机转动的角度(范围为0~180)
width_shift_range = 0.2, #数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数)
height_shift_range = 0.2, #同上,只不过这里是垂直
horizontal_flip = True, #是否进行随机水平翻转
vertical_flip = False) #是否进行随机垂直翻转
#计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理
datagen.fit(dataset.train_images)
#利用生成器开始训练模型
self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
batch_size = batch_size),
samples_per_epoch = dataset.train_images.shape[0],
nb_epoch = nb_epoch,
validation_data = (dataset.valid_images, dataset.valid_labels))
MODEL_PATH = './liziqiang.face.model.h5'
def save_model(self, file_path = MODEL_PATH):
self.model.save(file_path)
def load_model(self, file_path = MODEL_PATH):
self.model = load_model(file_path)
def evaluate(self, dataset):
score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose = 1)
print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))
#识别人脸
def face_predict(self, image):
#依然是根据后端系统确定维度顺序
if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
image = resize_image(image) #尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) #与模型训练不同,这次只是针对1张图片进行预测
elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
image = resize_image(image)
image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))
#浮点并归一化
image = image.astype('float32')
image /= 255
#给出输入属于各个类别的概率,我们是二值类别,则该函数会给出输入图像属于0和1的概率各为多少
result = self.model.predict_proba(image)
print('result:', result)
#给出类别预测:0或者1
result = self.model.predict_classes(image)
#返回类别预测结果
return result[0]
if __name__ == '__main__':
dataset = Dataset('./data/')
dataset.load()
model = Model()
model.build_model(dataset)
#先前添加的测试build_model()函数的代码
model.build_model(dataset)
#测试训练函数的代码
model.train(dataset)
if __name__ == '__main__':
dataset = Dataset('./data/')
dataset.load()
model = Model()
model.build_model(dataset)
model.train(dataset)
model.save_model(file_path = './model/zhengfei.face.model.h5')
if __name__ == '__main__':
dataset = Dataset('./data/')
dataset.load()
#评估模型
model = Model()
model.load_model(file_path = './model/zhengfei.face.model.h5')
model.evaluate(dataset)
由此可见该网络将近有六千万个参数需要训练,(这种架构算是中量级的)我的CPU温度立马就飙到了100度,如果毕业后有钱了一定搞一块专用的图像处理芯片
训练后的结果如下:
Epoch 1/4
560/560 [==============================] - 7232s 13s/step - loss: 0.4136 - acc: 0.8128 - val_loss: 0.0261 - val_acc: 0.9898
Epoch 2/4
560/560 [==============================] - 1020s 2s/step - loss: 0.0383 - acc: 0.9886 - val_loss: 0.0018 - val_acc: 0.9992
Epoch 3/4
560/560 [==============================] - 3403s 6s/step - loss: 0.0085 - acc: 0.9979 - val_loss: 0.0027 - val_acc: 0.9992
Epoch 4/4
560/560 [==============================] - 1016s 2s/step - loss: 0.0148 - acc: 0.9953 - val_loss: 0.0022 - val_acc: 0.9996
(18018, 64, 64, 3)
12612 train samples
5406 valid samples
9009 test samples
9009/9009 [==============================] - 221s 25ms/step
acc: 99.96%
由于该架构的参数过多,训练的时间大概需要一个小时,耐心等待就好。因为时间过久,在这里只迭代了4次,最终准确率也达到了99.96%
此时的模型也已经搭建好了,接下来就是检验的时候了
四,识别人脸
import cv2
import sys
import gc
from face_train import Model
if __name__ == '__main__':
if len(sys.argv) == 1:
print("Usage:%s camera_id\r\n" % (sys.argv[0]))
sys.exit(0)
#加载模型
model = Model()
model.load_model(file_path = './model/zhengfei.face.model.h5')
#框住人脸的矩形边框颜色
color = (0, 255, 0)
#捕获指定摄像头的实时视频流
cap = cv2.VideoCapture(0)
#人脸识别分类器本地存储路径
cascade_path = "haarcascade_frontalface_alt2.xml"
#循环检测识别人脸
while True:
ret, frame = cap.read() #读取一帧视频
if ret is True:
#图像灰化,降低计算复杂度
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
else:
continue
#使用人脸识别分类器,读入分类器
cascade = cv2.CascadeClassifier(cascade_path)
#利用分类器识别出哪个区域为人脸
faceRects = cascade.detectMultiScale(frame_gray, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
if len(faceRects) > 0:
for faceRect in faceRects:
x, y, w, h = faceRect
#截取脸部图像提交给模型识别这是谁
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
faceID = model.face_predict(image)
#如果是“我”
if faceID == 0:
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness = 2)
#文字提示是谁
cv2.putText(frame,'zhengfei',
(x + 30, y + 30), #坐标
cv2.FONT_HERSHEY_SIMPLEX, #字体
1, #字号
(255,0,255), #颜色
2)#字的线宽
else:
if faceID == 1:
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness = 2)
#文字提示是谁
cv2.putText(frame,'lilicheng',
(x + 30, y + 30), #坐标
cv2.FONT_HERSHEY_SIMPLEX, #字体
1, #字号
(0,0,255), #颜色
2)#字的线宽
else:
pass
cv2.imshow("识别朕", frame)
#等待10毫秒看是否有按键输入
k = cv2.waitKey(10)
#如果输入q则退出循环
if k & 0xFF == ord('q'):
break
#释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
最终结果: