Полное руководство по преобразованию кода из Kinect SDK бета-версии в последнюю версию Kinect SDK

У меня есть семестровый проект с Kinect. Я должен улучшить определенное приложение и добавить к нему новые функциональные возможности. Проблема возникает из-за того, что приложение использует устаревший kinect SDK. Некоторые из дополнительных функций, которые я хочу добавить (лично), должны использовать новый Kinect SDK. Есть ли краткое руководство по переводу Kinect SDK Beta на новейший SDK? Какие изменения были внесены помимо сборочных ссылок?

2 ответа

Решение

Я нашел следующую информацию от этого поста:

Вся заслуга информации здесь и далее идет к оригинальному постеру этой статьи. Я просто делюсь своими знаниями

Если вы работали с бета-версией Kinect SDK до 1 февраля, возможно, вы испытали смятение в связи с количеством изменений API, которые были введены в v1.

Для получения правого и левого суставов код, который вы использовали для написания

Joint jointRight = sd.Joints[JointID.HandRight];
Joint jointLeft = sd.Joints[JointID.HandLeft];

Сначала нужно создать скелет так

Skeleton[] skeletons = new Skeleton[0];

и тогда ты должен пройти через скелет

 foreach (Skeleton skel in skeletons)

и тогда вы получите суставы, используя

Joint rightHand = skeleton.Joints[JointType.HandRight];
Joint leftHand = skeleton.Joints[JointType.HandLeft];

для высоты камеры вы использовали, чтобы написать это

_nui.NuiCamera.ElevationAngle = 17;

теперь вы просто используете созданный вами датчик (ниже объясняется, как он заменил класс Runtime) и пишете

sensor.ElevationAngle = 17;

Манипуляции с цветным изображением кадра - это то, что должно было быть написано раньше

    rawImage.Source = e.ColorImageFrame.ToBitmapSource();

Теперь вы должны открыть рамку colorimage и проверить, возвращено ли что-то перед выполнением вышеупомянутого. И преобразование в источник растрового изображения также изменилось. Преобразование так

 using (var videoFrame = e.OpenColorImageFrame())
            {
                if (videoFrame != null)
                {
                    var bits = new byte[videoFrame.PixelDataLength];
                    videoFrame.CopyPixelDataTo(bits);
                }
            }

Однако после портирования нескольких приложений Kinect с бета-версии 2 на v1 я, наконец, начал видеть шаблон изменений. По большей части, это просто вопрос замены одного набора стандартного кода на другой набор стандартного кода. Любые уникальные части кода по большей части можно оставить в покое.

В этой статье я хочу продемонстрировать пять простых преобразований кода, которые облегчат вам переход от бета-версии 2 к Kinect SDK v1. Я сделаю это фрагментом по шаблону фрагментом.

Пространства имен были смещены. Microsoft.Research.Kinect.Nui теперь просто Microsoft.Kinect. К счастью, Visual Studio делает решение пространств имен относительно простым, поэтому мы можем просто двигаться дальше.

Тип Runtime, объект контроллера для работы с потоками данных из Kinect, теперь называется типом KinectSensor. Хватая экземпляр этого также изменилась. Раньше вы только что создали новый экземпляр:

Runtime nui = new Runtime();

Теперь вы вместо этого получаете экземпляр KinectSensor из статического массива, содержащего все KinectSensor, подключенные к вашему ПК.

KinectSensor sensor = KinectSensor.KinectSensors[0];

Инициализация объекта KinectSensor для начала чтения цветового потока, потока глубины или потока скелета также изменилась. В бета-версии 2 процедура инициализации выглядела не очень.NET-у. В v1 это было решительно исправлено. Бета 2-код для инициализации потока глубины и скелета выглядел так:

_nui.SkeletonFrameReady += new EventHandler( _nui_SkeletonFrameReady ); _nui.DepthFrameReady += new EventHandler( _nui_DepthFrameReady ); _nui.Initialize(RuntimeOptions.UseDepth, RuntimeOptions.UseSkeletalTracking); _nui.DepthStream.Open(ImageStreamType.Depth , 2 , ImageResolution.Resolution320x240 , ImageType.DepthAndPlayerIndex);

