Здравствуйте!

В этой статье мы расскажем Вам идею о том, как заставить манекена в Unreal Engine 5 повторять движения за человеком в кадре в реальном времени при помощи Python, нейронных сетей и API-запросов, а также поделимся наработками проекта “Виртуальный аватар без мокап-костюма”.

Расскажем об установке ПО, приведем инструкцию по созданию и анимированию персонажа в Unreal Engine, поговорим о нейросетях и главной причине остановки работы - API.

Изменение позы в UE5
Изменение позы в UE5

Зачем проект? Рассказываем про мокап-костюм

Это костюм для записи движений на съемочной площадке с помощью датчиков. Несколько камер, расположенных под разными углами к движению, снимают динамику маркеров-отражателей или светодиодов, распределенных по телу движущегося «актёра». Далее, специальное ПО вычисляет координаты каждого маркера в пространстве в определенные моменты времени, соотнося данные с каждой камеры.

Мокап-костюм
Мокап-костюм

В киноиндустрии на данный момент существует ряд проблем, связанных с мокап-костюмом, а именно:

  1. Цена
    Мокап-костюм, используемый при создании фильмов, стоит от 400 тыс. руб (!!!), что существенно влияет на бюджет фильма и компании в целом.

  2. Сложность
    Подготовка актера к съемке является довольно трудозатратной: чего только стоит надеть этот костюм и настроить все датчики… Да и подготовка съемочной площадки также требует немалых усилий.

  3. Время
    Кроме того, после съемки актеров в мокап костюмах требуется монтаж и наложение, что существенно увеличивает время производства фильма.

Выяснив вышеперечисленные недостатки, мы решили создать альтернативный вариант считывания движений человека в кадре.
Перейдем к его реализации!

Начало работы

Установка ПО

Сначала через лаунчер Epic Games скачаем движок Unreal Engine версии 5.0 и выше (мы используем v5.3.2) и Quixel Bridge, из которого загружаем MetaHuman.
Обратите внимание!
Движок весит очень много, поэтому необходимо, чтобы на диске было не менее 60 Гб.

Создание проекта и импорты.

После запуска UE, выберем создание проекта от 3-го лица в категории Игры. Зададим название в графе Project Name, а настройки, изображенные справа, оставим без изменений. Create!

Создание проекта
Создание проекта

Не волнуйтесь, в Unreal-е многие загрузки занимают длительное время.

Теперь мы можем поставить на поле некого робота, которого мы хотим анимировать. Обратите внимание, что сейчас мы не можем сразу поставить детализированного персонажа, похожего на реального человека - пока только доступны роботоподобные манекены. Для добавления такого железного человека перейдем в Content Drawer, перетащим его на поле.

Поле проекта
Поле проекта

Теперь сделаем импорты для работы с MetaHuman.
Войдем в систему Quixel Bridge. Выберем персонажа Trey, потому что так будет проще сопоставить опорные точки скелета персонажа с опорными точками манекена (в ином случае мы столкнулись с несоответствием роста манекена и персонажа, что привело к необходимости дополнительной настройки).

Импорт персонажа
Импорт персонажа

Для импорта найденного персонажа - навести курсор и нажать на загрузку, затем Export.

Создание лица

После установки Unreal Engine и MetaHuman мы перешли к освоению программы. Начали с простого: повторения мимики с видео на лице аватара.

Для начала мы установили приложение LiveLink, позволяющее снимать движения лица в режиме реального времени с помощью совместимого iPhone или iPad.

Затем мы загрузили видео в Unreal Engine и приступили к созданию нашего аватара. Добавили три фронта: анфас (front), левый и правый профили (left и right).

Добавление фронтов
Добавление фронтов
Предварительный результат
Предварительный результат

И последнее – наложение кожи на аватара (для этого необходимо импортировать «человека») и анимация мимики:

Наложение кожи на аватара
Наложение кожи на аватара

Перейдем непосредственно к созданию персонажа в Unreal Engine.

Создание аватара. Как настроить синхронизацию манекена и MetaHuman

Откроем несколько элементов в рабочем пространстве:
Сontent Drawer -> MetaHumans -> Trey выбирем блупринт BP_Trey, ThirdPerson -> Blueprints -> BP_ThirdPersonCharacter, ThirdPerson -> Blueprints -> BP_ThirdPersonGameMode.

Настроим BP_Trey:
Перейдя в настройки класса (Class Settings), выберем в качестве родительского класса персонажа Trey - BP_ThirdPersonCharacter (вклыдка Details, графа Parent Class).

