基于景深数据的用户交互

骨骼数据中关节点不仅有X,Y值,还有一个深度值

除了使用WPF的3D特性外,在布局系统中可以根据深度值来设定可视化元素的尺寸大小来达到某种程序的立体效果。

下面的例子使用Canvas.ZIndex属性来设置元素的层次,手动设置控件的大小并使用ScaleTransform来根据深度值的改变来进行缩放。用户界面包括一些圆形,每一个圆代表一定的深度。应用程序跟踪用户的手部关节点,以手形图标显示,图标会根据用户手部关节点的深度值来进行缩放,用户离Kinect越近,手形图表越大,反之越小。

<Window x:Class="KinectDepthBasedInteraction.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="1080" Width="1920" WindowState="Maximized" Background="White">
<Window.Resources>
<Style x:Key="TargetLabel" TargetType="TextBlock">
<Setter Property="FontSize" Value="40"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
</Window.Resources>
<Viewbox>
<Grid x:Name="LayoutRoot" Width="1920" Height="1280">
<Image x:Name="DepthImage"/>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock x:Name="DebugLeftHand" Style="{StaticResource TargetLabel}" Foreground="Black"/>
<TextBlock x:Name="DebugRightHand" Style="{StaticResource TargetLabel}" Foreground="Black"/>
</StackPanel> <Canvas>
<Ellipse x:Name="Target3" Fill="Orange" Height="200" Width="200" Canvas.Left="776" Canvas.Top="162" Canvas.ZIndex="1040" />
<TextBlock Text="3" Canvas.Left="860" Canvas.Top="206" Panel.ZIndex="1040" Style="{StaticResource TargetLabel}" /> <Ellipse x:Name="Target4" Fill="Purple" Height="150" Width="150" Canvas.Left="732" Canvas.Top="320" Canvas.ZIndex="940" />
<TextBlock Text="4" Canvas.Left="840" Canvas.Top="372" Panel.ZIndex="940" Style="{StaticResource TargetLabel}" /> <Ellipse x:Name="Target5" Fill="Green" Height="120" Width="120" Canvas.Left="880" Canvas.Top="592" Canvas.ZIndex="840" />
<TextBlock Text="5" Canvas.Left="908" Canvas.Top="590" Panel.ZIndex="840" Style="{StaticResource TargetLabel}" /> <Ellipse x:Name="Target6" Fill="Blue" Height="100" Width="100" Canvas.Left="352" Canvas.Top="544" Canvas.ZIndex="740" />
<TextBlock Text="6" Canvas.Left="368" Canvas.Top="582" Panel.ZIndex="740" Style="{StaticResource TargetLabel}" /> <Ellipse x:Name="Target7" Fill="Red" Height="85" Width="85" Canvas.Left="378" Canvas.Top="192" Canvas.ZIndex="640" />
<TextBlock Text="7" Canvas.Left="422" Canvas.Top="226" Panel.ZIndex="640" Style="{StaticResource TargetLabel}" /> <Image x:Name="LeftHandElement" Source="/KinectDepthBasedInteraction;component/Images/hand.png" Width="80" Height="80" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<ScaleTransform x:Name="LeftHandScaleTransform" ScaleX="1" CenterY="-1" />
</Image.RenderTransform>
</Image> <Image x:Name="RightHandElement" Source="/KinectDepthBasedInteraction;component/Images/hand.png" Width="80" Height="80" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<ScaleTransform x:Name="RightHandScaleTransform" CenterY="1" ScaleX="1" />
</Image.RenderTransform>
</Image>
</Canvas>
</Grid>
</Viewbox>
</Window>

不同颜色的圆形代表不同的深度,例如名为Target3的元素代表距离为3英尺。Target3的长宽比Target7要大,这简单的通过缩放可以实现。在我们的实例程序中,我们将其大小进行硬编码,实际的程序中,应该根据特定要求可以进行缩放。Canvas容器会根据子元素的Canvas.ZIndex的值对元素在垂直于计算机屏幕的方向上进行排列,例如最上面的元素,其Canvas.ZIndex最大。如果两个元素有相同的ZIndex值,那么会根据其在XAML中声明的顺序进行显示,在XAML中,后面声明的元素在之前声明的元素的前面。对于Canvas的所有子元素,ZIndex值越大,离屏幕越近,越小离屏幕越远。将深度值取反刚好能达到想要的效果。这意味这我们不能直接使用深度值来给ZIndex来赋值,而要对它进行一点转换。Kinect能够产生的最大深度值为13.4英尺,相应的,我们将Canvas.Zindex的取值范围设置为0-1340,将深度值乘以100能获得更好的精度。因此Target5的Canvas.ZIndex设置为840(13.5-5=8.4*100=840)。

