做者自转,原文连接:http://blog.csdn.net/nmlh7448...express
曾经微软宣传Kinect宣传的很火,但一直没有舍得买一台。第一次接触是在某个Hackathon上,想作一个空气鼠标的项目,借助Kinect实现的,感受这个产品挺惊艳。最近千方百计借到一台一代的Kinect for Windows,还有微软官方的开发书籍(《Kinect应用开发实战——用最天然的方式与机器对话》),略研究了下Kinect的开发。编程
关于Kinect的介绍网上有不少资料,这里再也不赘述。既然是开发微软自家的产品,确定要上微软全家桶,VS2015(C#)+SDK V1.8+Developer toolkit V1.8。其中SDK能够直接在微软官网上下载,除了官方SDK,还有其它的SDK,我不是很了解,因此不敢妄言介绍。一代Kinect有windows和Xbox 360两个版本,windows版本的Kinect前面写着“Kinect”,而Xbox 360版本前面写着“Xbox 360”,xbox版的链接电脑须要有转接线,可是很诡异的是我曾经直接用Xbox版的链接电脑也成功了。而且我最开始安装的SDK是V2.0,也能成功跑起来Kinect V1……虽然说SDK V2.0只能驱动二代Kinect,但也许微软仍是照顾了旧版本的硬件吧。不过为了稳妥,仍是安装SDK V1.8,而且使用Kinect for Windows。
将Kinect链接上电脑以后,能够打开Developer toolkit browser,运行其中某一个demo,来检验Kinect是否正常工做。通常状况下,正常工做是Kinect正面绿灯一直亮。在这里不得不吐槽下Kinect的电源线质量问题,两次接触Kinect都是电源线有问题。这时只有USB供电,电压不足,状态是红灯一直亮,这种状况下更换电源线就行了。c#
环境配好以后,打开VS2015,新建一个WPF窗体工程的解决方案,而后在引用里面添加Kinect v1.8,而后在程序中using Microsoft.Kinect便可。Kinect视频方面主要包括采集彩色数据、采集深度数据、追踪骨骼三个功能,此外还有经过麦克风阵列采集声音数据。windows
Kinect有两个摄像头,分别是彩色摄像头和深度摄像头,因此第一个程序就是实现获取两个摄像头采集到的彩色视频流和深度视频流。在MainWindow.xaml文件里,在工具箱中选中Image,向窗体中添加两个大小为640*480的Image,不重叠,分别命名为depthImage和colorImage;在Window标签中添加属性Loaded="Window_Loaded" Closed=Window_Closed,最终Xaml文件代码以下:数组
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:KinectWpfApplication1" xmlns:WpfViewers="clr-namespace:Microsoft.Samples.Kinect.WpfViewers;assembly=Microsoft.Samples.Kinect.WpfViewers"> x:Class="KinectWpfApplication1.MainWindow" mc:Ignorable="d" Title="MainWindow" Height="590" Width="1296" Loaded="Window_Loaded" Closed="Window_Closed"> <Grid> <Image x:Name="depthImage" HorizontalAlignment="Left" Height="480" Margin="650,0,-0.4,0" VerticalAlignment="Top" Width="640"/> <Image x:Name="colorImage" HorizontalAlignment="Left" Height="480" VerticalAlignment="Top" Width="640"/> </Grid> </Window>
Kinect的调用是使用已经封装好的KinectSensor类,用于管理Kinect资源。该类一样支持多个Kinect同时工做,由于我只弄到一台,因此多台Kinect的状况不予考虑。定义KinectSensor _kinect;在Window_Load()中添加函数StartKinect(),而后定义StartKinect函数以下:多线程
private void StartKinect() { if (KinectSensor.KinectSensors.Count <= 0) { MessageBox.Show("No Kinect device foound!"); return; } _kinect = KinectSensor.KinectSensors[0]; //MessageBox.Show("Status:" + _kinect.Status); _kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30); _kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30); _kinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(KinectColorFrameReady); _kinect.DepthFrameReady += new EventHandler<DepthImageFrameReadyEventArgs>(KinectDepthFrameReady); //_kinect.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(_kinect_AllFrameReady); _kinect.Start(); }
第一个if能够判断有几台Kinect工做,若是没有就提示,而后获取第一台Kinect设备。定义彩色视频流和深度视频流的格式,包括颜色格式、视频大小和帧速。一代Kinect只有640480FPS30和1280720FPS12,二代的图像分辨率和帧速都比一代优秀。接下来注册事件,这里要介绍一下Kinect的两种模型——事件模型和轮询模型,事件模型就如同上述代码中,彩色视频采集到一帧以后会触发事件ColorFrameReady,而后在事件属性ColorFrameReadyArgs中处理数据,深度视频和骨骼追踪也是如此,除了分别处理事件,还有三种帧都采集完毕后触发的AllFramesReady,可是集中处理的代码运行后十分卡顿,因此我没有使用这一事件;另外一种是轮询模型,与事件模型的“等待Kinect给数据”不一样,该模型是去向Kinect“主动要数据”,这种方法更快,也更适合多线程,这一模型之后会介绍。事件模型的优势在于代码可读性好,对编程语言来讲显得更加优雅。咱们注册的处理深度数据和彩色视频数据的方法代码以下:编程语言
private void KinectColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) { using (ColorImageFrame colorImageFrame = e.OpenColorImageFrame()) { if (colorImageFrame == null) return; byte[] pixels = new byte[colorImageFrame.PixelDataLength]; colorImageFrame.CopyPixelDataTo(pixels); int stride = colorImageFrame.Width * 4; colorImage.Source = BitmapSource.Create>(colorImageFrame.Width, colorImageFrame.Height, 96, 96, PixelFormats.Bgr32, null, pixels, stride); } } private void KinectDepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) { using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame()) { if (depthImageFrame == null) return; short[] depthPixelData = new short[depthImageFrame.PixelDataLength]; depthImageFrame.CopyPixelDataTo(depthPixelData); byte[] pixels = ConvertDepthFrameToColorFrame(depthPixelData, ((KinectSensor)sender).DepthStream); int stride = depthImageFrame.Width * 4; depthImage.Source = BitmapSource.Create(depthImageFrame.Width, depthImageFrame.Height, 96, 96, PixelFormats.Bgr32, null, pixels, stride); } }
ConvertDepthFrameToColorFrame() 是将深度数据流转为彩色数据,以便在Image控件上显示。ide
/// <summary> /// 将16位灰阶深度图转为32位彩色深度图 /// </summary> /// <param name="depthImageFrame">16位灰阶深度图</param> /// <param name="depthImageStream">用于得到深度数据流的相关属性</param> /// <returns></returns> private byte[] ConvertDepthFrameToColorFrame(short[] depthImageFrame, DepthImageStream depthImageStream) { byte[] depthFrame32 = new byte[depthImageStream.FrameWidth * depthImageStream.FrameHeight * bgr32BytesPerPixel]; //经过常量获取有效视距,不用硬编码 int tooNearDepth = depthImageStream.TooNearDepth; int tooFarDepth = depthImageStream.TooFarDepth; int unknowDepth = depthImageStream.UnknownDepth; for (int i16 = 0, i32 = 0; i16 < depthImageFrame.Length && i32 < depthFrame32.Length; i16++, i32 += 4) { int player = depthImageFrame[i16] & DepthImageFrame.PlayerIndexBitmask; int realDepth = depthImageFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth; //经过位运算,将13位的深度图裁剪位8位 byte intensity = (byte)(~(realDepth >> 4)); if (player == 0 && realDepth == 0) { depthFrame32[i32 + redIndex] = 255; depthFrame32[i32 + greenIndex] = 255; depthFrame32[i32 + blueIndex] = 255; } else if (player == 0 && realDepth == tooFarDepth) { //深紫色 depthFrame32[i32 + redIndex] = 66; depthFrame32[i32 + greenIndex] = 0; depthFrame32[i32 + blueIndex] = 66; } else if (player == 0 && realDepth == unknowDepth) { //深棕色 depthFrame32[i32 + redIndex] = 66; depthFrame32[i32 + greenIndex] = 66; depthFrame32[i32 + blueIndex] = 33; } else { depthFrame32[i32 + redIndex] = (byte)(intensity >> intensityShiftByPlayerR[player]); depthFrame32[i32 + greenIndex] = (byte)(intensity >> intensityShiftByPlayerG[player]); depthFrame32[i32 + blueIndex] = (byte)(intensity >> intensityShiftByPlayerB[player]); } } return depthFrame32; }
其中player是Kinect经过深度数据判断出视野内有多少人,人体区域用鲜艳的颜色标记。
最后,在 Window_Closed 中关闭Kinect:函数
private void Window_Closed(object sender, EventArgs e) { if (_kinect != null) { if (_kinect.Status == KinectStatus.Connected) { _kinect.Stop(); } } }
完成代码后,就能够生成并运行了。文章末尾会附上完整代码。工具
Kinect SDK支持用C++和C#开发,由于C#比较简单再加上VS2015的足够智能化,许多方法直接看函数名就知道用处,因此我选择使用C#。微软在那本书中介绍了NUI的概念,再加上对Kinect开发的了解,以及最近 HoloLens 发行,我感受Kinect + HoloLens 才是绝配——一个负责处理数据和显示,一个负责人机交互链接现实世界和虚拟世界。NUI必然是将来的趋势,而实现NUI 90%会依靠Kinect或者其它功能相似 Kinect 的设备来实现。虽然 Kinect 市场占有率很小,应用也很是少,但不得不令我猜想微软在下很大的一盘棋,藉此来定义将来的操做系统。
代码中还包括将深度数据转为256色灰阶图像并用亮绿色标记人体区域的方法。但这个方法不知为何一识别出人体就会变得很是卡顿,但愿有大神看到后能告知一下。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Microsoft.Kinect; using System.Threading; namespace KinectWpfApplication1 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { private KinectSensor _kinect; const float maxDepthDistance = 4095; const float minDepthDistance = 850; const float maxDepthDistancOddset = maxDepthDistance - minDepthDistance; private const int redIndex = 2; private const int greenIndex = 1; private const int blueIndex = 0; private static readonly int[] intensityShiftByPlayerR = { 1, 2, 0, 2, 0, 0, 2, 0 }; private static readonly int[] intensityShiftByPlayerG = { 1, 2, 2, 0, 2, 0, 0, 1 }; private static readonly int[] intensityShiftByPlayerB = { 1, 0, 2, 2, 0, 2, 0, 2 }; private static readonly int bgr32BytesPerPixel = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8; public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { StartKinect(); } private void StartKinect() { if (KinectSensor.KinectSensors.Count <= 0) { MessageBox.Show("No Kinect device foound!"); return; } _kinect = KinectSensor.KinectSensors[0]; //MessageBox.Show("Status:" + _kinect.Status); _kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30); _kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30); _kinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(KinectColorFrameReady); _kinect.DepthFrameReady += new EventHandler<DepthImageFrameReadyEventArgs>(KinectDepthFrameReady); //_kinect.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(_kinect_AllFrameReady); _kinect.Start(); } private void KinectColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) { using (ColorImageFrame colorImageFrame = e.OpenColorImageFrame()) { if (colorImageFrame == null) return; byte[] pixels = new byte[colorImageFrame.PixelDataLength]; colorImageFrame.CopyPixelDataTo(pixels); int stride = colorImageFrame.Width * 4; colorImage.Source = BitmapSource.Create(colorImageFrame.Width, colorImageFrame.Height, 96, 96, PixelFormats.Bgr32, null, pixels, stride); } } private void KinectDepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) { using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame()) { if (depthImageFrame == null) return; short[] depthPixelData = new short[depthImageFrame.PixelDataLength]; depthImageFrame.CopyPixelDataTo(depthPixelData); byte[] pixels = ConvertDepthFrameToColorFrame(depthPixelData, ((KinectSensor)sender).DepthStream); int stride = depthImageFrame.Width * 4; depthImage.Source = BitmapSource.Create(depthImageFrame.Width, depthImageFrame.Height, 96, 96, PixelFormats.Bgr32, null, pixels, stride); } /* using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame()) { if (depthImageFrame == null) return; byte[] pixels = ConvertDepthFrameToGrayFrame(depthImageFrame); int stride = depthImageFrame.Width * 4; depthImage.Source = BitmapSource.Create(depthImageFrame.Width, depthImageFrame.Height, 96, 96, PixelFormats.Bgr32, null, pixels, stride); }*/ } /// <summary> /// 单色直方图计算公式,返回256色灰阶,颜色越黑越远。 /// </summary> /// <param name="dis">深度值,有效值为......</param> /// <returns></returns> private static byte CalculateIntensityFromDepth(int dis) { return (byte)(255 - (255 * Math.Max(dis - minDepthDistance, 0) / maxDepthDistancOddset)); } /// <summary> /// 生成BGR32格式的图片字节数组 /// </summary> /// <param name="depthImageFrame"></param> /// <returns></returns> private byte[] ConvertDepthFrameToGrayFrame(DepthImageFrame depthImageFrame) { short[] rawDepthData = new short[depthImageFrame.PixelDataLength]; depthImageFrame.CopyPixelDataTo(rawDepthData); byte[] pixels = new byte[depthImageFrame.Height * depthImageFrame.Width * 4]; for (int depthIndex = 0, colorIndex = 0; depthIndex < rawDepthData.Length && colorIndex < pixels.Length; depthIndex++, colorIndex += 4) { int player = rawDepthData[depthIndex] & DepthImageFrame.PlayerIndexBitmask; int depth = rawDepthData[depthIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth; if (depth <= 900) { //离Kinect很近 pixels[colorIndex + blueIndex] = 255; pixels[colorIndex + greenIndex] = 0; pixels[colorIndex + redIndex] = 0; } else if (depth > 900 && depth < 2000) { pixels[colorIndex + blueIndex] = 0; pixels[colorIndex + greenIndex] = 255; pixels[colorIndex + redIndex] = 0; } else if (depth >= 2000) { //离Kinect超过2米 pixels[colorIndex + blueIndex] = 0; pixels[colorIndex + greenIndex] = 0; pixels[colorIndex + redIndex] = 255; } //单色直方图着色 byte intensity = CalculateIntensityFromDepth(depth); pixels[colorIndex + blueIndex] = intensity; pixels[colorIndex + greenIndex] = intensity; pixels[colorIndex + redIndex] = intensity; //若是是人体区域,用亮绿色标记 /*if (player > 0) { pixels[colorIndex + blueIndex] = Colors.LightGreen.B; pixels[colorIndex + greenIndex] = Colors.LightGreen.G; pixels[colorIndex + redIndex] = Colors.LightGreen.R; }*/ } return pixels; } /// <summary> /// 将16位灰阶深度图转为32位彩色深度图 /// </summary> /// <param name="depthImageFrame">16位灰阶深度图</param> /// <param name="depthImageStream">用于得到深度数据流的相关属性</param> /// <returns></returns> private byte[] ConvertDepthFrameToColorFrame(short[] depthImageFrame, DepthImageStream depthImageStream) { byte[] depthFrame32 = new byte[depthImageStream.FrameWidth * depthImageStream.FrameHeight * bgr32BytesPerPixel]; //经过常量获取有效视距,不用硬编码 int tooNearDepth = depthImageStream.TooNearDepth; int tooFarDepth = depthImageStream.TooFarDepth; int unknowDepth = depthImageStream.UnknownDepth; for (int i16 = 0, i32 = 0; i16 < depthImageFrame.Length && i32 < depthFrame32.Length; i16++, i32 += 4) { int player = depthImageFrame[i16] & DepthImageFrame.PlayerIndexBitmask; int realDepth = depthImageFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth; //经过位运算,将13位的深度图裁剪位8位 byte intensity = (byte)(~(realDepth >> 4)); if (player == 0 && realDepth == 0) { depthFrame32[i32 + redIndex] = 255; depthFrame32[i32 + greenIndex] = 255; depthFrame32[i32 + blueIndex] = 255; } else if (player == 0 && realDepth == tooFarDepth) { //深紫色 depthFrame32[i32 + redIndex] = 66; depthFrame32[i32 + greenIndex] = 0; depthFrame32[i32 + blueIndex] = 66; } else if (player == 0 && realDepth == unknowDepth) { //深棕色 depthFrame32[i32 + redIndex] = 66; depthFrame32[i32 + greenIndex] = 66; depthFrame32[i32 + blueIndex] = 33; } else { depthFrame32[i32 + redIndex] = (byte)(intensity >> intensityShiftByPlayerR[player]); depthFrame32[i32 + greenIndex] = (byte)(intensity >> intensityShiftByPlayerG[player]); depthFrame32[i32 + blueIndex] = (byte)(intensity >> intensityShiftByPlayerB[player]); } } return depthFrame32; } private void Window_Closed(object sender, EventArgs e) { if (_kinect != null) { if (_kinect.Status == KinectStatus.Connected) { _kinect.Stop(); } } } } }