人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5

人脸表情识别 | Python+Keras+PyQt5

参考学习文章:
Keras|基于深度学习的人脸表情识别系统
PyQt5+QtDesigner编写摄像头界面程序(一)——pyqt5、qtdesigner安装和环境设置
本次设计参考以上两篇文章,非常感谢博主们的分享,我收获良多!

目录

  • 人脸表情识别 | Python+Keras+PyQt5
    • 一、前言
      • (一)本设计简介
      • (二)语言选择
      • (三)环境选择
        • 1.TensorFlow简介
        • 2.Keras简介
      • (三)本设计相关知识学习
      • (四)本设计环境搭建步骤
    • 二、本设计成果展示
      • (一)无PyQt5界面:
      • (二)与PyQt5界面结合:
    • 三、本设计主要代码结构讲解
      • (一)人脸表情识别和情绪分类部分
        • 1.卷积神经网络模型的训练
        • 1.1数据集的获取
          • (1)数据集下载
          • (2)数据集格式转换
        • 1.2搭建卷积神经网络模型
        • 1.3训练模型
        • 1.4保存训练模型
        • 2.面部表情的识别
        • 2.1加载pre-model网络与权重;
        • 2.2利用opencv对图像预处理;
          • 1.打开系统摄像头
          • 2.人脸识别
          • 3.灰度处理、裁切、翻转、几何归一化等处理
        • 2.3人脸表情识别
        • 2.4表情分类器检测
      • (二)PyQt5界面部分
        • 1.PyQt+QtDesigner等工具的安装与设置;
        • 2.基于QtDesigner的界面设计;
        • 3.各部分程序的编写;
        • 4.笔记本摄像头的读取、显示和参数控制;
    • 四、主代码展示

一、前言

(一)本设计简介

本设计是基于Python的人脸表情识别和情绪分类,在TensorFlow框架下,使用其内部高级API——Keras搭建训练模型,并利用OpenCV库中的相关函数共同实现人脸识别、人脸表情识别、情绪分类的目的,最后借助PyQt5增加界面,通过界面可对输入的画面进行调整颜色、调整曝光度、亮度等操作,还可以通过“开始”、“暂停”、“结束”功能控制识别过程的通断,通过“录像”功能保存所需的画面,从而使整个设计更友好、更加人性化。

(二)语言选择

本设计选用相对简单易上手的Python语言,其具体的介绍可查看Python百度百科,此处不做过多陈述。

(三)环境选择

1.TensorFlow简介

具体内容请查看TensorFlow官网。TensorFlow是Google开源的基于数据流图的机器学习框架,支持python和c++程序开发语言。TensorFlow支持卷积神经网络(CNN)和循环卷积网络(RNN),以及RNN的一个特例长短期记忆网络(LSTM),以上都是目前在计算机视觉、语音识别、自然语言处理方面最流行的深度神经网络模型。
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第1张图片

在TensorFlow官网中介绍了TensorFlow的以下六大优势:
●高度灵活性
●真正的可移植性
●将科研和产品结合在一起
●自动求微分
●多语言支持
●最优化性能
因为本设计仅使用TensorFlow框架作为程序运行环境,主要是使用其高级API——Keras,因此不过多介绍。

2.Keras简介

具体内容请查看Keras官网。Keras是一个高级的Python神经网络框架。Keras已经被添加到TensorFlow中,成为其默认的框架,作为TensorFlow高级API之一。Keras作为TensorFlow的高层封装,可以与TensorFlow联合使用,用它快速搭建原型。
Keras是高度封装的,非常适合新手使用,代码更新速度较快,有大量公开示例代码,文档和讨论区也比较完善。
在Keras官网描述了其以下几个有点:
●模块化:模型的各部分,如神经层、成本函数、优化器、初始化、激活函数、规范化都是独立的模块,可以组合在一起来创建模型。
●极简主义:每个模块都保持简短和简单。
●易扩展性:很容易添加新模块,因此Keras更适合做进一步的高级研究。
●使用Python语言:模型使用Python实现,非常易于调试和扩展。
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第2张图片

(三)本设计相关知识学习

Python学习笔记(一)
Python+OpenCV(一)——基础操作
Python+Tensorflow学习(二)——初试keras

(四)本设计环境搭建步骤

Win10+Python3.6.5+Anaconda3-5.2.0+Tensorflow安装

二、本设计成果展示

(一)无PyQt5界面:

人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第3张图片

(二)与PyQt5界面结合:

人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第4张图片

三、本设计主要代码结构讲解

本设计主要代码结构整体上分为两大部分,在各部分再细分以下几点:
代码主要结构主要分为两大部分:
一、人脸表情识别和情绪分类部分
(一)卷积神经网络模型的训练
1数据集的获取
2.加载pre-model网络与权重;
3.训练模型
4.保存训练模型
(二)面部表情识别
1.打开摄像头
2.人脸识别
3.图像预处理
4.人脸表情识别
5.情绪分类
二、PyQt5界面创建
1.PyQt+QtDesigner及opencv等工具的安装与设置;
2.基于QtDesigner的界面设计;
3.各部分程序的编写;
4.笔记本摄像头的读取、显示和参数控制;
以下逐点介绍。

