Распознавание эмоций (Emotion Detection)
Распознавание эмоций – классическая задача Компьютерного зрения (CV), процесс определения человеческих эмоций на изображении.
Если бы кто-то показал вам фотографию человека и попросил вас угадать, что он чувствует, скорее всего, у вас было бы довольно хорошее представление об этом. Что, если бы ваш компьютер мог делать то же самое? Что, если бы он мог стать еще лучше, чем вы? Кажется абсурдной мыслью, правда?
Три основных компонента обнаружения эмоций:
- Предварительная обработка изображения
- Извлечение Признаков (Feature)
- Классификация признаков
Выделение лица на изображении
Распознавание лица — важный шаг в распознавании эмоций. Он удаляет части изображения, которые не имеют отношения к делу. Вот один из способов обнаружения лиц на изображениях.
import dlib
import numpy as np
frontalface_detector = dlib.get_frontal_face_detector()
def rect_to_bb(rect):
x = rect.left()
y = rect.top()
w = rect.right() - x
h = rect.bottom() - y
return (x, y, w, h)
def detect_face(image_url):
try:
url_response = urllib.request.urlopen(image_url)
img_array = np.array(bytearray(url_response.read()), dtype=np.uint8)
image = cv2.imdecode(img_array, -1)
rects = frontalface_detector(image, 1)
if len(rects) < 1:
return "No Face Detected"
for (i, rect) in enumerate(rects):
(x, y, w, h) = rect_to_bb(rect)
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
plt.imshow(image, interpolation='nearest')
plt.axis('off')
plt.show()
Другой способ сделать это — использовать предварительно обученную модель детектора лиц dlib
, которая также используется в следующем сниппете.

Выделение частей лица
Ориентиры лица — это набор ключевых точек на изображениях лица человека. Точки определяются их координатами (x, y) на изображении. Эти точки используются для определения местоположения и представления выступающих областей лица, таких как глаза, брови, нос, рот и линия подбородка.
В качестве модели лицевых ориентиров мы использовали предварительно обученную модель обнаружения лицевых ориентиров dlib
, которая обнаруживает 68 двумерных точек на лице человека.
Вы можете загрузить модель следующим образом:
import dlib
import numpy as np
frontalface_detector = dlib.get_frontal_face_detector()
landmark_predictor=dlib.shape_predictor('./shape_predictor_68_face_landmarks.dat')
def get_landmarks(image_url):
try:
url_response = urllib.request.urlopen(image_url)
img_array = np.array(bytearray(url_response.read()), dtype=np.uint8)
image = cv2.imdecode(img_array, -1)
except Exception as e:
print ("Please check the URL and try again!")
return None,None
faces = frontalface_detector(image, 1)
if len(faces):
landmarks = [(p.x, p.y) for p in landmark_predictor(image, faces[0]).parts()]
else:
return None,None
return image,landmarks
def image_landmarks(image,face_landmarks):
radius = -1
circle_thickness = 4
image_copy = image.copy()
for (x, y) in face_landmarks:
cv2.circle(image_copy, (x, y), circle_thickness, (255,0,0), radius)
plt.imshow(image_copy, interpolation='nearest')
plt.axis('off')
plt.show()
После использования модели ваш вывод должен выглядеть так:

