Полное руководство по преобразованию кода из 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 и таким же образом манипулировать объектами. Хитбокс, который он создает, также очень удобен для обычного пользователя.