设置好Canvas.ZIndex对于可视化元素的布局已经足够,但是不能够根据深度视觉效果对物体进行缩放。对于Kinect应用程序,Z值其他输入设备不能提供的,如果没有根据节点深度数据进行的缩放,那么这以独特的Z值就浪费了。缩放比例可能需要测试以后才能确定下来。

double z = hand.Position.Z*FeetPerMeters;
Canvas.SetZIndex(cursorElement, (int)(1200- (z * 100)));
cursorScale.ScaleX = 12 / z * (isLeft ? -1 : 1);
cursorScale.ScaleY = 12 / z;
 
 
编译并运行程序,将手距Kinect不同距离,界面上的手形图标会随着距离的大小进行缩放;同时界面上用于调试的信息也在变化,还可以注意到,随着远近的不同,参考深度标注图案,手形图标在界面上的深度值也会相应的发生变化,有时候在图标在某些标签的前面,有时候在某些标签后面。
 

public partial class MainWindow : Window
{
private KinectSensor kinectDevice;
private Skeleton[] frameSkeletons;
private WriteableBitmap depthImage;
private Int32Rect depthImageRect;
private short[] depthPixelData;
private int depthImageStride;
private const float FeetPerMeters = 3.2808399f; private KinectSensor KinectDevice
{
get { return this.kinectDevice; }
set
{
if (kinectDevice != null)
{
kinectDevice = null;
this.kinectDevice.Stop();
this.kinectDevice.AllFramesReady -= kinectDevice_AllFramesReady;
this.kinectDevice.SkeletonStream.Disable(); this.DepthImage.Source = null;
this.depthImage = null;
this.depthImageStride = ; this.frameSkeletons = null; this.frameSkeletons = null;
} kinectDevice = value; if (kinectDevice != null)
{
if (kinectDevice.Status == KinectStatus.Connected)
{
kinectDevice.AllFramesReady+=kinectDevice_AllFramesReady;
kinectDevice.SkeletonStream.Enable();
kinectDevice.DepthStream.Enable(); DepthImageStream depthStream = this.kinectDevice.DepthStream;
this.depthImage = new WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, , , PixelFormats.Bgr32, null);
this.depthImageRect = new Int32Rect(,,(int)Math.Ceiling(this.depthImage.Width),(int)Math.Ceiling(this.depthImage.Height));
this.depthImageStride = depthStream.FrameWidth * ;
this.depthPixelData = new short[depthStream.FramePixelDataLength];
this.DepthImage.Source = this.depthImage; this.frameSkeletons = new Skeleton[this.kinectDevice.SkeletonStream.FrameSkeletonArrayLength];
kinectDevice.Start();
}
}
}
}
public MainWindow()
{
InitializeComponent(); this.Loaded += (s, e) =>
{
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
this.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected);
};
} private void KinectSensors_StatusChanged(Object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Initializing:
case KinectStatus.Connected:
case KinectStatus.NotPowered:
case KinectStatus.NotReady:
case KinectStatus.DeviceNotGenuine:
this.KinectDevice = e.Sensor;
break;
case KinectStatus.Disconnected:
//TODO: Give the user feedback to plug-in a Kinect device.
this.KinectDevice = null;
break;
default:
//TODO: Show an error state
break;
}
} private void kinectDevice_AllFramesReady(Object sender, AllFramesReadyEventArgs e)
{
using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
{
if (depthFrame != null)
{
using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
{
if (skeletonFrame != null)
{
ProcessDepthFrame(depthFrame);
ProcessSkeletonFrame(skeletonFrame);
}
}
}
}
} private void ProcessDepthFrame(DepthImageFrame depthFrame)
{
int depth;
int gray;
int bytesPerPixel = ;
byte[] enhPixelData = new byte[depthFrame.Width*depthFrame.Height*bytesPerPixel]; depthFrame.CopyPixelDataTo(this.depthPixelData); for (int i = , j = ; i < this.depthPixelData.Length;i++,j+=bytesPerPixel )
{
depth = this.depthPixelData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth; if (depth==)
{
gray = 0xFF;
}
else
{
gray = ( * depth / 0xFFF);
}
enhPixelData[j] = (byte)gray;
enhPixelData[j + ] = (byte)gray;
enhPixelData[j + ] = (byte)gray;
} this.depthImage.WritePixels(this.depthImageRect, enhPixelData, this.depthImageStride, );
} private void ProcessSkeletonFrame(SkeletonFrame frame)
{
frame.CopySkeletonDataTo(this.frameSkeletons);
Skeleton skeleton = GetPrimarySkeleton(this.frameSkeletons);
if (skeleton != null)
{
TrackHand(skeleton.Joints[JointType.HandLeft], LeftHandElement, LeftHandScaleTransform, LayoutRoot, true);
TrackHand(skeleton.Joints[JointType.HandRight], RightHandElement, RightHandScaleTransform, LayoutRoot, false);
}
} private Skeleton GetPrimarySkeleton(Skeleton[] skeleton)
{
Skeleton primarySkeleton = null;
if (skeleton != null)
{
for (int i = ; i < skeleton.Length; i++)
{
if (skeleton[i].TrackingState == SkeletonTrackingState.Tracked)
{
if (primarySkeleton == null)
{
primarySkeleton = skeleton[i];
}
else
{
if (primarySkeleton.Position.Z > skeleton[i].Position.Z)
{
primarySkeleton = skeleton[i];
}
} }
}
}
return primarySkeleton;
} private void TrackHand(Joint hand, FrameworkElement cursorElement, ScaleTransform cursorScale, FrameworkElement container, bool isLeft)
{
if (hand.TrackingState != JointTrackingState.NotTracked)
{
double z = hand.Position.Z * FeetPerMeters;
//double z = hand.Position.Z ;
cursorElement.Visibility = System.Windows.Visibility.Visible;
Point cursorCenter = new Point(cursorElement.ActualWidth / 2.0, cursorElement.ActualHeight / 2.0); Point jointPoint = GetJointPoint(this.KinectDevice, hand, container.RenderSize, cursorCenter);
Canvas.SetLeft(cursorElement, jointPoint.X);
Canvas.SetTop(cursorElement, jointPoint.Y);
Canvas.SetZIndex(cursorElement, (int)( - (z * ))); cursorScale.ScaleX = / z * ((isLeft) ? - : );
cursorScale.ScaleY = / z;
if (hand.JointType == JointType.HandLeft)
{
DebugLeftHand.Text = String.Format("Left Hand:{0:0.00} Feet", z);
}
else
{
DebugRightHand.Text = String.Format("Right Hand:{0:0.00} Feet", z);
}
}
else
{
DebugLeftHand.Text = String.Empty;
DebugRightHand.Text = String.Empty;
}
} private Point GetJointPoint(KinectSensor kinectSensor, Joint hand, Size containerSize, Point cursorCenter)
{
DepthImagePoint point = kinectSensor.MapSkeletonPointToDepth(hand.Position, KinectDevice.DepthStream.Format);
point.X = (int)((point.X * containerSize.Width / kinectSensor.DepthStream.FrameWidth) - cursorCenter.X);
point.Y = (int)((point.Y * containerSize.Height / kinectSensor.DepthStream.FrameHeight) - cursorCenter.Y);
return new Point(point.X, point.Y);
}
}

