Друзья! Наверняка многие из вас уже слышали про когнитивные сервисы, которые позволяют одним вызовом REST API решать сложные задачи — определять эмоции и возраст человека по фотографии, делать машинный перевод текста и т.д. Часто когнитивные сервисы внедряют в приложения или веб-бекенд. Сегодня наш большой друг, сотрудник компании VRTech и Game-разработчик Григорий Дядиченко расскажет нам, как внедрять когнитивные сервисы в игры на Unity, а также пригласит вас на митап Unity-разработчиков, где можно будет обсудить это подробнее.



В этой статье мне бы хотелось рассказать про интеграцию Microsoft Cognitive Services в Unity; про то, как делать HTTP запросы к сервисам через класс WWW (если вдруг кто-то ещё не сталкивался с этим и не знает) и рассказать, с какими неожиданными для меня проблемами я столкнулся, разрабатывая приложение с использованием этих сервисов для Google Play.
 


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

Когда-то я уже писал статью про когнитивные сервисы, и даже конкретно про Emotions API. В ней использовалась библиотека для UWP, которую нельзя использовать в Unity проекте. Поэтому недавно мне пришла в голову идея, что неплохо было бы написать обёртку для этих сервисов для Unity. И я взялся за дело.

Эти сервисы являются интересным и недорогим инструментом для создания “вау эффекта” на выставках, сбора контактов и подобных задач. Работать с ними в принципе в разы проще, нежели с тем же OpenCV. В контексте разработки игр можно сделать прикольную плюшку для игрока, которая позволяет генерировать аватарку игроку по его фотографии.

Перейдём к описанию самой обёртки. На данный момент в ней частично покрыты Emotions API и Face API.
 


Взаимодействие с решением построено очень просто. Вы создаёте нужный вам сервис, указывая в конструкторе SubscriptionKey (для удобства в демо сценах для их хранения создан ScriptableObject), а дальше создаёте корутину, в которой забираете необходимые вам данные.
 
Пример
private IEnumerator CheckEmotions()
{
    EmotionService emoServ = new EmotionService(SubscriptionKeys.Instance.EmotionsApiKey);
    while (true)
    {
         yield return new WaitForEndOfFrame();
         yield return emoServ.GetEmoInfoCoroutine(_WebCam.Screenshot);
         var emotions = emoServ.LastEmotions;
         if (emotions != null)
         {
              _MaxEmotionValue.text = GetMaxEmotionOnScreenshot(emotions);
         }
         yield return new WaitForSeconds(DELAY);
     }
}


Бесплатную пробную версию subscription key можно получить на сайте когнитивных сервисов Microsoft
 
Итак, зачем тут корутины? Дело в том, что самый удобный способ обращаться к сервисам — это Rest API. Проще всего в Unity это делается с помощью класса WWW, в котором запрос работает асинхронно. Есть множество способов дождаться его выполнения.

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

А можно сделать корутинами, что и реализовано в данной версии обёртки.
 
Пример
private IEnumerator CreateRecognizeRequestAndSaveResponseCoroutine(
           string contentHeader, 
           byte[] data)
{
    Dictionary<string, string> headers = new Dictionary<string, string>();
    headers.Add(Constants.SUB_KEY_HEADER, _SubscriptionKey);
    headers.Add(Constants.CONTENT_TYPE_HEADER, contentHeader);
    WWW request = new WWW(_RecognizeRequestUrl, data, headers);
    yield return new WaitUntil(() => request.isDone);
    ParseEmotionsFromJson(request.text);
}


Данный способ работает неплохо, и устраивал меня, когда я писал своё приложение под андроид. Так как анализ фотографий занимает некоторое время, чтобы пользователь не сидел без дела, я решил интегрировать рекламу. Но в ходе интеграции рекламы появились неожиданные проблемы. Пользователь смотрит рекламу, профиль анализируется — профит, но не тут-то было. Тут меня ждала особенность, про которую я не знал относительно Unity Ads на андроиде. Дело в том, что во время показа рекламы блокируется главный поток, поэтому для анализа профиля было решено вынести всё в отдельный поток.
 
Там меня ждало новое, но вполне логичное открытие. Оказывается, класс WWW может работать только в главном потоке. Поэтому пришлось всё писать на System.Net (версии 2.0, так как в Unity именно она). И я бы выложил это решение в репозиторий, но там потребовалось подписывать SSL-сертификат, что неочевидно, и может приводить к непредвиденным последствиям у пользователя обёртки. Если вдруг кому-то будет интересно, то я могу её выложить отдельным проектом на гитхабе, но с точки зрения реализации там нет ничего сложного.
 
(Не самый красивый пример сделанный на скорую руку)

Пример в отдельном потоке
private void CreateRecognizeRequest()
{
    Clear();
    _Thread = new Thread(Run);
    _Thread.Start(); 
}
private void Run()
{
    WebHeaderCollection headers = new WebHeaderCollection();
    headers.Add(Constants.SUB_KEY_HEADER, _SubscriptionKey);
    var request = HttpWebRequest.Create(_RecognizeRequestUrl);
    request.ContentType = _ContentHeader;
    request.Headers = headers;
    request.ContentLength = _Data.Length;
    request.Method = WebRequestMethods.Http.Post;
    var dataStream = request.GetRequestStream();
    dataStream.Write(_Data, 0, _Data.Length);
    dataStream.Close();
    var response = request.GetResponse();
    dataStream = response.GetResponseStream();
    StreamReader reader = new StreamReader(dataStream);
    string responseString = reader.ReadToEnd();

    reader.Close();
    dataStream.Close();
    response.Close();
    Debug.Log(responseString);
    if (!TryParseEmotionsFromJson(responseString))
    {
        Run();
    }
    else
    {
        _IsDataReady = true;
    }
}


Ещё одно забавное открытие было обнаружено при тестировании Face API. Хотелось сделать такой эффект идеальной улыбки.
 


Face API может возвращать тот же набор эмоций, что и Emotions API. Но в ходе тестов я обнаружил, что результаты разнятся, при этом Emotions API работает чуть более стабильно и точно. Поэтому для данного эффекта, лендмарки лица (чтобы правильно поставить звёздочку) забирались из Face API, а эмоции — из Emotions API.

В ближайшее время я планирую вернуться к реализации этой обёртки и к поиску новых приколов, связанных с использованием Microsoft Cognitive Services. А пока в проекте есть Demo сцены, в которых показано простейшее взаимодействие EmotionService с веб камерой. Кроме того, некоторые полезные утилиты для скриншотов (скрипт, который делает скриншот определённого RectTransform к примеру)

Возвращаясь к обёртке, скачать и следить за её развитием можно в Github репозитории. (Возможно после митапа дойдут руки написать документацию)
 

Unity Moscow Meetup #3


7 июня в ВШБИ пройдёт третий митап Unity разработчиков в Москве. Если вы занимаетесь разработкой на Unity или она вам интересна — приходите! Мероприятие бесплатное, регистрация обязательна, зарегистрироваться и узнать более подробную информацию можно тут

А так же, чтобы сделить за последующими мероприятиями и посмотреть материалы с прошлых встреч, можете вступить в группы:

> VK: vk.com/unimosmeet
> FB: www.facebook.com/groups/unimosmeet
 

Об авторе


Дядиченко Григорий — ведущий Unity разработчик в VRTech. Организатор Unity Moscow Meetup. Увлекается алгоритмами на графах, разработкой игр и всем, что связано с компьютерной графикой.
Поделиться с друзьями
-->

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