Kinect – 入门 – 变身不可思议的绿巨人






4.90/5 (89投票s)
Kinect 入门 - 创建项目、控制摄像头角度并使用骨骼跟踪
- 下载 KinectGettingStarted - Basic - 22.97 KB
- 下载 KinectGettingStarted - Camera - 22.88 KB
- 下载 KinectGettingStarted - Skeleton - 302.47 KB
引言
2011 年 6 月 16 日,微软发布了 Kinect .NET SDK Beta,我立即下载并尝试了一下,效果惊人!!!
在本文中,我将向您展示如何开始使用 Kinect SDK,然后我们将继续介绍如何使用 SDK 控制摄像头角度,最后我将通过一个不错的示例展示如何 变身令人难以置信的绿巨人。
背景
Kinect for Windows SDK Beta 是一个面向应用程序开发者的编程工具包。它使学术界和爱好者社区能够轻松访问连接到运行 Windows 7 操作系统的计算机的 Microsoft Kinect 设备所提供的功能。
Kinect for Windows SDK Beta 包含驱动程序、用于原始传感器流和人体运动跟踪的丰富 API、安装文档和资源材料。它通过 Microsoft Visual Studio 2010 为使用 C++、C# 或 Visual Basic 构建应用程序的开发者提供了 Kinect 功能。
步骤 1 – 准备您的环境
为了使用 Kinect .NET SDK,您需要满足以下要求:
支持的操作系统和架构
- Windows 7 (x86 或 x64)
硬件要求
- 配备双核、2.66 GHz 或更快的处理器
- 支持 Microsoft® DirectX® 9.0c 功能的 Windows 7 兼容显卡
- 2 GB RAM
- Kinect for Xbox 360® 传感器—零售版,包括专用的 USB/电源线
软件要求
- Microsoft Visual Studio® 2010 Express 或其他 Visual Studio 2010 版本
- Microsoft .NET Framework 4.0 (随 Visual Studio 2010 一起安装)
- Kinect for Windows SDK Beta 下载页面
步骤 2:创建新的 WPF 项目
添加对 Microsoft.Research.Kinect.Nui
的引用(位于 - C:\Program Files (x86)\Microsoft Research KinectSDK)并确保示例的解决方案文件目标为 **x86 平台**,因为此 Beta SDK 仅包含 x86 库。

应用程序必须通过调用 Runtime.Initialize 来初始化 Kinect 传感器,然后才能在 Runtime 对象上调用任何其他方法。Runtime.Initialize 会初始化内部帧捕获引擎,该引擎会启动一个线程,用于从 Kinect 传感器检索数据,并在帧准备好时向应用程序发出信号。它还会初始化收集和处理传感器数据的子系统。如果 Initialize 方法未能找到 Kinect 传感器,它将抛出 InvalidOperationException,因此 Runtime.Initialize 的调用出现在 try/catch 块中。
创建 Windows 加载事件并调用 InitializeNui
private void InitializeNui()
{
try
{
//Declares _kinectNui as a Runtime object,
//which represents the Kinect sensor instance.
_kinectNui = new Runtime();
//Open the video and depth streams, and sets up the event handlers
//that the runtime calls when a video, depth, or skeleton frame is ready
//An application must initialize the Kinect sensor by calling
//Runtime.Initialize before calling any other methods on the Runtime object.
_kinectNui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex |
RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
//To stream color images:
// • The options must include UseColor.
// • Valid image resolutions are Resolution1280x1024 and Resolution640x480.
// • Valid image types are Color, ColorYUV, and ColorYUVRaw.
_kinectNui.VideoStream.Open(ImageStreamType.Video, 2,
ImageResolution.Resolution640x480, ImageType.ColorYuv);
//To stream depth and player index data:
// • The options must include UseDepthAndPlayerIndex.
// • Valid resolutions for depth and player index data are
//Resolution320x240 and Resolution80x60.
// • The only valid image type is DepthAndPlayerIndex.
_kinectNui.DepthStream.Open(ImageStreamType.Depth, 2,
ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
lastTime = DateTime.Now;
_kinectNui.VideoFrameReady +=
new EventHandler<ImageFrameReadyEventArgs>(NuiVideoFrameReady);
_kinectNui.DepthFrameReady +=
new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
}
步骤 3:显示视频
Video
和 Depth
都返回 PlanarImage
,我们只需要创建一个新的 Bitmap
并在 UI 上显示。
视频帧就绪事件处理程序
void NuiVideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
PlanarImage Image = e.ImageFrame.Image;
image.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, null,
Image.Bits, Image.Width * Image.BytesPerPixel);
imageCmyk32.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Cmyk32, null,
Image.Bits, Image.Width * Image.BytesPerPixel);
}
深度帧就绪事件处理程序
深度不同,因为返回的图像是 16 位,我们需要将其转换为 32 位,我使用了与 SDK 中相同的方法。
void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
var Image = e.ImageFrame.Image;
var convertedDepthFrame = convertDepthFrame(Image.Bits);
depth.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32,
null, convertedDepthFrame, Image.Width * 4);
CalculateFps();
}
// Converts a 16-bit grayscale depth frame which includes player
// indexes into a 32-bit frame
// that displays different players in different colors
byte[] convertDepthFrame(byte[] depthFrame16)
{
for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length &&
i32 < depthFrame32.Length; i16 += 2, i32 += 4)
{
int player = depthFrame16[i16] & 0x07;
int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);
// transform 13-bit depth information into an 8-bit intensity appropriate
// for display (we disregard information in most significant bit)
byte intensity = (byte)(255 - (255 * realDepth / 0x0fff));
depthFrame32[i32 + RED_IDX] = intensity;
depthFrame32[i32 + BLUE_IDX] = intensity;
depthFrame32[i32 + GREEN_IDX] = intensity;
}
return depthFrame32;
}
void CalculateFps()
{
++totalFrames;
var cur = DateTime.Now;
if (cur.Subtract(lastTime) > TimeSpan.FromSeconds(1))
{
int frameDiff = totalFrames - lastFrames;
lastFrames = totalFrames;
lastTime = cur;
frameRate.Text = frameDiff.ToString() + " fps";
}
}
步骤 4:控制摄像头角度
现在,我将展示控制 Kinect 摄像头角度(更改摄像头位置)有多么容易。
您可以控制的最小和最大角度是存在的,但正如您从最后一张图片(右侧)中看到的,您可以手动移动 Kinect 传感器,角度会自动更改。



