tensorflow下基於DNN實現實時分辨人臉微表情


參加學校的國創比賽的時候,我們小組的項目有一部分內容需要用到利用攝像頭實現實時檢測人臉的表情,因為最近都在看深度學習方面的相關知識,所以就自己動手實現了一下這個小Demo.參考網上的資料,發現大部分是使用CNN和DNN進行學習,經過本人親自實踐,我發現DNN的識別效果更佳~(樓主接下來就要講的是基於DNN的模型,要是你對CNN的模型感興趣,歡迎私戳樓主~)

所需環境:opencv + tensorflow1.8 + pycharm

代碼以及模型的下載地址:https://github.com/tgpcai/Microexpression_recognition(如果喜歡請幫樓主點個start~)

最后實現的結果圖:

 

目錄

1.數據集簡介

2.代碼實現

(0)實現Demo的代碼組織結構

(1)數據預處理

(2)訓練模型

(3)調用模型實現人臉微表情識別

3.個人遇坑以及總結


 

1.數據集簡介

人臉表情識別程序所采用的數據集為FER2013(facial-expression-recognition 2013)以及中科院的微表情數據,該數據集需要在kaggle網

站上進行下載,下載地址為:https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data

下載的數據是壓縮文件,對其進行手動解壓,解壓后文件格式是csv,文件名為fer2013.csv,對這個文件進行提取可以得到三個文件,具體的提取過程會在后續進行介紹:

 

其中train文件的emotion一共有7種,csv文件中每個數字對應一種表情,7種表情包括:0 - 'angry', 1 - 'disgusted',2 - 'fearful', 3 - 'happy', 4 - 'sad', 5 - 'surprised', 6 - 'neutral'(這也是咱們能識別的表情)

打開train.csv文件每一行除了emotion的標記之外,還有一幅影像,只不過在csv文件里,影像用灰度值表示了,csv文件打開之后,里面的內容如下:

 

不過可能由於FER2013數據集的質量不太好,大部分模型的驗證精度只有60%(如googleNet和AlexNet都只有63%)左右,好一點的可以到70%。我自己訓練的模型驗證經度只有55-56左右!

網上很多參考資料都把灰度值表示的影像變成了.jpg能看的圖片,但實際上並不需要這樣做,因為你把這灰度值的影像變為.jpg,后面還是需要經過一系列步驟在轉換成灰度值,不過我也會貼出如何把這灰度值影響變為.jpg結尾的圖片!

中科院的數據集如下:

同樣對其進行灰度化、圖像大小的處理,之后方可當成輸入數據進行模型的訓練。

 2.代碼實現

 (0)實現Demo的代碼組織結構

demo.py 調用系統攝像頭完成實時識別人臉微表情

main.py 包含訓練模型、測試模型的接口

model.py DNN算法的實現

utils.py 對數據集合的預處理

 (1)數據預處理

  有兩種方式,第一種方式只是為了看看數據集的圖片,而不是只能看到灰度值,而真正參與模型的訓練的數據處理方式是第二種!

  1)把這灰度值的影像變為.jpg

 1 import csv
 2 import os
 3 
 4 #數據集路徑
 5 database_path = 'C:/Users/tgp/Desktop/Machine Learning/人臉識別/fer2013'
 6 
 7 #將路徑組合后返回
 8 csv_file = os.path.join(database_path, 'fer2013.csv')
 9 train_csv = os.path.join(database_path, 'train.csv')
