В современном мире наши самые глубокие эмоции часто выражаются не словами, а тонкими нюансами выражения лица. Эти мгновенные вспышки эмоций, будь то краткая улыбка или краткий удивленный взгляд, часто передают гораздо больше, чем слова. Но что, если бы мы могли использовать возможности искусственного интеллекта (ИИ) для исследования этого царства невысказанных эмоций? Что, если бы мы могли научить машину понимать и интерпретировать человеческие эмоции, просто анализируя выражение лица? Добро пожаловать в увлекательное путешествие по распознаванию эмоций с помощью ИИ.
В этой записной книжке мы погружаемся в сложный процесс распознавания эмоций из видеоданных с использованием ИИ, управляемого захватывающим сочетанием библиотек Python и преобразующих моделей ИИ. Нашим гидом в этом путешествии является модель ViT-Face-Expression от Hugging Face, предварительно обученная модель на основе трансформера, специально разработанная для задач обнаружения эмоций.
Мы будем использовать широкий спектр библиотек Python, чтобы ориентироваться в этом увлекательном ландшафте. Мы будем использовать numpy
и pandas
для обработки данных, matplotlib
и seaborn
для визуализации данных и moviepy
для обработки видео. Для обработки изображений мы воспользуемся возможностями Python Imaging Library (PIL
). Мы будем использовать MTCNN
facenet_pytorch
для точного распознавания лиц и трансформеры для использования передовой модели ViT-Face-Expression.
Присоединяйтесь к нам, когда мы пройдемся по обнаружению эмоций в режиме реального времени, кадр за кадром, в видеоклипе. Мы будем решать различные задачи, включая обнаружение лиц, обработку кадров без обнаруженных лиц и визуализацию изменения вероятности каждой эмоции на протяжении всего видео. Мы также обсудим интерпретацию наших результатов и более широкие применения и последствия распознавания эмоций.
Импорт библиотек
import os # Set cache directories for XDG and Hugging Face Hub os.environ['XDG_CACHE_HOME'] = '/home/msds2023/jlegara/.cache' os.environ['HUGGINGFACE_HUB_CACHE'] = '/home/msds2023/jlegara/.cache' import torch # Set device to GPU if available, otherwise use CPU device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') print('Running on device: {}'.format(device)) import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas import seaborn as sns from tqdm.notebook import tqdm from moviepy.editor import VideoFileClip, ImageSequenceClip import torch from facenet_pytorch import (MTCNN) from transformers import (AutoFeatureExtractor, AutoModelForImageClassification, AutoConfig) from PIL import Image, ImageDraw
Загрузить видеоданные
# Load your video scene = 'White Chicks - short.mp4' clip = VideoFileClip(scene) # Save video frames per second vid_fps = clip.fps # Get the video (as frames) video = clip.without_audio() video_data = np.array(list(video.iter_frames()))
Обнаружение однокадровых эмоций
Функция detect_emotions(image)
является центральной частью нашего процесса обнаружения эмоций для однокадрового обнаружения эмоций. Он принимает в качестве входных данных один кадр изображения (в формате PIL Image) и выполняет несколько операций, чтобы в конечном итоге отобразить обнаруженное лицо и гистограмму соответствующих вероятностей эмоций.
Во-первых, функция создает копию изображения для рисования, чтобы убедиться, что мы не перезаписываем исходное изображение. Затем он использует модель MTCNN
, популярный детектор лиц на основе глубокого обучения, для обнаружения лиц на изображении. Если лицо обнаружено, функция вырезает лицо из изображения, используя координаты ограничивающей рамки, возвращенные MTCNN.
Затем он предварительно обрабатывает обрезанное лицо, чтобы оно соответствовало входному формату, требуемому нашей моделью обнаружения эмоций. Затем предварительно обработанное изображение лица проходит через модель обнаружения эмоций, которая возвращает логиты, представляющие необработанные прогнозные оценки для каждого класса эмоций. Эти логиты преобразуются в вероятности с помощью функции softmax.
Затем функция извлекает сопоставление идентификаторов классов с метками классов (т. е. именами эмоций), используя конфигурацию предварительно обученной модели trpakov/vit-face-expression
. Это сопоставление используется для преобразования вероятностей класса (преобразованных в список Python) в словарь, который связывает каждую метку эмоции с соответствующей вероятностью.
Наконец, функция использует matplotlib и seaborn для создания фигуры с двумя подграфиками — один для отображения обнаруженного лица, а другой для визуализации вероятностей эмоций в виде горизонтальной гистограммы. Полосы имеют цветовую кодировку в соответствии с эмоциями, которые они представляют. Полученный рисунок обеспечивает визуальное понимание прогнозов модели обнаружения эмоций для данного кадра изображения.
def detect_emotions(image): """ Detect emotions from a given image, displays the detected face and the emotion probabilities in a bar plot. Parameters: image (PIL.Image): The input image. Returns: PIL.Image: The cropped face from the input image. """ # Create a copy of the image to draw on temporary = image.copy() # Use the MTCNN model to detect faces in the image sample = mtcnn.detect(temporary) # If a face is detected if sample[0] is not None: # Get the bounding box coordinates of the face box = sample[0][0] # Crop the detected face from the image face = temporary.crop(box) # Pre-process the cropped face to be fed into the # emotion detection model inputs = extractor(images=face, return_tensors="pt") # Pass the pre-processed face through the model to # get emotion predictions outputs = model(**inputs) # Apply softmax to the logits to get probabilities probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1) # Retrieve the id2label attribute from the configuration id2label = AutoConfig.from_pretrained( "trpakov/vit-face-expression" ).id2label # Convert probabilities tensor to a Python list probabilities = probabilities.detach().numpy().tolist()[0] # Map class labels to their probabilities class_probabilities = {id2label[i]: prob for i, prob in enumerate(probabilities)} # Define colors for each emotion colors = { "angry": "red", "disgust": "green", "fear": "gray", "happy": "yellow", "neutral": "purple", "sad": "blue", "surprise": "orange" } palette = [colors[label] for label in class_probabilities.keys()] # Prepare a figure with 2 subplots: one for the face image, # one for the bar plot fig, axs = plt.subplots(1, 2, figsize=(15, 6)) # Display the cropped face in the left subplot axs[0].imshow(np.array(face)) axs[0].axis('off') # Create a horizontal bar plot of the emotion probabilities in # the right subplot sns.barplot(ax=axs[1], y=list(class_probabilities.keys()), x=[prob * 100 for prob in class_probabilities.values()], palette=palette, orient='h') axs[1].set_xlabel('Probability (%)') axs[1].set_title('Emotion Probabilities') axs[1].set_xlim([0, 100]) # Set x-axis limits to show percentages # Show the plot plt.show() # Initialize MTCNN model for single face cropping mtcnn = MTCNN( image_size=160, margin=0, min_face_size=200, thresholds=[0.6, 0.7, 0.7], factor=0.709, post_process=True, keep_all=False, device=device ) # Load the pre-trained model and feature extractor extractor = AutoFeatureExtractor.from_pretrained( "trpakov/vit-face-expression" ) model = AutoModelForImageClassification.from_pretrained( "trpakov/vit-face-expression" )
Этот раздел кода используется для проверки нашей функции обнаружения эмоций на разных кадрах видео. Пять кадров (10-й, 40-й, 200-й, 355-й и 380-й) выбираются произвольно, чтобы обеспечить разнообразный образец видео. Для каждого выбранного кадра выполняются следующие шаги:
- Кадр извлекается из видеоданных (которые хранятся в виде списка массивов numpy, представляющих отдельные кадры).
- Кадр преобразуется в объект
PIL
Image. Это необходимо, потому что наша функция detect_emotions ожидает ввода в виде изображенияPIL
. - Функция
detect_emotions
вызывается с изображениемPIL
в качестве входных данных. Эта функция обнаруживает лицо на изображении, прогнозирует вероятность различных эмоций и отображает обнаруженное лицо рядом с гистограммой прогнозируемых вероятностей эмоций.
Наблюдая за выходными данными для разных кадров, мы можем понять, насколько хорошо работает наша функция обнаружения эмоций в разных случаях видео.
# Choose a frame frame = video_data[10] # choosing the 10th frame # Convert the frame to a PIL image and display it image = Image.fromarray(frame) detect_emotions(image)
# Choose a frame frame = video_data[40] # choosing the 40th frame # Convert the frame to a PIL image and display it image = Image.fromarray(frame) detect_emotions(image)
# Choose a frame frame = video_data[200] # choosing the 200th frame # Convert the frame to a PIL image and display it image = Image.fromarray(frame) detect_emotions(image)
# Choose a frame frame = video_data[355] # choosing the 355th frame # Convert the frame to a PIL image and display it image = Image.fromarray(frame) detect_emotions(image)
# Choose a frame frame = video_data[380] # choosing the 380th frame # Convert the frame to a PIL image and display it image = Image.fromarray(frame) detect_emotions(image)
В запоминающейся автомобильной сцене из фильма «Белые цыпочки» мы видим персонажей Латрелла Спенсера и Маркуса Коупленда, который работает под прикрытием как Тиффани Уилсон, которые вместе едут на машине. Во время их живого пения под песню Ванессы Карлтон «Тысяча миль» наша система распознавания эмоций выделяет интересную динамику.
Маркус Коупленд (Марлон Уэйанс), замаскированный под Тиффани Уилсон, проявляет эмоции страха и печали, возможно, отражение дискомфорта и беспокойства персонажа в этом веселом сценарии. С другой стороны, Латрелл Спенсер (Терри Крюс), который с энтузиазмом распевает песню, по мнению нашей модели, демонстрирует счастье.
Эти результаты подчеркивают способность системы точно распознавать и различать различные эмоциональные состояния даже в таких сложных и полных юмора сценах, как эта. Это прекрасный пример того, как технология обнаружения эмоций может предоставить интригующую информацию о наших любимых моментах на экране.
Покадровое обнаружение эмоций
Функция detect_emotions
играет решающую роль в нашем конвейере покадрового обнаружения эмоций. Он принимает изображение в качестве входных данных и инициирует двухэтапный процесс.
Первый шаг включает в себя обнаружение лиц на изображении с использованием модели многозадачных каскадных сверточных сетей (MTCNN
), как и в нашем обнаружении одного кадра. Если на изображении идентифицируется лицо, его область извлекается для формирования нового обрезанного изображения.
На втором этапе это обрезанное изображение лица подвергается предварительной обработке для подготовки к модели обнаружения эмоций. Эта процедура включает преобразование изображения в тензор PyTorch, который модель может эффективно интерпретировать. Затем тензор проходит через нашу модель обнаружения эмоций, основанную на архитектуре Vision Transformer (ViT) — современной модели для задач классификации изображений.
Модель генерирует прогнозы в виде логитов для каждой эмоции. Затем функция softmax преобразует эти логиты в вероятности. Используя атрибут id2label
из конфигурации модели, мы сопоставляем вероятности с соответствующими метками эмоций. В результате получается словарь, содержащий предсказанные эмоции и связанные с ними вероятности.
Если функция не обнаружит ни одного лица на изображении, она вернет None
как для изображения лица, так и для вероятностей класса. Это тщательное рассмотрение гарантирует, что наш последующий анализ основан на достоверных и значимых данных, тем самым повышая надежность наших выводов.
def detect_emotions(image): """ Detect emotions from a given image. Returns a tuple of the cropped face image and a dictionary of class probabilities. """ temporary = image.copy() # Detect faces in the image using the MTCNN group model sample = mtcnn.detect(temporary) if sample[0] is not None: box = sample[0][0] # Crop the face face = temporary.crop(box) # Pre-process the face inputs = extractor(images=face, return_tensors="pt") # Run the image through the model outputs = model(**inputs) # Apply softmax to the logits to get probabilities probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1) # Retrieve the id2label attribute from the configuration config = AutoConfig.from_pretrained( "trpakov/vit-face-expression" ) id2label = config.id2label # Convert probabilities tensor to a Python list probabilities = probabilities.detach().numpy().tolist()[0] # Map class labels to their probabilities class_probabilities = { id2label[i]: prob for i, prob in enumerate(probabilities) } return face, class_probabilities return None, None
Функция create_combined_image
принимает обнаруженное изображение лица и словарь вероятностей эмоций в качестве входных данных и генерирует комбинированное изображение, на котором изображено лицо вместе с горизонтальным столбчатым графиком вероятностей эмоций.
В этой функции мы сначала устанавливаем схему цветового кодирования для разных эмоций, назначая каждой из них уникальный цвет. Затем функция генерирует новую фигуру с двумя подграфиками, один для отображения изображения лица, а другой для гистограммы вероятностей эмоций.
На левом подграфике обнаруженное лицо отображается без какой-либо оси, подчеркивая визуальное содержание по любой числовой ссылке. На правом подграфике создается seaborn
горизонтальная полоса, отображающая каждую эмоцию в соответствии с ее вероятностью. Этот гистограммный график предлагает немедленное визуальное сравнение предсказанных эмоций, выступая в качестве эффективного эмоционального профиля для обнаруженного лица.
Затем функция преобразует фигуру matplotlib
, содержащую лицо и график, в массив numpy
, используя буфер холста. Это преобразование необходимо для реконструкции видео и для сохранения целостности сгенерированного визуального контента. Наконец, функция закрывает фигуру, чтобы освободить память, и возвращает массив numpy
, представляющий объединенное изображение. Это комбинированное изображение обеспечивает согласованную визуальную сводку, эффективно инкапсулируя изображение лица и соответствующие ему эмоциональные вероятности.
def create_combined_image(face, class_probabilities): """ Create an image combining the detected face and a barplot of the emotion probabilities. Parameters: face (PIL.Image): The detected face. class_probabilities (dict): The probabilities of each emotion class. Returns: np.array: The combined image as a numpy array. """ # Define colors for each emotion colors = { "angry": "red", "disgust": "green", "fear": "gray", "happy": "yellow", "neutral": "purple", "sad": "blue", "surprise": "orange" } palette = [colors[label] for label in class_probabilities.keys()] # Create a figure with 2 subplots: one for the # face image, one for the barplot fig, axs = plt.subplots(1, 2, figsize=(15, 6)) # Display face on the left subplot axs[0].imshow(np.array(face)) axs[0].axis('off') # Create a barplot of the emotion probabilities # on the right subplot sns.barplot(ax=axs[1], y=list(class_probabilities.keys()), x=[prob * 100 for prob in class_probabilities.values()], palette=palette, orient='h') axs[1].set_xlabel('Probability (%)') axs[1].set_title('Emotion Probabilities') axs[1].set_xlim([0, 100]) # Set x-axis limits # Convert the figure to a numpy array canvas = FigureCanvas(fig) canvas.draw() img = np.frombuffer(canvas.tostring_rgb(), dtype='uint8') img = img.reshape(canvas.get_width_height()[::-1] + (3,)) plt.close(fig) return img
Блок кода по существу снижает частоту кадров видео в два раза, что приводит к уменьшению количества кадров вдвое. Этот процесс, называемый пропуском кадров, помогает оптимизировать вычислительную эффективность последующих операций, таких как обнаружение эмоций, которые могут быть ресурсоемкими. Переменная skips
определяет количество кадров, которые нужно пропустить после каждого выбранного кадра, и в этом случае каждый второй кадр выбирается из исходного видео для дальнейшей обработки.
skips = 2 reduced_video = [] for i in tqdm(range(0, len(video_data), skips)): reduced_video.append(video_data[i])
Этот раздел кода управляет основными задачами проекта. Для каждого кадра в сокращенном наборе видеоданных скрипт выполняет следующие действия:
- Сначала он преобразует фрейм в соответствующий тип данных (
uint8
), чтобы он был совместим с функциейdetect_emotions
. - Затем вызывается функция
detect_emotions
, принимающая в качестве входных данных каждый кадр, преобразованный в изображение PIL. Функция возвращает обрезанное лицо (если оно найдено) и связанные с ним вероятности эмоций. - Если в кадре было обнаружено лицо, создается составное изображение, объединяющее обрезанное лицо и столбчатую диаграмму, отображающую вероятности эмоций. Затем это изображение добавляется в список
combined_images
для дальнейшего использования. - Если в кадре не было обнаружено ни одного лица, вероятность эмоций устанавливается на
None
для этого конкретного кадра. - Наконец, вероятности эмоций (или
None
, если лицо не было обнаружено) добавляются к спискуall_class_probabilities
. Этот список накапливает вероятности эмоций для каждого кадра и составляет ключевую часть выходных данных для проекта.
# Define a list of emotions emotions = ["angry", "disgust", "fear", "happy", "neutral", "sad", "surprise"] # List to hold the combined images combined_images = [] # Create a list to hold the class probabilities for all frames all_class_probabilities = [] # Loop over video frames for i, frame in tqdm(enumerate(reduced_video), total=len(reduced_video), desc="Processing frames"): # Convert frame to uint8 frame = frame.astype(np.uint8) # Call detect_emotions to get face and class probabilities face, class_probabilities = detect_emotions(Image.fromarray(frame)) # If a face was found if face is not None: # Create combined image for this frame combined_image = create_combined_image(face, class_probabilities) # Append combined image to the list combined_images.append(combined_image) else: # If no face was found, set class probabilities to None class_probabilities = {emotion: None for emotion in emotions} # Append class probabilities to the list all_class_probabilities.append(class_probabilities)
В этом сегменте сценария список объединенных изображений, сгенерированный на предыдущем шаге, преобразуется обратно в видеоклип с помощью функции ImageSequenceClip
из библиотеки moviepy.editor
. Параметр fps
(кадры в секунду) задается в соответствии с частотой кадров исходного видео, разделенной на количество пропусков кадров, сделанных ранее.
Затем видео отображается в среде IPython с использованием метода ipython_display
. Это видео визуализирует результаты обнаружения эмоций на покадровой основе, при этом каждый кадр отображает обнаруженное лицо и гистограмму, показывающую распределение вероятности каждой эмоции. Результат может предоставить полезную информацию об эмоциональной динамике на протяжении всего видео.
# Convert list of images to video clip clip_with_plot = ImageSequenceClip(combined_images, fps=vid_fps/skips) # Choose the frame rate (fps) according to your requirement # Write the video to a file with a specific frame rate clip_with_plot.write_videofile("output_video.mp4", fps=vid_fps/skips) # Display the clip clip_with_plot.ipython_display(width=900)
Этот сегмент кода создает график временного ряда, демонстрирующий изменение вероятности обнаруженных эмоций на протяжении всего видео. Он переводит список вероятностей в DataFrame, масштабирует их в процентах, а затем отображает их во времени. Отдельный цвет для каждой эмоции обеспечивает четкое визуальное разделение.
Полученная визуализация может служить инструментом для определения диапазонов времени, отмеченных конкретными эмоциями. Кроме того, это может быть полезным для выявления сцен со значительным эмоциональным сдвигом, таких как кульминации или поворотные моменты в повествовании, предоставляя ценную информацию для анализа видео.
# Define colors for each emotion colors = { "angry": "red", "disgust": "green", "fear": "gray", "happy": "yellow", "neutral": "purple", "sad": "blue", "surprise": "orange" } # Convert list of class probabilities into a DataFrame df = pd.DataFrame(all_class_probabilities) # Convert probabilities to percentages df = df * 100 # Create a line plot plt.figure(figsize=(15,8)) for emotion in df.columns: plt.plot(df[emotion], label=emotion, color=colors[emotion]) plt.xlabel('Frame Order') plt.ylabel('Emotion Probability (%)') plt.title('Emotion Probabilities Over Time') plt.legend() plt.show()
Заключение
Этот проект успешно продемонстрировал, как применять глубокое обучение к задаче обнаружения эмоций в видео с использованием модели на основе Transformer, в частности ViT. Мы достигли этого, проанализировав кадры из выбранной сцены в фильме «Белые цыпочки», выявив эмоции, которые демонстрировали персонажи Латрелл Спенсер и Маркус/Тиффани во время комично запоминающейся поездки на автомобиле.
Результаты были поучительны, показывая четкие эмоциональные сдвиги, которые соответствовали развитию сцены. Обнаруженные эмоции хорошо коррелировали с ситуациями персонажей, подтверждая эффективность модели.
Рекомендации
Хотя эта модель доказала свою эффективность для этой задачи, стоит отметить, что ее можно улучшить. Например, обнаружение эмоций является более сложным, чем просто анализ выражений лица, поскольку оно часто включает контекст, диалог и язык тела. Будущая работа может быть сосредоточена на интеграции этих дополнительных факторов для более целостного понимания.
Более того, эффективность обработки может быть повышена, особенно для видео высокого разрешения. В этом отношении может помочь реализация пакетной обработки или использование методов аппаратного ускорения.
Наконец, производительность модели в различных условиях освещения и с различной ориентацией лица может быть оценена и улучшена. Для более точного и надежного обнаружения эмоций важно обеспечить надежную работу в различных реальных сценариях.
Документация по генеративному ИИ
Приложение OpenAI GPT-4, или ChatGPT, было ключевым компонентом в выполнении этого проекта. Его интеграция охватила несколько этапов проекта:
- Идея. Модель ИИ использовалась в качестве партнера по мозговому штурму, определяя цели проекта и шаги для их достижения.
- Помощь в написании кода. ChatGPT был надежным помощником для решения задач кодирования, включая понимание ошибок, отладку и предоставление решений для сложных задач кодирования.
- Документация и комментирование. Модель ИИ руководила процессом комментирования кода и документирования процедур, объясняя сложные задачи простым и доступным языком.
- Рассказывание историй. ChatGPT был использован для создания убедительного повествования о целях, выводах и последствиях проекта, помогая контекстуализировать техническую работу в более широком контексте.
- Написание отчета и корректура. ChatGPT сыграл важную роль в написании этого отчета, обеспечив ясность, точность и удобочитаемость, а также являясь профессиональным корректором.
Успешное завершение этого проекта подчеркивает неоценимую роль моделей ИИ, таких как ChatGPT, в современных научных исследованиях и разработках. ChatGPT зарекомендовал себя как универсальный инструмент, повышающий производительность, снижающий когнитивную нагрузку и улучшающий передачу сложных идей. Этот опыт настоятельно рекомендует рассмотреть такие инструменты ИИ для будущих научных исследований.