company_banner

Играем с эмоциями: Microsoft Cognitive Services + Unity

    Друзья! Наверняка многие из вас уже слышали про когнитивные сервисы, которые позволяют одним вызовом 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. Увлекается алгоритмами на графах, разработкой игр и всем, что связано с компьютерной графикой.
    Microsoft
    606.87
    Microsoft — мировой лидер в области ПО и ИТ-услуг
    Share post

    Similar posts

    Comments 0

    Only users with full accounts can post comments. Log in, please.