10 val_csv = os.path.join(database_path, 'val.csv')
11 test_csv = os.path.join(database_path, 'test.csv')
12 
13 with open(csv_file) as f:
14     #使用csv中的reader()打開.csv文件
15     csvr = csv.reader(f)
16     
17     #將迭代器指向文件的第二行,因為第一行為標簽
18     header = next(csvr)
19     rows = [row for row in csvr]
20     
21     #按最后一列的標簽將數據集進行分割
22     trn = [row[:-1] for row in rows if row[-1] == 'Training']  
23     csv.writer(open(train_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + trn)
24     print(len(trn))
25  
26     val = [row[:-1] for row in rows if row[-1] == 'PublicTest']
27     csv.writer(open(val_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + val)
28     print(len(val))
29  
30     tst = [row[:-1] for row in rows if row[-1] == 'PrivateTest']
31     csv.writer(open(test_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + tst)
32     print(len(tst))
 1 import csv
 2 import os
 3 from PIL import Image
 4 import numpy as np
 5 
 6 database_path = 'C:/Users/tgp/Desktop/Machine Learning/人臉識別/fer2013'
 7 
 8 train_csv = os.path.join(database_path, 'train.csv')
 9 val_csv = os.path.join(database_path, 'val.csv')
10 test_csv = os.path.join(database_path, 'test.csv')
11  
12 train_set = os.path.join(database_path, 'train')
13 val_set = os.path.join(database_path, 'val')
14 test_set = os.path.join(database_path, 'test')
15  
16 for save_path, csv_file in [(train_set, train_csv), (val_set, val_csv), (test_set, test_csv)]:
17     if not os.path.exists(save_path):
18         os.makedirs(save_path)
19  
20     num = 1
21     with open(csv_file) as f:
22         csvr = csv.reader(f)
23         header = next(csvr)
24         #使用enumerate遍歷csvr中的標簽(label)和特征值(pixel)
25         for i, (label, pixel) in enumerate(csvr):
26             #將特征值的數組轉化為48*48的矩陣
27             pixel = np.asarray([float(p) for p in pixel.split()]).reshape(48, 48)
28             subfolder = os.path.join(save_path, label)
29             if not os.path.exists(subfolder):
30                 os.makedirs(subfolder)
31             #將該矩陣轉化為RGB圖像,再通過convert轉化為8位灰度圖像,L指灰度圖模式,L=R*299/1000+G*587/1000+B*114/1000
32             im = Image.fromarray(pixel).convert('L')
33             image_name = os.path.join(subfolder, '{:05d}.jpg'.format(i))
34             print(image_name)
35             im.save(image_name)

執行完上面的代碼,你就可以在相應的路徑看到如下圖:

這樣就完成了從灰度圖到可視化的轉變。

  2)簡便的數據預處理(訓練模型時采用的辦法)

  1 import collections
  2 import numpy as np
  3 import pandas as pd
  4 from tensorflow.python.framework import dtypes, random_seed
  5 
  6 
  7 def load_data(data_file):
  8     data = pd.read_csv(data_file)
  9     pixels = data['pixels'].tolist()
 10     width = 48
 11     height = 48
 12     faces = []
 13     for pixel_sequence in pixels:
 14         # 從csv中獲取人臉的數據
 15         face = [int(pixel) for pixel in pixel_sequence.split(' ')]
 16         # 把臉的數據變為48*48像素,利用plt.imshow即可打印出圖片
 17         face = np.asarray(face).reshape(width, height)
 18         faces.append(face)
 19     # 把faces從列表變為三維矩陣。(35887,)----->(35887,48,48)
 20     faces = np.asarray(faces)
 21     # 添加維度,將faces從(35887,48,48)------>(35887,48,48,1)
 22     faces = np.expand_dims(faces, -1)
 23     # one-hot編碼,把屬於該類表情置1,其余為0,並轉換為矩陣
 24     emotions = pd.get_dummies(data['emotion']).as_matrix()
 25     return faces, emotions
 26 
 27 
 28 class DataSet(object):
 29     def __init__(self, images, labels, reshape=True, dtype=dtypes.float32, seed=None):
 30         seed1, seed2 = random_seed.get_seed(seed)
 31         np.random.seed(seed1 if seed is None else seed2)
 32         if reshape:
 33             # 將images(35887,48,48,1)變為(35887,2304)
 34             assert images.shape[3] == 1
 35             images = images.reshape(images.shape[0],images.shape[1]*images.shape[2])
 36 
 37         # 類型轉換,並進行灰度處理
 38         if dtype == dtypes.float32:
 39             images = images.astype(np.float32)
 40             images = np.multiply(images, 1.0 / 255.0)
 41         # 設置私有屬性
 42         self._num_examples = images.shape[0]
 43         self._images = images
 44         self._labels = labels
 45         self._epochs_completed = 0
 46         self._index_in_epoch = 0
 47 
 48     @property
 49     def images(self):
 50         return self._images
 51 
 52     @property
 53     def labels(self):
 54         return self._labels
 55 
 56     @property
 57     def num_examples(self):
 58         return self.num_examples
 59 
 60     @property
 61     def epochs_completed(self):
 62         self._epochs_completed
 63 
 64     # 批量獲取訓練數據
 65     def next_batch(self, batch_size,shuffle=True):
 66         start = self._index_in_epoch
 67         if self._epochs_completed == 0 and start == 0 and shuffle:
 68             # 打亂順序
 69             perm0 = np.arange(self._num_examples)
 70             np.random.shuffle(perm0)
 71             self._images = self._images[perm0]
 72             self._labels = self._labels[perm0]
 73 
 74         if start + batch_size > self._num_examples:
 75             self._epochs_completed += 1
 76             rest_num_examples = self._num_examples - start
 77             images_rest_part = self._images[start:self._num_examples]
 78             labels_rest_part = self._labels[start:self._num_examples]
 79             # 當剩余的數據不夠一次batch_size,就在之前的數據中隨機選取並進行組合
 80             if shuffle:
 81                 perm = np.arange(self._num_examples)
 82                 np.random.shuffle(perm)
 83                 self._images = self._images[perm]
 84                 self._labels = self._labels[perm]
 85             start = 0
 86             self._index_in_epoch = batch_size - rest_num_examples
 87             end = self._index_in_epoch
 88             images_new_part = self._images[start:end]
 89             labels_new_part = self._labels[start:end]
 90             return np.concatenate((images_rest_part, images_new_part), axis=0), np.concatenate(
 91                 (labels_rest_part, labels_new_part), axis=0)
 92         else:
 93             self._index_in_epoch += batch_size
 94             end = self._index_in_epoch
 95             return self._images[start:end], self._labels[start:end]
 96 
 97 
 98 def input_data(train_dir, dtype = dtypes.float32, reshape = True, seed=None):
 99     training_size = 28709
100     validation_size = 3589
101     test_size = 3589
102 
103     train_faces, train_emotions = load_data(train_dir)
104     print("Data load success!")
105 
106     # 驗證數據
107     validation_faces = train_faces[training_size: training_size + validation_size]
108     validation_emotions = train_emotions[training_size: training_size + validation_size]
109 
110     # 測試數據
111     test_faces = train_faces[training_size + validation_size:]
112     test_emotions = train_emotions[training_size + validation_size:]
113 
114     # 訓練數據
115     train_faces = train_faces[: training_size]
116     train_emotions = train_emotions[: training_size]
117 
118     Datasets = collections.namedtuple('Datasets', ['train', 'validation', 'test'])
119     train = DataSet(train_faces, train_emotions, reshape=reshape,)
120     validation = DataSet(validation_faces, validation_emotions, dtype=dtype, reshape=reshape, seed=seed)
121     test = DataSet(test_faces, test_emotions, dtype=dtype, reshape=reshape, seed=seed)
122     return Datasets(train=train, validation=validation, test=test)

到此,我們就完成了對fer2013數據的處理。

 

 (2)訓練模型

 采用的是DNN模型,個人感覺DNN的大致過程和CNN較為相似,樓主有一篇博客就是利用CNN實現手寫數字的識別,感興趣或者對於DNN實現算法不是很理解的,可以跳過去看看。

 1 import os
 2 import cv2
 3 import tensorflow as tf
 4 from utils import *
 5 
 6 EMOTIONS = ['angry', 'disgusted', 'fearful', 'happy', 'sad', 'surprised', 'neutral']
 7 
 8 
 9 def deepnn(x):
10     x_image = tf.reshape(x, [-1, 48, 48, 1])
11     # conv1
12     W_conv1 = weight_variables([5, 5, 1, 64])
13     b_conv1 = bias_variable([64])
14     h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
15     # pool1
16     h_pool1 = maxpool(h_conv1)
17     # norm1
18     norm1 = tf.nn.lrn(h_pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
19 
20     # conv2
21     W_conv2 = weight_variables([3, 3, 64, 64])
22     b_conv2 = bias_variable([64])
23     h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
24     norm2 = tf.nn.lrn(h_conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
25     h_pool2 = maxpool(norm2)
26 
27     # Fully connected layer
28     W_fc1 = weight_variables([12 * 12 * 64, 384])
29     b_fc1 = bias_variable([384])
30     h_conv3_flat = tf.reshape(h_pool2, [-1, 12 * 12 * 64])
31     h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, W_fc1) + b_fc1)
32 
33     # Fully connected layer
34     W_fc2 = weight_variables([384, 192])
35     b_fc2 = bias_variable([192])
36     h_fc2 = tf.matmul(h_fc1, W_fc2) + b_fc2
37 
38     # linear
39     W_fc3 = weight_variables([192, 7])
40     b_fc3 = bias_variable([7])
41     y_conv = tf.add(tf.matmul(h_fc2, W_fc3), b_fc3)
42 
43     return y_conv
44 
45 
46 def conv2d(x, W):
47     return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
48 
49 
50 def maxpool(x):
51     return tf.nn.max_pool(x, ksize=[1, 3, 3, 1],
52                         strides=[1, 2, 2, 1], padding='SAME')
53 
54 
55 def weight_variables(shape):
56     initial = tf.truncated_normal(shape, stddev=0.1)
57     return tf.Variable(initial)
58 
59 
60 def bias_variable(shape):
61     initial = tf.constant(0.1, shape=shape)
62     return tf.Variable(initial)
63 
64 
65 def train_model(train_data):
66     fer2013 = input_data(train_data)
67     max_train_steps = 30001
68 
69     x = tf.placeholder(tf.float32, [None, 2304])
70     y_ = tf.placeholder(tf.float32, [None, 7])
71 
72     y_conv = deepnn(x)
73 
74     cross_entropy = tf.reduce_mean(
75         tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
76     train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
77     correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
78     accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
79 
80     with tf.Session() as sess:
81         saver = tf.train.Saver()
82         sess.run(tf.global_variables_initializer())
83 
84         for step in range(max_train_steps):
85             batch = fer2013.train.next_batch(25)
86             if step % 100 == 0:
87                 train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1]})
88                 print('step %d, training accuracy %g' % (step, train_accuracy))
89             if step + 1 == max_train_steps:
90                 saver.save(sess, './models/emotion_model', global_step=step + 1)
91             train_step.run(feed_dict={x: batch[0], y_: batch[1]})
92 
93 
94 def image_to_tensor(image):
95     tensor = np.asarray(image).reshape(-1, 2304) * 1 / 255.0
96     return tensor