(一)人脸表情识别和情绪分类部分

在人脸表情识别和情绪分类部分分为过程:卷积神经网络模型的训练与面部表情的识别。

1.卷积神经网络模型的训练

1.1数据集的获取

(1)数据集下载

为了节约素材收集时间,同时也为更公平的评价模型以及人脸表情识别分类器的性能,我们采用使用公开的数据集。
本次设计使用了kaggle面部表情识别竞赛所使用的fer2013人脸表情数据库,图片统一以csv的格式存储,利用python可将csv文件转为单通道灰度图片,并根据标签将其分类在不同的文件夹中。
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第5张图片
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第6张图片

(2)数据集格式转换

首先根据用途label分成三个csv(分别是训练集(train)、测试集(test)、验证集(val));

# -*- coding = utf-8 -*-
# @Time : 2021/8/7 09:12
# @Author : 西兰花
# @File : convert_fer2013.py
# @Software : PyCharm

"""
根据用途label分成三个csv(分别是训练集(train)、测试集(test)、验证集(val));
"""

import csv

database_path = 'F:/test05/表情识别/表情识别/emotion_classifier-master/fer2013/'
datasets_path = './fer2013/'
csv_file = database_path+'fer2013.csv'
train_csv = datasets_path+'train.csv'
val_csv = datasets_path+'val.csv'
test_csv = datasets_path+'test.csv'


with open(csv_file) as f:
    csvr = csv.reader(f)
    header = next(csvr)
    print(header)
    rows = [row for row in csvr]
    
    trn = [row[:-1] for row in rows if row[-1] == 'Training']
    csv.writer(open(train_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + trn)
    print(len(trn))

    val = [row[:-1] for row in rows if row[-1] == 'PublicTest']
    csv.writer(open(val_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + val)
    print(len(val))        

    tst = [row[:-1] for row in rows if row[-1] == 'PrivateTest']
    csv.writer(open(test_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + tst)
    print(len(tst))

文件夹“test”存放测试集数据;
文件夹“train”存放训练集数据;
文件夹“valt”存放验证集数据;
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第7张图片
各文件夹中将图片分为七种,并存放在相应标签(label)中,标签“0”至“7”分别对应情绪“angry”、 “disgust”、“fear”、“happy”、“sad”、“surprise”、“neutral”。
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第8张图片
将图像转换为单通道灰度图:

# -*- coding = utf-8 -*-
# @Time : 2021/8/7 09:18
# @Author : 西兰花
# @File : convert_csv2gray.py
# @Software : PyCharm

"""
将图像转换为单通道灰度图
"""
import csv
import os
from PIL import Image
import numpy as np


datasets_path = r'.\fer2013'
train_csv = os.path.join(datasets_path, 'train.csv')
val_csv = os.path.join(datasets_path, 'val.csv')
test_csv = os.path.join(datasets_path, 'test.csv')

train_set = os.path.join(datasets_path, 'train')
val_set = os.path.join(datasets_path, 'val')
test_set = os.path.join(datasets_path, 'test')

for save_path, csv_file in [(train_set, train_csv), (val_set, val_csv), (test_set, test_csv)]:
    if not os.path.exists(save_path):
        os.makedirs(save_path)

    num = 1
    with open(csv_file) as f:
        csvr = csv.reader(f)
        header = next(csvr)
        for i, (label, pixel) in enumerate(csvr):
            pixel = np.asarray([float(p) for p in pixel.split()]).reshape(48, 48)
            subfolder = os.path.join(save_path, label)
            if not os.path.exists(subfolder):
                os.makedirs(subfolder)
            im = Image.fromarray(pixel).convert('L')
            image_name = os.path.join(subfolder, '{:05d}.jpg'.format(i))
            # print(image_name)
            im.save(image_name)

单通道灰度图:
人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第9张图片

1.2搭建卷积神经网络模型

数据集已成功获取,并按照label进行分类,第二步是建立卷积神经网络模型(CNN),本步骤是实现人脸表情识别关键一步。

在前人的基础上,本设计在输入层后加入了1*1的卷积层,使输入增加了非线性的表示、加深了网络、提升了模型的表达能力,同时基本不增加计算量。
模型代码如下:

 def build_model(self):
        self.model = Sequential()   # Sequential模型是keras两种模型之一,另一种是model模型
        """构建模型"""
        # 第一层卷积,需要指定input_shape的参数
        self.model.add(Conv2D(32, (1, 1), strides=1, padding='same', input_shape=(img_size, img_size, 1)))
        self.model.add(Activation('relu'))  # 激活函数
        self.model.add(Conv2D(32, (5, 5), padding='same'))
        self.model.add(Activation('relu'))
        self.model.add(MaxPooling2D(pool_size=(2, 2)))  # 最大池化

        self.model.add(Conv2D(32, (3, 3), padding='same'))
        self.model.add(Activation('relu'))
        self.model.add(MaxPooling2D(pool_size=(2, 2)))

        self.model.add(Conv2D(64, (5, 5), padding='same'))
        self.model.add(Activation('relu'))
        self.model.add(MaxPooling2D(pool_size=(2, 2)))

        self.model.add(Flatten())
        self.model.add(Dense(2048))     # 全连接层
        self.model.add(Activation('relu'))
        self.model.add(Dropout(0.5))
        self.model.add(Dense(1024))
        self.model.add(Activation('relu'))
        self.model.add(Dropout(0.5))
        self.model.add(Dense(num_classes))
        self.model.add(Activation('softmax'))
        self.model.summary()    # 显示训练模型结构

通过 self.model.summary() 函数查看训练模型结构;训练模型结构如下:

模型:Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 48, 48, 32)        64        
_________________________________________________________________
activation_1 (Activation)    (None, 48, 48, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 48, 48, 32)        25632     
_________________________________________________________________
activation_2 (Activation)    (None, 48, 48, 32)        0         
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 24, 24, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 24, 24, 32)        0         
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 12, 12, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 12, 12, 64)        51264     
_________________________________________________________________
activation_4 (Activation)    (None, 12, 12, 64)        0         
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 6, 6, 64)          0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 2304)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 2048)              4720640   
_________________________________________________________________
activation_5 (Activation)    (None, 2048)              0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 2048)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 1024)              2098176   
_________________________________________________________________
activation_6 (Activation)    (None, 1024)              0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 1024)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 7)                 7175      
_________________________________________________________________
activation_7 (Activation)    (None, 7)                 0         
=================================================================
Total params: 6,912,199
Trainable params: 6,912,199
Non-trainable params: 0