Настройка BP_Trey
Настройка BP_Trey

Далее изменим иерархию. В компонентах (слева, Components) перенесем Body в Mesh, удалим Root.

Иерархия
Иерархия

Если мы скомпилируем проект (Compile), то получим ошибку.

Нажмем на строку с ошибкой. Откроется Set Update Animation in Editor. Перенесем Mesh на рабочее пространство и установим связь с Get Children Components. Теперь компиляция пройдет успешно.

Успешная компиляция
Успешная компиляция

Далее, перейдем на вкладку Viewport, чтобы настроить персонажа с манекеном. Обнулим положение и поворот персонажа в блоке Transform справа.

Нажатием по Live Retarget -> UseLiveRetargetMode откроем детали UseLiveRetargetMode и установим “галочку” в категории Default Value -> UseLiveRetargetMode. Увидим, что MetaHuman принял ту же позу, что и манекен.

Настройка UseLiveRetargetMode
Настройка UseLiveRetargetMode

Теперь в функциях перейдем по LiveRetargetSetup, где в схеме найдем узел RTGMetaHuman. Нажатием на browse увидим расположение соответствующего Animation Blueptint в Content Drawer.

Расположение Animation BluePrint
Расположение Animation BluePrint

Необходимо продублировать RTG_metahuman, переименовав его: далее мы будем настраивать этот дубликат. Настроим источник и таргет, выбрав соответствующие шаблоны. Настроим корень, как глобально масштабируемый (Translation Mode при нажатии на Root) для дублирования анимационных ресурсов.

Настройка источника, таргета
Настройка источника, таргета
Настройка корня
Настройка корня

Теперь надо более точно сопоставить тело манекена с персонажем. Перейдем в режим редактирования позы (Edit Retarget Pose), убедимся, что позы не совпадают полностью. Настроим показ костей, а затем, используя повороты суставов, самостоятельно повернем плечи и стопы, чтобы добиться наложения.

Настройка показа костей
Настройка показа костей
Персонаж. Ручной поворот кистей и стопы
Персонаж. Ручной поворот кистей и стопы

Сохраним и изменим ретаргетинг для анимации (RTG_metahuman_base_skel_AnumBP)

Изменение ретаргетинга для анимации
Изменение ретаргетинга для анимации

Здесь в AnimGraph-е, в позе для ретаргетинга (Retarget Pose and Mesh), изменим ресурс на тот, что был создан только что (RTG_Manny…). Скомпилируем и закроем.

Изменение ресурса
Изменение ресурса

Теперь перейдем в BP_Trey. Видим, что персонаж повторяет движение манекена. Чтобы убрать видимость манекена, нажмем на сетку персонажа, в деталях по поиску найдем видимость и уберем ее. В Visibility Based Anim Tick Option выберем Always Tuck Pose and Refresh Bones, для обеспечения синхронизации.

Убираем видимость сетки персонажа
Убираем видимость сетки персонажа

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

Настройка LOD
Настройка LOD

В режиме игры BP_ThirdPersonGameMode изменим родительский класс по умолчанию на блупринт персонажа Trey.

Смена родительского класса
Смена родительского класса

Последняя настройка. Из режима персонажа от 3 лица скопируем весь график событий кроме Event BeginPlay и вставим в график событий BP_Trey. Соединить добавленный в граф событий персонажа Event BeginPlay и Hair LODSetup со вставленным графиком.

Граф событий
Граф событий

Скомпилируем и сохраним. Готово! Мы настроили синхронизацию манекена с MetaHuman.

Управление частями тела персонажа

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

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

Способ 1. TwoBoneIK

Первый способ заключается в использовании метода TwoBoneIK.

Во-первых, он удобен в использовании. С помощью построения небольшого графа, добавления пары узлов программа корректно сработала.

Во_вторых, он предоставляет возможность покоординатно менять положение костей и суставов. Эта задача была важна для нас, поскольку данные о позе человека на изображении приходят в виде массива точек (словарь {кость : [x, y, probability]}).

С помощью TwoBoneIK нам удалось задавать координаты суставов, костей тела и сразу же визуально замечать изменения в позе.

Таким образом, данный метод очень удобен в простоте, в возможности работать с каждой костью отдельно.

Выполнение

Нажатием на персонажа на игровом поле, откроем его блупринт BP_Trey. Зайдем в его сетку в компонентах слева. В деталях сетки увидим класс анимации. Перейдем к его местоположению. Двойным нажатием на подсвеченный элемент в Content Browser откроем ABP.

Открытие ABP
Открытие ABP