訓練完模型后在你相應的文件夾里面會出現如下圖所示:

這就是訓練模型保存的參數,樓主最后也會貼出自己訓練的模型,方便你們下載和使用。

(3)調用模型實現人臉微表情識別

采用的方法是調用攝像頭實時識別人臉微表情,具體的過程為:調用opencv自帶的人臉識別器,調用系統攝像頭拍攝出人臉圖片,對人臉圖片進行預處理,將處理完成的圖片傳入模型,最后將模型分析的結果反饋至運行的窗口

如下圖所示:這些都是opencv自帶的有關臉部識別的識別器。

一般采用的是這幾個:

其中個人認為_alt2的識別器效果最好。

具體如何調用系統攝像頭以及實時別人微表情的代碼如下:

  1 import cv2
  2 import numpy as np
  3 import sys
  4 import tensorflow as tf
  5 import PIL.Image as Image
  6 import matplotlib.pyplot as plt
  7 from model import *
  8 
  9 # 加載opencv自帶的人臉識別器
 10 CASC_PATH = 'D:/Anaconda3/Lib/site-packages/cv2/data/haarcascade_frontalface_alt2.xml'
 11 cascade_classifier = cv2.CascadeClassifier(CASC_PATH)
 12 # 人臉七種微表情
 13 EMOTIONS = ['angry', 'disgusted', 'fearful', 'happy', 'sad', 'surprised', 'neutral']
 14 
 15 
 16 def format_image(image):
 17     # image如果為彩色圖:image.shape[0][1][2](水平、垂直像素、通道數)
 18     if len(image.shape) > 2 and image.shape[2] == 3:
 19         # 將圖片變為灰度圖
 20         image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
 21         # 它可以檢測出圖片中所有的人臉,並將人臉用vector保存各個人臉的坐標、大小(用矩形表示)
 22         # 調整scaleFactor參數的大小,可以增加識別的靈敏度,推薦1.1
 23         faces = cascade_classifier.detectMultiScale(image, scaleFactor=1.1, minNeighbors=5)
 24     # 如果圖片中沒有檢測到人臉,則返回None
 25     if not len(faces) > 0:
 26         return None, None
 27     # max_are_face包含了人臉的坐標,大小
 28     max_are_face = faces[0]
 29     # 在所有人臉中選一張最大的臉
 30     for face in faces:
 31         if face[2] * face[3] > max_are_face[2] * max_are_face[3]:
 32             max_are_face = face
 33 
 34     # 這兩步可有可無
 35     face_coor = max_are_face
 36     image = image[face_coor[1]:(face_coor[1] + face_coor[2]), face_coor[0]:(face_coor[0] + face_coor[3])]
 37     # 調整圖片大小,變為48*48
 38     try:
 39         image = cv2.resize(image, (48, 48), interpolation=cv2.INTER_CUBIC)
 40     except Exception:
 41         print("problem during resize")
 42         return None, None
 43 
 44     return image, face_coor
 45 
 46 
 47 def demo(modelPath, showBox=True):
 48     # 調用模型分析人臉微表情
 49 #    tf.reset_default_graph()
 50     face_x = tf.placeholder(tf.float32, [None, 2304])
 51     y_conv = deepnn(face_x)
 52     probs = tf.nn.softmax(y_conv)
 53 
 54     # 加載模型
 55     saver = tf.train.Saver()
 56     ckpt = tf.train.get_checkpoint_state(modelPath)
 57     sess = tf.Session()
 58     if ckpt and ckpt.model_checkpoint_path:
 59         saver.restore(sess, ckpt.model_checkpoint_path)
 60         print("Restore model sucsses!!\nNOTE: Press 'a' on keyboard to capture face.")
 61 
 62     # feelings_facesy用來存儲emojis表情
 63     feelings_faces = []
 64     for index, emotion in enumerate(EMOTIONS):
 65         # imread函數(文件路徑,讀取方式)
 66         # cv2.IMREAD_COLOR:讀入一副彩色圖片;(1)返回三維矩陣,且為[120,120,3]
 67         # cv2.IMREAD_GRAYSCALE:以灰度模式讀入圖片;(0)返回二維矩陣,且為[120,120]
 68         # cv2.IMREAD_UNCHANGED:讀入一幅圖片,並包括其alpha通道(-1)返回三維矩陣,且為[120,120,4]
 69         feelings_faces.append(cv2.imread('D:/Dict/Facial-Expression-Recognition-master/data/emojis/' + emotion + '.png', 1))
 70 
 71     # 獲取筆記本的攝像頭,
 72     video_captor = cv2.VideoCapture(0)
 73 
 74     emoji_face = []
 75     result = None
 76     while True:
 77         # 獲取攝像頭的每幀圖片,若獲得,則ret的值為True,frame就是每一幀的圖像,是個三維矩陣
 78         ret, frame = video_captor.read()
 79 
 80         detected_face, face_coor = format_image(frame)
 81         if showBox:
 82             if face_coor is not None:
 83                 # 獲取人臉的坐標,並用矩形框出
 84                 [x, y, w, h] = face_coor
 85                 cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 0), 2)
 86 
 87         # 每隔10ms刷新一次,並且等當鍵盤輸入a的時候,截取圖像,因為是64位系統所以必須要0xFF == ord('a')
 88         if cv2.waitKey(1) & 0xFF == ord('a'):
 89             if detected_face is not None:
 90                 cv2.imwrite('a.jpg', detected_face)
 91                 print(detected_face)
 92                 print("獲取成功")
 93                 # 將圖片變為tensorflow可以接受的格式
 94                 tensor = image_to_tensor(detected_face)
 95                 result = sess.run(probs, feed_dict={face_x: tensor})
 96                 print(result)
 97 
 98         if result is not None:
 99             for index, emotion in enumerate(EMOTIONS):
