fbpx

Разрабатываем AR-маску с трекингом лица с помощью Huawei AR Engine

В прошлой статье мы рассмотрели как интегрировать Huawei AR Engine в Unity и создали простейшее приложение, реализующее SLAM (simultaneous localization & mapping) и находящее в окружающем пространстве плоские поверхности, на которых впоследтсвии мы размещали виртуальные объекты.

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

Чтобы не расписывать заново все шаги по интеграции движка в Unity я отправляю читателя в прошлую статью. Кратко напомню, что нужно импортировать плагин Huawei AR Engine, затем создать контроллер сцены, создать PreviewCamera, родителем которой должен быть этот контроллер, и создать у контроллера два скрипта: Session Component и Tracking Controller. Эта конструкция является некоей базой — она общая для всех AR приложений, использующих AR Engine.

После этого мы уже начинаем развивать приложении в ту сторону, в которую нужно. Первое, что нужно сделать – подобрать файл конфигурации. В прошлый раз мы использовали WorldTrackingConfig, чтобы трекать окружение. Сегодня мы трекаем лицо, поэтому берем FaceTrackingConfig. Для каждого отдельного кейса в плагине присутсвует свой конфигурационный файл. Задаем этот файл в поле Config скрипта Session Component;

Теперь нам нужно немного подправить скрипт Tracking Controller: 

namespace Common

{

using UnityEngine;

using System.Collections.Generic;

using HuaweiARUnitySDK;

using System.Collections;

using System;

using Common;

public class TrackingController : MonoBehaviour

{

     [Tooltip("plane prefabs")]

     public GameObject facePrefabs;

 

     private List<ARFace> newFaces = new List<ARFace>();

 

     public void Update()

     {

         _DrawFace();

     }

 

     private void _DrawFace()

        {

         newFaces.Clear();

         ARFrame.GetTrackables<ARFace>(newFaces, ARTrackableQueryFilter.NEW);

         for (int i = 0; i < newPlanes.Count; i++)

         {

             GameObject faceObject = Instantiate(facePrefabs, Vector3.zero, Quaternion.identity, transform);

             faceObject.GetComponent<FaceVisualizer>().Initialize(newFaces[i]);

         }

     }

}

}

Если вы сравните его с этим же скриптом из предыдущего примера, то увидите, что единственное что поменялось по сути – теперь мы получаем из объекта кадра ARFrame объекты лица ARFace вместо объектов плоскостей ARPlane. Во всем остальном скрипт абсолютно аналогичен предыдущему. Мы также получаем лицо, а потом передаем его скрипту FaceVisualizer, который отвечает за визуализации. Скрипт FaceVisualizer при этом может быть присоединен к любому префабу в сцене.

Скучная часть позади, теперь приступаем к главному – к визуализации.

В скрипте FaceVisualizer первое, что мы делаем – пишем метод Initialize():

     public void Initialize(ARFace face)

     {

         m_face = face;

     }

Сначала мы получаем наш объект лица.

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

camera.transform.position = ARFrame.GetPose().position;

camera.transform.rotation = ARFrame.GetPose().rotation;

После этого мы создаем объект facecenter. В будущем этот объект будет располагаться в геометрическом центре меша лица и повторять его движение. На данном этапе нам нужно перевести этот объект в систему координат камеры:

         facecenter.transform.position = camera.transform.position;

         facecenter.transform.rotation = camera.transform.rotation;

         facecenter.transform.SetParent(camera.transform);

Последнее, что нужно сделать при инциализации – создать объект head, то есть нашу маску, и перевести этот объект в систему координат facecenter. Команда head.transform.Rotate(0,90,0) нужна затем, чтобы на этапе инициализации развернуть маску под правильным углом относительно центра лица.  

         head.transform.position = facecenter.transform.position;

         head.transform.rotation = facecenter.transform.rotation;

         head.transform.Rotate(0,90,0);

         head.transform.SetParent(facecenter.transform);

Теперь перейдем к методу Update():

     public void Update()

     {

        camera.transform.position = ARFrame.GetPose().position;

        camera.transform.rotation = ARFrame.GetPose().rotation;

}

Сначала мы получим новые значения положения и поворота камеры. Затем мы получим положение и поворот лица. В данном случае GetPose() возвращает их в системе координат камеры. После этого мы задаем полученную трансформацию объекту facecenter, причем координату z нужно инвертировать:

        pose = m_face.GetPose();

        facecenter.transform.position = new Vector3(pose.position.x,pose.position.y,-pose.position.z);

        facecenter.transform.rotation = pose.rotation;

