利用CNN进行面部表情识别
本文是論文《Facial Emotion Recognition: State of the Art Performance on FER2013》的復(fù)現(xiàn),感謝原作者Yousif Khaireddin和Zhuofa Chen。
本文采用的數(shù)據(jù)集是FER2013。
文章目錄
- 前言
- 系統(tǒng)設(shè)計(jì)
- 數(shù)據(jù)預(yù)處理
- 數(shù)據(jù)集分割
- 數(shù)據(jù)增強(qiáng)
- VGGNet 網(wǎng)絡(luò)結(jié)構(gòu)
- 神經(jīng)網(wǎng)絡(luò)的優(yōu)化方法
- 基于Nesterov momentum的SGD方法
- 學(xué)習(xí)速率監(jiān)測(cè)器
- 系統(tǒng)實(shí)現(xiàn)
- 實(shí)驗(yàn)環(huán)境
- 本地環(huán)境
- 使用GPU訓(xùn)練
- 數(shù)據(jù)集概覽
- 樣本概覽
- 數(shù)據(jù)預(yù)處理
- 構(gòu)建我們的神經(jīng)網(wǎng)絡(luò)
- 數(shù)據(jù)增強(qiáng)
- 系統(tǒng)測(cè)試
- 訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)
- 可視化訓(xùn)練效果
- 評(píng)估測(cè)試效果
- 使用混淆矩陣進(jìn)行分析
- 實(shí)時(shí)人臉表情識(shí)別
- 系統(tǒng)總結(jié)
前言
面部情緒識(shí)別是指識(shí)別傳達(dá)恐懼、快樂(lè)和厭惡等基本情緒的表情。它在人機(jī)交互中起著重要作用,可應(yīng)用于數(shù)字廣告、在線游戲、客戶(hù)反饋評(píng)估和醫(yī)療保健等方面。隨著計(jì)算機(jī)視覺(jué)技術(shù)的進(jìn)步,在受控條件和一致環(huán)境下拍攝的圖像中能夠?qū)崿F(xiàn)較高的表情識(shí)別準(zhǔn)確率,從而使這一技術(shù)得到運(yùn)用。在自然條件下,由于類(lèi)內(nèi)變異較大和類(lèi)間變異較小,例如面部姿勢(shì)的變化和表情之間的細(xì)微差異,表情識(shí)別技術(shù)面臨挑戰(zhàn)。
計(jì)算機(jī)視覺(jué)技術(shù)的發(fā)展一直致力于提高此類(lèi)問(wèn)題的分類(lèi)精度。在圖像分類(lèi)中,卷積神經(jīng)網(wǎng)絡(luò)(CNN)由于其計(jì)算效率和特征提取能力而顯示出巨大的潛力。它們是FER最廣泛使用的深度模型。一個(gè)包含復(fù)雜自然環(huán)境條件的具有挑戰(zhàn)性的典型數(shù)據(jù)集是FER2013。它在2013年的國(guó)際機(jī)器學(xué)習(xí)會(huì)議(ICML)上被引入,并成為比較情感識(shí)別模型性能的基準(zhǔn)。該數(shù)據(jù)集的績(jī)效因子估計(jì)為65.5%。基于此,我們使用FER2013數(shù)據(jù)集作為我們的研究對(duì)象。
在本次項(xiàng)目實(shí)踐中,我們的目標(biāo)是利用CNN訓(xùn)練FER2013數(shù)據(jù)集,并實(shí)現(xiàn)實(shí)時(shí)的表情識(shí)別系統(tǒng)。
系統(tǒng)設(shè)計(jì)
數(shù)據(jù)預(yù)處理
數(shù)據(jù)集分割
為了訓(xùn)練FER2013數(shù)據(jù)集,我們參照ICML官方設(shè)計(jì)的訓(xùn)練(Training)、驗(yàn)證(Validation)、測(cè)試(Test)數(shù)據(jù)集的分割方法,即80%作為訓(xùn)練數(shù)據(jù)集,10%作為驗(yàn)證數(shù)據(jù)集,10%作為測(cè)試數(shù)據(jù)集。
數(shù)據(jù)增強(qiáng)
為了能讓我們的卷積神經(jīng)網(wǎng)絡(luò)對(duì)表情識(shí)別有更加可靠的自適應(yīng)性,我們可以在神經(jīng)網(wǎng)絡(luò)的訓(xùn)練中使用數(shù)據(jù)增強(qiáng)(Data Augmentation)。基于批量(Batch)數(shù)據(jù)的實(shí)時(shí)訓(xùn)練方式,我們的數(shù)據(jù)增強(qiáng)方法如下表所示。
| 1 | Zoom | ±20% | 對(duì)圖像做隨機(jī)縮放 |
| 2 | Width/Height Shift | ±20% | 水平/垂直平移 |
| 3 | Rotation | ±10% | 隨機(jī)旋轉(zhuǎn)角度 |
| 4 | Horizontal Flip | 水平鏡像 |
VGGNet 網(wǎng)絡(luò)結(jié)構(gòu)
VGGNet是一種用于大規(guī)模圖像處理和模式識(shí)別的經(jīng)典卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)。我們的搭建的VGGNet變體如下圖所示。
該網(wǎng)絡(luò)由4個(gè)卷積級(jí)(Convolutional Stages)和3個(gè)全連接層(Fully Connected Layers)組成。每個(gè)卷積級(jí)包含兩個(gè)卷積塊(Convolutional Blocks)和一個(gè)最大池化層(Max Pooling)。卷積塊由卷積層(Convolution)、ReLU激活函數(shù)和批標(biāo)準(zhǔn)化層(Batch Normalization)組成。批標(biāo)準(zhǔn)化能夠加速神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)過(guò)程,減少內(nèi)部協(xié)方差偏移,以及防止梯度消失或爆炸。前兩個(gè)全連接層之后是ReLU激活函數(shù)。第三個(gè)全連接層用于最終分類(lèi),使用SoftMax激活函數(shù)。卷積級(jí)的作用是特征提取、降維和非線性。經(jīng)過(guò)全連接層的訓(xùn)練,我們可以根據(jù)提取的特征對(duì)輸入圖像進(jìn)行分類(lèi)。
神經(jīng)網(wǎng)絡(luò)的優(yōu)化方法
基于Nesterov momentum的SGD方法
我們回顧基本的mini-bacth SGD方法,其原理是,采用訓(xùn)練數(shù)據(jù)的一部分,生成批量樣本(mini-batch),然后對(duì)批量樣本,使用隨機(jī)梯度下降法(SGD)更新權(quán)值(weights)和偏置(biases),如下面的公式所示。
{wjkl←wjkl+Δwjkl=wjkl?ηm∑?wjlbjl←bjl+Δbjl=bjl?ηm∑?bjl\left\{ \begin{array}{l} w_{jk}^l\gets\ w_{jk}^l+\Delta w_{jk}^l=w_{jk}^l-\frac{\eta}{m}\sum{\nabla w^l_j}\\ b_j^l\gets\ b_j^l+\Delta b_j^l=b_j^l-\frac{\eta}{m}\sum{\nabla b^l_j} \end{array} \right. {wjkl?←?wjkl?+Δwjkl?=wjkl??mη?∑?wjl?bjl?←?bjl?+Δbjl?=bjl??mη?∑?bjl??
其中,η\etaη 就是學(xué)習(xí)速率,m是mini-batch的樣本數(shù)量。
mini-bacth SGD的核心是對(duì)權(quán)值梯度?w\nabla w?w和偏置的梯度?b\nabla b?b,經(jīng)過(guò)反向傳播的方式進(jìn)行更新。因此,我們也把基于SGD方法的神經(jīng)網(wǎng)絡(luò)稱(chēng)為BP神經(jīng)網(wǎng)絡(luò)。
進(jìn)一步,我們使用動(dòng)量(momentum)方法更好地完成對(duì)權(quán)值的更新。當(dāng)使用SGD訓(xùn)練參數(shù)時(shí),有時(shí)候會(huì)下降的非常慢,并且可能會(huì)陷入到局部最小值中。momentum的引入就是為了加快學(xué)習(xí)過(guò)程,特別是對(duì)于高曲率、小但一致的梯度,或者噪聲比較大的梯度能夠很好的加快學(xué)習(xí)過(guò)程。
我們引入速度變量v=v1,v2,?v=v_1,v_2,\cdotsv=v1?,v2?,?,其中每一個(gè)對(duì)應(yīng)wjw_jwj?變量。然后我們將上述公式中關(guān)于權(quán)值的梯度下降更新規(guī)則w←w′=w?η?Cw\gets w^\prime = w-\eta \nabla Cw←w′=w?η?C改成如下的公式。
{v←v′=μv?η?Cw←w′=w+v′\left\{ \begin{array}{l} v\gets v^\prime = \mu v-\eta \nabla C\\ w\gets w^\prime = w + v^\prime \end{array} \right. {v←v′=μv?η?Cw←w′=w+v′?
其中, μ\muμ是一個(gè)超參數(shù),其值越大,則之前的梯度對(duì)現(xiàn)在的方向影響越大。
最后,Nesterov momentum是對(duì)momentum的改進(jìn),可以理解為Nesterov動(dòng)量在標(biāo)準(zhǔn)動(dòng)量方法中添加了一個(gè)校正因子。與momentum的唯一區(qū)別就是計(jì)算梯度的不同,Nesterov先用當(dāng)前的速度v更新一遍參數(shù),再用更新的臨時(shí)參數(shù)計(jì)算梯度。即上述公式中的梯度計(jì)算先使用如下的公式。
{g^←+1m?θ∑iL(f(xi;θ+αv))v←v′=μv?ηg^w←w′=w+v′\left\{ \begin{array}{l} \hat{g} \gets + \frac{1}{m}\nabla_\theta \sum_i{L(f(x_i;\theta+\alpha v))}\\ v\gets v^\prime = \mu v - \eta \hat{g}\\ w\gets w^\prime = w + v^\prime \end{array} \right. ????g^?←+m1??θ?∑i?L(f(xi?;θ+αv))v←v′=μv?ηg^?w←w′=w+v′?
學(xué)習(xí)速率監(jiān)測(cè)器
學(xué)習(xí)速率通常會(huì)影響神經(jīng)網(wǎng)絡(luò)的訓(xùn)練的效果,當(dāng)評(píng)價(jià)指標(biāo)不再提升時(shí),我們應(yīng)該降低學(xué)習(xí)速率,因?yàn)榇藭r(shí),較慢的學(xué)習(xí)速率能找到更精準(zhǔn)的網(wǎng)絡(luò)。
我們使用Reduce Learning Rate on Plateau(RLRP)策略:當(dāng)評(píng)估標(biāo)準(zhǔn)停止提升時(shí),降低一定的學(xué)習(xí)速率。當(dāng)學(xué)習(xí)停止時(shí),模型總是會(huì)受益于降低 2-10 倍的學(xué)習(xí)速率。我們檢測(cè)某個(gè)數(shù)據(jù)并且當(dāng)這個(gè)數(shù)據(jù)在一定“有耐心”的訓(xùn)練輪之后還沒(méi)有進(jìn)步,那么學(xué)習(xí)速率就會(huì)被降低。
系統(tǒng)實(shí)現(xiàn)
實(shí)驗(yàn)環(huán)境
本地環(huán)境
對(duì)于網(wǎng)絡(luò)的構(gòu)建,我們使用本地環(huán)境先行驗(yàn)證。本地環(huán)境的版本參數(shù)如下表所示。
| Python | 3.7.2 |
| Tensorflow | 2.6.2 |
| Keras | 2.6.0 |
| OpenCV | 3.4.2 |
使用GPU訓(xùn)練
我們使用Kaggle提供的在線環(huán)境訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò),配置有GPU模塊。
然后將訓(xùn)練完的模型,再適配至本地環(huán)境,進(jìn)行真實(shí)的人臉表情識(shí)別預(yù)測(cè)。
數(shù)據(jù)集概覽
FER2013數(shù)據(jù)集共有35887個(gè)樣本,如下面的輸出所示。
data = pd.read_csv('../input/fer2013/fer2013.csv') # 查看數(shù)據(jù)集形狀 data.shapeOutput: (35887, 3)
我們使用的FER2013數(shù)據(jù)集,以CSV格式呈現(xiàn),如下圖所示。
其中,第0列是表情對(duì)應(yīng)的數(shù)字類(lèi)別,從0~1分別對(duì)應(yīng)著表情:Angry(生氣)、Disgust(厭惡)、Fear(害怕)、Happy(高興)、Sad(生氣)、Surprise(驚訝)、Neutral(中立)。
第二列是圖像的像素?cái)?shù)據(jù),以行向量的形式呈現(xiàn),使用空格分隔。像素值介于[0,255][0,255][0,255]之間。
第三列是該樣本的用途,有Training、PublicTest、PrivateTest。從輸出結(jié)果可知,訓(xùn)練數(shù)據(jù)有80%的占比,測(cè)試數(shù)據(jù)和驗(yàn)證數(shù)據(jù)各占10%。
#查看數(shù)據(jù)集的分類(lèi)情況 #80% 訓(xùn)練, 10% 驗(yàn)證 and 10% 測(cè)試 data.Usage.value_counts() Training 28709 PublicTest 3589 PrivateTest 3589 Name: Usage, dtype: int64樣本概覽
查看表情分類(lèi)數(shù)據(jù),如下圖所示。
#查看表情分類(lèi)數(shù)據(jù) emotion_map = {0: 'Angry', 1: 'Disgust', 2: 'Fear', 3: 'Happy', 4: 'Sad', 5: 'Surprise', 6: 'Neutral'} emotion_counts = data['emotion'].value_counts().sort_index().reset_index() emotion_counts.columns = ['emotion', 'number'] emotion_counts['emotion'] = emotion_counts['emotion'].map(emotion_map) emotion_counts# %% # 繪制類(lèi)別分布條形圖 %matplotlib inline %config InlineBackend.figure_format = 'svg' plt.figure(figsize=(6, 4)) sns.barplot(x=emotion_counts.emotion, y=emotion_counts.number) plt.title('Class distribution') plt.ylabel('Number', fontsize=12) plt.xlabel('Emotions', fontsize=12) plt.show()
從上面的圖我們可以知道,Disgust類(lèi)的樣本數(shù)量比較少,這是一個(gè)分布不太均勻的數(shù)據(jù)集。
上圖所示的是一些樣本的示例圖片,其生成代碼如下:
def row2image_label(row):pixels, emotion = row['pixels'], emotion_map[row['emotion']]img = np.array(pixels.split())img = img.reshape(48, 48)image = np.zeros((48, 48, 3))image[:, :, 0] = imgimage[:, :, 1] = imgimage[:, :, 2] = imgreturn image.astype(np.uint8), emotion%matplotlib inline %config InlineBackend.figure_format = 'svg' plt.figure(0, figsize=(16, 10)) for i in range(1, 8):face = data[data['emotion'] == i - 1].iloc[0]img, label = row2image_label(face)plt.subplot(2, 4, i)plt.imshow(img)plt.title(label)plt.show()數(shù)據(jù)預(yù)處理
數(shù)據(jù)預(yù)處理部分,主要完成了下面四個(gè)事情:
經(jīng)過(guò)處理后的各個(gè)子數(shù)據(jù)集的樣本分布情況如下圖所示。
def CRNO(df, dataName):df['pixels'] = df['pixels'].apply(lambda pixel_sequence: [int(pixel) for pixel in pixel_sequence.split()])data_X = np.array(df['pixels'].tolist(), dtype='float32').reshape(-1, width, height, 1) / 255.0data_Y = to_categorical(df['emotion'], num_classes)print(dataName, f"_X shape: {data_X.shape}, ", dataName, f"_Y shape: {data_Y.shape}")return data_X, data_Ytrain_X, train_Y = CRNO(data_train, "train") #training data val_X, val_Y = CRNO(data_val, "val") #validation data test_X, test_Y = CRNO(data_test, "test") #test data各子數(shù)據(jù)集的輸入和預(yù)期輸出的形狀如下:
train _X shape: (28709, 48, 48, 1), train _Y shape: (28709, 7) val _X shape: (3589, 48, 48, 1), val _Y shape: (3589, 7) test _X shape: (3589, 48, 48, 1), test _Y shape: (3589, 7)構(gòu)建我們的神經(jīng)網(wǎng)絡(luò)
我們的神經(jīng)網(wǎng)絡(luò)的整體結(jié)構(gòu)已在前文中給出, 每個(gè)層的具體參數(shù)如下表所示。
具體代碼如下所示,注意到代碼中已經(jīng)設(shè)置了SGD的具體優(yōu)化參數(shù)。
# ## 構(gòu)建我們的CNN # # ### CNN 結(jié)構(gòu): # Conv Sages 1 --> Conv Stages 2 --> Conv Stages 3 --> Conv Stages 4 --> Flatten --> Full Connection --> Softmax Output Layer # # ### Conv Stages # Conv Block --> Max Pooling # # ### Conv Block # Conv --> BN --> ReLU# %% model = Sequential()# ---------- Convolutional Stages 1 ---------- # ***** Conv Block a ***** model.add(Conv2D(64, kernel_size=(3, 3), input_shape=(width, height, 1),data_format='channels_last', padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # ***** Conv Block b ***** model.add(Conv2D(64, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # max pooling model.add(MaxPooling2D(pool_size=(2, 2)))# ---------- Convolutional Stages 2 ---------- # ***** Conv Block a ***** model.add(Conv2D(128, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # ***** Conv Block b ***** model.add(Conv2D(128, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # max pooling model.add(MaxPooling2D(pool_size=(2, 2)))# ---------- Convolutional Stages 3 ---------- # ***** Conv Block a ***** model.add(Conv2D(256, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # ***** Conv Block b ***** model.add(Conv2D(256, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # max pooling model.add(MaxPooling2D(pool_size=(2, 2)))# ---------- Convolutional Stages 4 ---------- # ***** Conv Block a ***** model.add(Conv2D(512, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # ***** Conv Block b ***** model.add(Conv2D(512, kernel_size=(3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # max pooling model.add(MaxPooling2D(pool_size=(2, 2)))# Flatten model.add(Flatten())# Full connection model.add(Dense(4096, activation='relu', kernel_regularizer=l2())) model.add(Dropout(rate_drop)) model.add(Dense(4096, activation='relu', kernel_regularizer=l2())) model.add(Dropout(rate_drop))#output layer model.add(Dense(num_classes, activation='softmax', kernel_regularizer=l2()))model.compile(loss=['categorical_crossentropy'],optimizer=SGD(momentum=0.9, nesterov=True ,decay=1e-4),metrics=['accuracy'])model.summary()數(shù)據(jù)增強(qiáng)
根據(jù)前文,使用Keras框架自帶的ImageDataGenerator方法,編寫(xiě)如下代碼。
# 數(shù)據(jù)增強(qiáng) data_generator = ImageDataGenerator(zoom_range=0.2,width_shift_range=0.2,height_shift_range=0.2,rotation_range=10,featurewise_std_normalization=False,horizontal_flip=True)系統(tǒng)測(cè)試
訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)
設(shè)置訓(xùn)練參數(shù)如下:
#初始化參數(shù) num_classes = 7 width, height = 48, 48 num_epochs = 300 batch_size = 128 num_features = 64 rate_drop = 0.1進(jìn)行訓(xùn)練:
es = EarlyStopping(monitor='val_loss', patience=10, mode='min', restore_best_weights=True)reduce_lr = ReduceLROnPlateau(monitor='val_accuracy', factor=0.75, patience=5, verbose=1)history = model.fit(data_generator.flow(train_X, train_Y, batch_size),# steps_per_epoch=len(train_X) / batch_size,batch_size=batch_size,epochs=num_epochs,verbose=2,callbacks=[es, reduce_lr],validation_data=(val_X, val_Y))注意到,在上述代碼中,使用了兩個(gè)策略監(jiān)測(cè)我們的網(wǎng)絡(luò):
部分訓(xùn)練輸出信息如下:
2021-12-26 05:35:09.313687: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2) Epoch 1/300 2021-12-26 05:35:10.991111: I tensorflow/stream_executor/cuda/cuda_dnn.cc:369] Loaded cuDNN version 8005 225/225 - 20s - loss: 2.0907 - accuracy: 0.2503 - val_loss: 1.9219 - val_accuracy: 0.2494 Epoch 2/300 225/225 - 12s - loss: 1.8205 - accuracy: 0.2714 - val_loss: 1.8866 - val_accuracy: 0.2611 Epoch 3/300 225/225 - 12s - loss: 1.6999 - accuracy: 0.3240 - val_loss: 1.8933 - val_accuracy: 0.3090……Epoch 00020: ReduceLROnPlateau reducing learning rate to 0.007499999832361937. ……Epoch 00037: ReduceLROnPlateau reducing learning rate to 0.005624999874271452. ……Epoch 00048: ReduceLROnPlateau reducing learning rate to 0.004218749818392098.Epoch 49/300 225/225 - 13s - loss: 0.7174 - accuracy: 0.7382 - val_loss: 1.0229 - val_accuracy: 0.6559我們觀察到,訓(xùn)練過(guò)程中存在3次學(xué)習(xí)速率調(diào)整,最終在第49次迭代時(shí)提前終止訓(xùn)練。
可視化訓(xùn)練效果
代碼如下:
%matplotlib inline %config InlineBackend.figure_format = 'svg' fig, axes = plt.subplots(1, 2, figsize=(18, 6)) # 繪制訓(xùn)練和驗(yàn)證精度曲線 axes[0].plot(history.history['accuracy']) axes[0].plot(history.history['val_accuracy']) axes[0].set_title('Model accuracy') axes[0].set_ylabel('Accuracy') axes[0].set_xlabel('Epoch') axes[0].legend(['Train', 'Validation'], loc='upper left')# 繪制訓(xùn)練和驗(yàn)證損失曲線 axes[1].plot(history.history['loss']) axes[1].plot(history.history['val_loss']) axes[1].set_title('Model loss') axes[1].set_ylabel('Loss') axes[1].set_xlabel('Epoch') axes[1].legend(['Train', 'Validation'], loc='upper left') plt.show()通過(guò)觀察曲線,我們可以得知神經(jīng)網(wǎng)絡(luò)后期存在輕微的過(guò)擬合現(xiàn)象。
評(píng)估測(cè)試效果
我們對(duì)測(cè)試數(shù)據(jù)集,進(jìn)行評(píng)估分析,代碼如下:
test_true = np.argmax(test_Y, axis=1) test_pred = np.argmax(model.predict(test_X), axis=1) print("CNN Model Accuracy on test set: {:.4f}".format(accuracy_score(test_true, test_pred)))輸出信息如下:
CNN Model Accuracy on test set: 0.6704最終,我們的VGGNet網(wǎng)絡(luò),對(duì)各個(gè)數(shù)據(jù)集的準(zhǔn)確率,如下表所示。
| Train | 73.28% |
| Validation | 65.59% |
| Test | 67.04% |
使用混淆矩陣進(jìn)行分析
繪制混淆矩陣,以分析表情之間是否會(huì)相互混淆,代碼如下:
fusion_matrix(y_true, y_pred, classes,normalize=False,title=None,cmap=plt.cm.Blues):"""此函數(shù)打印和繪制混淆矩陣可以通過(guò)設(shè)置“normalize=True”來(lái)應(yīng)用規(guī)范化。"""if not title:if normalize:title = 'Normalized confusion matrix'else:title = 'Confusion matrix, without normalization'# 計(jì)算混淆矩陣cm = confusion_matrix(y_true, y_pred)# 僅使用數(shù)據(jù)中顯示的標(biāo)簽classes = classesif normalize:cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]#print("Normalized confusion matrix")#else:#print('Confusion matrix, without normalization')#print(cm)fig, ax = plt.subplots(figsize=(12, 6))im = ax.imshow(cm, interpolation='nearest', cmap=cmap)ax.figure.colorbar(im, ax=ax)# 顯示所有的標(biāo)記...ax.set(xticks=np.arange(cm.shape[1]),yticks=np.arange(cm.shape[0]),# ... 用相應(yīng)的列表?xiàng)l目標(biāo)記它們xticklabels=classes, yticklabels=classes,title=title,ylabel='True label',xlabel='Predicted label')# 旋轉(zhuǎn)x軸標(biāo)簽并設(shè)置其對(duì)齊方式。plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor")# 在數(shù)據(jù)維度上循環(huán)并創(chuàng)建文本批注fmt = '.2f' if normalize else 'd'thresh = cm.max() / 2.for i in range(cm.shape[0]):for j in range(cm.shape[1]):ax.text(j, i, format(cm[i, j], fmt),ha="center", va="center",color="white" if cm[i, j] > thresh else "black")fig.tight_layout()return ax# %% # 繪制歸一化混淆矩陣 %matplotlib inline %config InlineBackend.figure_format = 'svg' plot_confusion_matrix(test_true, test_pred, classes=emotion_labels, normalize=True, title='Normalized confusion matrix') plt.show()輸出的混淆矩陣如下圖所示。通過(guò)分析混淆矩陣,可知:Disgust比較容易和其他表情混淆,這是由于Disgust的樣本數(shù)本身就很少。
實(shí)時(shí)人臉表情識(shí)別
將已經(jīng)訓(xùn)練好的模型存入本地,使用攝像頭實(shí)時(shí)捕捉人臉,并識(shí)別出相應(yīng)的表情。我們的思路是,從捕獲的圖像中,先使用人臉檢測(cè)器,檢測(cè)出人臉區(qū)域,然后將該區(qū)域?qū)嵤┗叶然?#xff0c;并將圖片大小縮放至48×4848\times 4848×48,最后送入我們的模型,進(jìn)行預(yù)測(cè),得到相應(yīng)的表情輸出。相應(yīng)的代碼如下:
import cv2 as cv import numpy as np from keras import modelsmodel = models.load_model('./FER_Model.h5')emotion_map = {0: 'Angry', 1: 'Disgust', 2: 'Fear', 3: 'Happy', 4: 'Sad', 5: 'Surprise', 6: 'Neutral'}cap = cv.VideoCapture(0) if not cap.isOpened():print("Can not open camera!")exit()while True:# 逐幀捕獲ret, frame = cap.read()# 轉(zhuǎn)換成灰度圖像gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)classifier = cv.CascadeClassifier("./haarcascade_frontalface_default.xml")faceRects = classifier.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))color = (0, 0, 255)if len(faceRects): # 大于0則檢測(cè)到人臉for faceRect in faceRects: # 單獨(dú)框出每一張人臉x, y, w, h = faceRect# 框出人臉cv.rectangle(frame, (x, y), (x + h, y + w), color, 2)# 獲取人臉源src = gray[y:y + w, x:x + h]# 縮放至48*48img = cv.resize(src, (48, 48))# 歸一化img = img / 255.# 擴(kuò)展維度x = np.expand_dims(img, axis=0)x = np.array(x, dtype='float32').reshape(-1, 48, 48, 1)# 預(yù)測(cè)輸出y = model.predict(x)output_class = np.argmax(y[0])cv.putText(frame, emotion_map[output_class], (200, 100), cv.FONT_HERSHEY_COMPLEX,2.0, (0, 0, 250), 5)cv.imshow("frame", frame)if cv.waitKey(1) == ord('q'):break cap.release() cv.destroyAllWindows()上述代碼中,haarcascade_frontalface_default.xml是由OpenCV提供的人臉檢測(cè)器。
識(shí)別效果樣例如下圖所示。
系統(tǒng)總結(jié)
本系統(tǒng)實(shí)現(xiàn)了一種基于卷積神經(jīng)網(wǎng)絡(luò)的人臉表情識(shí)別模型,采用VGGNet神經(jīng)網(wǎng)絡(luò),使用基于Nesterov momentum的SGD方法訓(xùn)練神經(jīng)網(wǎng)絡(luò),并使用學(xué)習(xí)速率監(jiān)測(cè)器減輕過(guò)擬合現(xiàn)象。最終,我們使用訓(xùn)練好的網(wǎng)絡(luò),構(gòu)建出了可以實(shí)時(shí)識(shí)別人臉表情的一個(gè)系統(tǒng)。
本系統(tǒng)的識(shí)別精準(zhǔn)度能夠達(dá)到67%左右,尚有很大的提升空間,可改進(jìn)的方向有:
通過(guò)本次項(xiàng)目實(shí)踐,更好地掌握了有關(guān)機(jī)器視覺(jué)方面的理論知識(shí),并與卷積神經(jīng)網(wǎng)絡(luò)結(jié)合,同時(shí),增強(qiáng)了我們的實(shí)戰(zhàn)能力。
總結(jié)
以上是生活随笔為你收集整理的利用CNN进行面部表情识别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python中的函数概述
- 下一篇: 《SAP从入门到精通》——1.3 SAP