В данной статье мы рассмотрим создание системы распознавания эмоций на лицах людей на основе платы Raspberry Pi 4 и пакетов OpenCV, TensorFlow и Keras. Мы будем использовать заранее (предварительно) обученную модель распознавания эмоций на лицах людей в видео потоке реального времени. Мы использовали набор данных “FER2013” для обучения модели с помощью свёрточной нейронной сети (Convolutional Neural Network, CNN), похожей на VGG.
Система распознавания эмоций (выражения лица, мимики) на лицах людей может быть использована во множестве приложений. Например, с ее помощью можно изучать или анализировать чувства людей. Также некоторые компании используют такие системы для анализа уровня депрессии своих работников. Также подобные технологии иногда используются и в компьютерных играх, где с их помощью производители игр пытаются оценить степень удовлетворенности людей, играющих в их творение.
Общие принципы работы нашего проекта
Для распознавание эмоций (выражения лица) с помощью Raspberry Pi мы в нашем проекте будем выполнять следующую последовательность шагов:
- Обнаруживать лица во входном видеопотоке реального времени.
- Выделять значащую для нас область лица (ROI).
- Использовать модель распознавания выражения лица для определения эмоционального состояния человека.
В нашем проекте мы будем использовать 6 классов распознаваемых эмоций: 'Angry' (сердитый), 'Fear' (страх), 'Happy' (счастливый), 'Neutral' (безразличный), 'Sad' (грустный), 'Surprise' (удивление). То есть распознаваемые изображения эмоций должны относиться к одному из этих классов.
Также на нашем сайте вы можете посмотреть следующие проекты, в которых плата Raspberry Pi совместно с библиотекой OpenCV использовалась для обработки изображений:
- определение социальной дистанции с помощью Raspberry Pi и OpenCV;
- обнаружение движения на видео с помощью Raspberry Pi и OpenCV;
- определение пола и возраста людей с помощью Raspberry Pi и OpenCV;
- обнаружение масок на лицах людей с помощью Raspberry Pi и OpenCV;
- распознавание лиц с помощью Raspberry Pi и библиотеки OpenCV.
Необходимые компоненты
- Плата Raspberry Pi 4 (купить на AliExpress).
- Камера для Raspberry Pi (купить на AliExpress).
Установка OpenCV на Raspberry Pi 4
Перед началом установки OpenCV обновите программное обеспечение платы Raspberry Pi до последней версии с помощью команды:
1 |
sudo apt-get update |
Затем установим необходимые дополнения, которые нам понадобятся для дальнейшей установки OpenCV.
1 2 3 4 5 6 |
sudo apt-get install libhdf5-dev -y sudo apt-get install libhdf5-serial-dev –y sudo apt-get install libatlas-base-dev –y sudo apt-get install libjasper-dev -y sudo apt-get install libqtgui4 –y sudo apt-get install libqt4-test –y |
Далее установим OpenCV на Raspberry Pi с помощью установщика pip.
1 |
pip3 install opencv-contrib-python==4.1.0.25 |
Установка Tensorflow и Keras на Raspberry Pi 4
Перед установкой Tensorflow и Keras установим ряд необходимых библиотек:
1 2 3 4 5 6 7 8 9 10 |
sudo apt-get install python3-numpy sudo apt-get install libblas-dev sudo apt-get install liblapack-dev sudo apt-get install python3-dev sudo apt-get install libatlas-base-dev sudo apt-get install gfortran sudo apt-get install python3-setuptools sudo apt-get install python3-scipy sudo apt-get update sudo apt-get install python3-h5py |
Далее непосредственно установим библиотеки Tensorflow и Keras с помощью pip. Если в вашей системе используется python3 в качестве окружения по умолчанию для python, то в терминале, соответственно, необходимо использовать команду pip3.
1 2 |
pip3 install tensorflow pip3 install keras |
Объяснение программы Raspberry Pi для распознавания эмоций
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты. Полный каталог со всем необходимым для данного проекта можно скачать по следующей ссылке.
Скачанный вами каталог проекта будет содержать подкаталог (Haarcascades), файл на Python (emotion1.py) и модель (ferjj.h5).
Вначале кода программы мы подключим (импортируем) все используемые библиотеки.
Примечание: мы используем TensorFlow API чтобы импортировать библиотеку Keras.
1 2 3 4 5 |
from tensorflow.keras import Sequential from tensorflow.keras.models import load_model import cv2 import numpy as np from tensorflow.keras.preprocessing.image import img_to_array |
Далее загрузим заранее обученную модель (находится в скачанном каталоге проекта) с помощью функции load_model() из библиотеки Keras. Затем создадим список (словарь), в котором мы назначим метки нашим 6 используемым классам (эмоций).
1 2 3 4 |
# We have 6 labels for the model class_labels = {0: 'Angry', 1: 'Fear', 2: 'Happy', 3: 'Neutral', 4: 'Sad', 5: 'Surprise'} classes = list(class_labels.values()) # print(class_labels) |
После этого укажем путь к классификатору Haarcascade с помощью функции CascadeClassifier() из библиотеки OpenCV.
1 |
face_classifier = cv2.CascadeClassifier('./Haarcascades/haarcascade_frontalface_default.xml') |
Далее мы используем функцию text_on_detected_boxes() для придания внешнего вида меткам, которые мы будем подписывать сверху на обнаруженном лице человека. Параметры в функции text_on_detected_boxes() мы использовали по умолчанию, однако вы можете изменить их на те значения, которые будут вам необходимы.
1 2 3 4 5 6 |
# This function is for designing the overlay text on the predicted image boxes. def text_on_detected_boxes(text,text_x,text_y,image,font_scale = 1, font = cv2.FONT_HERSHEY_SIMPLEX, FONT_COLOR = (0, 0, 0), FONT_THICKNESS = 2, rectangle_bgr = (0, 255, 0)): |
Тестирование работы распознавания эмоций на изображениях
Внутри функции face_detector_image(img) функция cvtColor() будет использоваться для преобразования входного изображения в черно-белое (с оттенками серого) как показано на следующем рисунке.
Затем с изображения мы будем извлекать зону, существенную нам для анализа лица (Region Of Interest, ROI). Используемая нами функция будет возвращать нам 3 важных параметра: ROI лица, координаты лица и исходное изображение. Также вокруг обнаруженного лица будет рисоваться прямоугольник.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def face_detector_image(img): gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY) # Convert the image into GrayScale image faces = face_classifier.detectMultiScale(gray, 1.3, 5) if faces is (): return (0, 0, 0, 0), np.zeros((48, 48), np.uint8), img allfaces = [] rects = [] for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) roi_gray = gray[y:y + h, x:x + w] roi_gray = cv2.resize(roi_gray, (48, 48), interpolation=cv2.INTER_AREA) allfaces.append(roi_gray) rects.append((x, w, y, h)) return rects, allfaces, img |
Далее мы будем передавать данные входного изображения в модель распознавания эмоций с помощью функции face_detector_image(img).
После обработки данных моделью выходной результат будет отображаться на экране вместе с обнаруженным лицом. Выходной результат будет браться из списка class_labels, который мы создали ранее. Мы будем использовать функцию text_on_detected_boxes() чтобы задать внешний вид меткам, которые будут выводиться на экране рядом с обнаруженными лицами. Функция imshow() будет использоваться для отображения окна.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def emotionImage(imgPath): img = cv2.imread(imgPath) rects, faces, image = face_detector_image(img) i = 0 for face in faces: roi = face.astype("float") / 255.0 roi = img_to_array(roi) roi = np.expand_dims(roi, axis=0) # make a prediction on the ROI, then lookup the class preds = classifier.predict(roi)[0] label = class_labels[preds.argmax()] label_position = (rects[i][0] + int((rects[i][1] / 2)), abs(rects[i][2] - 10)) i = + 1 # Overlay our detected emotion on the picture text_on_detected_boxes(label, label_position[0],label_position[1], image) cv2.imshow("Emotion Detector", image) cv2.waitKey(0) cv2.destroyAllWindows() |
Распознавание эмоций (выражения лица) в видеопотоке
Для обнаружения лица в видеопотоке будет использоваться функция face_detector_video(img). В данную функцию входной кадр видео передается в виде изображения. Эта функция возвращает координаты обнаруженного лица, его область существенную для анализа (ROI) и исходный кадр. Функция rectangle() используется для рисования прямоугольника вокруг обнаруженного лица.
1 2 3 4 5 6 7 8 9 10 11 |
def face_detector_video(img): # Convert image to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_classifier.detectMultiScale(gray, 1.3, 5) if faces is (): return (0, 0, 0, 0), np.zeros((48, 48), np.uint8), img for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), thickness=2) roi_gray = gray[y:y + h, x:x + w] roi_gray = cv2.resize(roi_gray, (48, 48), interpolation=cv2.INTER_AREA) return (x, w, y, h), roi_gray, img |
В этом фрагменте кода мы будем использовать нашу модель для распознавания выражения лица (эмоции) в видеопотоке и отображения выходного результата работы модели в видеопотоке в реальном времени.
В первых двух строках этой функции мы извлекаем кадр (frame) из входного видеопотока. Затем мы передаем этот кадр в функцию face_detector_video(frame). Далее функция predict() из классификатора используется для распознавания выражений (эмоций) обнаруженных лиц (на кадре). Затем для каждой распознанной эмоции мы назначаем метку исходя из значений записанных в списке class_labels. И, наконец, функция imshow() используется для отображения на экране распознанной эмоции рядом с каждым обнаруженным лицом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def emotionVideo(cap): while True: ret, frame = cap.read() rect, face, image = face_detector_video(frame) if np.sum([face]) != 0.0: roi = face.astype("float") / 255.0 roi = img_to_array(roi) roi = np.expand_dims(roi, axis=0) # make a prediction on the ROI, then lookup the class preds = classifier.predict(roi)[0] label = class_labels[preds.argmax()] label_position = (rect[0] + rect[1]//50, rect[2] + rect[3]//50) text_on_detected_boxes(label, label_position[0], label_position[1], image) # You can use this function for your another opencv projects. fps = cap.get(cv2.CAP_PROP_FPS) cv2.putText(image, str(fps),(5, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) else: cv2.putText(image, "No Face Found", (5, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) cv2.imshow('All', image) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() |
Далее следует основная (главная) функция нашей программы. Внутри нее могут использоваться функции emotionVideo() и emotionImage() в зависимости от того где вы хотите распознавать эмоции на лицах людей – на изображении или в видеопотоке. Если вы хотите распознавать эмоции на изображении, то закомментируйте первые две строки главной функции и раскомментируйте две ее оставшиеся строки. Но перед этим убедитесь, что вы записали правильный путь к входному изображению в переменной IMAGE_PATH.
1 2 3 4 5 |
if __name__ == '__main__': camera = cv2.VideoCapture(0) # If you are using an USB Camera then Change use 1 instead of 0. emotionVideo(camera) # IMAGE_PATH = "provide the image path" # emotionImage(IMAGE_PATH) # If you are using this on an image please provide the path |
Тестирование работы проекта распознавания эмоций
Перед тем, как запускать программу проекта на выполнение, подключите к плате Raspberry Pi модуль камеры как показано на следующем рисунке.
После этого убедитесь корректно ли работает камера. Если все нормально, то запустите программу проекта на выполнение. После этого у вас на экране откроется новое окно, в которое будет транслироваться видеопоток с камеры. Если программа распознает во входном видеопотоке эмоцию на лице какого-нибудь человека, то она нарисует вокруг этого лица прямоугольник зеленого цвета и сверху над ним подпишет название распознанной эмоции. Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы на Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
from tensorflow.keras import Sequential from tensorflow.keras.models import load_model import cv2 import numpy as np from tensorflow.keras.preprocessing.image import img_to_array # загружаем модель model = Sequential() classifier = load_model('ferjj.h5') # в нашей модели будет 6 классов для распознавания # поэтому назначим 6 меток для нашей модели class_labels = {0: 'Angry', 1: 'Fear', 2: 'Happy', 3: 'Neutral', 4: 'Sad', 5: 'Surprise'} classes = list(class_labels.values()) # print(class_labels) face_classifier = cv2.CascadeClassifier('./Haarcascades/haarcascade_frontalface_default.xml') # эта функция используется для задания внешнего вида надписи, отображающей распознанную эмоцию def text_on_detected_boxes(text,text_x,text_y,image,font_scale = 1, font = cv2.FONT_HERSHEY_SIMPLEX, FONT_COLOR = (0, 0, 0), FONT_THICKNESS = 2, rectangle_bgr = (0, 255, 0)): # определяем ширину и высоту текстового поля (text_width, text_height) = cv2.getTextSize(text, font, fontScale=font_scale, thickness=2)[0] # Set the Coordinates of the boxes box_coords = ((text_x-10, text_y+4), (text_x + text_width+10, text_y - text_height-5)) # Draw the detected boxes and labels cv2.rectangle(image, box_coords[0], box_coords[1], rectangle_bgr, cv2.FILLED) cv2.putText(image, text, (text_x, text_y), font, fontScale=font_scale, color=FONT_COLOR,thickness=FONT_THICKNESS) # распознавание эмоций на изображении def face_detector_image(img): gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY) # конвертируем изображение в серое faces = face_classifier.detectMultiScale(gray, 1.3, 5) if faces is (): return (0, 0, 0, 0), np.zeros((48, 48), np.uint8), img allfaces = [] rects = [] for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) roi_gray = gray[y:y + h, x:x + w] roi_gray = cv2.resize(roi_gray, (48, 48), interpolation=cv2.INTER_AREA) allfaces.append(roi_gray) rects.append((x, w, y, h)) return rects, allfaces, img def emotionImage(imgPath): img = cv2.imread(imgPath) rects, faces, image = face_detector_image(img) i = 0 for face in faces: roi = face.astype("float") / 255.0 roi = img_to_array(roi) roi = np. expand_dims(roi, axis=0) # определяем область, существенную для анализа (ROI), затем осуществляем поиск класса preds = classifier.predict(roi)[0] label = class_labels[preds.argmax()] label_position = (rects[i][0] + int((rects[i][1] / 2)), abs(rects[i][2] - 10)) i = + 1 # выводим название распознанной эмоции поверх изображения text_on_detected_boxes(label, label_position[0],label_position[1], image) cv2.imshow("Emotion Detector", image) cv2.waitKey(0) cv2.destroyAllWindows() # распознавание эмоций в видеопотоке def face_detector_video(img): # преобразуем изображение в серое gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_classifier.detectMultiScale(gray, 1.3, 5) if faces is (): return (0, 0, 0, 0), np.zeros((48, 48), np.uint8), img for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), thickness=2) roi_gray = gray[y:y + h, x:x + w] roi_gray = cv2.resize(roi_gray, (48, 48), interpolation=cv2.INTER_AREA) return (x, w, y, h), roi_gray, img def emotionVideo(cap): while True: ret, frame = cap.read() rect, face, image = face_detector_video(frame) if np.sum([face]) != 0.0: roi = face.astype("float") / 255.0 roi = img_to_array(roi) roi = np.expand_dims(roi, axis=0) # определяем область, существенную для анализа (ROI), затем осуществляем поиск класса preds = classifier.predict(roi)[0] label = class_labels[preds.argmax()] label_position = (rect[0] + rect[1]//50, rect[2] + rect[3]//50) text_on_detected_boxes(label, label_position[0], label_position[1], image) # вы можете использовать эту функцию в других своих проектах на основе opencv fps = cap.get(cv2.CAP_PROP_FPS) cv2.putText(image, str(fps),(5, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) else: cv2.putText(image, "No Face Found", (5, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) cv2.imshow('All', image) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': camera = cv2.VideoCapture(0) # если вы используете USB камеру, то используйте в качестве аргумента этой функции 1 вместо 0 emotionVideo(camera) # IMAGE_PATH = "provide the image path" # emotionImage(IMAGE_PATH) # если вы собираетесь распознавать эмоции не в видеопотоке, а на изображении, то раскомментируйте эту строку |
здравствуйте, не работает ссылка на Гугл диск с кодом, не могли бы вы прислать файлы или создать новый диск
Добрый день. Пока не могу восстановить доступ к этой ссылке потому что она не моя, а авторов статьи. У них на сайте точно такая же ссылка сейчас, я проверил, и тоже естественно не работает. Тут остается только надеяться на то, что они перезальют ссылку. Обычно они все на гитхаб выкладывают, странно почему здесь они использовали гугл диск. Ну можно еще попробовать перейти по этой ссылке, используя впн. Гугл же недавно закрыл несколько сервисов для доступа к ним из России