В этой статье мы сосредоточимся на обнаружении эмоций (т. Е. Идентификации человеческих эмоций) с использованием машинного обучения.

Если кто-то показывает чью-то фотографию и просит вас угадать, что он/она/они могут чувствовать, вы можете просто идентифицировать или иметь более высокие шансы, что вы сможете идентифицировать это. Но что, если изображение передать компьютеру и попросить его идентифицировать? Удастся ли это сделать? Да, мы собираемся сделать то же самое. Но что, если он может работать лучше, чем вы? Кажется абсурдной мыслью, не так ли?

В этой статье мы увидим, как вы сможете создать свой собственный проект по обнаружению эмоций с помощью машинного обучения.

Обнаружение эмоций состоит из трех основных компонентов:

  1. Обработка изображений
  2. Извлечение функций
  3. Классификация функций

Как показано выше, есть пять основных шагов для обнаружения эмоций. Давайте разберемся в этих шагах подробно.

Входное изображение:

Чтобы обнаружить эмоции, в первую очередь нам понадобится какое-то изображение, видео, из которого мы должны будем обнаружить эмоции. Здесь мы принимаем изображение / фото в качестве входных данных.

Распознавание лиц:

После получения входного изображения первое, что нам нужно сделать, это определить лицо для дальнейшей обработки. Обнаружение лиц — важный этап процесса обнаружения эмоций, который удалит части изображения, которые не имеют отношения к делу. Здесь я объясняю один из способов обнаружения лиц на заданных входных изображениях.

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()Feature Classification

Есть также много других способов сделать это, например, использовать предварительно обученную модель dlib.

Ориентиры лица:

После обнаружения лиц на изображениях мы перейдем к следующему процессу — лицевым ориентирам. Ориентиры лица — это набор ключевых точек или, можно сказать, 2D-координат изображения человеческого лица.

Точки определяются их координатами (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()

В этой модели характерными чертами лицевых ориентиров являются:

Jawline = 0–16
Eyebrows = 17–26
Nose = 27–35
Left eye = 36–41
Right eye = 42–47
Mouth = 48–67

Один из способов различить две эмоции — посмотреть, открыты ли у человека рот и глаза или нет. Мы можем найти евклидово расстояние между точками конкретно на рту, если человек удивлен, расстояние будет больше, чем было бы, если бы он не был удивлен.

Предварительная обработка данных

Прежде чем использовать данные, важно пройти ряд шагов, называемых предварительной обработкой. Это упрощает обработку данных.

Мы будем использовать модифицированную версию набора данных fer2013, состоящую из пяти меток эмоций.

Набор данных хранится в csvfile. Каждая строка в csvfile обозначает экземпляр. Каждый экземпляр имеет два атрибута столбца:

  • Пиксели изображения, хранящиеся в строковом формате
  • Целочисленное кодирование целевой метки

Всего имеется 20 000 изображений, равномерно распределенных по пяти эмоциям. Изображения представляют собой обрезанные изображения 48*48 оттенков серого. CSV-файл состоит из сглаженного массива изображений, хранящихся в виде строки.

The target labels are integer encoded in the csvfile. They are mapped as follows:
0 — -> Angry
1 — -> Happy
2 — -> Sad
3 — -> Surprise
4 — -> Neutral

Загрузка набора данных

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()

Этот набор данных содержит необработанные значения пикселей изображений.

Разделить данные

Как обсуждалось в прошлый раз, данные необходимо разделить на два разных набора:

  1. Учебный набор: алгоритм будет читать или «тренироваться» на этом снова и снова, чтобы попытаться изучить свою задачу.
  2. Набор для тестирования: алгоритм тестируется на этих данных, чтобы увидеть, насколько хорошо он работает.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(dataX, dataY, test_size=0.1,random_state=42,stratify =dataY)

Стандартизируйте данные

Стандартизация — это процесс помещения различных переменных в одну и ту же шкалу. Он масштабирует данные, чтобы получить среднее значение 0 и стандартное отклонение 1. Это преобразование центрирует данные.

from sklearn.preprocessing import StandardScalerscaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)

Линейные модели

K-ближайшие соседи

KNN — это непараметрический алгоритм обучения, то есть он не делает никаких предположений о распределении данных. В качестве данных использовались евклидовы расстояния между точками.

from sklearn.neighbors import KNeighborsClassifierknn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, Y_train)
predictions = knn.predict(X_test)

Чтобы найти точность модели:

from sklearn.metrics import accuracy_scoreprint(accuracy_score(Y_test, predictions)*100)

