视觉/图像重磅干货,第一时间送达!
来源 | 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 个主要步骤:
在这个实现中,我们将使用最先进的面部识别模型之一,MTCNN 用于第 2 步。它有一个基于 Keras 的稳定 Python 版本,可在此处获得。
对于第 3 步,我们将训练我们自己的定制模型。但是,为了减少工作量和提高准确性,您可能需要考虑迁移学习技术。许多预训练模型,包括VGG-face、FaceNet、GoogLeNet可用。请注意,这些预训练模型可能具有不同的输入大小要求。因此,需要相应地处理从步骤 2 中识别的人脸。
人脸识别近年来已经成为深度学习的成熟应用。已经提出了许多算法来快速准确地检测图像/视频中的人脸。MTCNN 就是其中之一,它基于 FaceNet。
在 Python 的实现中,模型已经过预训练和优化,因此我们可以直接使用该模型。尽管如此,了解模型的输出仍然很重要。
[
{
'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 返回一个字典列表,其中每个字典代表在照片中检测到的人脸。每张脸都被表示为一个边界框——一个围绕脸的矩形。
情感模型是从CKPlus Facial Emotion 数据集训练而来的。该数据集包含来自 7 个情绪类别的 981 张图像:愤怒、蔑视、厌恶、恐惧、快乐、悲伤和惊讶。每张图像为灰度,固定尺寸为 48*48
年龄和性别模型是从UTKface 数据集训练而来的。该数据集包含超过 2 万张图像。每张图片都标有年龄、性别和种族。完整照片和裁剪的脸部照片都可供下载。在本文中,我们将使用完整的照片并实施我们自己的人脸对齐方法以提高准确性。
我们需要使用 MTCNN 或任何其他面部识别模型从整张照片中裁剪人脸。然而,这些算法中的大多数会根据检测到的人脸的大小和位置给出不同形状的边界框。
深度学习模型要求输入图像具有标准化大小(警告:不适用于全卷积网络,超出本文范围)。因此,有必要调整裁剪面的大小。直接调整大小是最常见和最直接的方法,但也有明显的缺点——面部变形。如下图所示,直接调整大小后脸部明显变宽。这将对我们的模型性能产生负面影响。
一张理想的裁剪人脸照片应该是人脸位于中心,没有失真和所需的大小。如果所需的大小是正方形,则以下方法可以解决问题。
如果所需的尺寸不是正方形,则需要调整第 3 步和第 4 步,以保持与所需边的比例相同。
对于年龄和性别模型,我们将使用 MTCNN 对完整照片使用居中调整大小的方法。两个模型所需的输入大小都设置为 (224, 224, 3)。
由于其图像格式(灰度)和小体积,它不是用于情感预测的最理想数据集。优点是所有图像都被很好地裁剪和对齐,因此有利于快速原型制作。
该数据集的一个注释:对于每个情绪类别,个人面孔重复 3 次。因此,如果随机进行训练/测试拆分,则会发生目标泄漏。建议根据主题拆分或在随机拆分之前删除重复项。
在三个目标中,年龄是最艰巨的任务。有时甚至人们在猜测别人的年龄时也会出错。因此,我们需要一个更深层次的模型来进行年龄预测。一般来说,这些是典型的卷积神经网络。请注意,所有这些模型结构都没有经过调整或优化。本文的目的不包括微调深度学习模型。
年龄模型:
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'])
性别模型:
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'])
情绪模型:
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 从您的网络摄像头捕获视频(第 2 行)。对于每一帧,它都会将其转换为 RGB 格式(第 19 行)。这个 RGB 帧将被发送到 detect_face 函数(第 22 行),该函数首先使用 MTCNN 检测帧中的所有人脸,并且对于每个人脸,使用 3 个经过训练的模型进行预测以生成结果。这些结果与人脸边界框位置(上、右、下、左)一起返回。
然后,OpenCV 利用边界框位置在框架上绘制矩形(第 27 行)并在文本中显示预测结果(第 29 行 - 第 32 行)。
可以在源代码中找到detect_face 函数的实现。请注意,由于情感模型是从灰度图像中训练出来的,因此 RGB 图像在被情感模型预测之前需要进行灰度处理。
OpenCV主脚本:
# 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()
下载1:Pytoch常用函数手册
下载2:145个OpenCV实例应用代码
—THE END—
本文分享自 OpenCV与AI深度学习 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!