Пользовательские жесты, Kinect + Unity. Часть 2

в 5:10, , рубрики: gesture recognition, kinect v2, machine learning, unity3d, Блог компании Singularis, машинное обучение, обработка изображений

Мы продолжаем наш туториал об использовании кастомных жестов в связке Kinect+Unity. В первой части мы рассмотрели процесс обучения жестов, в результате чего у нас получилась обученная модель в виде .gdb файла. Сегодня мы будем использовать эту модель в Unity.

Пользовательские жесты, Kinect + Unity. Часть 2 - 1

Настройка 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 (список будет в сообщениях об ошибках).
Структура пустого проекта с добавленными пакетами:

Пользовательские жесты, Kinect + Unity. Часть 2 - 2

Запустим готовый пример, чтобы убедиться, что все работает. В скачанном пакете для Unity есть два примера: GreenScreen и KinectView. Из примера KinectView добавим две папки «Materials» и «Scripts» и файл «MainScene.unity». Откроем сцену (MainScene.unity) и запустим. Должно получится примерно следующее:

Пользовательские жесты, Kinect + Unity. Часть 2 - 3

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

Сперва добьемся, чтобы по срабатыванию жеста в лог выводилось сообщение.
Из 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 + Unity. Часть 2 - 4

Это значит, что мы успешно открыли и загрузили жесты (в нашем случае один) из нашей обученной модели.

Детектирование жестов

Очевидно, что жесты нужно детектировать у человека, поэтому нам нужно получить список всех людей, которых видит 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». Пример лога:
Пользовательские жесты, Kinect + Unity. Часть 2 - 5

Мы искали активного человека для того, чтобы распознавать его жесты. Если мы нашли человека, сохраняем его 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

Автор: Singularis

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js