Наша точность составила около 50%, поэтому мы попробовали несколько нелинейных моделей.

Вы можете попробовать ввести необработанные значения пикселей в модель и посмотреть, как это повлияет на точность модели.

Нелинейные модели

Многослойный персептрон

MLP — это подкатегория нейронных сетей. Они состоят из одного или нескольких слоев нейронов. Входные слои — это место, куда подаются данные, после которых может быть один или несколько скрытых слоев. Прогнозы исходят из выходного слоя.

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, SGDmlp_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)

Наша точность с использованием пикселей составила около 50%, она увеличилась, когда вместо использования значений пикселей мы использовали расстояния между ориентирами лица. Однако нам нужны были еще более точные модели, поэтому мы решили использовать CNN.

Сверточные нейронные сети

width, height = 48, 48X_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')

Для повышения производительности вы можете настроить отсев, количество плотных слоев и активацию функций. Мы также использовали трансферное обучение с CNN под названием VGG, которая представляет собой предварительно обученную свёрточную нейронную сеть для классификации изображений.

Оценка

Наилучшие результаты были получены при использовании VGG, которые были верны примерно в 68–70% случаев, но даже линейные модели работали очень хорошо. Хотя точность 50% не кажется чем-то большим, это все же больше, чем если бы вы взяли картинку и метку наугад. Там вы будете правы примерно в 20% случаев.

Однако в этом конкретном наборе данных VGG работает даже лучше, чем люди. Разница между CNN и MLP заключалась в том, что CNN извлекали признаки, которые они считали важными сами по себе, в то время как мы передавали либо пиксели, либо ориентиры в качестве признаков MLP.

Многослойный персептрон

MLP — это подкатегория нейронных сетей. Они состоят из одного или нескольких слоев нейронов. Входные слои — это место, куда подаются данные, после которых может быть один или несколько скрытых слоев. Прогнозы исходят из выходного слоя.

из keras.models import Sequential
из keras.utils import to_categorical
из keras.layers import Dense, Dropout, Flatten, Activation
из keras.losses import categorical_crossentropy
из keras.callbacks import EarlyStopping, ModelCheckpoint
из keras.models import load_model
из keras.optimizers import Adam, SGDmlp_model = Sequential()mlp_model.add(Dense(1024, input_shape = (2304),), активация = 'relu' , kernel_initializer='glorot_normal'))mlp_model.add(Dense(512, активация = 'relu', kernel_initializer='glorot_normal'))mlp_model.add(Dense(5, активация = 'softmax'))mlp_model.compile(потеря= categorical_crossentropy, оптимизатор = SGD (lr = 0,001), metrics = ['точность']) контрольная точка = ModelCheckpoint ('best_mlp_model.h5', verbose = 1,
монитор = 'val_acc', save_best_only = True, режим =' 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)

Наша точность с использованием пикселей составила около 50%, она увеличилась, когда вместо использования значений пикселей мы использовали расстояния между ориентирами лица. Однако нам нужны были еще более точные модели, поэтому мы решили использовать CNN.

Сверточные нейронные сети

ширина, высота = 48, 48X_train = X_train.reshape(len(X_train),высота,ширина)X_test = X_test.reshape(len(X_test),высота,ширина)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), активация='relu', padding='same', input_shape = (ширина, высота, 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, активация='relu'))cnn_model.add(Dropout(0.2))cnn_model.add(Dense(5, активация='softmax'))checkpoint = ModelCheckpoint('best_cnn_model.h5', verbose=1 , monitor='val_loss',save_best_only=True, mode='auto')cnn_model.compile(loss=categorical_crossentropy,
оптимизатор=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= Истина) cnn_model.save('cnn_model.h5')

Для повышения производительности вы можете настроить отсев, количество плотных слоев и активацию функции. Мы также использовали трансферное обучение с CNN под названием VGG, которая представляет собой предварительно обученную свёрточную нейронную сеть для классификации изображений.

Оценка

Наилучшие результаты были получены при использовании VGG, которые были верны примерно в 68–70% случаев, но даже линейные модели работали очень хорошо. Хотя точность 50% не кажется чем-то большим, это все же больше, чем если бы вы взяли картинку и метку наугад. Там вы будете правы примерно в 20% случаев.

Однако в этом конкретном наборе данных VGG работает даже лучше, чем люди. Разница между CNN и MLP заключалась в том, что CNN извлекали признаки, которые они считали важными сами по себе, в то время как мы передавали либо пиксели, либо ориентиры в качестве признаков MLP.