Kinect 开发 —— 骨骼追踪 (下)的更多相关文章

  1. Kinect 开发 —— 骨骼追踪进阶(上)

    Kinect传感器核心只是发射红外线,并探测红外光反射,从而可以计算出视场范围内每一个像素的深度值.从深度数据中最先提取出来的是物体主体和形状,以及每一个像素点的游戏者索引信息.然后用这些形状信息来匹 ...

  2. Kinect 开发 —— 骨骼追踪

    骨骼追踪技术通过处理景深数据来建立人体各个关节的坐标,骨骼追踪能够确定人体的各个部分,如那部分是手,头部,以及身体.骨骼追踪产生X,Y,Z数据来确定这些骨骼点.骨骼追踪系统采用的景深图像处理技术使用更 ...

  3. Kinect 开发 —— 骨骼追踪(下)

    Kinect 连线游戏 在纸上将一些列数字(用一个圆点表示)从小到大用线连起来.游戏逻辑很简单,只不过我们在这里要实现的是动动手将这些点连起来,而不是用笔或者鼠标. 在开始写代码之前,需要明确定义我们 ...

  4. Kinect 开发 —— 手势识别(下)

    基本手势追踪 手部追踪在技术上和手势识别不同,但是它和手势识别中用到的一些基本方法是一样的.在开发一个具体的手势控件之前,我们先建立一个可重用的追踪手部运动的类库以方便我们后续开发.这个手部追踪类库包 ...

  5. Kinect 开发 —— 语音识别(下)

    使用定向麦克风进行波束追踪 (Beam Tracking for a Directional Microphone) 可以使用这4个麦克风来模拟定向麦克风产生的效果,这个过程称之为波束追踪(beam ...

  6. Kinect 开发 —— 面部追踪

    SDK1.5中新增了人脸识别类库:Microsoft.Kinect.Toolkit.FaceTracking使得在Kinect中进行人脸识别变得简单,该类库的源代码也在Developer Toolki ...

  7. Kinect 开发 —— 骨骼数据与彩色影像和深度影像的对齐

    在显示彩色影像和深度影像时最好使用WriteableBitmap对象: 要想将骨骼数据影像和深度影像,或者彩色影像叠加到一起,首先要确定深度影像的分辨率和大小,为了方便,这里将深度影像数据和彩色影像数 ...

  8. ]Kinect for Windows SDK开发入门(六):骨骼追踪基础 上

    原文来自:http://www.cnblogs.com/yangecnu/archive/2012/04/06/KinectSDK_Skeleton_Tracking_Part1.html Kinec ...

  9. Kinect 开发 —— 近距离探测

    如何将Kinect设备作为一个近距离探测传感器.为了演示这一点,我们处理的场景可能在以前看到过.就是某一个人是否站在Kinect前面,在Kinect前面移动的是人还是什么其他的物体.当我们设置的触发器 ...

