使用Keras和OpenCV实时预测年龄、性别和情绪 (详细步骤+源码)

2022-05-26 14:45:03 浏览数 (1)

视觉/图像重磅干货,第一时间送达!

来源 | https://towardsdatascience.com/real-time-age-gender-and-emotion-prediction-from-webcam-with-keras-and-opencv-bde6220d60a

作者 | Sun Weiran

翻译 | OpenCV与AI深度学习

导读

本文将介绍如何使用 Keras 和 OpenCV 从网络摄像头实时预测年龄、性别和情绪。(公众号:OpenCV与AI深度学习)

背景介绍

在 Covid-19 时代,我们变得更加依赖虚拟互动,例如 Zoom 会议/团队聊天。这些直播网络摄像头视频已成为可供探索的丰富数据源。本文将探讨年龄、性别和情绪预测的实例,例如,这些应用可以帮助销售人员更好地了解他们的客户。

演示

来自我的网络摄像头的实时预测(作者提供的 gif)

整体架构

整体实现结构(作者供图)

如上图所示,该实现包含 4 个主要步骤:

  1. 从网络摄像头接收输入帧
  2. 识别网络摄像头中的人脸并为 3 个深度学习模型(即年龄、性别和情感模型)准备这些图像
  3. 将处理后的人脸发送到模型并接收预测结果
  4. 将带有边界框的预测结果渲染到屏幕上

在这个实现中,我们将使用最先进的面部识别模型之一,MTCNN 用于第 2 步。它有一个基于 Keras 的稳定 Python 版本,可在此处获得。

对于第 3 步,我们将训练我们自己的定制模型。但是,为了减少工作量和提高准确性,您可能需要考虑迁移学习技术。许多预训练模型,包括VGG-face、FaceNet、GoogLeNet可用。请注意,这些预训练模型可能具有不同的输入大小要求。因此,需要相应地处理从步骤 2 中识别的人脸。

使用 MTCNN 进行人脸识别

人脸识别近年来已经成为深度学习的成熟应用。已经提出了许多算法来快速准确地检测图像/视频中的人脸。MTCNN 就是其中之一,它基于 FaceNet。

在 Python 的实现中,模型已经过预训练和优化,因此我们可以直接使用该模型。尽管如此,了解模型的输出仍然很重要。

代码语言:javascript复制
[
    {
        'box': [277, 90, 48, 63],
        'keypoints':
        {
            'nose': (303, 131),
            'mouth_right': (313, 141),
            'right_eye': (314, 114),
            'left_eye': (291, 117),
            'mouth_left': (296, 143)
        },
        'confidence': 0.99851983785629272
    }
]

对于每张图像,MTCNN 返回一个字典列表,其中每个字典代表在照片中检测到的人脸。每张脸都被表示为一个边界框——一个围绕脸的矩形。

  • box: [x, y, width, height],x和y是边界框左上角的坐标
  • 关键点:检测到的面部标志点字典
  • 置信度:模型对检测到的人脸的置信度得分,1 表示最有信心。

年龄/性别/情感模型训练数据集

情感模型是从CKPlus Facial Emotion 数据集训练而来的。该数据集包含来自 7 个情绪类别的 981 张图像:愤怒、蔑视、厌恶、恐惧、快乐、悲伤和惊讶。每张图像为灰度,固定尺寸为 48*48

年龄和性别模型是从UTKface 数据集训练而来的。该数据集包含超过 2 万张图像。每张图片都标有年龄、性别和种族。完整照片和裁剪的脸部照片都可供下载。在本文中,我们将使用完整的照片并实施我们自己的人脸对齐方法以提高准确性。

图像预处理——UTKface 数据集

我们需要使用 MTCNN 或任何其他面部识别模型从整张照片中裁剪人脸。然而,这些算法中的大多数会根据检测到的人脸的大小和位置给出不同形状的边界框。

深度学习模型要求输入图像具有标准化大小(警告:不适用于全卷积网络,超出本文范围)。因此,有必要调整裁剪面的大小。直接调整大小是最常见和最直接的方法,但也有明显的缺点——面部变形。如下图所示,直接调整大小后脸部明显变宽。这将对我们的模型性能产生负面影响。

