首页 > 技术文章 > [python] 机器学习 卷积神经网络 用迁移学习实现人脸识别

conver 2019-07-11 12:53 原文

项目简介:

目标:识别全班61个人的人脸。

实现途径:卷积神经网络

  1. 用全班采集的照片训练直接训练自己的模型(图片格式132*197,每人10张,8张加入训练集,1张validation,1张test)
  2. 调用keras.application中的base_model(xception、inception、resnet50、VGG16、VGG19)做特征提取,更换我们自己的全链接层。
  3. 把basemodel的顶层的卷积层和池化层放开+全链接层

方法:用了第三种【不要放开太多层,否则提前用大量图片训练的模型就失去了效果,过拟合】,放开了block5。

人脸识别+图像分析(打印每次训练的损失函数和准确度)+打印图片和测试是否正确+用opencv框出人脸

下面是带着详细注释的代码【其实我也没有学懂——调包侠,调用opencv的代码不一定正确】

# coding=utf-8
from keras import Sequential
from keras import backend as K
import os, shutil
import matplotlib.pyplot as plt
from keras import layers
from keras import models
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint
from keras.applications import VGG16
import numpy as np
from PIL import Image, ImageDraw, ImageFont
#import cv2
K.tensorflow_backend._get_available_gpus()

base_dir = 'D:/program/project0701/tjuData/'
train_dir = os.path.join(base_dir, 'train')#os.path.join()函数:连接两个或更多的路径名组件
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

labels = []### 空列表list
fd = open('D:/program/project0701/lab.txt', 'r')#组号 英文名/n
for x in fd.readlines():
    labels.append(x[:-1])### 除了最后一个取全部,去掉最后一个回车
#print(labels) 
#print(len(labels))
labels = np.array(labels)


conv_base = VGG16(weights='imagenet',
                  include_top=False,  #不含顶部分类
                  input_shape=(132, 197, 3))
conv_base.summary()
"""
weight::告诉程序将网络的卷积层和max pooling层对应的参数传递过来,将它们初始化成对应的网络层次。
include_top::表示是否也要把Flatten()后面的网络层也下载过来
VGG16对应的这层网络用来将图片划分到1000个不同类别中;
由于我们只用来区分猫狗两个类别,因此我们去掉它这一层。
input_shape告诉网络,我们输入图片的大小是132*197像素
"""
conv_base.trainable = True
#冻结卷积基——因为Dense层是层层随机初始化conv_base.trainable = False
set_trainable = False
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True  #把第5层放开了
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False

model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(61, activation='softmax'))#sigmoid,二分类


model.compile(loss='categorical_crossentropy',  #多分类,交叉熵损失函数  
              optimizer=optimizers.RMSprop(lr=1e-4),  #lr学习率可以改 优化函数 α
              metrics=['acc'])


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,
      fill_mode='nearest')

validation_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(## 以据文件夹路径为参数,自动生成经过数提升/归一化后的数据和标签
        train_dir,                                  # 训练数据路径,train 文件夹下包含每一类的子文件夹
        target_size=(132,197),                      # 图片大小resize成132*197
        batch_size=61,                              #批处理参数,它的极限值为训练集样本总数,当数据量比较少时,可以将batch_size值设置为全数据集
        class_mode='categorical')                   ## 使用多分类,返回1-D 的二值标签
'''classes '''
validation_generator = validation_datagen.flow_from_directory(
        validation_dir,
        target_size=(132, 197),
        batch_size=61,  #
        class_mode='categorical')  


checkpointer = ModelCheckpoint(filepath='noface1-1.pretrained.finetuning.augmentation.model.weights.best.hdf5',
                               verbose=1,
                               save_best_only=True)
history = model.fit_generator(
      train_generator,
      steps_per_epoch=8,  #batch_size*steps_per_epoch=总训练数据个数 防止训练不均匀
      epochs=50,
      validation_data=validation_generator,#validation data并不用于更新权重,只是用是来检测loss和accuracy等指标的
      validation_steps=1,  #
      callbacks=[checkpointer],
      verbose=2)