В этой модели специфическими ориентирами для черт лица являются:
Линия подбородка: 0–16
Брови: 17–26
Нос: 27–35
Левый глаз: 36–41
Правый глаз: 42–47
Рот: 48–67
Один из способов различить две эмоции — посмотреть, открыты ли у человека рот и глаза или нет. Мы можем найти евклидово расстояние между точками конкретно на рту, если человек удивлен, расстояние будет больше, чем было бы в спокойном состоянии.
Предварительная обработка данных
Прежде чем использовать данные, важно пройти ряд шагов, называемых предварительной обработкой. Это упрощает обработку данных. Мы будем использовать модифицированную версию набора данных fer2013
, состоящую из пяти меток эмоций.
Набор данных хранится в файле формата csv. Каждое Наблюдение (Observation) имеет два атрибута:
- Пиксели изображения, хранящиеся в строковом формате
- Целочисленное кодирование Целевой переменной (Target Variable)
Всего имеется 20 000 черно-белых изображений пяти эмоций размером 48*48.
Целевые метки закодированы целым числом:
0 — -> Злой
1 — -> Счастливый
2 — -> Грустный
3 — -> Сюрприз
4 — -> нейтральный
Загрузим Датасет (Dataset):
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
label_map={"0":"ANGRY","1":"HAPPY","2":"SAD","3":"SURPRISE","4":"NEUTRAL"}
df = pd.read_csv("./ferdata.csv")
df.head()
Этот набор данных содержит необработанные значения пикселей изображений.
Разделение данных
Как обсуждалось в прошлый раз, данные необходимо разделить на два разных набора:
- Тренировочные данные (Train Data): алгоритм будет обучаться на этой части датасета снова и снова, чтобы выполнить свою задачу.
- Тестовые данные (Test Data): алгоритм проверяют на этих данных, чтобы увидеть, насколько хорошо он работает.
Стандартизация данных
Стандартизация — это процесс помещения различных переменных в одну и ту же шкалу. Она масштабирует данные, чтобы привести среднее значение к нулю и стандартное отклонение к единице. Это преобразование центрирует данные.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Линейные модели
kNN
Метод K-ближайших соседей (kNN) — это непараметрический алгоритм обучения, то есть он не делает никаких предположений о распределении данных. В качестве данных использовались Евклидовы расстояния (Euclidean Distance) между точками.
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, Y_train)
predictions = knn.predict(X_test)
Чтобы вычислить Точность (Accuracy), используем встроенный модуль accuracy_score()
:
from sklearn.metrics import accuracy_score
print(accuracy_score(Y_test, predictions)*100)
Наша точность составила около 50%, поэтому мы попробовали несколько нелинейных моделей.
Вы можете попробовать ввести необработанные значения пикселей в модель и посмотреть, как это повлияет на точность модели.
Нелинейные модели
Многослойные персептроны
Многослойный перцептрон (MLP) — это разновидность Нейронных сетей (Neural Network). Они состоят из одного или нескольких слоев нейронов. Входной слой — это место, куда подаются данные, после которых может быть один или несколько скрытых слоев. Прогнозы поступают из выходного слоя.
from keras.models import Sequential
from keras.utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Activation
from keras.losses import categorical_crossentropy
from keras.callbacks import EarlyStopping, ModelCheckpoint
from keras.models import load_model
from keras.optimizers import Adam, SGD
mlp_model = Sequential()
mlp_model.add(Dense(1024, input_shape = (2304,), activation = 'relu', kernel_initializer='glorot_normal'))
mlp_model.add(Dense(512, activation = 'relu', kernel_initializer='glorot_normal'))
mlp_model.add(Dense(5, activation = 'softmax'))
mlp_model.compile(loss=categorical_crossentropy, optimizer=SGD(lr=0.001), metrics=['accuracy'])
checkpoint = ModelCheckpoint('best_mlp_model.h5',verbose=1,
monitor='val_acc', save_best_only=True,mode='auto')
mlp_history = mlp_model.fit(X_train, y_train, batch_size=batch_size,
epochs=epochs, verbose=1, callbacks=[checkpoint], validation_data(X_test, y_test),shuffle=True)
Наша точность с использованием MLP составила около 50%, она увеличилась, когда вместо значений пикселей мы использовали расстояния между ориентирами лица. Однако нам нужна еще более точная модель, поэтому мы решили использовать CNN.
Сверточные нейронные сети (CNN)
width, height = 48, 48
X_train = X_train.reshape(len(X_train),height,width)
X_test = X_test.reshape(len(X_test),height,width)
X_train = np.expand_dims(X_train,3)
X_test = np.expand_dims(X_test,3)
cnn_model = Sequential()
cnn_model.add(Conv2D(5000, kernel_size=(4, 4), activation='relu', padding='same', input_shape = (width, height, 1)))
cnn_model.add(BatchNormalization())
cnn_model.add(MaxPooling2D(pool_size=(3, 3), strides=(4, 4)))
cnn_model.add(Dropout(0.2))
cnn_model.add(Flatten())
cnn_model.add(Dense(2000, activation='relu'))
cnn_model.add(Dropout(0.2))
cnn_model.add(Dense(5, activation='softmax'))
checkpoint = ModelCheckpoint('best_cnn_model.h5', verbose=1, monitor='val_loss',save_best_only=True, mode='auto')
cnn_model.compile(loss=categorical_crossentropy,
optimizer=Adam(lr=0.001, beta_1=0.9, beta_2=0.999),
metrics=['accuracy'])
cnn_history = cnn_model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, callbacks=[checkpoint],
validation_data=(X_test, y_test),shuffle=True)
cnn_model.save('cnn_model.h5')
Для повышения производительности вы можете настроить отсев, количество плотных слоев и активацию функции. Мы также использовали трансферное обучение с сверточную нейросеть под названием Группа визуальной геометрии (VGG), которая представляет собой предварительно обученную свёрточную нейронную сеть для классификации изображений.
Оценка
Наилучшие результаты были получены при использовании VGG, которые были верны примерно в 68–70% случаев, но даже линейные модели работали очень хорошо. Хотя точность 50% не кажется чем-то достойным, это все же больше, чем если бы вы взяли картинку и присвоили ей метку эмоции наугад (что эквивалентно 20%-й точности).
Автор оригинальной статьи: Aarohi Gupta