一张理想的裁剪人脸照片应该是人脸位于中心,没有失真和所需的大小。如果所需的大小是正方形,则以下方法可以解决问题。

  1. 从 MTCNN 获取面部边界框
  2. 找到边界框的中心点
  3. 找到边界框的高度和宽度之间的最大值
  4. 根据中心和最大边长绘制新的边界框
  5. 将裁剪后的人脸从新边界框调整为所需大小

如果所需的尺寸不是正方形,则需要调整第 3 步和第 4 步,以保持与所需边的比例相同。

对于年龄和性别模型,我们将使用 MTCNN 对完整照片使用居中调整大小的方法。两个模型所需的输入大小都设置为 (224, 224, 3)。

图像预处理——CKPlus Facial Emotion 数据集

由于其图像格式(灰度)和小体积,它不是用于情感预测的最理想数据集。优点是所有图像都被很好地裁剪和对齐,因此有利于快速原型制作。

该数据集的一个注释:对于每个情绪类别,个人面孔重复 3 次。因此,如果随机进行训练/测试拆分,则会发生目标泄漏。建议根据主题拆分或在随机拆分之前删除重复项。

模型结构

在三个目标中,年龄是最艰巨的任务。有时甚至人们在猜测别人的年龄时也会出错。因此,我们需要一个更深层次的模型来进行年龄预测。一般来说,这些是典型的卷积神经网络。请注意,所有这些模型结构都没有经过调整或优化。本文的目的不包括微调深度学习模型。

年龄模型:

代码语言:javascript复制
input = Input(shape=(224, 224, 3))

cnn1 = Conv2D(128, kernel_size=3, activation='relu')(input)
cnn1 = Conv2D(128, kernel_size=3, activation='relu')(cnn1)
cnn1 = Conv2D(128, kernel_size=3, activation='relu')(cnn1)
cnn1 = MaxPool2D(pool_size=3, strides=2)(cnn1)

cnn2 = Conv2D(128, kernel_size=3, activation='relu')(cnn1)
cnn2 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn2 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn2 = MaxPool2D(pool_size=3, strides=2)(cnn2)

cnn3 = Conv2D(256, kernel_size=3, activation='relu')(cnn2)
cnn3 = Conv2D(256, kernel_size=3, activation='relu')(cnn3)
cnn3 = Conv2D(256, kernel_size=3, activation='relu')(cnn3)
cnn3 = MaxPool2D(pool_size=3, strides=2)(cnn3)

cnn4 = Conv2D(512, kernel_size=3, activation='relu')(cnn3)
cnn4 = Conv2D(512, kernel_size=3, activation='relu')(cnn4)
cnn4 = Conv2D(512, kernel_size=3, activation='relu')(cnn4)
cnn4 = MaxPool2D(pool_size=3, strides=2)(cnn4)

dense = Flatten()(cnn4)
dense = Dropout(0.2)(dense)
dense = Dense(1024, activation='relu')(dense)
dense = Dense(1024, activation='relu')(dense)

output = Dense(1, activation='linear', name='age')(dense)

model = Model(input, output)
model.compile(optimizer=Adam(0.0001), loss='mse', metrics=['mae'])

性别模型:

代码语言:javascript复制
input = Input(shape=(224, 224, 3))
cnn1 = Conv2D(36, kernel_size=3, activation='relu')(input)
cnn1 = MaxPool2D(pool_size=3, strides=2)(cnn1)
cnn2 = Conv2D(64, kernel_size=3, activation='relu')(cnn1)
cnn2 = MaxPool2D(pool_size=3, strides=2)(cnn2)
cnn3 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn3 = MaxPool2D(pool_size=3, strides=2)(cnn3)
cnn4 = Conv2D(256, kernel_size=3, activation='relu')(cnn3)
cnn4 = MaxPool2D(pool_size=3, strides=2)(cnn4)
cnn5 = Conv2D(512, kernel_size=3, activation='relu')(cnn4)
cnn5 = MaxPool2D(pool_size=3, strides=2)(cnn5)
dense = Flatten()(cnn5)
dense = Dropout(0.2)(dense)
dense = Dense(512, activation='relu')(dense)
dense = Dense(512, activation='relu')(dense)
output = Dense(1, activation='sigmoid', name='gender')(dense)
sex_model = Model(input, output)
sex_model.compile(optimizer=Adam(learning_rate=0.0001), loss='binary_crossentropy', metrics=['accuracy'])