1.3训练模型

在训练过程中使用ImageDataGenerator()函数实现数据增强,并通过flow_from_directory()函数根据文件名划分label;优化算法选择SGD,损失函数选择categorical_crossentropy;

    def train_model(self):
        sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
        # 指定损失函数和优化器
        self.model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
        # 自动扩充训练样本
        train_datagen = ImageDataGenerator(
            rescale=1./255,
            shear_range=0.2,
            zoom_range=0.2,
            horizontal_flip=True)
        # 归一化验证集
        val_datagen = ImageDataGenerator(rescale=1./255)
        eval_datagen = ImageDataGenerator(rescale=1./255)
        # 以文件分类名划分label
        train_generator = train_datagen.flow_from_directory(
                root_path+'/train',
                target_size=(img_size, img_size),
                color_mode='grayscale',
                batch_size=batch_siz,
                class_mode='categorical')
        val_generator = val_datagen.flow_from_directory(
                root_path+'/val',
                target_size=(img_size,img_size),
                color_mode='grayscale',
                batch_size=batch_siz,
                class_mode='categorical')
        eval_generator = eval_datagen.flow_from_directory(
                root_path+'/test',
                target_size=(img_size,img_size),
                color_mode='grayscale',
                batch_size=batch_siz,
                class_mode='categorical')
        # early_stopping = EarlyStopping(monitor='loss',patience=3)
        # 用model.fit()函数来训练模型,输入训练集和测试数据
        history_fit = self.model.fit_generator(
                train_generator,
                steps_per_epoch=800/(batch_siz/32),     # 28709
                nb_epoch=nb_epoch,
                validation_data=val_generator,
                validation_steps=2000,
                # callbacks=[early_stopping]
                )
        # history_eval=self.model.evaluate_generator(eval_generator, steps=2000)    # 用model.evaluate_generator()函数来评估模型
        history_predict=self.model.predict_generator(eval_generator, steps=2000)    # 训练模型预测

1.4保存训练模型

    def save_model(self):   # 存储训练模型数据
        # 使用model.to_json()函数只保存模型的结构,而不包含其权重及训练的配置(损失函数、优化器)
        model_json = self.model.to_json()
        with open(root_path+"/model_json.json", "w") as json_file:
            json_file.write(model_json)
        self.model.save_weights(root_path+'/model_weight.h5')
        self.model.save(root_path+'/model.h5')
        print('model saved')

到此,人脸表情识别训练模型已构建完毕。

2.面部表情的识别

2.1加载pre-model网络与权重;

keras模型分为model和weight两部分
保存model方法:通过json文件或yaml文件
json文件:
model_json = model.to_json()
with open(“model.json”, “w”) as json_file:
json_file.write(model_json)
yaml文件:
yaml_string = model.to_yaml()
保存权重的方法:通过保存权重(系数)
HDF5文件:
model.save_weights(“model.h5”)
print(“Saved model to disk”)

载入model的方法
json & hdf5:
#load json and create model
json_file = open(‘model.json’, ‘r’)
loaded_model_json = json_file.read()
json_file.close()
loaded_model = model_from_json(loaded_model_json)
from keras.models import load_model
model = load_model(‘model.h5’)
载入权重:
#load weights into new model
loaded_model.load_weights(“model.h5”)
print(“Loaded model from disk”)