В v1 этот шаблонный код был изменен, поэтому метод Initialize пропал, грубо замененный методом Start. Методы Open в потоках, в свою очередь, были заменены на Enable. Данные DepthAndPlayerIndex доступны только благодаря включенному потоку скелета. Также обратите внимание, что типы аргументов события для потоков глубины и цвета теперь отличаются. Вот тот же код в v1:

sensor.SkeletonFrameReady += 
    new EventHandler<SkeletonFrameReadyEventArgs>(
        sensor_SkeletonFrameReady
        );
sensor.DepthFrameReady += 
    new EventHandler<DepthImageFrameReadyEventArgs>(
        sensor_DepthFrameReady
        );
sensor.SkeletonStream.Enable();
sensor.DepthStream.Enable(
    DepthImageFormat.Resolution320x240Fps30
    );
sensor.Start();

Transform Smoothing: раньше было действительно легко сгладить скелетный поток в бета-версии 2. Вы просто включили его.

nui.SkeletonStream.TransformSmooth = true;

В версии v1 вы должны создать новый объект TransformSmoothParameters и передать его свойству enable каркасного потока. В отличие от бета-версии 2, вы также должны инициализировать значения самостоятельно, поскольку все они по умолчанию равны нулю.

sensor.SkeletonStream.Enable(
    new TransformSmoothParameters() 
    {   Correction = 0.5f
    , JitterRadius = 0.05f
    , MaxDeviationRadius = 0.04f
    , Smoothing = 0.5f });

Обработка событий потока: обработка готовых событий из потока глубины, видеопотока и скелетного потока также была намного проще. Вот как вы обрабатывали событие DepthFrameReady в бета-версии 2 (скелет и видео следовали той же схеме):

void _nui_DepthFrameReady(object sender , ImageFrameReadyEventArgs e) { var frame = e.ImageFrame; var planarImage = frame.Image; var bits = planarImage.Bits; // your code goes here }

Из соображений производительности более новый код v1 выглядит совсем иначе, и базовый API C++ немного просачивается. В v1 мы должны открыть рамку изображения и убедиться, что что-то было возвращено. Кроме того, мы создаем наш собственный массив байтов (для потока глубины он стал массивом шортов) и заполняем его из объекта фрейма. Тип PlanarImage, с которым вы, возможно, привыкли в бета-версии 2, полностью исчез. Также обратите внимание на ключевое слово using для удаления объекта ImageFrame. Транслитерация приведенного выше кода теперь выглядит следующим образом:

void sensor_DepthFrameReady(object sender
    , DepthImageFrameReadyEventArgs e)
{
    using (var depthFrame = e.OpenDepthImageFrame())
    {
        if (depthFrame != null)
        {
            var bits =
                new short[depthFrame.PixelDataLength];
            depthFrame.CopyPixelDataTo(bits);
            // your code goes here
        }
    }
}

Я заметил, что многие сайты и библиотеки, которые использовали Kinect SDK beta 2, до сих пор не перенесены в Kinect SDK v1. Я, конечно, понимаю нерешительность, учитывая, насколько сильно изменился API.

Однако, если вы будете следовать этим пяти простым правилам перевода, вы сможете очень быстро конвертировать примерно 80% своего кода.

С последним SDK ваш SkeletonFrameReady обратный вызов должен выглядеть примерно так:

private Skeleton[] _skeletons = new Skeleton[0];

private void OnSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
    using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
    {
        if (skeletonFrame == null || skeletonFrame.SkeletonArrayLength == 0)
            return;

        // resize the skeletons array if needed
        if (_skeletons.Length != skeletonFrame.SkeletonArrayLength)
            _skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];

        // get the skeleton data
        skeletonFrame.CopySkeletonDataTo(_skeletons);

        foreach (var skeleton in _skeletons)
        {
            // skip the skeleton if it is not being tracked
            if (skeleton.TrackingState != SkeletonTrackingState.Tracked)
                continue;

            leftElbow = skeleton.Joints[JointType.ElbowLeft];
            rightHand = skeleton.Joints[JointType.HandRight];
        }
    }
}

Заметить, что SkeletonData а также JointID более не существует. Вы получаете коллекцию Skeleton объекты, каждый с Joints массив. Вы можете вытащить отдельные суставы, используя JointType ENUM.