"""
Keras在使用.fit_generator训练模型时的过程:
    Keras调用提供给.fit_generator的生成器函数(在本例中为aug.flow)
    生成器函数为.fit_generator函数生成一批大小为BS的数据
    .fit_generator函数接受批量数据,执行反向传播,并更新模型中的权重
    重复该过程直到达到期望的epoch数量

    将训练数据的总数除以批量大小的结果作为steps_per_epoch的值。
    一旦Keras到达这一步,它就会知道这是一个新的epoch。

verbose:日志显示
verbose = 0 为不在标准输出流输出日志信息
verbose = 1 为输出进度条记录
verbose = 2 为每个epoch输出一行记录
"""

#从此开始的一段是绘图分析
model.load_weights('noface1-1.pretrained.finetuning.augmentation.model.weights.best.hdf5')
#最近在训练一个多位数字手写体的模型,然后发现,我用ModelCheckpoint 保存了训练过程中的结果最好一轮的参数。
# 后续用模型来预测新样本的时候,就从直接本地加载训练的模型

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))
def smooth_curve(points, factor=0.8):
  smoothed_points = []
  for point in points:
    if smoothed_points:
      previous = smoothed_points[-1]
      smoothed_points.append(previous * factor + point * (1 - factor))
    else:
      smoothed_points.append(point)
  return smoothed_points

plt.plot(epochs,
         smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs,
         smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs,
         smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs,
         smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()


test_datagen = ImageDataGenerator(rescale=1./255)
test_generator = test_datagen.flow_from_directory(
        test_dir,
        target_size=(132, 197),
        batch_size=61,
        shuffle = False,
        class_mode='categorical')
"""Whether to shuffle(调动,变换位置) the data (default: True)  
        If set to False, sorts the data in alphanumeric order. """
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)
"""
def imgshow(img):
    image = Image.fromarray((img * 255).astype('uint8')).convert('RGB')
    return image

fig = plt.figure(figsize=(20, 12))
pred = model.predict_generator(test_generator, verbose=1, steps = 1)
test_datas, test_labels = test_generator.next()
lens = len(test_datas)
face_cascade = cv2.CascadeClassifier("D:/PySpace/face/haarcascade_frontalface_default.xml")

# print(print(test_labels))
for i in range(61):
    img = imgshow(test_datas[i])
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  #
    ax = fig.add_subplot(7, 10, i + 1, xticks=[], yticks=[])
    faces = face_cascade.detectMultiScale(gray, 1.2, 5)  # 1.3和5是特征的最小、最大检测窗口,它改变检测结果也会改变
    result = []
    for (x, y, width, height) in faces:
        result.append((x, y, x + width, y + height))
    draw_instance = ImageDraw.Draw(img)
    for (x1, y1, x2, y2) in faces:
        draw_instance.rectangle((x1, y1, x2, y2), outline=(255, 0, 0))
    ax.imshow(img)
    ax.set_title("{} \n{}".format(labels[np.argmax(pred[i])], labels[np.argmax(test_labels[i])]),
                 color=('green' if np.argmax(pred[i]) == np.argmax(test_labels[i]) else 'red'), size=20)
plt.show()

#在原图像上画矩形,框出所有人脸。
#调用Image模块的draw方法,Image.open获取图像句柄,ImageDraw.Draw获取该图像的draw实例,然后调用该draw实例的rectangle方法画矩形(矩形的坐标即
#detectFaces返回的坐标),outline是矩形线条颜色(B,G,R)。
#注:原始图像如果是灰度图,则去掉outline,因为灰度图没有RGB可言。drawEyes、detectSmiles也一样。

def detectFaces(image_name):
    img = cv2.imread(image_name)
    face_cascade = cv2.CascadeClassifier("D:/PySpace/face/haarcascade_frontalface_default.xml")
    if img.ndim == 3:
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    else:
        gray = img #if语句:如果img维度为3,说明不是灰度图,先转化为灰度图gray,如果不为3,也就是2,原图就是灰度图

    faces = face_cascade.detectMultiScale(gray, 1.2, 5)#1.3和5是特征的最小、最大检测窗口,它改变检测结果也会改变
    result = []
    for (x,y,width,height) in faces:
        result.append((x,y,x+width,y+height))
    return result
def drawFaces(image_name):
    faces = detectFaces(image_name)
    if faces:
        img = Image.open(image_name)
        draw_instance = ImageDraw.Draw(img)
        for (x1,y1,x2,y2) in faces:
            draw_instance.rectangle((x1,y1,x2,y2), outline=(255, 0,0))
        img.save('drawfaces_'+image_name)

"""

用自己训练的模型的代码

推荐阅读