emo_labels = ['angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral']
num_class = len(emo_labels)
# 使用json文件打开keras中model部分
json_file = open(model_path+'model_json.json')
loaded_model_json = json_file.read()
json_file.close()
model = model_from_json(loaded_model_json)
# keras.models.load_model()读取网络、权重
# keras.models.load_weights()仅读取权重
# 打开hdf5文件,即权重存放的文件
model.load_weights(model_path+'model_weight.h5')

2.2利用opencv对图像预处理;

在Python中提供强大的视觉处理库——OpenCV,我们可以里OpenCV库中提供的函数实现人脸识别,进而对识别到的人脸进行灰度处理、裁切、翻转、几何归一化等处理,并通过双线内插值算法(cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]))将图像统一重塑为48*48像素。

1.打开系统摄像头

通过 cv2.VideoCapture()函数调用系统摄像头。值为0表示调取系统摄像头,值为路径+文件名(仅限视频/图片)表示打开视频/图片文件;

# 捕获指定摄像头的实时视频流
    cap = cv2.VideoCapture(0)
2.人脸识别

使用haarcascade_frontalface_alt.xml级联器进行人脸识别,或者使用lbpcascade_frontalcatface.xml级联器;之前笔者分别使用这两个级联器进行人脸识别,两者识别率不相上下,也可能是跟环境有关,因此笔者认为二者都适用,具体测试可查看Python+OpenCV(十七)——人脸识别。

 # 人脸识别分类器本地存储路径
    cascade_path = root_path+"haarcascade_frontalface_alt.xml"    # 哈尔级联器
3.灰度处理、裁切、翻转、几何归一化等处理
# 图像灰化,降低计算复杂度
        frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        
        # 使用人脸识别分类器,读入分类器
        cascade = cv2.CascadeClassifier(cascade_path)                

        # 利用分类器识别出哪个区域为人脸
        faceRects = cascade.detectMultiScale(frame_gray, scaleFactor=1.1, minNeighbors=1, minSize=(120, 120))

        if len(faceRects) > 0:                 
            for faceRect in faceRects: 
                x, y, w, h = faceRect
                images = []
                rs_sum = np.array([0.0]*num_class)
                # 截取脸部图像提交给模型识别表情
                image = frame_gray[y: y + h, x: x + w]  # 灰度处理
                # cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
                # src:[必需]原图像
                # dsize:[必需]输出图像所需大小
                # fx:[必需]沿水平轴的比例因子
                # fy:[必需]沿垂直轴的比例因子
                # interpolation:[必需]插值方式
                image = cv2.resize(image, (img_size, img_size))     # 将灰度处理后的图像按比例调整为像素大小48*48
                image = image*(1./255)
                images.append(image)    # 在末尾添加新的图片
                images.append(cv2.flip(image, 1))
                images.append(cv2.resize(image[2:45, :], (img_size, img_size)))

2.3人脸表情识别

使用之前训练好的模型进行预测,对多个处理过的脸部预测结果进行线性加权融合,最后得出预测结果;

def predict_emotion(face_img):
    face_img = face_img * (1. / 255)
    resized_img = cv2.resize(face_img, (img_size, img_size))  # ,interpolation=cv2.INTER_LINEAR
    rsz_img = []
    rsh_img = []
    results = []
    # print (len(resized_img[0]),type(resized_img))
    rsz_img.append(resized_img[:, :])  # resized_img[1:46,1:46]
    rsz_img.append(resized_img[2:45, :])
    rsz_img.append(cv2.flip(rsz_img[0], 1))
    # rsz_img.append(cv2.flip(rsz_img[1],1))

    '''rsz_img.append(resized_img[0:45,0:45])
    rsz_img.append(resized_img[2:47,0:45])
    rsz_img.append(resized_img[2:47,2:47])
    rsz_img.append(cv2.flip(rsz_img[2],1))
    rsz_img.append(cv2.flip(rsz_img[3],1))
    rsz_img.append(cv2.flip(rsz_img[4],1))'''
    i = 0
    for rsz_image in rsz_img:
        rsz_img[i] = cv2.resize(rsz_image, (img_size, img_size))
        # =========================
        # cv2.imshow('%d'%i,rsz_img[i])
        i += 1
    # why 4 parameters here, what's it means?
    for rsz_image in rsz_img:
        rsh_img.append(rsz_image.reshape(1, img_size, img_size, 1))
    i = 0
    for rsh_image in rsh_img:
        list_of_list = model.predict_proba(rsh_image, batch_size=32, verbose=1)  # predict
        result = [prob for lst in list_of_list for prob in lst]
        results.append(result)
    return results

2.4表情分类器检测

predict_proba()函数:模型预测输入样本属于每种类别的概率,概率和为1,每个位置的概率分别对应classes_中对应位置的类别标签。predict_proba输出概率最大值索引位置对应的classes_元素就是样本所属的类别。