100                 # 將七種微表情的文字添加到圖片中
101                 cv2.putText(frame,emotion,(10,index*20 + 20),cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 0), 1)
102                 # 將七種微表情的概率用矩形表現出來
103                 cv2.rectangle(frame,(130, index*20 + 10),(130+int(result[0][index]*100), (index + 1) * 20 + 4), (255, 0, 0), -1)
104                 # 獲取人臉微表情相應的emojis表情
105                 emoji_face = feelings_faces[np.argmax(result[0])]
106 
107             # 將emojis表情添加到圖片中的指定位置 方法1:
108             frame[200:320, 10:130, :] = emoji_face[:, :, :]
109             cv2.imwrite('b.jpg', frame)
110             # 將emojis表情添加到圖片中的指定位置 方法2:
111             # for c in range(0, 1):
112             #     frame[200:320, 10:130, c] = emoji_face[:, :, c] * (emoji_face[:, :, 3] / 255.0) + frame[200:320, 10:130, c] * (1.0 - emoji_face[:, :, 3] / 255.0)
113 
114         cv2.imshow('face', frame)
115         if cv2.waitKey(10) & 0xFF == ord('q'):
116             break
117     # 釋放系統攝像頭,關閉窗口
118     video_captor.release()
119     cv2.destroyAllWindows()