Теперь построим нехитрую схему с нашей функцией, как показано на изображении. Чтобы найти node с Output Pose - нажмем пару раз на стрелку “назад”. После создания схемы, скомпилируем этап.

Поиск node с Output Pose
Поиск node с Output Pose

Теперь нам надо указать, какую кость мы двигаем. Чтобы посмотреть иерархию костей, надо перейти в соответствующий mesh, найдя его в Content Drawer.

Иерархия костей
Иерархия костей

Нажатием на node функции в деталях установим название кости IKBone, EffectorLocationSpace, EffectorSpaceBoneName, Joint Target Location Space, Joint Target Location Space Bone Name.

Установка названий костей
Установка названий костей

Скомпилируем, зададим положение координат, которое мы хотим.

Задание положения координат
Задание положения координат

Теперь и наш персонаж принял то же положение, что и ABP.

Персонаж
Персонаж

Способ 2. PoseableMesh

В данном случае удалось удалось делать API-запросы, однако метод не привел к изменению позы манекена на поле.

Приведем пример с запросами API.
Мы перешли к тестированию апи запросов на обычном BP с родительским классом Actor.

Создадим новый блупринт класс, установим ему родительский класс Actor, переименуем.

BluePrint класс
BluePrint класс

Откроем созданный объект и добавим в его компоненты сетку.

Добавление сетки в компоненты объекта
Добавление сетки в компоненты объекта

Перейдем в детали сетки и установим skeletal mesh.

Установка skeletal mesh
Установка skeletal mesh

Теперь в графе событий составим схему установки локации кости:

Схема установки локации кости
Схема установки локации кости

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

Отсутствие изменений
Отсутствие изменений

VitPose и OpenCV

После ознакомления с Unreal Engine мы поняли, что хоть и MetaHuman – отличный вариант для повторения мимики человека, он не работает с движениями тела. Тогда наша команда начала копаться в нейронках и искать подходящие для нас варианты:

  • Tensorflow - библиотека для машинного обучения, позволяющая автоматически находить и классифицировать образы, достигая качества человеческого восприятия;

  • Pose AI – плагин для Unreal Engine, захватывающий движение всего тела;

  • Posenet – модель, предназначенная для обнаружения ключевых точек тела на фигурах людей;

  • DollarsMono – плагин для получения информации о движении в режиме реального времени и отправки информации о костях в Unity или Unreal Engine для управления моделями скелетов. Минусы – платно;

  • GoogleAI – модуль для анимации кистей рук. Минусы – получение точек только на кистях рук;

  • YOLOv8 - новейшее семейство моделей обнаружения объектов, сегментации экземпляров и классификации изображений на базе YOLO от Ultralytics, обеспечивающих самые современные характеристики.

Перебрав и изучив эти варианты, мы наткнулись на easyViTPose – плагин для простой и быстрой 2d-оценки людей и животных в разных позах с использованием SOTA ViTPose в режиме реального времени и с поддержкой нескольких скелетов.

Данный модуль считывает аж 133 точки на теле человека, что и послужило решающим фактором при выборе нейронки для нашего проекта.
Сначала мы скачали easyViTPose с гитхаба:

  
   # Блок загрузки необходимых библиотек и модулей
   !git clone https://github.com/JunkyByte/easy_ViTPose.git
   !cd easy_ViTPose/ && pip install -r requirements.txt && pip install -e .
   !pip install huggingface_hub
   python -m easy_ViTPose/easy_ViTPose/vit_utils/visualization.py
  

Далее мы определили основные константы, такие как путь к файлам, размер модели, датасет и т.д.:

  
   # Определение необходимых констант
   MODEL_SIZE = 'b'  #@param ['s', 'b', 'l', 'h']
   YOLO_SIZE = 's'  #@param ['s', 'n']
   DATASET = 'wholebody'  #@param ['coco_25', 'coco', 'wholebody', 'mpii', 'aic', 'ap10k', 'apt36k']
   ext = '.pth'
   ext_yolo = '.pt'
   # Определение необходимых путей
   import os
   from huggingface_hub import hf_hub_download
   MODEL_TYPE = "torch"
   YOLO_TYPE = "torch"
   REPO_ID = 'JunkyByte/easy_ViTPose'
   FILENAME = os.path.join(MODEL_TYPE, f'{DATASET}/vitpose-' + MODEL_SIZE + f'-{DATASET}') + ext
   FILENAME_YOLO = 'yolov8/yolov8' + YOLO_SIZE + ext_yolo
 
   print(f'Downloading model {REPO_ID}/{FILENAME}')
   model_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME)
   yolo_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME_YOLO)
  

