Друзья!
Наверняка многие из вас уже слышали про когнитивные сервисы, которые позволяют одним вызовом 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. Увлекается алгоритмами на графах, разработкой игр и всем, что связано с компьютерной графикой.
Автор: shwars