在初始化之后,创建一个 Kinect Nui 对象。
private Camera _cam;
_cam = _kinectNui.NuiCamera;
txtCameraName.Text = _cam.UniqueDeviceName;
这是摄像头定义
namespace Microsoft.Research.Kinect.Nui
{
public class Camera
{
public static readonly int ElevationMaximum;
public static readonly int ElevationMinimum;
public int ElevationAngle { get; set; }
public string UniqueDeviceName { get; }
public void GetColorPixelCoordinatesFromDepthPixel
(ImageResolution colorResolution, ImageViewArea viewArea,
int depthX, int depthY, short depthValue, out int colorX,
out int colorY);
}
}
步骤 5:上下移动
现在,当您执行此操作时,可以按如下方式控制摄像头角度:
要增加摄像头角度,您只需要增加摄像头的 ElevationAngle
,摄像头有 Min
和 Max
角度可以控制,所以不要害怕将其推得太远。
private void BtnCameraUpClick(object sender, RoutedEventArgs e)
{
try
{
_cam.ElevationAngle = _cam.ElevationAngle + 5;
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
catch (ArgumentOutOfRangeException outOfRangeException)
{
//Elevation angle must be between Elevation Minimum/Maximum"
MessageBox.Show(outOfRangeException.Message);
}
}
以及向下
private void BtnCameraDownClick(object sender, RoutedEventArgs e)
{
try
{
_cam.ElevationAngle = _cam.ElevationAngle - 5;
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
catch (ArgumentOutOfRangeException outOfRangeException)
{
//Elevation angle must be between Elevation Minimum/Maximum"
MessageBox.Show(outOfRangeException.Message);
}
}
背景:通过骨骼跟踪变身令人难以置信的绿巨人
Kinect for Windows SDK 的一个主要优势在于它能够快速识别站在传感器前的人体骨骼关节,并且无需任何训练即可使用。
NUI Skeleton API 提供有关站在 Kinect 传感器阵列前的最多两个玩家的位置信息,并附带详细的位置和方向信息。
数据以一组点(称为骨骼位置)的形式提供给应用程序代码,这些点组成一个骨骼,如下图所示。此骨骼代表用户当前的位置和姿势。
使用骨骼数据的应用程序必须在 NUI 初始化时指明这一点,并必须启用骨骼跟踪。
《维特鲁威人》有 20 个点,在 Kinect
SDK 中称为 Joints。


步骤 6:注册到 SkeletonFrameReady
确保您使用 **UseSkeletalTracking
** 进行 Initialize
,否则骨骼跟踪将不起作用。
_kinectNui.Initialize(RuntimeOptions.UseColor |
RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
_kinectNui.SkeletonFrameReady +=
new EventHandler<SkeletonFrameReadyEventArgs>(SkeletonFrameReady);
Kinect
NUI 最多只能跟踪 2 个骨骼,
if (SkeletonTrackingState.Tracked != data.TrackingState) continue;
表示该骨骼正在被跟踪,未被跟踪的骨骼仅提供其位置而不提供关节信息;如果全身都适合画面,则会渲染骨骼。
为 Kinect 开发时,调试并非易事——每次想要测试时都需要站起来。
骨骼关节由 TrackingID enum
标记,该枚举定义了其参考位置
namespace Microsoft.Research.Kinect.Nui
{
public enum JointID
{
HipCenter,
Spine,
ShoulderCenter,
Head,
ShoulderLeft,
ElbowLeft,
WristLeft,
HandLeft,
ShoulderRight,
ElbowRight,
WristRight,
HandRight,
HipLeft,
KneeLeft,
AnkleLeft,
FootLeft,
HipRight,
KneeRight,
AnkleRight,
FootRight,
Count,
}
}
步骤 7:获取关节位置
Joint
的位置定义在 Camera Space 中,我们需要将其转换为我们的 Size
和 Position
。
深度图像空间
深度图的图像帧大小为 640x480、320x240 或 80x60 像素,每个像素代表在特定 x 和 y 坐标处到最近对象的距离(以毫米为单位)。像素值为 0 表示传感器在此位置未找到任何对象。图像帧的 x 和 y 坐标不代表房间中的物理单位,而是深度成像传感器上的像素。x 和 y 坐标的解释取决于光学和成像传感器的具体细节。出于讨论目的,此投影空间被称为深度图像空间。
骨骼空间
玩家骨骼位置以 x、y 和 z 坐标表示。与深度图像空间的坐标不同,这三个坐标以米表示。x、y 和 z 轴是深度传感器的身体轴。这是一个右手坐标系,将传感器阵列置于原点,正 z 轴沿传感器阵列指向的方向延伸。正 y 轴向上延伸,正 x 轴向左延伸(相对于传感器阵列),如图 5 所示。出于讨论目的,此坐标表示法被称为骨骼空间。
private Point getDisplayPosition(Joint joint)
{
float depthX, depthY;
_kinectNui.SkeletonEngine.SkeletonToDepthImage(joint.Position, out depthX, out depthY);
depthX = Math.Max(0, Math.Min(depthX * 320, 320)); //convert to 320, 240 space
depthY = Math.Max(0, Math.Min(depthY * 240, 240)); //convert to 320, 240 space
int colorX, colorY;
ImageViewArea iv = new ImageViewArea();
// only ImageResolution.Resolution640x480 is supported at this point
_kinectNui.NuiCamera.GetColorPixelCoordinatesFromDepthPixel
(ImageResolution.Resolution640x480, iv, (int)depthX, (int)depthY,
(short)0, out colorX, out colorY);
// map back to skeleton.Width & skeleton.Height
return new Point((int)(imageContainer.Width * colorX / 640.0) - 30,
(int)(imageContainer.Height * colorY / 480) - 30);
}
步骤 8:根据关节类型放置图像
类型为 Vector4
的位置(x
、y
、z
、w
— 前三个属性定义了在相机空间中的位置。最后一个属性(w
)给出位置的质量级别(范围在 0-1 之间),表示该骨骼的重心。
此值是唯一可用于被动玩家的位置值。
void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
foreach (SkeletonData data in e.SkeletonFrame.Skeletons)
{
//Tracked that defines whether a skeleton is 'tracked' or not.
//The untracked skeletons only give their position.
if (SkeletonTrackingState.Tracked != data.TrackingState) continue;
//Each joint has a Position property that is defined by a Vector4: (x, y, z, w).
//The first three attributes define the position in camera space.
//The last attribute (w)
//gives the quality level (between 0 and 1) of the
foreach (Joint joint in data.Joints)
{
if (joint.Position.W < 0.6f) return;// Quality check
switch (joint.ID)
{
case JointID.Head:
var heanp = getDisplayPosition(joint);
Canvas.SetLeft(imgHead, heanp.X);
Canvas.SetTop(imgHead, heanp.Y);
break;
case JointID.HandRight:
var rhp = getDisplayPosition(joint);
Canvas.SetLeft(imgRightHand, rhp.X);
Canvas.SetTop(imgRightHand, rhp.Y);
break;
case JointID.HandLeft:
var lhp = getDisplayPosition(joint);
Canvas.SetLeft(imgLefttHand, lhp.X);
Canvas.SetTop(imgLefttHand, lhp.Y);
break;
}
}
}
}
尽情享用!
历史
- 2011 年 6 月 18 日:初始发布