我有一个关于 kinect 的学期项目。我必须改进某个应用程序并为其添加新功能。问题的出现是由于该应用程序使用了过时的 kinect sdk。我希望(个人)添加的一些额外功能需要使用新的 Kinect SDK。是否有关于从 Kinect SDK Beta 转移到最新 SDK 的快速指南?除了程序集引用之外,还做了哪些更改?
2 回答
我从这篇文章中找到了以下信息:
此处及以后信息的所有功劳归于该文章的原始海报。我只是分享他的知识
如果您在 2 月 1 日之前使用过 Kinect SDK 的 beta 2,您可能会对 v1.0 中引入的 API 更改数量感到沮丧。
为了获得左右关节,您使用编写的代码
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 应用程序从 beta 2 移植到 v1 之后,我终于开始看到变化的模式。在大多数情况下,只需将一组样板代码替换为另一组样板代码即可。代码的任何独特部分大部分都可以单独放置。
在这篇文章中,我想演示五种简单的代码转换,它们将简化您从 beta 2 到 Kinect SDK v1 的过程。我会一个样板片段一个样板片段来做。
命名空间已被转移。Microsoft.Research.Kinect.Nui 现在只是 Microsoft.Kinect。幸运的是,Visual Studio 使得解析命名空间相对容易,所以我们可以继续。
Runtime 类型是用于处理来自 Kinect 的数据流的控制器对象,现在称为 KinectSensor 类型。抓取它的一个实例也发生了变化。您曾经像这样新建一个实例:
Runtime nui = new Runtime();
现在,您改为从包含连接到 PC 的所有 KinectSensor 的静态数组中获取 KinectSensor 的实例。
KinectSensor sensor = KinectSensor.KinectSensors[0];
初始化 KinectSensor 对象以开始读取颜色流、深度流或骨架流也发生了变化。在 beta 2 中,初始化过程看起来不是很 .NET-y。在 v1 中,这已被大大清除。用于初始化深度和骨架流的 beta 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();
变换平滑:在 beta 2 中平滑骨架流曾经非常容易。您只需打开它。
nui.SkeletonStream.TransformSmooth = true;
在 v1 中,您必须创建一个新的 TransformSmoothParameters 对象并将其传递给骨架流的 enable 属性。与 beta 2 不同,您还必须自己初始化这些值,因为它们都默认为零。
sensor.SkeletonStream.Enable(
new TransformSmoothParameters()
{ Correction = 0.5f
, JitterRadius = 0.05f
, MaxDeviationRadius = 0.04f
, Smoothing = 0.5f });
流事件处理:处理来自深度流、视频流和骨架流的就绪事件也变得容易得多。以下是您在 beta 2 中处理 DepthFrameReady 事件的方式(骨架和视频遵循相同的模式):
void _nui_DepthFrameReady(object sender , ImageFrameReadyEventArgs e) { var frame = e.ImageFrame; var planarImage = frame.Image; var bits = planarImage.Bits; // your code goes here }
出于性能原因,较新的 v1 代码看起来非常不同,并且底层 C++ API 泄漏了一点。在 v1 中,我们需要打开图像框架并检查以确保返回了某些内容。此外,我们创建自己的字节数组(对于深度流,这已成为一个短裤数组)并从帧对象填充它。您可能在 beta 2 中熟悉的 PlanarImage 类型已经完全消失了。还要注意处理 ImageFrame 对象的 using 关键字。上面代码的音译现在看起来是这样的:
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
您可以使用枚举拉出单个关节。
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;
对于缩放,您所要做的就是定义真实世界中的哪个位置(ala:Kinect 坐标)等于屏幕的左、右、顶部和底部。你只是告诉你的应用程序“这个 Kinect 坐标等于这个屏幕像素”。
是否需要缩放?
为了与屏幕上的对象进行交互,需要进行某种缩放。Kinect 以米为单位返回相对于其视野的值。如果没有扩展,它就不是一个可用的系统。
请记住,缩放并不是 Kinect 或新旧 SDK 独有的。您有一个正在工作的坐标系,以及需要转换到的另一个坐标系。发生在许多不同的情况下。您正在做的是说一个坐标系中的“这个”位置等于另一个坐标系中的“那个”位置。
有两种基本方法可以确定现实世界中的哪个位置等于一个像素。
一种是采用 Kinect 的坐标系并将其映射到屏幕上。这意味着 Kinect 中的 0,0 等于屏幕上的 0,0。然后,您获取 Kinect 系统的外部边界并将它们映射到屏幕分辨率。
我不推荐这个。它创造了一个非常大的工作空间,并且会让用户感到沮丧。
另一种方法是创建一个“命中框”。看看我上面做的两行翻译。这会在身体周围创建一个击球框以进行工作。使用右手,屏幕的左侧等于左肩的 x 线;屏幕右侧是您右肩右侧的一小段距离(它是您右肩的 x 坐标加上您两个肩膀之间的距离)。屏幕的垂直位置映射在您的头部和臀部之间。
此方法允许用户站在 Kinect 视野中的任何位置,并以相同的方式操作对象。它创建的命中框也非常适合普通用户使用。