深度学习(二)——从零自己制作数据集到利用deepNN实现夸张人脸表情的实时监测(tensorflow实现)
一、背景介紹
這篇文章主要參考我的上一篇文章:深度學習(一)——deepNN模型實現攝像頭實時識別人臉表情(C++和python3.6混合編程)。由于上一篇文章的模型所采用的數據集為fer2013,前面也介紹過這個基于這個數據集的模型識別人臉表情的準確率大概在70%左右,而fer2013數據集僅僅包含了7類常見的表情,無法對于更多樣的表情進行識別。因此本文針對這個問題,自己采集人臉表情數據,采集人臉數據的方法可以參考我的文章:從零開始制作人臉表情的數據集,采集好數據集之后進行模型訓練,實現夸張人臉表情的實時監測,用的模型還是deepNN。關于整個文件的結構,可以直接參考文章最后面。
主要參考:
[1]深度學習(一)——deepNN模型實現攝像頭實時識別人臉表情(C++和python3.6混合編程)
[2]從零開始制作人臉表情的數據集
二、數據集準備
1.?haarcascade_frontalface_default.xml文件
這里跟之前文章的思路是一樣的,仍然用到了haarcascade_frontalface_default.xml文件。如何獲取該文件,我在上一篇文章中有詳細說明,這里不再過多介紹。
2.夸張人臉表情數據集
首先設計10類夸張人臉表情,我這里取了吃驚,大哭,高興,撅嘴,皺眉,抬頭,低頭,向左看,向右看,憂郁這10類表情。下面的關鍵是獲取這10類表情的數據。具體關于表情數據集的獲取及制作,可以參考:從零開始制作人臉表情的數據集。這里需要注意的是,向左看和向右看的圖像不能進行鏡像處理!!!
自動制作好表情之后,仍需要自己進行簡單的手動處理,主要去除一些明顯的非人臉圖像。之后盡量保證每個每類的照片數量近似相等,這里我選擇每類都有表情圖像100張。
三、模型實現
1.制作數據標簽
因為模型是以分類的思想來做的,因此我們需要對每一類的表情數據打上標簽(label)。我的想法是,在每個文件夾下讀取相應的圖片,然后將其路徑和標簽一起保存在一個txt文本中。這里先給出代碼:
# 生成圖像及標簽文件https://blog.csdn.net/u010682375/article/details/77746489 import osdef generate(dir,label):files = os.listdir(dir)files.sort()print('start...')listText = open(dir + '\\' + 'zzz_list.txt', 'w')for file in files:fileType = os.path.split(file)if fileType[1] == '.txt':continuename = file + ' ' + str(int(label)) + '\n'listText.write(dir + name)listText.close()print('down!')if __name__ == '__main__':generate('data/chijing/', 0)generate('data/daku/', 1)generate('data/gaoxing/', 2)generate('data/juezui/', 3)generate('data/zhoumei/', 4)generate('data/taitou/', 5)generate('data/ditou/', 6)generate('data/xiangzuokan/', 7)generate('data/xiangyoukan/', 8)generate('data/youyu/', 9)一共有10種表情,所以自然有10種label,且label的編號從0~9。編寫好上述程序之后執行程序,在每個表情數據文件夾下面都會生成一個txt文檔,以吃驚表情為例,在'data/chijing/'路徑下,找到zzz_list.txt文件,打開即可看到:
里面記錄了所有吃驚表情的圖片路徑及標簽。
接下來我們需要手動做的是,將這10類表情的txt文件匯總成一個txt文件,放在目錄'data/'路徑下,并命名為list.txt,即將所有的圖像和標簽制作完畢。
2.批量讀取數據
做好數據集和標簽之后,接下來是編寫數據讀取函數。這個函數的主要功能就是,輸入list.txt文件,它能夠自動提取txt里面的所有圖片及其相對應的標簽。下面先直接給出代碼:
import numpy as np from PIL import Imagedef load_data(txt_dir):fopen = open(txt_dir, 'r')lines = fopen.read().splitlines() # 逐行讀取txtcount = len(open(txt_dir, 'rU').readlines()) # 計算txt有多少行data_set = np.empty((count, 128, 128, 1), dtype="float32")label = np.zeros((count, 10), dtype="uint8")i = 0for line in lines:line = line.split(" ") # 利用空格進行分割img = Image.open(line[0])print(i, img.size)# img = skimage.io.image(line[0])label[i, int(line[1])] = 1img = img.convert('L') # 轉灰度圖像array = np.asarray(img, dtype="float32")data_set[i, :, :, 0] = arrayi += 1return data_set, labelif __name__ == '__main__':txt_dir = 'data/list.txt'data_set, label = load_data(txt_dir)print(data_set.shape)print(label.shape)編寫完上述代碼可以直接運行,如果代碼和txt文件沒問題的話,那最終會輸出data和label的維度。
3.訓練模型
準備好了數據之后,接下來則是訓練模型。下面先給出訓練模型的代碼:
import os import tensorflow as tf import numpy as np from read_data import load_dataEMOTIONS = ['chijing', 'daku', 'gaoxing', 'juezui', 'zhoumei','taitou', 'ditou', 'xiangzuokan', 'xiangyoukan', 'youyu']def deepnn(x):x_image = tf.reshape(x, [-1, 128, 128, 1])# conv1w_conv1 = weight_variables([5, 5, 1, 64])b_conv1 = bias_variable([64])h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1)# pool1h_pool1 = maxpool(h_conv1)# norm1norm1 = tf.nn.lrn(h_pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)# conv2w_conv2 = weight_variables([3, 3, 64, 64])b_conv2 = bias_variable([64])h_conv2 = tf.nn.relu(conv2d(norm1, w_conv2) + b_conv2)norm2 = tf.nn.lrn(h_conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)h_pool2 = maxpool(norm2)# Fully connected layerw_fc1 = weight_variables([32 * 32 * 64, 384])b_fc1 = bias_variable([384])h_conv3_flat = tf.reshape(h_pool2, [-1, 32 * 32 * 64])h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, w_fc1) + b_fc1)# Fully connected layerw_fc2 = weight_variables([384, 192])b_fc2 = bias_variable([192])h_fc2 = tf.matmul(h_fc1, w_fc2) + b_fc2# linearw_fc3 = weight_variables([192, 10]) # 一共10類b_fc3 = bias_variable([10]) # 一共10類y_conv = tf.add(tf.matmul(h_fc2, w_fc3), b_fc3)return y_convdef weight_variables(shape):initial = tf.truncated_normal(shape, stddev=0.1)return tf.Variable(initial)def bias_variable(shape):initial = tf.constant(0.1, shape=shape)return tf.Variable(initial)def conv2d(x, w):return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')def maxpool(x):return tf.nn.max_pool(x, ksize=[1, 3, 3, 1],strides=[1, 2, 2, 1], padding='SAME')def train_model():# 構建模型----------------------------------------------------------x = tf.placeholder(tf.float32, [None, 16384])y_ = tf.placeholder(tf.float32, [None, 10])y_conv = deepnn(x)cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))# 構建完畢----------------------------------------------------------# 讀取數據data_set, label = load_data('./data/list.txt')max_train_epochs = 30001batch_size = 100if not os.path.exists('./models/emotion_model'):os.makedirs('./models/emotion_model')with tf.Session() as sess:saver = tf.train.Saver()sess.run(tf.global_variables_initializer())batch_num = int(data_set.shape[0] / batch_size)for i in range(max_train_epochs):for j in range(batch_num):train_image = data_set[j * batch_size:j * batch_size + batch_size]train_image = train_image.reshape(-1, 128*128)train_label = label[j * batch_size:j * batch_size + batch_size]train_label = np.reshape(train_label, [-1, 10])train_step.run(feed_dict={x: train_image, y_: train_label})if i % 1 == 0:train_accuracy = accuracy.eval(feed_dict={x: train_image, y_: train_label})print('epoch %d, training accuracy %f' % (i, train_accuracy))if i % 50 == 0:saver.save(sess, './models/emotion_model', global_step=i + 1)if __name__ == '__main__':train_model()編寫訓練模型代碼的思路很簡單:首先是編寫deepNN模型結構,其次是在train函數中編寫網絡結構及相關參數,然后讀取訓練數據傳入模型,進行訓練并保存訓練結果即可。編寫好了之后直接運行。模型每訓練50個epoch會保存一次,模型保存的路徑為'./models/emotion_model'。
4.模型測試
訓練好之后,最后一步就是模型的測試。這一步主要做的就是,加載訓練好的模型,并打開攝像頭,實時判斷人臉表情。下面直接給出代碼:
from train_model import *EMOJI_DIR = './files/emotion/' CASC_PATH = './haarcascade_frontalface_alt.xml' cascade_classifier = cv2.CascadeClassifier(CASC_PATH)def format_image(image):'''函數功能:轉換圖像的格式'''if len(image.shape) > 2 and image.shape[2] == 3:image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)faces = cascade_classifier.detectMultiScale(image, scaleFactor=1.3, minNeighbors=5)# None is no face found in imageif not len(faces) > 0:return None, Nonemax_are_face = faces[0]for face in faces:if face[2] * face[3] > max_are_face[2] * max_are_face[3]:max_are_face = face# face to imageface_coor = max_are_faceimage = image[face_coor[1]:(face_coor[1] + face_coor[2]), face_coor[0]:(face_coor[0] + face_coor[3])]# Resize image to network sizetry:image = cv2.resize(image, (128, 128), interpolation=cv2.INTER_CUBIC)except Exception:print("[+} Problem during resize")return None, Nonereturn image, face_coordef face_dect(image):"""檢測圖像中有多少張臉"""if len(image.shape) > 2 and image.shape[2] == 3:image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)faces = cascade_classifier.detectMultiScale(image, scaleFactor=1.3, minNeighbors=5)if not len(faces) > 0:return Nonemax_face = faces[0]for face in faces:if face[2] * face[3] > max_face[2] * max_face[3]:max_face = faceface_image = image[max_face[1]:(max_face[1] + max_face[2]), max_face[0]:(max_face[0] + max_face[3])]try:image = cv2.resize(face_image, (48, 48), interpolation=cv2.INTER_CUBIC) / 255.except Exception:print("[+} Problem during resize")return Nonereturn face_image, imagedef resize_image(image, size):try:image = cv2.resize(image, size, interpolation=cv2.INTER_CUBIC) / 255.except Exception:print("+} Problem during resize")return Nonereturn imagedef image_to_tensor(image):tensor = np.asarray(image).reshape(-1, 128*128) * 1 / 255.0return tensordef demo(modelPath, showBox=False):# 構建模型---------------------------------------------------face_x = tf.placeholder(tf.float32, [None, 128*128])y_conv = deepnn(face_x)probs = tf.nn.softmax(y_conv)# 構建完畢---------------------------------------------------# 存儲器saver = tf.train.Saver()ckpt = tf.train.get_checkpoint_state(modelPath)sess = tf.Session()# 加載模型if ckpt and ckpt.model_checkpoint_path:saver.restore(sess, ckpt.model_checkpoint_path)print('Restore model sucsses!!')# 加載emojifeelings_faces = []for index, emotion in enumerate(EMOTIONS):feelings_faces.append(cv2.imread(EMOJI_DIR + emotion + '.png', -1))video_captor = cv2.VideoCapture(0)emoji_face = []result = Nonewhile True:# 打開攝像頭并做準備ret, frame = video_captor.read()detected_face, face_coor = format_image(frame)if showBox:if face_coor is not None:[x, y, w, h] = face_coorcv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)if cv2.waitKey(10):if detected_face is not None:# 如果存在人臉圖像,則存儲一張樣片,并進行表情識別tensor = image_to_tensor(detected_face)# 識別人臉的情緒,并計算情緒分類的概率result = sess.run(probs, feed_dict={face_x: tensor})if result is not None:for index, emotion in enumerate(EMOTIONS):# 輸出字體,內容為emotion的各個概率,顏色為綠色cv2.putText(frame, emotion, (10, index * 20 + 20), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 0), 1)# 輸出矩形框出人臉cv2.rectangle(frame, (130, index * 20 + 10), (130 + int(result[0][index] * 100), (index + 1) * 20 + 4),(255, 0, 0), -1)# 輸出對應的emoji_faceemoji_face = feelings_faces[np.argmax(result[0])]emoji_face = cv2.resize(emoji_face, (120, 120))for c in range(0, 3):frame[300:420, 10:130, c] = emoji_face[:, :, c] * (emoji_face[:, :, 2] / 255.0) + frame[200:320, 10:130,c] * (1.0 - emoji_face[:, :, 2] / 255.0)cv2.imshow('face', frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakdef main(CHECKPOINT_DIR):if True:demo(CHECKPOINT_DIR)if __name__ == '__main__':CHECKPOINT_DIR = './files/ckpt'main(CHECKPOINT_DIR)執行上述代碼之前,需要先將訓練好的模型放到路徑'./files/ckpt/'下,并準備好emoji表情(將其裁剪為120*120大小),并放到路徑'./files/emotion/'下,執行上述代碼,即可打開攝像頭實現人臉表情的監測。我自己只訓練了5000次,感覺模型的效果并不好,有一些表情目前還無法準確識別。后續我還會進行更深一步研究。
補充一下將emoji圖像resize成120*120大小的圖像代碼:
from PIL import Image import osdef resize_emotion(inupt_dir, output_dir):# 獲取輸入文件夾中的所有文件/夾,并改變工作空間files = os.listdir(inupt_dir)os.chdir(inupt_dir)# 判斷輸出文件夾是否存在,不存在則創建if (not os.path.exists(output_dir)):os.makedirs(output_dir)for file in files:# 判斷是否為文件,文件夾不操作if (os.path.isfile(file)):img = Image.open(file)img = img.resize((120, 120), Image.ANTIALIAS)img.save(os.path.join(output_dir, file))if __name__ == '__main__':inupt_dir = './files/emoji/'output_dir = './files/emotion/'resize_emotion(inupt_dir, output_dir)四、分析總結
1.自己采集數據時一定要注意向左看和向右看的圖像不能進行鏡像處理。
2.模型效果目前并不好,后續我覺得可以添加更多的數據量進行訓練。
3.整個文件的所有結構為:
-- get_image.py # 爬取數據集的程序 -- img_preprocessing.py # 人臉數據裁剪及其預處理 -- img_augument.py # 數據增廣程序 -- make_label.py # 制作人臉標簽,生成txt的程序 -- read_data.py # 利用list.txt讀取圖像數據及標簽的程序 -- train_model.py # 利用deepNN訓練模型 -- test.py # 測試程序,利用攝像頭實時判斷人臉表情 -- haarcascade_frontalface_alt.xml -- files # 存儲了訓練好的模型和emoji圖像|------ ckpt|------ checkpoint|------ emotion_model-5001.data-00000-of-00001|------ ......|------ emotion # 存儲了處理好的emoji圖像|------ chijing.jpg|------ ...... -- data # 處理好的數據集|------ list.txt|------ chijing|------ img01.jpg|------ ......|------ daku|------ img01.jpg|------ ......|------ gaoxing|------ img01.jpg|------ ......|------ juezui|------ img01.jpg|------ ......|------ zhoumei|------ img01.jpg|------ ......|------ taitou|------ img01.jpg|------ ......|------ ditou|------ img01.jpg|------ ......|------ xiangzuokan|------ img01.jpg|------ ......|------ xiangyoukan|------ img01.jpg|------ ......|------ youyu|------ img01.jpg|------ ......?
總結
以上是生活随笔為你收集整理的深度学习(二)——从零自己制作数据集到利用deepNN实现夸张人脸表情的实时监测(tensorflow实现)的全部內容,希望文章能夠幫你解決所遇到的問題。