list_of_list = model.predict_proba(image, batch_size=32, verbose=1)     # predict
                    result = [prob for lst in list_of_list for prob in lst]
                    rs_sum += np.array(result)
                print(rs_sum)
                label = np.argmax(rs_sum)
                emo = emo_labels[label]
                print('Emotion : ', emo)    # 输出情绪分类结果

人脸表情识别和情绪分类 | Python+TensorFlow(框架)+Keras+PyQt5_第10张图片

(二)PyQt5界面部分

(具体操作请参考本文章开头的参考文章)

1.PyQt+QtDesigner等工具的安装与设置;

2.基于QtDesigner的界面设计;

3.各部分程序的编写;

4.笔记本摄像头的读取、显示和参数控制;

四、主代码展示

由于工程庞大,以下仅展示主要代码:
(代码中主要功能基本上附上了注释,如有疑惑部分可查看本文章开头列出的参考文章,或者评论本文章皆可)

# -*- coding = utf-8 -*-
# @Time : 2021/8/19 19:45
# @Author : 西兰花
# @File : QtOpenCV.py
# @Software : PyCharm

"""
代码主要结构主要分为两大部分:
一、人脸表情识别和情绪分类部分
(一)卷积神经网络模型的训练
1数据集的获取
2.加载pre-model网络与权重;
3.训练模型
4.保存训练模型
(二)面部表情识别
1.打开摄像头
2.人脸识别
3.图像预处理
4.人脸表情识别
5.情绪分类
二、PyQt5界面创建
1.PyQt+QtDesigner及opencv等工具的安装与设置;
2.基于QtDesigner的界面设计;
3.各部分程序的编写;
4.笔记本摄像头的读取、显示和参数控制;
"""


# PyQt界面部分
from OboardCamDisp import Ui_MainWindow
# import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5.QtCore import QTimer, QCoreApplication
from PyQt5.QtGui import QPixmap
# import cv2
import qimage2ndarray
import time
# 人脸表情识别+情绪分类部分
import cv2
import sys
import numpy as np
from keras.models import model_from_json
root_path = './pic/'
model_path = root_path+'/model/'
img_size = 48   # 图像像素大小48*48
# load json and create model arch
"""
keras模型分为model和weight两部分
保存model方法:通过json文件或yaml文件
json文件:
model_json = model.to_json()
with open("model.json", "w") as json_file:
    json_file.write(model_json)
yaml文件:
yaml_string = model.to_yaml()
保存权重的方法:通过保存权重(系数)
HDF5文件:
model.save_weights("model.h5")
print("Saved model to disk")

载入model的方法
json & hdf5:
# load json and create model
json_file = open('model.json', 'r')
loaded_model_json = json_file.read()
json_file.close()
loaded_model = model_from_json(loaded_model_json)
from keras.models import load_model
model = load_model('model.h5')
载入权重:
# load weights into new model
loaded_model.load_weights("model.h5")
print("Loaded model from disk")
"""
emo_labels = ['angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral']
num_class = len(emo_labels)
# 使用json文件打开keras中model部分
json_file = open(model_path+'model_json.json')
loaded_model_json = json_file.read()
json_file.close()
model = model_from_json(loaded_model_json)
# keras.models.load_model()读取网络、权重
# keras.models.load_weights()仅读取权重
# 打开hdf5文件,即权重存放的文件
model.load_weights(model_path+'model_weight.h5')


