人脸情绪识别(使用深度学习和OpenCV)
前言
在这篇博客中,我将分享如何结合深度学习和OpenCV来创建一个能够实时识别人脸情感的系统。
本文主要为我个人的学习心得与总结,鉴于网络上知识信息的零散性,我特意进行了整合与阐述。若在整理过程中存在疏漏或错误之处,恳请各位不吝赐教,给予指正。需要强调的是,本文纯粹为学习交流之用,并不产生任何经济收益。若在文中出现任何涉及禁止转载或侵权的图片、文字内容,敬请及时与我取得联系,我将立即进行修正。
目录
前言
项目简介
模型构建
导入必备的库
数据预处理
模型构建
训练模型
保存模型
实现面部表情识别:加载、训练和保存模型
实时应用
代码分享与访问
总结
项目简介
情感检测在计算机科学领域中扮演着重要的角色,尤其在人机交互和情感智能应用中。本项目旨在利用深度学习技术,特别是卷积神经网络(CNN),来实现实时的情感检测。此外,OpenCV作为一个开源的计算机视觉库,为我们提供了处理图像和视频流的强大工具。
模型构建
导入必备的库
我们需要导入用到的Python库,包括数据处理库Pandas、科学计算库NumPy、深度学习框架Keras、以及计算机视觉库OpenCV
import pandas as pd import numpy as np from keras.models import Sequential from keras.layers import Conv2D,MaxPooling2D,Dense,Dropout,Flatten from keras.losses import categorical_crossentropy from keras.optimizers import Adam from keras.utils import to_categorical
数据预处理
在能够进行表情识别之前,我们需要加载并预处理数据。这一过程包括读取图像数据,将数据分割为训练和测试集,对数据进行标准化处理,以及将标签转换为one-hot编码格式。以下是数据预处理的代码段:
def load_and_preprocess_data(file_path): Catch_bat = pd.read_csv(file_path) X_train, Y_train, X_test, Y_test = [], [], [], [] for index, row in Catch_bat.iterrows(): val = row['pixels'].split(' ') try: if 'Training' in row['Usage']: X_train.append(np.array(val, 'float32')) Y_train.append(row['emotion']) elif 'PublicTest' in row['Usage']: X_test.append(np.array(val, 'float32')) Y_test.append(row['emotion']) except: pass X_train = np.array(X_train, 'float32') Y_train = np.array(Y_train, 'float32') X_test = np.array(X_test, 'float32') Y_test = np.array(Y_test, 'float32') X_train = (X_train - np.mean(X_train, axis=0)) / np.std(X_train, axis=0) X_test = (X_test - np.mean(X_test, axis=0)) / np.std(X_test, axis=0) labels = 7 Y_train = to_categorical(Y_train, num_classes=labels) Y_test = to_categorical(Y_test, num_classes=labels) width, height = 48, 48 X_train = X_train.reshape(X_train.shape[0], width, height, 1) X_test = X_test.reshape(X_test.shape[0], width, height, 1) return X_train, Y_train, X_test, Y_test
这段代码首先使用pandas读取CSV文件,然后根据“Usage”列将数据分割为训练集和测试集。接着,它将图像数据标准化,并将标签转换为one-hot编码格式。
模型构建
此函数的目的是构建一个卷积神经网络模型,用于面部表情的分类。以下是数据预处理的代码段:
def build_model(input_shape, num_classes): model = Sequential() model.add(Conv2D(64, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) model.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Dropout(0.5)) model.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) model.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Dropout(0.5)) model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) model.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Flatten()) model.add(Dense(512, activation='relu')) model.add(Dropout(0.2)) model.add(Dense(num_classes, activation='softmax')) return model
- model = Sequential():初始化一个顺序模型。这是Keras中最简单的模型类型,允许我们按顺序一层层地添加模型层。
- model.add(Conv2D(...)):添加卷积层。这里使用64和128个滤波器(也就是输出空间的维度),每个卷积层后都跟着一个激活函数ReLU来增加非线性。
- MaxPooling2D(...):添加最大池化层,用于下采样,减少参数数量,防止过拟合。
- Dropout(0.5)和Dropout(0.2):添加Dropout层,随机丢弃一部分神经元(分别是50%和20%),以防止过拟合。
- Flatten():将多维的输入一维化,常用于卷积层到全连接层的过渡。
- Dense(...):添加全连接层。第一个全连接层有512个神经元,最后一个输出层的神经元数目等于分类的数目,使用softmax激活函数进行多分类。
训练模型
def train_model(model, X_train, Y_train, X_test, Y_test, batch_size=64, epochs=1): model.compile(loss=categorical_crossentropy, optimizer=Adam(), metrics=['accuracy']) model.fit(X_train, Y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(X_test, Y_test), shuffle=True)
这个函数用于训练上述定义的卷积神经网络模型。主要步骤包括:
- model.compile(...):配置训练模型。使用categorical_crossentropy作为损失函数,这在多分类问题中很常见。Adam优化器用于调整权重,metrics=['accuracy']意味着训练和测试期间将监控精度。
- model.fit(...):这里开始训练过程。使用提供的训练数据X_train和Y_train,批量大小batch_size,重复训练epochs次。同时,使用X_test和Y_test作为验证集来监控训练过程中的性能。
保存模型
def save_model(model, model_file, weights_file): model_json = model.to_json() with open(model_file, 'w') as json_file: json_file.write(model_json) model.save_weights(weights_file)
最后,这个函数用于保存训练好的模型,以便将来可以重新加载并使用,而无需重新训练。
- model_json = model.to_json():首先,将模型的架构转换为JSON格式的字符串。
- with open(model_file, 'w') as json_file:然后,将这个字符串写入一个文件中,保存模型的架构。
- model.save_weights(weights_file):最后,调用save_weights方法将模型的权重保存到另一个文件中。
实现面部表情识别:加载、训练和保存模型
在我们的项目中,我们使用了fer2013.csv数据集,它包含数千个标记了七种基本情感(愤怒、厌恶、恐惧、快乐、悲伤、惊讶和中性)的面部表情图像
# 加载和预处理数据 X_train, Y_train, X_test, Y_test = load_and_preprocess_data('fer2013.csv') # 构建模型 input_shape = X_train.shape[1:] num_classes = 7 model = build_model(input_shape, num_classes) # 训练模型 train_model(model, X_train, Y_train, X_test, Y_test) # 保存模型 save_model(model, 'file_name.json', 'file_name.h5')
实时应用
import cv2 import numpy as np from keras.models import model_from_json with open('file_name.json', 'r') as json_file: loaded_model_json = json_file.read() model = model_from_json(loaded_model_json) model.load_weights('file_name.h5') face_prototxt = 'deploy.prototxt.txt' face_model = 'res10_300x300_ssd_iter_140000.caffemodel' face_net = cv2.dnn.readNetFromCaffe(face_prototxt, face_model) emotions = ('angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral') emotion_colors = { 'angry': (0, 0, 255), 'disgust': (0, 255, 0), 'fear': (255, 0, 0), 'happy': (255, 255, 0), 'sad': (255, 0, 255), 'surprise': (0, 255, 255), 'neutral': (255, 255, 255) } font = cv2.FONT_HERSHEY_SIMPLEX def process_image(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blob = cv2.dnn.blobFromImage(cv2.resize(img, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) face_net.setInput(blob) faces = face_net.forward() for i in range(faces.shape[2]): confidence = faces[0, 0, i, 2] if confidence > 0.5: box = faces[0, 0, i, 3:7] * np.array([img.shape[1], img.shape[0], img.shape[1], img.shape[0]]) (startX, startY, endX, endY) = box.astype("int") face = gray[startY:endY, startX:endX] face = cv2.resize(face, (48, 48)) pixels = np.expand_dims(face, axis=0) pixels = pixels / 255.0 pred = model.predict(pixels) max_index = np.argmax(pred[0]) pred_emotion = emotions[max_index] color = emotion_colors.get(pred_emotion, (255, 255, 255)) cv2.rectangle(img, (startX, startY), (endX, endY), color, 3) cv2.putText(img, pred_emotion, (int(startX), int(startY)), font, 1, color, 2) return img def process_image_file(file_path): img = cv2.imread(file_path) processed_img = process_image(img) cv2.namedWindow('image', 0) cv2.imshow('image', processed_img) cv2.waitKey(0) cv2.destroyAllWindows() process_image_file('you_image.jpg')
在这段代码中,我们加载了面部表情识别模型和人脸检测模型,然后定义了一个处理图像的函数。该函数首先将图像转换为灰度图,并使用人脸检测模型检测人脸。然后,对每个检测到的人脸进行表情识别,并根据预测结果在图像上绘制矩形和文本。最后,我们调用了处理单张图像文件的函数,并传入了一个示例图像文件的路径。
除了能够处理静态图像,我们还可以让我们的系统实时处理视频流,并对其中的人脸进行情绪识别。下面是添加处理视频功能的代码:
def process_video(video_path, output_path): cap = cv2.VideoCapture(video_path) fps = int(cap.get(cv2.CAP_PROP_FPS)) frame_size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter(output_path, fourcc, fps, frame_size) while True: ret, frame = cap.read() if not ret: break processed_frame = process_image(frame) resize_frame = cv2.resize(processed_frame, frame_size) cv2.imshow('video', resize_frame) out.write(resize_frame) key = cv2.waitKey(1) if key == ord('q') or cv2.getWindowProperty('video', cv2.WND_PROP_VISIBLE)在上述代码中,我们添加了一个名为 process_video的函数,它接受输入视频文件的路径和输出视频文件的路径。这个函数会打开输入视频文件,逐帧读取视频流,并对每一帧进行情绪识别处理。然后,将处理后的帧写入输出视频文件。最后,释放资源并关闭所有窗口。
为了让我们的情绪识别系统更加实用和互动,我们进一步扩展了其功能,使之能够实时处理来自摄像头的视频流。以下是实现此功能的详细代码:
def process_camera(): cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break processed_frame = process_image(frame) resize_frame = cv2.resize(processed_frame, (1000, 700)) cv2.imshow('frame', resize_frame) key = cv2.waitKey(1) if key == ord('q') or cv2.getWindowProperty('frame', cv2.WND_PROP_VISIBLE)在这段代码中,我们首先通过 cv2.VideoCapture(0) 打开系统默认的摄像头。接下来,程序进入一个循环,在这个循环中持续从摄像头读取帧。对于每一帧,我们调用 process_image 函数进行所需的图像处理。为了更好的显示效果,我们还将处理后的帧大小调整为 1000x700 像素。如果用户按下 'q' 键或关闭显示窗口,循环将终止,释放摄像头资源并关闭所有开启的窗口。
代码分享与访问
为了方便大家更好地理解和使用我在本文中提到的技术和方法,我已经将相关代码上传至我的GitHub仓库。
您可以通过以下链接访问我GitHub仓库,并下载或查看这些代码:
GitHub - maxuan777/Face-Emotion-Recognition-: 基于opencv与深度学习的人脸情绪识别
在仓库中,您可以找到与本文内容相对应的代码文件和项目结构。这些代码已经经过我的测试与验证,应该可以正常运行。当然,如果您在使用过程中遇到任何问题或建议,欢迎在下方留言,我会尽快回复并帮助您解决。
总结
感谢您的阅读与支持!如果您对我的博客或代码有任何意见或建议,欢迎在下方留言或私信我。