情绪模型:

代码语言:javascript复制
input = Input(shape=(48, 48, 1))
cnn1 = Conv2D(36, kernel_size=3, activation='relu')(input)
cnn1 = MaxPool2D(pool_size=3, strides=2)(cnn1)
cnn2 = Conv2D(64, kernel_size=3, activation='relu')(cnn1)
cnn2 = MaxPool2D(pool_size=3, strides=2)(cnn2)
cnn3 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn3 = MaxPool2D(pool_size=3, strides=2)(cnn3)
dense = Flatten()(cnn3)
dense = Dropout(0.3)(dense)
dense = Dense(256, activation='relu')(dense)
output = Dense(7, activation='softmax', name='race', kernel_regularizer=l1(1))(dense)
emotion_model = Model(input, output)
emotion_model.compile(optimizer=Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['categorical_accuracy'])

与 OpenCV 的集成说明

基本上,openCV 从您的网络摄像头捕获视频(第 2 行)。对于每一帧,它都会将其转换为 RGB 格式(第 19 行)。这个 RGB 帧将被发送到 detect_face 函数(第 22 行),该函数首先使用 MTCNN 检测帧中的所有人脸,并且对于每个人脸,使用 3 个经过训练的模型进行预测以生成结果。这些结果与人脸边界框位置(上、右、下、左)一起返回。

然后,OpenCV 利用边界框位置在框架上绘制矩形(第 27 行)并在文本中显示预测结果(第 29 行 - 第 32 行)。

可以在源代码中找到detect_face 函数的实现。请注意,由于情感模型是从灰度图像中训练出来的,因此 RGB 图像在被情感模型预测之前需要进行灰度处理。

OpenCV主脚本:

代码语言:javascript复制
# Get a reference to webcam 
video_capture = cv2.VideoCapture(0)

emotion_dict = {
    0: 'Surprise',
    1: 'Happy', 
    2: 'Disgust',
    3: 'Anger',
    4: 'Sadness',
    5: 'Fear',
    6: 'Contempt'
}

while True:
    # Grab a single frame of video
    ret, frame = video_capture.read()

    # Convert the image from BGR color (which OpenCV uses) to RGB color 
    rgb_frame = frame[:, :, ::-1]

    # Find all the faces in the current frame of video
    face_locations = detect_face(rgb_frame)

    # Display the results
    for top, right, bottom, left, sex_preds, age_preds, emotion_preds in face_locations:
        # Draw a box around the face
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
        
        sex_text = 'Female' if sex_preds > 0.5 else 'Male'
        cv2.putText(frame, 'Sex: {}({:.3f})'.format(sex_text, sex_preds), (left, top-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 1)
        cv2.putText(frame, 'Age: {:.3f}'.format(age_preds), (left, top-25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 1)
        cv2.putText(frame, 'Emotion: {}({:.3f})'.format(emotion_dict[np.argmax(emotion_preds)], np.max(emotion_preds)), (left, top-40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 1)
        
    # Display the resulting image
    cv2.imshow('Video', frame)

    # Hit 'q' on the keyboard to quit!
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Release handle to the webcam
video_capture.release()
cv2.destroyAllWindows()

待优化内容

  • 更好的情绪预测数据集
  • 由于计算资源的限制,只有来自 UTKface 数据集的 5k 图像用于年龄/性别模型训练。使用更多图像可以提高模型性能
  • 图像增强
  • 迁移学习/调优模型架构 源码与模型文件下载:
  • https://github.com/ianforme/face-prediction

下载1:Pytoch常用函数手册

下载2:145个OpenCV实例应用代码

—THE END—

0 人点赞