# PyQt5创建界面
class CamShow(QMainWindow, Ui_MainWindow):   # 定义一个类,实现程序的主要功能
    def __del__(self):
        try:
            self.camera.release()  # 释放资源
        except:
            return

    def __init__(self, parent=None):    # 初始化函数
        super(CamShow, self).__init__(parent)
        self.setupUi(self)
        self.PrepSliders()      # PrepSliders()函数实现各个slider和对应的spinbox的关联,保证两个控件的值始终相等
        self.PrepWidgets()      # PrepWidgets()函数初始化各个控件
        self.PrepParameters()   # PrepParameters()函数定义并初始化程序运行过程中会用到的变量
        # CallBackFunctions()函数各个控件背后的功能函数的集合,定义了在程序界面上进行某项操作后实际执行的代码
        self.CallBackFunctions()
        """Timer = QTimer()函数、Timer.timeout.connect(self.TimerOutFun)函数
                   计时器的定义和调用,Timer = QTimer()函数定义了一个定时器,等我们执行计时器开始的代码后,该计时器就开始计时,
                   每次计时结束都会调用一次函数TimerOutFun,通过计时器实现对摄像头图像的循环读取和显示"""
        self.Timer = QTimer()
        self.Timer.timeout.connect(self.TimerOutFun)

    # Slider和SpinBox
    def PrepSliders(self):
        self.RedColorSld.valueChanged.connect(self.RedColorSpB.setValue)
        self.RedColorSpB.valueChanged.connect(self.RedColorSld.setValue)
        self.GreenColorSld.valueChanged.connect(self.GreenColorSpB.setValue)
        self.GreenColorSpB.valueChanged.connect(self.GreenColorSld.setValue)
        self.BlueColorSld.valueChanged.connect(self.BlueColorSpB.setValue)
        self.BlueColorSpB.valueChanged.connect(self.BlueColorSld.setValue)
        self.ExpTimeSld.valueChanged.connect(self.ExpTimeSpB.setValue)
        self.ExpTimeSpB.valueChanged.connect(self.ExpTimeSld.setValue)
        self.GainSld.valueChanged.connect(self.GainSpB.setValue)
        self.GainSpB.valueChanged.connect(self.GainSld.setValue)
        self.BrightSld.valueChanged.connect(self.BrightSpB.setValue)
        self.BrightSpB.valueChanged.connect(self.BrightSld.setValue)
        self.ContrastSld.valueChanged.connect(self.ContrastSpB.setValue)
        self.ContrastSpB.valueChanged.connect(self.ContrastSld.setValue)

    # 控件初始化
    def PrepWidgets(self):
        self.PrepCamera()
        self.StopBt.setEnabled(False)
        self.RecordBt.setEnabled(False)
        self.GrayImgCkB.setEnabled(False)
        self.RedColorSld.setEnabled(False)
        self.RedColorSpB.setEnabled(False)
        self.GreenColorSld.setEnabled(False)
        self.GreenColorSpB.setEnabled(False)
        self.BlueColorSld.setEnabled(False)
        self.BlueColorSpB.setEnabled(False)
        self.ExpTimeSld.setEnabled(False)
        self.ExpTimeSpB.setEnabled(False)
        self.GainSld.setEnabled(False)
        self.GainSpB.setEnabled(False)
        self.BrightSld.setEnabled(False)
        self.BrightSpB.setEnabled(False)
        self.ContrastSld.setEnabled(False)
        self.ContrastSpB.setEnabled(False)

    # 初始化摄像头
    def PrepCamera(self):
        try:
            # camera=cv2.VideoCapture(0)函数,调用OpenCV的VideoCapture函数打开摄像头
            # 并使用变量self.camera代表该摄像头,参数0意味着打开笔记本自带的摄像头
            self.camera = cv2.VideoCapture(0)
            print("摄像头已打开!")
            self.MsgTE.clear()  # 清空文本框MsgTE中的内容
            # 在文本框MsgTE中显示'Oboard camera connected.'
            # append()函数表示在现有的内容后继续添加内容
            # setPlainText函数,用括号中的文本覆盖原来的文本
            self.MsgTE.append('Oboard camera connected.')
            self.MsgTE.setPlainText()
        except Exception as e:
            self.MsgTE.clear()
            self.MsgTE.append(str(e))

    # 参数初始化
    def PrepParameters(self):
        # 变量self.RecordFlag,该值为0时不保存视频,为1时开始保存视频
        self.RecordFlag = 0
        # 变量self.RecordPath定义默认的文件存储路径
        self.RecordPath = 'F:/test05/表情识别/表情识别/emotion_classifier-master/Qt/'
        # self.FilePathLE.setText(self.RecordPath)将路径名显示在文本框FilePathLE中
        self.FilePathLE.setText(self.RecordPath)
        # self.Image_num定义读取图片的次数
        self.Image_num = 0
        # self.R、self.G、self.B为三个颜色通道的强度增益系数
        self.R = 1
        self.G = 1
        self.B = 1

        # 初始化摄像头参数控件的值,即读取摄像头的曝光、增益、亮度、对比度等参数,并将这些值显示在相应的控件上
        self.ExpTimeSld.setValue(self.camera.get(15))
        self.SetExposure()
        self.GainSld.setValue(self.camera.get(14))
        self.SetGain()
        self.BrightSld.setValue(self.camera.get(10))
        self.SetBrightness()
        self.ContrastSld.setValue(self.camera.get(11))
        self.SetContrast()
        self.MsgTE.clear()

    # 控件回调函数
    def CallBackFunctions(self):
        self.FilePathBt.clicked.connect(self.SetFilePath)
        self.ShowBt.clicked.connect(self.StartCamera)
        self.StopBt.clicked.connect(self.StopCamera)
        self.RecordBt.clicked.connect(self.RecordCamera)
        self.ExitBt.clicked.connect(self.ExitApp)
        self.GrayImgCkB.stateChanged.connect(self.SetGray)
        self.ExpTimeSld.valueChanged.connect(self.SetExposure)
        self.GainSld.valueChanged.connect(self.SetGain)
        self.BrightSld.valueChanged.connect(self.SetBrightness)
        self.ContrastSld.valueChanged.connect(self.SetContrast)
        self.RedColorSld.valueChanged.connect(self.SetR)
        self.GreenColorSld.valueChanged.connect(self.SetG)
        self.BlueColorSld.valueChanged.connect(self.SetB)

    # 实现灰度转换
    # 通道R
    def SetR(self):
        R = self.RedColorSld.value()
        self.R = R/255

    # 通道G
    def SetG(self):
        G = self.GreenColorSld.value()
        self.G = G/255

    # 通道B
    def SetB(self):
        B = self.BlueColorSld.value()
        self.B = B/255

    def SetContrast(self):
        contrast_toset=self.ContrastSld.value()
        try:
            self.camera.set(11,contrast_toset)
            self.MsgTE.setPlainText('The contrast is set to ' + str(self.camera.get(11)))
        except Exception as e:
            self.MsgTE.setPlainText(str(e))

    def SetBrightness(self):
        brightness_toset=self.BrightSld.value()
        try:
            self.camera.set(10,brightness_toset)
            self.MsgTE.setPlainText('The brightness is set to ' + str(self.camera.get(10)))
        except Exception as e:
            self.MsgTE.setPlainText(str(e))

    def SetGain(self):
        gain_toset=self.GainSld.value()
        try:
            self.camera.set(14,gain_toset)
            self.MsgTE.setPlainText('The gain is set to '+str(self.camera.get(14)))
        except Exception as e:
            self.MsgTE.setPlainText(str(e))

    # 摄像头参数设置
    def SetExposure(self):
        try:
            exposure_time_toset = self.ExpTimeSld.value()
            self.camera.set(15,exposure_time_toset)
            self.MsgTE.setPlainText('The exposure time is set to '+str(self.camera.get(15)))
        except Exception as e:
            self.MsgTE.setPlainText(str(e))

    # 图像显示的颜色控制
    def SetGray(self):
        if self.GrayImgCkB.isChecked():
            self.RedColorSld.setEnabled(False)
            self.RedColorSpB.setEnabled(False)
            self.GreenColorSld.setEnabled(False)
            self.GreenColorSpB.setEnabled(False)
            self.BlueColorSld.setEnabled(False)
            self.BlueColorSpB.setEnabled(False)
        else:
            self.RedColorSld.setEnabled(True)
            self.RedColorSpB.setEnabled(True)
            self.GreenColorSld.setEnabled(True)
            self.GreenColorSpB.setEnabled(True)
            self.BlueColorSld.setEnabled(True)
            self.BlueColorSpB.setEnabled(True)

    # 摄像头的读取和显示
    # 按键“开始”
    def StartCamera(self):
        self.ShowBt.setEnabled(False)
        self.StopBt.setEnabled(True)
        self.RecordBt.setEnabled(True)
        self.GrayImgCkB.setEnabled(True)
        if self.GrayImgCkB.isChecked() == 0:
            self.RedColorSld.setEnabled(True)
            self.RedColorSpB.setEnabled(True)
            self.GreenColorSld.setEnabled(True)
            self.GreenColorSpB.setEnabled(True)
            self.BlueColorSld.setEnabled(True)
            self.BlueColorSpB.setEnabled(True)
        self.ExpTimeSld.setEnabled(True)
        self.ExpTimeSpB.setEnabled(True)
        self.GainSld.setEnabled(True)
        self.GainSpB.setEnabled(True)
        self.BrightSld.setEnabled(True)
        self.BrightSpB.setEnabled(True)
        self.ContrastSld.setEnabled(True)
        self.ContrastSpB.setEnabled(True)
        self.RecordBt.setText('录像')

        self.Timer.start(1)  # self.Timer.start(1)用来启动计时器,计时周期为1ms,即每隔1ms程序会自动调用一次TimerOutFun
        self.timelb = time.clock()

    # 保存图片或视频
    def SetFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.FilePathLE.setText(dirname)
            self.RecordPath = dirname+'/'

    # 人脸表情识别+情绪分类主要部分
    def TimerOutFun(self):
        success, frame = self.camera.read()
        if success:
            self.Image = self.ColorAdjust(frame)    # 调用图像色彩调节函数
            self.DispImg()      # 调用函数
            self.Image_num += 1
            if self.RecordFlag:
                self.video_writer.write(frame)
            if self.Image_num % 10 == 9:    # 计算帧频
                frame_rate = 10/(time.clock()-self.timelb)
                self.FmRateLCD.display(frame_rate)
                self.timelb = time.clock()
                self.ImgWidthLCD.display(self.camera.get(3))
                self.ImgHeightLCD.display(self.camera.get(4))
            if len(sys.argv) != 1:
                print("Usage:%s camera_id\r\n" % (sys.argv[0]))
                sys.exit(0)

            # 人脸识别分类器本地存储路径
            cascade_path = root_path + "haarcascade_frontalface_alt.xml"  # 哈尔级联器

            # 图像灰化,降低计算复杂度
            frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            # 使用人脸识别分类器,读入分类器
            cascade = cv2.CascadeClassifier(cascade_path)

            # 利用分类器识别出哪个区域为人脸
            faceRects = cascade.detectMultiScale(frame_gray, scaleFactor=1.1, minNeighbors=1, minSize=(120, 120))

            if len(faceRects) > 0:
                for faceRect in faceRects:
                    x, y, w, h = faceRect
                    images = []
                    rs_sum = np.array([0.0] * num_class)
                    # 截取脸部图像提交给模型识别表情
                    image = frame_gray[y: y + h, x: x + w]  # 灰度处理
                    # cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
                    # src:[必需]原图像
                    # dsize:[必需]输出图像所需大小
                    # fx:[必需]沿水平轴的比例因子
                    # fy:[必需]沿垂直轴的比例因子
                    # interpolation:[必需]插值方式
                    image = cv2.resize(image, (img_size, img_size))  # 将灰度处理后的图像按比例调整为像素大小48*48
                    image = image * (1. / 255)
                    images.append(image)  # 在末尾添加新的图片
                    images.append(cv2.flip(image, 1))
                    images.append(cv2.resize(image[2:45, :], (img_size, img_size)))
                    for img in images:
                        image = img.reshape(1, img_size, img_size, 1)
                        """
                        predict_proba()函数:
                        模型预测输入样本属于每种类别的概率,概率和为1,每个位置的概率分别对应classes_中对应位置的类别标签。
                        predict_proba输出概率最大值索引位置对应的classes_元素就是样本所属的类别。
                        """
                        list_of_list = model.predict_proba(image, batch_size=32, verbose=1)  # predict
                        result = [prob for lst in list_of_list for prob in lst]
                        rs_sum += np.array(result)
                    print(rs_sum)
                    label = np.argmax(rs_sum)
                    emo = emo_labels[label]
                    print('Emotion : ', emo)  # 输出情绪分类结果
                    # cv2.putText(frame, "Emotion: %s" % emo, (400, 400), cv2.FONT_HERSHEY_COMPLEX, 1, (255, 255, 0), 3)


                    # rectangle()框选识别到的人脸
                    # cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness=2)
                    # font = cv2.FONT_HERSHEY_SIMPLEX
                    # 文字显示该表情分类结果
                    # cv2.putText(frame, '%s' % emo, (x + 30, y + 30), font, 1, (255, 0, 255), 4)

        else:
            self.MsgTE.clear()
            self.MsgTE.setPlainText('Image obtaining failed.')

    def ColorAdjust(self, img):
        try:
            B = img[:, :, 0]
            G = img[:, :, 1]
            R = img[:, :, 2]
            B = B*self.B
            G = G*self.G
            R = R*self.R

            img1 = img
            img1[:, :, 0] = B
            img1[:, :, 1] = G
            img1[:, :, 2] = R
            return img1
        except Exception as e:
            self.MsgTE.setPlainText(str(e))

    # 检测人脸、色彩空间及格式转换
    def DispImg(self):
        if self.GrayImgCkB.isChecked():
            img = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
        else:
            img = cv2.cvtColor(self.Image, cv2.COLOR_BGR2RGB)
        qimg = qimage2ndarray.array2qimage(img)
        self.DispLb.setPixmap(QPixmap(qimg))
        self.DispLb.show()

    # 按键“暂停”
    def StopCamera(self):
        if self.StopBt.text() == '暂停':
            self.StopBt.setText('继续')
            self.RecordBt.setText('保存')
            self.Timer.stop()
        elif self.StopBt.text() == '继续':
            self.StopBt.setText('暂停')
            self.RecordBt.setText('录像')
            self.Timer.start(1)

    # 按键“录像”
    def RecordCamera(self):
        tag = self.RecordBt.text()
        if tag == '保存':
            try:
                image_name = self.RecordPath+'image'+time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))+'.jpg'
                print(image_name)
                cv2.imwrite(image_name, self.Image)
                self.MsgTE.clear()
                self.MsgTE.setPlainText('Image saved.')
            except Exception as e:
                self.MsgTE.clear()
                self.MsgTE.setPlainText(str(e))
        elif tag == '录像':
            self.RecordBt.setText('停止')

            video_name = self.RecordPath + 'video' + time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) + '.avi'
            fps = self.FmRateLCD.value()
            size = (self.Image.shape[1],self.Image.shape[0])
            fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
            self.video_writer = cv2.VideoWriter(video_name, fourcc, self.camera.get(5), size)
            self.RecordFlag=1
            self.MsgTE.setPlainText('Video recording...')
            self.StopBt.setEnabled(False)
            self.ExitBt.setEnabled(False)
        elif tag == '停止':
            self.RecordBt.setText('录像')
            self.video_writer.release()
            self.RecordFlag = 0
            self.MsgTE.setPlainText('Video saved.')
            self.StopBt.setEnabled(True)
            self.ExitBt.setEnabled(True)

    # 退出程序
    def ExitApp(self):
        self.Timer.Stop()
        self.camera.release()
        self.MsgTE.setPlainText('Exiting the application..')
        QCoreApplication.quit()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = CamShow()
    ui.show()
    sys.exit(app.exec_())

你可能感兴趣的:(人脸识别,深度学习,神经网络,python)