到此整個Demo就實現完成了。

 

3.個人遇坑以及總結

(1)數據預處理過程,大多都需要用到pandas和numpy這兩個庫,所以必須能夠較為熟練的掌握這兩個庫的使用

(2)網上的考察資料中對於調用 cascade_classifier.detectMultiScale這個函數時,對其一個參數scaleFactor設置為1.3,這直接導致對於人臉的識別不夠敏感,上網參閱相關資料后,得知這個值一般設置為1.1較為合適,且對於人臉的識別也比較敏感

(3)因為對於opencv這個庫使用不夠熟練,一開始一直碰壁,所以你要是和我一樣,建議先去這個網站了解一下OPENCV的大致使用方法:https://docs.opencv.org/trunk/d6/d00/tutorial_py_root.html

4.該模型的不足以及日后的發展

(1)目前划分的微表情類別較少,難以覆蓋人類多種交織的表情。

(2)對於某人表情識別的准備率還不夠高,例如厭惡。

(3)模型未能處理多幀連續表情的變化,這將限制我們對少數微表情的識別的准確率。

5.完善算法以及平台的應用

(1)采用LSTM(長短時記憶算法),新增加一個時間維度用於記錄多幀微表情的變化,然后在結果DNN模型完善算法。

(2)可以將此項目應用於檢測疲勞駕駛以及刑偵等領域

 

 

 

 

參考資料:https://docs.opencv.org/trunk/d6/d00/tutorial_py_root.html

     https://blog.csdn.net/labPqsdr/article/details/80704969

 


以上就是本次Demo的完成過程以及體會,歡迎交流

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM