Мы продолжаем наш туториал об использовании кастомных жестов в связке Kinect+Unity. В первой части мы рассмотрели процесс обучения жестов, в результате чего у нас получилась обученная модель в виде .gdb файла. Сегодня мы будем использовать эту модель в Unity.
Создадим проект в Unity, добавим скачанные пакеты Kinect’a: Assets -> Import package -> Custom package…, выбирем Kinect.VisualGestureBuilder.2.0.1410.19000.unitypackage и Kinect.2.0.1410.19000.unitypackage (версии могут отличаться). Может возникнуть проблема с тем, что некоторые файлы в этих пакетах одинаковые, Unity добавляет оба файла с именами File.cs и File 1.cs, в этом случае просто удаляем все файлы с индексом 1 (список будет в сообщениях об ошибках).
Структура пустого проекта с добавленными пакетами:
Запустим готовый пример, чтобы убедиться, что все работает. В скачанном пакете для Unity есть два примера: GreenScreen и KinectView. Из примера KinectView добавим две папки «Materials» и «Scripts» и файл «MainScene.unity». Откроем сцену (MainScene.unity) и запустим. Должно получится примерно следующее:
Если мы добились такого результата, то у нас все работает, можем приступать к основной части.
Сперва добьемся, чтобы по срабатыванию жеста в лог выводилось сообщение.
Из Kinect SDK нам понадобятся:
Кроме того, нам понадобятся два класса для обработки полученных кадров с Kinect’а: VisualGestureBuilderFrameReader и BodyFrameReader.
Подразумевается, что у нас на данном этапе пустой Unity проект с импортированными пакетами Kinect’а, как в примере выше.
Создадим пустой объект (GameObject -> Create Empty), назовем его KinectManager. Добавим к нашему объекту новый скрипт с именем KinectManagerScript (в данном туториале рассматриваются только скрипты, написанные на C#). Открываем скрипт, добавляем using’и:
Внутри класса объявляем объекты упомянутых выше классов:
Теперь нам нужно проинициализировать значения и добавить обработчики событий. Создадим метод InitKinect() и будем вызывать его в методе Start(). Для начала загрузим жесты из нашей модели:
Замечание: мы сталкивались с проблемой, когда Kinect включается не мгновенно, и, следовательно, метод Open() не отрабатывал. Это лечилось периодической проверкой флага IsAvailable в Update и открытием в случае, если Kinect доступен
Запускаем приложение, и если все хорошо, то мы увидим в логе следующее:
Это значит, что мы успешно открыли и загрузили жесты (в нашем случае один) из нашей обученной модели.
Очевидно, что жесты нужно детектировать у человека, поэтому нам нужно получить список всех людей, которых видит Kinect. Инициализируем объект _bodyFrameSource и _bodyFrameReader и добавим обработчик события «кадр пришел» (делаем все это в InitKinect):
Аналогично инициализируем _gestureFrameSource, поставим на паузу и добавим обработчик события:
В обработчике _bodyFrameReader_FrameArrived мы хотим получить информацию о людях и, если в кадре есть кто-нибудь, выбрать первого попавшегося (для простоты), жесты которого будем отслеживать.
Запустим приложение. Когда Kinect увидит хотя бы одного человека, мы увидим «_currentBody is not null», если никого в кадре нет — «_currentBody is null». Пример лога:
Мы искали активного человека для того, чтобы распознавать его жесты. Если мы нашли человека, сохраняем его id в _gestureFrameSource и убираем с паузы. Наше условие изменится на следующее:
Последнее, что нам нужно от Kinect’a – это обработчик непосредственно жестов. Проверяем валидность нашего id человека, получаем текущий кадр так же, как мы делали это в _bodyFrameReader_FrameArrived:
Получаем текущий результат распознавание дискретных жестов:
если есть какие-нибудь жесты, смотрим, наш жест или нет:
Пороговое значение Confidence зависит от качества вашего обучения, то есть это такое значение, ниже которого шум, а выше которого жест (вспомним «елочку» из первой части), подбирается оно эмпирически :)
Запустив приложение, мы увидим, что на один жест происходит множественное детектирование. Это связано с тем, что результаты для каждого кадра независимы, а так как жест выполняется не мгновенно (несколько кадров), то мы получаем положительный результат для каждого из кадров. Устранить это можно логическим флагом:
Последнее, что нам осталось сделать с KinectManagerScript, это сгенерировать событие, когда мы увидели жест. Объявляем:
и когда нашли жест:
На этом заканчиваем с KinectManagerScript. Вернемся к нашей сцене и создадим сферу. Назовем «MainSphere», добавим новый скрипт «MainSphereScript». В методе Start() создадим обработчик события, проверим с помощью лога, что все работает:
В принципе, это и есть цель нашего туториала, чтобы наш жест сделать некоторым событием, на которое мы можем подписаться и выполнить действия, которые нам необходимы. Для чистоты эксперимента добавим движения нашей сферы по жесту, в результате получится примерно следующее:
Скрипты:
MainSphereScript.cs
KinectManagerScript.cs
Настройка Kinect + Unity
Создадим проект в Unity, добавим скачанные пакеты Kinect’a: Assets -> Import package -> Custom package…, выбирем Kinect.VisualGestureBuilder.2.0.1410.19000.unitypackage и Kinect.2.0.1410.19000.unitypackage (версии могут отличаться). Может возникнуть проблема с тем, что некоторые файлы в этих пакетах одинаковые, Unity добавляет оба файла с именами File.cs и File 1.cs, в этом случае просто удаляем все файлы с индексом 1 (список будет в сообщениях об ошибках).
Структура пустого проекта с добавленными пакетами:
Запустим готовый пример, чтобы убедиться, что все работает. В скачанном пакете для Unity есть два примера: GreenScreen и KinectView. Из примера KinectView добавим две папки «Materials» и «Scripts» и файл «MainScene.unity». Откроем сцену (MainScene.unity) и запустим. Должно получится примерно следующее:
Если мы добились такого результата, то у нас все работает, можем приступать к основной части.
Сперва добьемся, чтобы по срабатыванию жеста в лог выводилось сообщение.
Из Kinect SDK нам понадобятся:
- KinectSensor – класс, представляющий сенсор Kinect;
- VisualGestureBuilderDatabase – класс, представляющий собой базу жестов;
- VisualGestureBuilderFrameSource – обработка жестов на кадрах, полученных с Kinect’а;
- Body – класс, представляющий человека;
- Gesture – класс, представляющий жест;
- BodyFrameSource — информация о найденных людях на кадрах, получаемых с Kinect'a.
Кроме того, нам понадобятся два класса для обработки полученных кадров с Kinect’а: VisualGestureBuilderFrameReader и BodyFrameReader.
Загрузка жестов в Unity
Подразумевается, что у нас на данном этапе пустой Unity проект с импортированными пакетами Kinect’а, как в примере выше.
Создадим пустой объект (GameObject -> Create Empty), назовем его KinectManager. Добавим к нашему объекту новый скрипт с именем KinectManagerScript (в данном туториале рассматриваются только скрипты, написанные на C#). Открываем скрипт, добавляем using’и:
using Microsoft.Kinect.VisualGestureBuilder;
using Microsoft.Kinect;
Внутри класса объявляем объекты упомянутых выше классов:
VisualGestureBuilderDatabase _dbGestures;
Windows.Kinect.KinectSensor _kinect;
VisualGestureBuilderFrameSource _gestureFrameSource;
Windows.Kinect.BodyFrameSource _bodyFrameSource;
VisualGestureBuilderFrameReader _gestureFrameReader;
Windows.Kinect.BodyFrameReader _bodyFrameReader;
Gesture _swipeUpDown; // наш жест
Windows.Kinect.Body[] _bodies; // все пользователи, найденные Kinect'ом
Windows.Kinect.Body _currentBody = null; //Текущий пользователь, жесты которого мы отслеживаем
public string _getsureBasePath = "upDown.gbd"; //Путь до нашей обученной модели
Теперь нам нужно проинициализировать значения и добавить обработчики событий. Создадим метод InitKinect() и будем вызывать его в методе Start(). Для начала загрузим жесты из нашей модели:
void InitKinect()
{
_dbGestures = VisualGestureBuilderDatabase.Create(_getsureBasePath);
_bodies = new Windows.Kinect.Body[6];
_kinect = Windows.Kinect.KinectSensor.GetDefault();
_kinect.Open();
_gestureFrameSource = VisualGestureBuilderFrameSource.Create(_kinect, 0);
foreach (Gesture gest in _dbGestures.AvailableGestures)
{
if (gest.Name == "UpDownSwipe_Right")
{
_gestureFrameSource.AddGesture(gest);
_swipeUpDown = gest;
Debug.Log("Added:" + gest.Name);
}
}
}
Замечание: мы сталкивались с проблемой, когда Kinect включается не мгновенно, и, следовательно, метод Open() не отрабатывал. Это лечилось периодической проверкой флага IsAvailable в Update и открытием в случае, если Kinect доступен
Запускаем приложение, и если все хорошо, то мы увидим в логе следующее:
Это значит, что мы успешно открыли и загрузили жесты (в нашем случае один) из нашей обученной модели.
Детектирование жестов
Очевидно, что жесты нужно детектировать у человека, поэтому нам нужно получить список всех людей, которых видит Kinect. Инициализируем объект _bodyFrameSource и _bodyFrameReader и добавим обработчик события «кадр пришел» (делаем все это в InitKinect):
_bodyFrameSource = _kinect.BodyFrameSource;
_bodyFrameReader = _bodyFrameSource.OpenReader();
_bodyFrameReader.FrameArrived += _bodyFrameReader_FrameArrived;
Аналогично инициализируем _gestureFrameSource, поставим на паузу и добавим обработчик события:
_gestureFrameReader = _gestureFrameSource.OpenReader();
_gestureFrameReader.IsPaused = true;
_gestureFrameReader.FrameArrived += _gestureFrameReader_FrameArrived;
В обработчике _bodyFrameReader_FrameArrived мы хотим получить информацию о людях и, если в кадре есть кто-нибудь, выбрать первого попавшегося (для простоты), жесты которого будем отслеживать.
Исходный код метода
void _bodyFrameReader_FrameArrived(object sender, Windows.Kinect.BodyFrameArrivedEventArgs args)
{
var frame = args.FrameReference;
using (var multiSourceFrame = frame.AcquireFrame())
{
multiSourceFrame.GetAndRefreshBodyData(_bodies); //обновляем данные о найденных людях
_currentBody = null;
foreach (var body in _bodies)
{
if (body != null && body.IsTracked)
{
_currentBody = body; // для простоты берем первого найденного человека
break;
}
}
if (_currentBody != null)
{
Debug.Log("_currentBody is not null");
}
else
{
Debug.Log("_currentBody is null");
}
}
}
Запустим приложение. Когда Kinect увидит хотя бы одного человека, мы увидим «_currentBody is not null», если никого в кадре нет — «_currentBody is null». Пример лога:
Мы искали активного человека для того, чтобы распознавать его жесты. Если мы нашли человека, сохраняем его id в _gestureFrameSource и убираем с паузы. Наше условие изменится на следующее:
if (_currentBody != null)
{
Debug.Log("_currentBody is not null");
_gestureFrameSource.TrackingId = _currentBody.TrackingId;
_gestureFrameReader.IsPaused = false;
}
else
{
Debug.Log("_currentBody is null");
_gestureFrameSource.TrackingId = 0;
_gestureFrameReader.IsPaused = true;
}
Последнее, что нам нужно от Kinect’a – это обработчик непосредственно жестов. Проверяем валидность нашего id человека, получаем текущий кадр так же, как мы делали это в _bodyFrameReader_FrameArrived:
if (_gestureFrameSource.IsTrackingIdValid)
{
Debug.Log("Tracking id is valid, value = " + _gestureFrameSource.TrackingId);
using (var frame = args.FrameReference.AcquireFrame())
{
if (frame != null)
{
/*…*/
}
}
}
Получаем текущий результат распознавание дискретных жестов:
var results = frame.DiscreteGestureResults;
если есть какие-нибудь жесты, смотрим, наш жест или нет:
if (results != null && results.Count > 0)
{
DiscreteGestureResult swipeUpDownResult;
results.TryGetValue(_swipeUpDown, out swipeUpDownResult);
Debug.Log("Result not null");
if (swipeUpDownResult.Confidence > 0.1)
{
Debug.Log("Up Down Gesture");
}
}
Пороговое значение Confidence зависит от качества вашего обучения, то есть это такое значение, ниже которого шум, а выше которого жест (вспомним «елочку» из первой части), подбирается оно эмпирически :)
Запустив приложение, мы увидим, что на один жест происходит множественное детектирование. Это связано с тем, что результаты для каждого кадра независимы, а так как жест выполняется не мгновенно (несколько кадров), то мы получаем положительный результат для каждого из кадров. Устранить это можно логическим флагом:
bool gestureDetected = false;
…
if (swipeUpDownResult.Confidence > 0.1)
{
if (!gestureDetected)
{
gestureDetected = true;
Debug.Log("Up Down Gesture");
}
}
else
{
gestureDetected = false;
}
Использование жестов
Последнее, что нам осталось сделать с KinectManagerScript, это сгенерировать событие, когда мы увидели жест. Объявляем:
public delegate void SimpleEvent();
public static event SimpleEvent OnSwipeUpDown;
и когда нашли жест:
if (!gestureDetected)
{
gestureDetected = true;
Debug.Log("Up Down Gesture");
if (OnSwipeUpDown!= null)
OnSwipeUpDown();
}
На этом заканчиваем с KinectManagerScript. Вернемся к нашей сцене и создадим сферу. Назовем «MainSphere», добавим новый скрипт «MainSphereScript». В методе Start() создадим обработчик события, проверим с помощью лога, что все работает:
void Start ()
{
KinectManagerScript.OnSwipeUpDown += new KinectManagerScript.SimpleEvent(KinectManagerScript_OnSwipeUpDown);
}
void KinectManagerScript_OnSwipeUpDown()
{
Debug.Log("upDown From listener");
}
В принципе, это и есть цель нашего туториала, чтобы наш жест сделать некоторым событием, на которое мы можем подписаться и выполнить действия, которые нам необходимы. Для чистоты эксперимента добавим движения нашей сферы по жесту, в результате получится примерно следующее:
Скрипты:
MainSphereScript.cs
KinectManagerScript.cs