Затем мы импортировали предобученную модель

  
   # Загрузка модели
   from easy_ViTPose import VitInference
   model = VitInference(model_path, yolo_path, MODEL_SIZE,
                     dataset=DATASET, yolo_size=320, is_video=False)
  

Далее мы запустили нейронку на одной картинке для демонстрации работоспособности и получили массив опорных точек

  
# Запуск модели на примере картинки
import numpy as np
from io import BytesIO
from PIL import Image
from urllib.request import urlopen
url = 'https://i.ibb.co/gVQpNqF/imggolf.jpg'
img = np.array(Image.open(BytesIO(urlopen(url).read())), dtype=np.uint8)
 
frame_keypoints = model.inference(img)
img = model.draw(show_yolo=True)
 
from google.colab.patches import cv2_imshow
cv2_imshow(img[..., ::-1])
  

После этого мы преобразовали массив точек в словарь

  
# Преобразование массива точек в словарь
keypoints = ['nose', 'left_eye','right_eye','left_ear','right_ear','left_shoulder','right_shoulder','left_elbow','right_elbow','left_wrist','right_wrist','left_hip','right_hip','left_knee','right_knee','left_ankle','right_ankle','left_big_toe','left_small_toe','left_heel','right_big_toe','right_small_toe','right_heel','face-0','face-1','face-2','face-3','face-4','face-5','face-6','face-7','face-8','face-9','face-10','face-11','face-12','face-13','face-14','face-15','face-16','face-17','face-18','face-19','face-20','face-21','face-22','face-23','face-24','face-25','face-26','face-27','face-28','face-29','face-30','face-31','face-32','face-33','face-34','face-35','face-36','face-37','face-38','face-39','face-40','face-41','face-42','face-43','face-44','face-45','face-46','face-47','face-48','face-49','face-50','face-51','face-52','face-53','face-54','face-55','face-56','face-57','face-58','face-59','face-60','face-61','face-62','face-63','face-64','face-65','face-66','face-67','left_hand_root','left_thumb1','left_thumb2','left_thumb3','left_thumb4','left_forefinger1','left_forefinger2','left_forefinger3','left_forefinger4','left_middle_finger1','left_middle_finger2','left_middle_finger3','left_middle_finger4','left_ring_finger1','left_ring_finger2','left_ring_finger3','left_ring_finger4','left_pinky_finger1','left_pinky_finger2','left_pinky_finger3','left_pinky_finger4','right_hand_root','right_thumb1','right_thumb2','right_thumb3','right_thumb4','right_forefinger1','right_forefinger2','right_forefinger3','right_forefinger4','right_middle_finger1','right_middle_finger2','right_middle_finger3','right_middle_finger4','right_ring_finger1', 'right_ring_finger2', 'right_ring_finger3', 'right_ring_finger4', 'right_pinky_finger1', 'right_pinky_finger2', 'right_pinky_finger3','right_pinky_finger4']
output = dict(zip(keypoints, frame_keypoints[0]))
  

В конце мы сделали запрос по API к объекту и функции TwoBoneIK в Unreal Engine

  
# API - запрос к объекту и функции TwoBoneIK в Unreal Engine
import os
import time
import requests
 
url = "http://localhost:30000/remote/object/call"
sess = requests.Session()
 
def change_bone(object_path, ikbone, effector_target, joint_target, x, y):
  sess.put(url, json = {
  	"objectPath" : object_path,
  	"functionName": "TwoBoneIK",
  	"parameters" : {
      	"IKBone" : ikbone,
      	"Effector Location Space" : "Bone Space",
      	"Effector Target": effector_target,
      	"Joint Target Location Space" : "Parent Bone Space",
      	"Joint Target": joint_target,
          "Effector Location": {"X": x, "Y": y, "Z":0}
  	}
  })
 
 
change_bone(object_path="D:/Unreal Projects/Avatar/Content/__ExternalActors__/ThirdPerson/Maps/ThirdPersonMap/9/1J/2GPO9HV94ZVG7HY4NP6IZ1.uasset",
            ikbone="hand_l", effector_target="hand_l", joint_target="upperarm_l", x=100, y=20)
  

Также мы продемонстрировали работу нейронной сети на видео

  
# Загружаем видео файл
video_capture = cv2.VideoCapture('video_file1.mp4')
writer = cv2.VideoWriter(
  	'output2.mp4',
      cv2.VideoWriter_fourcc(*'mp4v'),   # codec
  	25.0,  # fps
  	(int(video_capture.get(3)),int(video_capture.get(4))),  # width, height
  	isColor=len(frame.shape) > 2)
 
