基于Keras框架搭建并训练了卷积神经网络模型,用于人脸表情识别,训练集和测试集均采用kaggle的fer2013数据集。

达到如下效果:

keras人脸目标检测 keras人脸识别_表情识别

  

keras人脸目标检测 keras人脸识别_表情识别_02

                  

keras人脸目标检测 keras人脸识别_keras人脸目标检测_03

     

keras人脸目标检测 keras人脸识别_神经网络模型_04

打上了小小马赛克的博主。

 

整个表情识别系统分为两个过程:卷积神经网络模型的训练 与 面部表情的识别。

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

1.1获取数据集

    使用公开的数据集一方面可以节约收集数据的时间,另一方面可以更公平地评价模型以及人脸表情分类器的性能,因此,使用了kaggle面部表情识别竞赛所使用的fer2013人脸表情数据库。图片统一以csv的格式存储。首先用python将csv文件转为单通道灰度图片并根据标签将其分类在不同的文件夹中。

fer2013数据集链接:  https://pan.baidu.com/s/1M6XS8ovXbn8-UfQwcUnvVQ 密码: jueq

代码如下:

先将其根据用途label分成三个csv(分别是训练集、测试集、验证集)

import csv
import os

database_path = 'E:/Python/DeepLearning/emotion_classifier/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))

将其转换为单通道灰度图

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)

最终效果:

keras人脸目标检测 keras人脸识别_keras人脸目标检测_05

每个文件夹里都是:

keras人脸目标检测 keras人脸识别_卷积_06

里面则是:

keras人脸目标检测 keras人脸识别_表情识别_07

0-6文件夹分别label为:

angry ,disgust ,fear ,happy ,sad ,surprise ,neutral

1.2搭建卷积神经网络模型

数据集已经成功获取并根据label分类好了,接下来就是建立卷积神经网络模型,这是至关重要的一个步骤。

博主在google的论文Going deeper with convolutions中获得灵感,在输入层之后加入了1*1的卷积层使输入增加了非线性的表示、加深了网络、提升了模型的表达能力,同时基本不增加计算量。之后根据VGG网络的想法,尝试将5*5网络拆分为两层3*3但最后效果并不理想,在多次尝试了多种不同的模型并不断调整之后

最终网络模型结构如下:

 

种类

步长

填充

输出

丢弃

输入

 

 

 

48*48*1

 

卷积层1

1*1

1

 

48*48*32

 

卷积层2

5*5

1

2

48*48*32

 

池化层1

3*3

2

 

23*23*32

 

卷积层3

3*3

1

1

23*23*32

 

池化层2

3*3

2

 

11*11*32

 

卷积层4

5*5

1

2

11*11*64

 

池化层3

3*3

2

 

5*5*64

 

全连接层1

 

 

 

1*1*2048

50%

全连接层2

 

 

 

1*1*1024

50%

输出

 

 

 

1*1*7

 

模型的代码:

def build_model4(self):
        self.model=Sequential()
        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()

1.3训练模型

在训练过程中使用ImageDataGenerator实现数据增强,并通过flow_from_directory根据文件名划分label:

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')

优化算法选择了SGD

sgd=SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
        self.model.compile(loss='categorical_crossentropy',
                optimizer=sgd,
                #optimizer='rmsprop',
                metrics=['accuracy'])

而激活函数选择了硬饱和的ReLU。因为tanh和sigmoid在训练后期,产生了因没有进行归一化而梯度消失训练困难的问题。如下图:

keras人脸目标检测 keras人脸识别_卷积_08

批尺寸在多次尝试之后最终选择了128,迭代50次(在这之后val_loss呈上升趋势,val_acc呈下降趋势)

 

批尺寸

256

128

64

32

1

每次迭代的步数

100

200

400

800

25600

一次迭代的时间

205s

118s

76s

58s

大于30min

迭代的次数

50

50

50

50

----

测试集准确率

0.679

0.684

0.675

0.651

----

 

到这里,表情识别模型就已经训练完成了。接下来就是下一步,完成人脸的识别和将识别出的人脸输入现在这个训练好的模型!

2.人脸表情识别模块

2.1图像预处理

使用公开库opencv实现人脸识别,然后对识别到的人脸进行了裁切以及翻转,处理之后将图像进行几何归一化,通过双线内插值算法将图像统一重塑为48*48像素。

def face_detect(image_path):
    rootPath='E:\Python\Opencv\opencv\sources\data\haarcascades\\'
    cascPath=rootPath+'haarcascade_frontalface_alt.xml'
    faceCasccade=cv2.CascadeClassifier(cascPath)

    #load the img and convert it to bgrgray
    #img_path=image_path
    img=cv2.imread(image_path)
    img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

    #face detection
    faces = faceCasccade.detectMultiScale(
            img_gray,
            scaleFactor=1.1,
            minNeighbors=1,
            minSize=(30,30),
            )
    #print('img_gray:',type(img_gray))
    return faces,img_gray,img

keras人脸目标检测 keras人脸识别_神经网络模型_09

2.2人脸表情识别

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

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])
    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
    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

当然也可以不使用该方法,将单一的人脸原图输入,但是通过实验证明

该方法可以提高表情识别分类器在空间上对局部位移和轻微形变的鲁棒性,可以有效提高表情识别系统分类的准确率。

模型

模型1

模型2

模型3(最终采用的模型)

模型4

处理前的准确率

65.5

62.3

68.4

67.37

处理后的准确率

67.6

64.1

70.2

69.4

最终模型在测试集上的准确度为70.2%。

 

到这里整个人脸表情识别系统就算完成了。

3.效果展示

keras人脸目标检测 keras人脸识别_keras人脸目标检测_10

keras人脸目标检测 keras人脸识别_表情识别_11

 

keras人脸目标检测 keras人脸识别_keras人脸目标检测_12

最后附上实验环境

    PC:联想y50-70

    系统:win10

    语言:python3.6

    显卡:gtx860m