随机推荐

  1. IHttpHandler的学习(0)

    本片文章转自网络 问题1:什么是HttpHandler?(Handler:处理者:那就是对Http请求的处理拉) 问题2:什么是HttpModule? 问题3:什么时候应该使用HttpHandler什 ...

  2. 维生素C主要生理功能

    维C是:维生素C又叫抗坏血酸,是一种水溶性维生素. 维生素C主要生理功能 1. 促进骨胶原的生物合成.利于组织创伤口的更快愈合: 维生素C在体内参与多种反应,如参与氧化还原过程,在生物氧化和还原作用以 ...

  3. AlexNet (ImageNet模型)

    介绍 AlexNet是LeNet的一种更深更宽的版本.首次在CNN中应用ReLU.Dropout和LRN,GPU进行运算加速. 一共有13层,有8个需要训练参数的层(不包括池化层和LRN层),前5层是 ...

  4. selenium自动化框架介绍------unittest版本

    首先说下unittest的两个问题:1.未实现失败重跑 2.未实现远程的分布式(即多线程呼起多台远程计算机,并行进行用例的执行), 为什么要使用框架:  比较方便,只需要写用例就行,而不用考虑结构.还 ...

  5. BZOJ 3110 [Zjoi2013]K大数查询(整体二分)

    3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 11654  Solved: 3505[Submit][St ...

  6. [POI2010]GIL-Guilds(结论题)

    题意 给一张无向图,要求你用黑白灰给点染色,且满足对于任意一个黑点,至少有一个白点和他相邻:对于任意一个白点,至少有一个黑点与他相邻,对于任意一个灰点,至少同时有一个黑点和白点和灰点与他相邻,问能否成 ...

  7. 一:1.1 python程序与数据储存【进制转换】

    二进制 :0 1 [逢二进一]0+0=00+1=11+1=1011+1=100 1 1+ 1------ 100 八进制: 0 1 2 3 4 5 6 7 [逢八进一] 1+7=101+2=3 十进制 ...

  8. Dynamic Rankings(分块)

    P2617 Dynamic Rankings 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i ...

  9. 玩具(toy)

    题目 试题2:玩具(toy) 源代码:toy.cpp 输入文件:toy.in 输出文件:toy.out 时间限制:1s 空间限制:256MB 题目描述 商店正在出售小C最喜欢的系列玩具,在接下来的n周 ...

  10. ArcGIS api for javascript——查找任务-没有地图查找要素

    描述 本例展示了如何使用查找任务搜索数据里的记录,然后在HTML表格中显示结果. 尽管FindTask不需要显示一个地图,但是构造函数需要一个ArcGIS Server地图服务的URL. 本例使用ES ...