JointCollections возвращаются для каждого Skeleton и можно получить по телефону Skeleton.Joints, Вы можете ссылаться на массив для отдельного соединения или сохранить JointCollection выкл для какой-то другой обработки.

Масштабирование не является специфическим для SDK. При масштабировании вы берете координату реального мира из Kinect и отображаете ее на экране. То, как вы получаете эти координаты реального мира, может немного отличаться (например, как вы получаете доступ к скелетам), но само масштабирование не отличается. Там нет внутренней функции для масштабирования отдельного сустава, как myJoint.ScaleTo(),

Библиотека Coding4Fun имеет функцию масштабирования, которая позволит вам масштабировать позиции соединений в пикселях экрана. Кроме того, вы можете написать свой собственный, чтобы соответствовать конкретной потребности, например:

private static double ScaleY(Joint joint)
{
    double y = ((SystemParameters.PrimaryScreenHeight / 0.4) * -joint.Position.Y) + (SystemParameters.PrimaryScreenHeight / 2);
    return y;
}

private static void ScaleXY(Joint shoulderCenter, bool rightHand, Joint joint, out int scaledX, out int scaledY)
{
    double screenWidth = SystemParameters.PrimaryScreenWidth;

    double x = 0;
    double y = ScaleY(joint);

    // if rightHand then place shouldCenter on left of screen
    // else place shouldCenter on right of screen
    if (rightHand)
    {
        x = (joint.Position.X - shoulderCenter.Position.X) * screenWidth * 2;
    }
    else
    {
        x = screenWidth - ((shoulderCenter.Position.X - joint.Position.X) * (screenWidth * 2));
    }


    if (x < 0)
    {
        x = 0;
    }
    else if (x > screenWidth - 5)
    {
        x = screenWidth - 5;
    }

    if (y < 0)
    {
        y = 0;
    }

    scaledX = (int)x;
    scaledY = (int)y;
}

Или как то так:

double xScaled = (rightHand.Position.X - leftShoulder.Position.X) / ((rightShoulder.Position.X - leftShoulder.Position.X) * 2) * SystemParameters.PrimaryScreenWidth;
double yScaled = (rightHand.Position.Y - head.Position.Y) / (rightHip.Position.Y - head.Position.Y) * SystemParameters.PrimaryScreenHeight;

Для масштабирования все, что вы делаете, это определяете, где в реальном слове (аля: координаты Kinect) равны левая, правая, верхняя и нижняя части экрана. Вы просто говорите своему приложению, что "эта координата Kinect равна этому пикселю экрана".

НУЖНО МАСШТАБИРОВАНИЕ?

Для взаимодействия с объектами на экране требуется какое-то масштабирование. Kinect возвращает значения в метрах относительно поля зрения. Это не будет полезной системой без масштабирования.

Помните, что масштабирование не является чем-то уникальным для Kinect или для старого и нового SDK. У вас есть одна система координат, в которой вы работаете, и другая система координат, в которую вам нужно перейти. Бывает во многих разных ситуациях. То, что вы делаете, говорит, что "эта" позиция в одной системе координат равна "той" позиции в другой системе координат.

Есть два основных способа решить, какая позиция в реальном мире равна пикселю.

Один из них - взять систему координат Kinect и просто отобразить ее на экране. Это означает, что 0,0 в Kinect равно 0,0 на экране. Затем вы берете внешние границы системы Kinect и сопоставляете их с разрешением экрана.

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

Другой способ - создать "хит-бокс". Посмотрите на двухстрочный перевод, который я делаю выше. Это создает хит-бокс вокруг тела для работы. Используя правую руку, левая сторона экрана равна х-шнуру вашего левого плеча; правая сторона экрана - небольшое расстояние справа от вашего правого плеча (это координата x вашего правого плеча плюс расстояние между вашими двумя плечами). Вертикальное положение экрана отображается между вашей головой и бедрами.

Этот метод позволяет пользователю находиться в любом месте поля зрения Kinect и таким же образом манипулировать объектами. Хитбокс, который он создает, также очень удобен для обычного пользователя.

Другие вопросы по тегам