目 录
摘 要 I
Abstract II
第一章 绪论 1
1.1 研究背景及意义 1
1.2 研究现状 1
1.3 本文研究的主要内容 3
第二章 人脸表情识别基础 4
2.1 常见的表情数据库 4
2.2 人脸识别 5
2.3 图像预处理 6
2.3.1 几何归一化 6
2.3.2 灰度归一化 8
2.3.3 直方图均衡化 8
2.4 主流人工智能框架 9
2.5 本章小结 10
第三章 多通道输入的卷积神经网络 12
3.1 CNN的基本原理 12
3.1.1 CNN的结构 12
3.1.2 激活函数 14
3.2 基于MCI-CNN的人脸表情识别算法 15
3.2.1 MCI-CNN网络架构 15
3.2.2 CNN模型结构及参数设置 16
3.3 本章小结 17
第四章 人脸表情识别的模型设计与分析 18
4.1 表情识别整体框图 18
4.2 数据集预处理 19
4.3 数据增强 20
4.4 模型训练的优化 23
4.4.1 TensorFlow读取数据的机制 23
4.4.2 基于多线程随机shuffle队列训练样本 24
4.5 实验结果分析与效果展示 25
4.6 本章小结 29
第五章 总结与展望 31
5.1 本文总结 31
5.2 未来展望 31
参考文献 33
致谢 35
附录 36
附录一 数据集预处理代码 36
附录二 CNN模型代码 38
附录三 模型训练代码 43
附录四 测试代码 48
1.3本文研究的主要内容
本设计基于python语言,借助TensorFlow人工智能框架,实现了人脸表情识别的功能。现实场景下,人脸图像的采集很容易受到外界不可控因素的影响,导致人脸图像发生局部位移和轻微旋转。对发生局部位移和轻微旋转的人脸图像载入卷积神经网络模型,可想而知表情识别率很低,识别效果不尽人意。因此,怎样设计一个神经网络结构解决上述问题,就成为了关键。本文研究的主要内容如下:
第一章论述了人脸识别技术的研究背景及研究现状,并介绍了本设计的研究内容。
第二章阐述了人脸表情识别基础,对人脸表情识别系统的实现做足准备工作。介绍了常用的人工智能框架与人脸表情数据库;使用opencv公开库中自带的检测器,对输入图片进行检测并裁剪人脸部分;然后使用双线性插值法把人脸图像缩放到4848的尺寸;之后对人脸图像进行灰度归一化。
第三章主要介绍了多通道输入的卷积神经网络(MCI-CNN)的架构,单个卷积神经网络模型借鉴GoogleNet中11卷积升降维的思想对AlexNet进行了改进。介绍了线性加权融合算法,把多个单个卷积神经网络模型融合起来,进行综合表情分类。
第四章主要介绍了人脸表情识别的模型设计与分析,为了防止过拟合现象的发生,对fer2013数据集进行预处理和数据增强。针对模型训练效率的问题,采用了基于多线程随机shuffle队列的解决方法,样本的载入速度提高了10倍,读取样本数据与训练样本数据异步进行,两者协同作业,共同完成模型的训练工作。最后通过实验分析,本设计算法的有效性与可行性。
第五章主要介绍了本设计中工作内容的总结以及本设计中针对没有解决问题的未来展望。
本设计基于python语言,借助TensorFlow人工智能框架,主要实现人脸检测+表情识别的功能。人脸检测与裁剪部分在第二章已经介绍了,在第三章介绍了本设计的核心部分,多通道输入的卷积神经网络架构和单个子模型的结构。本章将上述的两个部分整合在一起,实现人脸表情的分类与评估。本文转载自http://www.biyezuopin.vip/onews.asp?id=14663本章主要从样本数据集的预处理,训练的优化算法,实验结果的分析与效果展示三个方面介绍基于python的人脸表情识别实验。
本设计的实验环境如下:
CPU: Intel® Core i7-7500U 4核2.7GHZ主频
开发环境:Windows 10,python3.7,TensorFlow
开发工具:Pycharm
"""
author: Zhou Chen
datetime: 2019/6/18 17:39
desc: 一些工具库
"""
from tensorflow.keras.preprocessing.image import load_img, img_to_array
def get_fer2013_images():
"""
从csv文件得到图片集
:return:
"""
import pandas as pd
import numpy as np
import scipy.misc as sm
import os
# 定义7种表情
emotions = {
'0': 'anger', # 生气
'1': 'disgust', # 厌恶
'2': 'fear', # 恐惧
'3': 'happy', # 开心
'4': 'sad', # 伤心
'5': 'surprised', # 惊讶
'6': 'neutral', # 中性
}
def save_image_from_fer2013(file):
faces_data = pd.read_csv(file)
root = '../data/fer2013/'
# 文件主要三个属性,emotion为表情列,pixels为像素数据列,usage为数据集所属列
data_number = 0
for index in range(len(faces_data)):
# 解析每一行csv文件内容
emotion_data = faces_data.loc[index][0] # emotion
image_data = faces_data.loc[index][1] # pixels
usage_data = faces_data.loc[index][2] # usage
# 将图片数据转换成48*48
image_array = np.array(list(map(float, image_data.split()))).reshape((48, 48))
folder = root + usage_data
emotion_name = emotions[str(emotion_data)]
image_path = os.path.join(folder, emotion_name)
if not os.path.exists(folder):
os.mkdir(folder)
if not os.path.exists(image_path):
os.mkdir(image_path)
# 图片文件名
image_file = os.path.join(image_path, str(index) + '.jpg')
sm.toimage(image_array).save(image_file)
data_number += 1
print('总共有' + str(data_number) + '张图片')
save_image_from_fer2013('../data/fer2013/fer2013.csv')
def get_jaffe_images():
"""
得到按照标签存放的目录结构的数据集同时对人脸进行检测
:return:
"""
import cv2
import os
emotions = {
'AN': 0,
'DI': 1,
'FE': 2,
'HA': 3,
'SA': 4,
'SU': 5,
'NE': 6
}
emotions_reverse = ['anger', 'disgust', 'fear', 'happy', 'sad', 'surprised', 'neutral']
def detect_face(img):
"""
检测人脸并裁减
:param img:
:return:
"""
cascade = cv2.CascadeClassifier('../data/params/haarcascade_frontalface_alt.xml')
rects = cascade.detectMultiScale(img, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE)
if len(rects) == 0:
# 没有检测到人脸
return []
rects[:, 2:] += rects[:, :2]
return rects
folder = '../data/jaffe'
files = os.listdir(folder)
images = []
labels = []
index = 0
for file in files:
img_path = os.path.join(folder, file) # 文件路径
img_label = emotions[str(img_path.split('.')[-3][:2])] # 文件名包含标签
labels.append(img_label)
img = cv2.imread(img_path, 1)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 下面裁减
rects_ = detect_face(img_gray)
for x1, y1, x2, y2 in rects_:
cv2.rectangle(img, (x1+10, y1+20), (x2-10, y2), (0, 255, 255), 2)
img_roi = img_gray[y1+20: y2, x1+10: x2-10]
img_roi = cv2.resize(img_roi, (48, 48))
images.append(img_roi)
# 若不裁减,即原数据集
# icons.append(cv2.resize(img_gray, (48, 48)))
index += 1
if not os.path.exists('../data/jaffe/Training'):
os.mkdir('../data/jaffe/Training')
for i in range(len(images)):
path_emotion = '../data/jaffe/Training/{}'.format(emotions_reverse[labels[i]])
if not os.path.exists(path_emotion):
os.mkdir(path_emotion)
cv2.imwrite(os.path.join(path_emotion, '{}.jpg'.format(i)), images[i])
print("load jaffe dataset")
def expression_analysis(distribution_possibility):
"""
根据概率分布显示直方图
:param distribution_possibility:
:return:
"""
import numpy as np
import matplotlib.pyplot as plt
import os
# 定义8种表情
emotions = {
'0': 'anger',
'1': 'disgust',
'2': 'fear',
'3': 'happy',
'4': 'sad',
'5': 'surprised',
'6': 'neutral',
'7': 'contempt'
}
y_position = np.arange(len(emotions))
plt.figure()
plt.bar(y_position, distribution_possibility, align='center', alpha=0.5)
plt.xticks(y_position, list(emotions.values()))
plt.ylabel('possibility')
plt.title('predict result')
if not os.path.exists('../results'):
os.mkdir('../results')
plt.show()
# plt.savefig('../results/rst.png')
def load_test_image(path):
"""
读取外部测试图片
:param path:
:return:
"""
img = load_img(path, target_size=(48, 48), color_mode="grayscale")
img = img_to_array(img) / 255.
return img
def index2emotion(index=0, kind='cn'):
"""
根据表情下标返回表情字符串
:param index:
:return:
"""
emotions = {
'发怒': 'anger',
'厌恶': 'disgust',
'恐惧': 'fear',
'开心': 'happy',
'伤心': 'sad',
'惊讶': 'surprised',
'中性': 'neutral',
'蔑视': 'contempt'
}
if kind == 'cn':
return list(emotions.keys())[index]
else:
return list(emotions.values())[index]
def cv2_img_add_text(img, text, left, top, text_color=(0, 255, 0), text_size=20):
"""
:param img:
:param text:
:param left:
:param top:
:param text_color:
:param text_size
:return:
"""
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
if isinstance(img, np.ndarray):
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
font_text = ImageFont.truetype(
"./assets/simsun.ttc", text_size, encoding="utf-8") # 使用宋体
draw.text((left, top), text, text_color, font=font_text)
return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
def get_faces_from_gray_image(img_path):
"""
获取图片中的人脸
:param img_path:
:return:
"""
import cv2
face_cascade = cv2.CascadeClassifier('./dataset/params/haarcascade_frontalface_alt.xml')
img = cv2.imread(img_path)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
img_gray,
scaleFactor=1.1,
minNeighbors=1,
minSize=(30, 30)
)
if len(faces) == 0:
return None
# 遍历每一个脸
faces_gray = []
for (x, y, w, h) in faces:
face_img_gray = img_gray[y:y + h + 10, x:x + w + 10]
face_img_gray = cv2.resize(face_img_gray, (48, 48))
faces_gray.append(face_img_gray)
return faces_gray
def get_feature_map(model, layer_index, channels, input_img=None):
"""
可视化每个卷积层学到的特征图
:param model:
:param layer_index:
:param channels:
:param input_img:
:return:
"""
if not input_img:
input_img = load_test_image('../data/demo.jpg')
input_img.shape = (1, 48, 48, 1)
from keras import backend as K
layer = K.function([model.layers[0].input], [model.layers[layer_index+1].output])
feature_map = layer([input_img])[0]
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 8))
for i in range(channels):
img = feature_map[:, :, :, i]
plt.subplot(4, 8, i+1)
plt.imshow(img[0], cmap='gray')
plt.show()
if __name__ == '__main__':
from model import CNN3
model = CNN3()
model.load_weights('../models/cnn3_best_weights.h5')
get_feature_map(model, 1, 32)