С объектом facecenter перемещается и объект маски head, поскольку facecenter является его родителем. Теперь наша маска расположена прямо поверх лица. В принципе на этом можно было бы закончить, но я предлагаю немного усложнить этот проект. Статичная маска – это, конечно, круто, но можно ее анимировать. Сделать так, чтобы маска повторяла движения отдельных частей лица. Например, чтобы когда вы закрываете глаза – маска делала то же самое. Или когда улыбаетесь – на маске тоже появлялась улыбка. 

Давайте попробуем реализовать улыбку. AR Engine умеет отслеживать состояния различных частей лица и возвращать эти состояния в виде числа в диапазоне от 0 до 1. Если мы говорим про глаза, то 0 будет означать, что глаз полностью открыт, а 1 – что полностью закрыт. Аналогичная история с улыбкой. Для начала получим состояния интересующих нас частей лица с помощью m_face.GetBlendShapeWithBlendName()[…]. В квадратных скобках нужно указать строку, соответствующую тому мимическому движению, которое вы хотите отследить. В нашем случае – “степень” улыбки левой и правой частей рта. И если эти значения превышают какое-то пороговое значение — мы можем произвести изменения в нашей маске. Например, заменить текстуру лица с нейтральной на улыбающуюся.       

        if (m_face.GetBlendShapeWithBlendName()["Animoji_Mouth_Smile_Right"] > 0.3 ||

            m_face.GetBlendShapeWithBlendName()["Animoji_Mouth_Smile_Left"] > 0.3)

        {

           nosmile.SetActive(false);

           smile.SetActive(true);

        }

        else

        {

           smile.SetActive(false);

           nosmile.SetActive(true);

        }

Полный список отслеживаемых мимических движений можно получить вот так:

StringBuilder sb;

var blendShapes = m_face.GetBlendShapeWithBlendName();

         foreach(var bs in blendShapes)

         {

             sb.Append(bs.Key);

             sb.Append(": ");

             sb.Append(bs.Value);

             sb.Append("\n");

         }

А вот, что у нас в итоге получается:

Маска готова и анимирована! Но весь потенциал трекинга лица все еще не раскрыт. m_face.GetBlendShapeWithBlendName()[…] позволяет извлекать мимику, но не позволяет работать с лицом на более глубоком уровне — на уровне чистой геометрии и отдельных полигонов и вершин поверхности лица. Давайте, например, попробуем сделать трекинг глаз. Парадоксально, но отследить степень открытости глаз можно в одну команду, а вот чтобы получить их координаты, придется попотеть. Для начала вернемся в метод Initialize() и добавим объекты для глаз, переводя их в систему координат центра лица:

eye1.transform.position = facecenter.transform.position;

         eye1.transform.rotation = facecenter.transform.rotation;

         eye1.transform.SetParent(facecenter.transform);

         eye2.transform.position = facecenter.transform.position;

         eye2.transform.rotation = facecenter.transform.rotation;

         eye2.transform.SetParent(facecenter.transform);

Так же во время инициализации нам нужно сделать следующее:

geom = m_face.GetFaceGeometry();

         l = geom.TriangleIndices[3985*3];

         r = geom.TriangleIndices[125*3];

 Мы сначала получаем геометрию лица. Далее, существуют методы geom.Vertices[] и geom.TriangleIndices[]. Первый возвращает одномерный массив вершин, из которых состоит поверхность лица. Второй массив содержит треугольники, из которых состоит поверхность лица, причем структура этого массива такова: он одномерен и состоит из идущих друг за другом троек значений. Каждое значение — это ссылка на массив geom.Vertices[], то есть вершина. Таким образом, нам нужно найти либо вершину либо треугольник, соответствующий той части лица, с которой мы хотим работать. Для этого существует еще один метод – geom.TriangleLabel(), который возвращает список треугольников, каждый из которых подписан одной из строк:

1: lower lip;

2: upper lip;

3: left eye;

4: right eye;

5: left eyebrow;

6: right eyebrow;

7: eyebrow center;

8: nose.

Соответственно пройдя по всему списку можно найти треугольники, соответствующие нужной части лица. После этого необходимо взять индекс этого треугольника и помножить его на 3, а затем использовать полученное значение в geom.TriangleIndices[] и получить ссылку на вершину. А далее дело за малым – в методе Update добавляем следующие строки:

eye1.transform.position = facecenter.transform.position;

           eye2.transform.position = facecenter.transform.position;

vertexr = geom.Vertices[r];

        vertexl = geom.Vertices[l];

        eye1.transform.Translate(vertexr.x,vertexr.y,vertexr.z);

        eye2.transform.Translate(vertexl.x,vertexl.y,vertexl.z);

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

Теперь посмотрим на результат:


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


Павел Кочетков, Huawei