while True:
 
	ret, frame_rgb = video_capture.read()
 
	if not ret:
    	break
 
	# Преобразуем кадр в RGB формат
	#frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
 
	# Детектируем людей и опорные точки скелета на кадре
	frame_keypoints = model.inference(frame_rgb)
	frame_rgb = model.draw(show_yolo=True)
 
	# Отображаем кадр с прямоугольниками и опорными точками
	#cv2_imshow(frame_rgb)
 
    writer.write(frame_rgb)
 
	if cv2.waitKey(1) & 0xFF == ord('q'):
    	break
writer.release()
  

Связь API и Unreal Engine

Отлично, кости движимы, мы знаем, как получить координаты точек тела, теперь приступим к этапу передачи координат в Unreal через API.

Настройка API. Версия с TwoBoneIK.

API запрос осуществляется через программу Insomnia.
Перед его созданием необходимо включить плагин API в UE5, перезагрузить проект.

Включение плагина API
Включение плагина API

Затем во встроенной консоли cmd слева внизу прописать WebControl.StartServer.
Далее пропишем запрос json формата.
Путь к объекту получаем нажатием правой кнопкой мыши по персонажу

Копирование пути к объекту
Копирование пути к объекту

Прописываем название функции и параметры. Отправляем запрос.

Итак, мы подошли к проблеме, с которой мы столкнулись при работе с TwoBoneIK. Мы не смогли подобраться к ней через путь к объекту в запросе API.

Предположения, почему не сработали запросы:

  1. Неправильное название вызываемой функции.

TwoBoneIK - название функции в UE. Прописав только ее, мы получаем ошибку.

Ошибка отсутствия функции TwoBoneIK
Ошибка отсутствия функции TwoBoneIK

В документации Unreal Engine мы нашли название AnimNode_TwoBoneIK при обращении в JSON.
Однако тоже получили ошибку.

Ошибка отсутствия функции AnimNode_TwoBoneIK
Ошибка отсутствия функции AnimNode_TwoBoneIK
  1. Неправильный путь к объекту

С помощью команды ниже мы выводим информацию об объектах.

Вывод информации об объектах
Вывод информации об объектах
Вывод информации об объектах
Вывод информации об объектах

Таким образом была найдена необходимая функция.

Необходимая функция
Необходимая функция

Посмотрим информацию по ней

Информация о функции
Информация о функции

На этом шаге возникли трудности с обращением к параметрам.

Обращение к Node, MaxStretchScale, к параметрам
Обращение к Node, MaxStretchScale, к параметрам

Обращение к функции через functionName:

Обращение через functionName
Обращение через functionName

Настройка API. Версия с PoseableMesh

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

Запрос на получение локации:

Запрос на получение локации
Запрос на получение локации

Установка локации:

Установка локации
Установка локации

Получение локации после ее установки. Видим, что координаты изменились.

Получение локации после ее установки
Получение локации после ее установки

Значения на выходе запроса GetLocation меняются, то есть обращение по API происходит успешно. Но мы не смогли выявить, где же именно в UE эти изменения происходят.

Предположения, почему не сработали запросы:

  1. Конфликт Mesh и PoseableMesh Возможно происходил конфликт сеток персонажа и сетки PoseableMesh. Мы перешли к тестированию апи запросов на обычном BP с родительским классом Actor.

Дерево скелета
Дерево скелета
  1. Неправильное соединение с персонажем. Возможно, в графе событий пропущен узел или в некотором месте пропущена связь объектов.

Заключение

Итак, мы выяснили, что для реализации синхронизации информации о позе человека на видеозаписи с движком Unreal Engine (чтобы создать соответствующей анимации детализированного персонажа) необходимо применить:

  • Метод машинного обучения на основе компьютерного зрения и алгоритмов динамического отслеживания движений;

  • Плагины и функции Unreal Engine

  • API-запросы в формате JSON для автоматической передачи обработанных данных в движок Unreal Engine

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

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

Авторы: Агафонова Екатерина ( @Katya156 ) и Гольцова Мария ( @marvisge )

Комментарии (3)


  1. ri1wing
    13.05.2024 10:06
    +1

    Опять в каком-то универе препод ставит автоматы за пост на хабре?


  1. Vipo
    13.05.2024 10:06

    Странно, не отобразились картинки!


  1. FurryMileon
    13.05.2024 10:06

    Нифига себе тут страданий. Лучше перейди на Unreal Engine 5.4, там уже столько заготовок и удобств.