上一篇文章介紹了Kinect for Windows SDK進階開發須要瞭解的一些內容,包括影像處理Coding4Fun Kinect工具類庫以及如何創建本身的擴展方法類庫來方便開發,接下來介紹了利用Kinect進行近距離探測的一些方法,限於篇幅緣由,僅僅介紹了近距離探測的三種方式。php
本文接上文將繼續介紹近距離探測中如何探測運動,如何獲取並保存產生的影像數據;而後將會介紹如何進行臉部識別,以及介紹全息圖(Holograme)的一些知識,最後介紹了一些值得關注的類庫和項目。html
目前,利用運動識別(motion detection)來進行近景識別是最有意思的一種方式。實現運動識別的基本原理是設置一個起始的基準RGB圖像,而後將從攝像頭獲取的每一幀影像和這個基準圖像進行比較。若是發現了差別,咱們能夠認爲有東西進入到了攝像頭的視野範圍。前端
不難看出這種策略是有缺陷的。在現實生活中,物體是運動的。在一個房間裏,某我的可能會輕微移動傢俱。在戶外,一輛汽車可能會啓動,風可能會將一些小樹吹的搖搖晃晃。在這些場景中,盡然沒有連續的移動動做,可是物體的狀態仍是發生了變化,依據以前的策略,系統會判斷錯誤。所以,在這些狀況下,咱們須要間歇性的更改基準圖像才能解決這一問題。ios
與咱們以前遇到的問題相比,完成這些任務看起來須要更強大的圖像分析處理工具。幸虧,以前介紹的開源OpenCV庫提供了某種複雜的實時圖像處理操做的能力。OpenCV是Intel公司在1999年發起的一個項目,它將一些高級的視覺研究成果加入到OpenCV庫中並開源貢獻給了全世界。2008年,一個名爲Willow Garage的科技孵化公司負責對該項目的更新和維護。幾乎同時EmguCV項目開始發起,他提供了對OpenCV的.Net包裝,使得咱們在.Net環境下可以使用OpenCV庫中的函數。下面咱們將使用EmguCV來完成運動檢測以及後面的幾個演示項目。c++
EmguCV項目的官方網站爲http://www.emgu.com/wiki/index.php/Main_Page 實際的源代碼和安裝包放在SourceForge(http://sourceforge.net/projects/emgucv/files/ )上。本文使用的Emgu版本爲2.3.0。Emgu的安裝過程很簡單直觀,只須要點擊下載好的可執行文件便可。不過有一點須要注意的是EmguCV彷佛在x86架構的計算機上運行的最好。若是在64位的機器上開發,最好爲Emgu庫的目標平臺指定爲x86,以下圖所示(你也能夠在官網上下載源碼而後本身在x64平臺上編譯)。web
要使用Emgu庫,須要添加對下面三個dll的引用:Emgu.CV、Emgu.CV.UI以及Emgu.Util。這些dll能夠在Emgu的安裝目錄下面找到,在個人機器上該路徑是:C:\Emgu\emgucv-windows-x86 2.3.0.1416\bin\。算法
由於Emgu是對C++類庫的一個.Net包裝,因此須要在dll所在的目錄放一些額外的非託管的dll,使得Emgu可以找到這些dll進行處理。Emgu在應用程序的執行目錄查找這些dll。若是在debug模式下面,則在bin/Debug目錄下面查找。在release模式下,則在bin/Release目錄下面。共有11個非託管的C++ dll須要放置在相應目錄下面,他們是opencv_calib3d231.dll, opencv_conrib231.dll, opencv_core231.dll,opencv_features2d231.dll, opencv_ffmpeg.dll, opencv_highgui231.dll, opencv_imgproc231.dll,opencv_legacy231.dll, opencv_ml231.dll, opencv_objectdetect231.dll, and opencv_video231.dll。這些dll能夠在Emgu的安裝目錄下面找到。爲了方便,能夠拷貝全部以opencv_開頭的dll。windows
就像以前文章中所討論的,經過其餘的工具來進行Kinect開發可能會使得代碼變得有些混亂。可是有時候,經過添加OpenCV和Emgu的圖像處理功能,可以開發出一些比較有意思的應用程序。例如,咱們可以實現一個真正的運動識別解決方案。網絡
在咱們的擴展方法庫中,咱們須要一些額外的擴展幫助方法。上一篇文章討論過,每一種類庫都有其本身可以理解的核心圖像類型。在Emgu中,這個核心的圖像類型是泛型的Image<TColor,TDepth>類型,它實現了Emgu.CV.IImage接口。下面的代碼展示了一些咱們熟悉的影像數據格式和Emgu特定的影像格式之間轉換的擴展方法。新建一個名爲EmguExtensions.cs的靜態類,並將其命名空間改成ImageManipulationMethods,和咱們以前ImageExtensions類的命名空間相同。咱們能夠將全部的的擴展方法放到同一個命名空間中。這個類負責三種不一樣影像數據類型之間的轉換:從Microsoft.Kinect.ColorFrameImage到Emgu.CV.Image<TColor,TDepth>,從System.Drawing.Bitmap到Emgu.CV.Image<TColor,TDepth>以及Emgu.CV.Image<TColor,TDepth>到System.Windows.Media.Imaging.BitmapSource之間的轉換。架構
namespace ImageManipulationExtensionMethods { public static class EmguImageExtensions { public static Image<TColor, TDepth> ToOpenCVImage<TColor, TDepth>(this ColorImageFrame image) where TColor : struct, IColor where TDepth : new() { var bitmap = image.ToBitmap(); return new Image<TColor, TDepth>(bitmap); } public static Image<TColor, TDepth> ToOpenCVImage<TColor, TDepth>(this Bitmap bitmap) where TColor : struct, IColor where TDepth : new() { return new Image<TColor, TDepth>(bitmap); } public static System.Windows.Media.Imaging.BitmapSource ToBitmapSource(this IImage image) { var source = image.Bitmap.ToBitmapSource(); return source; } } }
使用Emgu類庫來實現運動識別,咱們將用到在以前文章中講到的"拉數據"(polling)模型而不是基於事件的機制來獲取數據。這是由於圖像處理很是消耗系統計算和內存資源,咱們但願可以調節處理的頻率,而這隻能經過"拉數據"這種模式來實現。須要指出的是本例子只是演示如何進行運動識別,因此注重的是代碼的可讀性,而不是性能,你們看了理解了以後能夠對其進行改進。
由於彩色影像數據流用來更新Image控件數據源,咱們使用深度影像數據流來進行運動識別。須要指出的是,咱們全部用於運動追蹤的數據都是經過深度影像數據流提供的。如前面文章討論,CompositionTarget.Rendering事件一般是用來進行從彩色影像數據流中"拉"數據。可是對於深度影像數據流,咱們將會建立一個BackgroundWorker對象來對深度影像數據流進行處理。以下代碼所示,BackgroundWorker對象將會調用Pulse方法來"拉"取深度影像數據,並執行一些消耗計算資源的處理。當BackgroundWorker完成了一個循環,接着從深度影像數據流中"拉"取下一幅影像繼續處理。代碼中聲明瞭兩個名爲MotionHistory和IBGFGDetector的Emgu成員變量。這兩個變量一塊兒使用,經過相互比較來不斷更新基準影像來探測運動。
KinectSensor _kinectSensor; private MotionHistory _motionHistory; private IBGFGDetector<Bgr> _forgroundDetector; bool _isTracking = false; public MainWindow() { InitializeComponent(); this.Unloaded += delegate { _kinectSensor.ColorStream.Disable(); }; this.Loaded += delegate { _motionHistory = new MotionHistory( 1.0, //in seconds, the duration of motion history you wants to keep 0.05, //in seconds, parameter for cvCalcMotionGradient 0.5); //in seconds, parameter for cvCalcMotionGradient _kinectSensor = KinectSensor.KinectSensors[0]; _kinectSensor.ColorStream.Enable(); _kinectSensor.Start(); BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += (a, b) => Pulse(); bw.RunWorkerCompleted += (c, d) => bw.RunWorkerAsync(); bw.RunWorkerAsync(); }; }
下面的代碼是執行圖象處理來進行運動識別的關鍵部分。代碼在Emgu的示例代碼的基礎上進行了一些修改。Pluse方法中的第一個任務是將彩色影像數據流產生的ColorImageFrame對象轉換到Emgu中能處理的圖象數據類型。_forgroundDetector對象被用來更新_motionHistory對象,他是持續更新的基準影像的容器。_forgroundDetector還被用來與基準影像進行比較,以判斷是否發生變化。當從當前彩色影像數據流中獲取到的影像和基準影像有不一樣時,建立一個影像來反映這兩張圖片之間的差別。而後將這張影像轉換爲一系列更小的圖片,而後對運動識別進行分解。咱們遍歷這一些列運動的圖像來看他們是否超過咱們設定的運動識別的閾值。若是這些運動很明顯,咱們就在界面上顯示視頻影像,不然什麼都不顯示。
private void Pulse() { using (ColorImageFrame imageFrame = _kinectSensor.ColorStream.OpenNextFrame(200)) { if (imageFrame == null) return; using (Image<Bgr, byte> image = imageFrame.ToOpenCVImage<Bgr, byte>()) using (MemStorage storage = new MemStorage()) //create storage for motion components { if (_forgroundDetector == null) { _forgroundDetector = new BGStatModel<Bgr>(image , Emgu.CV.CvEnum.BG_STAT_TYPE.GAUSSIAN_BG_MODEL); } _forgroundDetector.Update(image); //update the motion history _motionHistory.Update(_forgroundDetector.ForgroundMask); //get a copy of the motion mask and enhance its color double[] minValues, maxValues; System.Drawing.Point[] minLoc, maxLoc; _motionHistory.Mask.MinMax(out minValues, out maxValues , out minLoc, out maxLoc); Image<Gray, Byte> motionMask = _motionHistory.Mask .Mul(255.0 / maxValues[0]); //create the motion image Image<Bgr, Byte> motionImage = new Image<Bgr, byte>(motionMask.Size); motionImage[0] = motionMask; //Threshold to define a motion area //reduce the value to detect smaller motion double minArea = 100; storage.Clear(); //clear the storage Seq<MCvConnectedComp> motionComponents = _motionHistory.GetMotionComponents(storage); bool isMotionDetected = false; //iterate through each of the motion component for (int c = 0; c < motionComponents.Count(); c++) { MCvConnectedComp comp = motionComponents[c]; //reject the components that have small area; if (comp.area < minArea) continue; OnDetection(); isMotionDetected = true; break; } if (isMotionDetected == false) { OnDetectionStopped(); this.Dispatcher.Invoke(new Action(() => rgbImage.Source = null)); return; } this.Dispatcher.Invoke( new Action(() => rgbImage.Source = imageFrame.ToBitmapSource()) ); } } } private void OnDetection() { if (!_isTracking) _isTracking = true; } private void OnDetectionStopped() { _isTracking = false; }
相比直接將影像顯示出來,若是能將錄製到的影像保存到硬盤上就行了。可是,影像錄製,是須要必定的技巧,在網上能夠看到不少例子演示如何將Kinect獲取到的影像以圖片的形式保存到本地,前面的博文也介紹了這一點,可是你不多看到如何演示將一個完整的視頻影像保存到本地硬盤上。幸運的是Emgu類庫提供了一個VideoWriter類型來幫助咱們實現這一功能。
下面的方法展現了Record和StopRecording方法如何將Kinect彩色影像攝像頭產生的數據流保存到avi文件中。咱們在D盤建立了一個vids文件夾,要寫入avi文件以前須要保證該文件夾存在。當錄製開始時,咱們使用當前時間做爲文件名建立一個文件,同時咱們建立一個list對象來保存從彩色影像數據流中獲取到的一幀幀影像。當中止錄製時,將list對象中的一些列Emgu影像最爲參數傳入到VideoWriter對象來將這些影像轉爲爲avi格式並保存到硬盤上。這部分代碼沒有對avi編碼,因此產生的avi文件很是大。咱們能夠對avi文件進行編碼壓縮而後保存到硬盤上,可是這樣會加大計算機的運算開銷。
bool _isRecording = false; string _baseDirectory = @"d:\vids\"; string _fileName; List<Image<Rgb, Byte>> _videoArray = new List<Image<Rgb, Byte>>(); void Record(ColorImageFrame image) { if (!_isRecording) { _fileName = string.Format("{0}{1}{2}", _baseDirectory, DateTime.Now.ToString("MMddyyyyHmmss"), ".avi"); _isRecording = true; } _videoArray.Add(image.ToOpenCVImage<Rgb, Byte>()); } void StopRecording() { if (!_isRecording) return; CvInvoke.CV_FOURCC('P', 'I', 'M', '1'); //= MPEG-1 codec CvInvoke.CV_FOURCC('M', 'J', 'P', 'G'); //= motion-jpeg codec (does not work well) CvInvoke.CV_FOURCC('M', 'P', '4', '2');//= MPEG-4.2 codec CvInvoke.CV_FOURCC('D', 'I', 'V', '3'); //= MPEG-4.3 codec CvInvoke.CV_FOURCC('D', 'I', 'V', 'X'); //= MPEG-4 codec CvInvoke.CV_FOURCC('U', '2', '6', '3'); //= H263 codec CvInvoke.CV_FOURCC('I', '2', '6', '3'); //= H263I codec CvInvoke.CV_FOURCC('F', 'L', 'V', '1'); //= FLV1 codec using (VideoWriter vw = new VideoWriter(_fileName, 0, 30, 640, 480, true)) { for (int i = 0; i < _videoArray.Count(); i++) vw.WriteFrame<Rgb, Byte>(_videoArray[i]); } _fileName = string.Empty; _videoArray.Clear(); _isRecording = false; }
本例中,運動識別最後一點代碼是簡單的修改從彩色影像數據流"拉"取數據部分的邏輯。使得不只將在探測到運動時將影像顯示到UI界面上,同時也調用Record方法。當沒有探測到運動時,UI界面上什麼也不顯示,並調用StopRecording方法。這部分代碼也經過演示如何分析原始數據流,並探測Kinect視野中經常使用的變化給出了一個原型,這些變化信息可能會提供頗有用的信息。
if (isMotionDetected == false) { OnDetectionStopped(); this.Dispatcher.Invoke(new Action(() => rgbImage.Source = null)); StopRecording(); return; } this.Dispatcher.Invoke( new Action(() => rgbImage.Source = imageFrame.ToBitmapSource()) ); Record(imageFrame);
EmguCV庫也能用來進行面部識別(face identify)。實際的面部識別,就是將一張圖像上的人物的臉部識別出來,這是個很複雜的過程,具體過程咱們這裏不討論。對一幅影像進行處理來找到包含臉部的那一部分是咱們進行面部識別的第一個步驟。
大多數面部識別軟件或多或少都是基於類哈爾特徵(Haar-like feature)來進行識別的,他是哈爾小波(Haar wavelets)的一個應用,經過一些列的數學方法來定義一個矩形形狀。2001年,Paul Viola和Michael Jones發表了Viola-Jones物體識別方法的框架,該框架基於識別Haar-like特徵進行的。該方法和其餘面部識別算法相比,所須要的運算量較小。因此這部分方法整合進了OpenCV庫。
OpenCV和EmguCV中的面部識別是創建在一系列定義好了的識別規則基礎之上的,規則以XML文件的形式存儲,最初格式是由Rainer Lienhart定義的。在Emgu示例代碼中該文件名爲haarcascade_frontalface_default.xml。固然還有一系列的能夠識別人物眼睛的規則在這些示例文件中,本例子中沒有用到。
要使用Kinect SDK來構造一個簡單的面部識別程序,首先建立一個名爲KinectFaceFinder的WPF應用程序,而後引用Microsoft.Kinect, System.Drawing, Emgu.CV, Emgu.CV.UI, 和Emgu.Util. 並將全部以opencv_*開頭的dll拷貝到程序編譯的目錄下面。最後將以前寫好的兩個擴展方法類庫ImageExtension.cs和EmguImageExtensions.cs拷貝到項目中。項目的前端代碼和以前的同樣。只是在root根節點下面添加了一個名爲rgbImage的Image控件。
<Window x:Class="FaceFinder.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" > <Grid > <Image HorizontalAlignment="Stretch" Name="rgbImage" VerticalAlignment="Stretch" /> </Grid> </Window>
後臺代碼中,首先在MainWindow的構造函數中實例化KinectSensor對象而後配置彩色影像數據。由於咱們使用EmguCV庫來進行影像處理,因此咱們能夠直接使用RGB影像而不是深度影像。以下代碼所示,代碼中使用了BackgroundWork對象來從彩色影像數據流中拉取數據。每一次處理完了以後,拉取下一幅,而後繼續處理。
KinectSensor _kinectSensor; public MainWindow() { InitializeComponent(); this.Unloaded += delegate { _kinectSensor.ColorStream.Disable(); }; this.Loaded += delegate { _kinectSensor = KinectSensor.KinectSensors[0]; _kinectSensor.ColorStream.Enable(); _kinectSensor.Start(); BackgroundWorker bw = new BackgroundWorker(); bw.RunWorkerCompleted += (a, b) => bw.RunWorkerAsync(); bw.DoWork += delegate { Pulse(); }; bw.RunWorkerAsync(); }; }
上面代碼中,Pluse方法處理BackgroundWork的DoWork事件,這個方法是這個例子的主要方法。下面的代碼簡單的對Emgu提供的示例代碼進行了一點修改。咱們基於提供的臉部識別規則文件實例化了一個新的HaarCascade類。而後咱們從彩色影像數據流獲取了一幅影像,而後將他轉換爲了Emgu可以處理的格式。而後對圖像進行灰度拉伸而後提升對比度來使得臉部識別更加容易。Haar識別準則應用到圖像上去來產生一些列的結構來指示哪一個地方是識別出來的臉部。處理完的影像而後轉換爲BitmapSource類型,最後後複製給Image控件。由於WPF線程的工做方式,咱們使用Dispatcher對象來在正確的線程中給Image控件賦值。
String faceFileName = "haarcascade_frontalface_default.xml"; public void Pulse() { using (HaarCascade face = new HaarCascade(faceFileName)) { var frame = _kinectSensor.ColorStream.OpenNextFrame(100); var image = frame.ToOpenCVImage<Rgb, Byte>(); using (Image<Gray, Byte> gray = image.Convert<Gray, Byte>()) //Convert it to Grayscale { //normalizes brightness and increases contrast of the image gray._EqualizeHist(); //Detect the faces from the gray scale image and store the locations as rectangle //The first dimensional is the channel //The second dimension is the index of the rectangle in the specific channel MCvAvgComp[] facesDetected = face.Detect( gray, 1.1, 10, Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new System.Drawing.Size(20, 20)); Image<Rgb, Byte> laughingMan = new Image<Rgb, byte>("laughing_man.jpg"); foreach (MCvAvgComp f in facesDetected) { image.Draw(f.rect, new Rgb(System.Drawing.Color.Blue), 2); } Dispatcher.BeginInvoke(new Action(() => { rgbImage.Source = image.ToBitmapSource(); })); } } }
運行程序能夠看到,藍色方框內就是識別出來的人的臉部,這種識別精度比簡單的使用骨骼追蹤來識別出人的頭部,而後識別出臉部要精確的多。
既然facesDetected包含了識別出來的臉部的位置信息,咱們可以使用臉部識別算法來創建一個現實加強應用。咱們能夠將一個圖片放到臉部位置,而不是用矩形框來顯示。下面的代碼顯示了咱們如何使用一個圖片替代藍色的矩形框。
Image<Rgb, Byte> laughingMan = new Image<Rgb, byte>("laughing_man.jpg"); foreach (MCvAvgComp f in facesDetected) { //image.Draw(f.rect, new Rgb(System.Drawing.Color.Blue), 2); var rect = new System.Drawing.Rectangle(f.rect.X - f.rect.Width / 2 , f.rect.Y - f.rect.Height / 2 , f.rect.Width * 2 , f.rect.Height * 2); var newImage = laughingMan.Resize(rect.Width, rect.Height, Emgu.CV.CvEnum.INTER.CV_INTER_LINEAR); for (int i = 0; i < (rect.Height); i++) { for (int j = 0; j < (rect.Width); j++) { if (newImage[i, j].Blue != 0 && newImage[i, j].Red != 0 && newImage[i, j].Green != 0) image[i + rect.Y, j + rect.X] = newImage[i, j]; } } }
運行程序,代碼效果以下。這個形象來自動漫《攻克機動隊》(Ghost in the Shell: Stand Alone Complex),講述的是在一個在將來全面監控下的社會,一個黑客,每當他的臉被監控的攝像頭捕獲到的時候,就在上面疊加一個笑臉。由於臉部識別的算法很好,因此這張笑臉圖像,和動漫中的形象很像,他會隨着臉部的大小和運動而變化。
Kinect的另外一個有趣的應用是僞全息圖(pseudo-hologram)。3D圖像能夠根據人物在Kinect前面的各類位置進行傾斜和移動。若是方法夠好,能夠營造出3D控件中3D圖像的效果,這樣能夠用來進行三維展現。由於WPF具備3D矢量繪圖的功能。因此這一點使用WPF和Kinect比較容易實現。下圖顯示了一個能夠根據觀察者位置進行旋轉和縮放的3D立方體。可是,只有一個觀察者時才能運行。
這個效果能夠追溯到Johnny Chung Lee在2008 年TED演講中對Wii Remote的破解演示。後來Johnny Lee在Kinect項目組工做了一段時間,併發起了AdaFruit競賽,來爲破解Kinect編寫驅動。在Lee的實現中,Wii Remote的紅外傳感器放置在一對眼鏡上。可以追蹤戴上該眼鏡的人的運動。界面上會顯示一個複雜的3D圖像,該圖像能夠根據這副眼鏡的運動建立全息圖的效果。
使用Kinect SDK實現這一效果很是簡單。Kinect已經在骨骼數據中提供了座標點的以米爲單位的X,Y和Z的值。比較困難的部分是,如何使用XAML建立一個3D矢量模型。在本例子中,我使用Blender這個工具,他是一個開源的3D模型建造工具,能夠在www.blender.org網站上下載到。可是,要將3D網格曲面導出爲XAML,須要爲Blender安裝一個插件。我使用的Blender版本是2.6,自己有這麼一個功能,雖然有些限制。Dan Lehenbauer在CodePlex上也有針對Blender開發的導出Xmal的插件,可是隻能在老版本的Blender上使用。
WPF中3D矢量圖像的核心概念是Viewport3D對象。Viewport3D對象能夠被認爲是一個3D空間,在這個空間內,咱們能夠放置對象,光源和照相機。爲了演示一個3D效果,咱們建立一個新的名爲KinectHologram項目,並添加對Microsoft.Kinect.dll的引用。在MainWindow的UI界面中,在root Grid對象下面建立一個新的ViewPort3D元素。下面的代碼顯示了一個立方體的代碼。代碼中惟一和Kinect進行交互的是Viewport3D照相機對象。所以頗有必要對照相機對象進行命名。
<Viewport3D> <Viewport3D.Camera> <PerspectiveCamera x:Name="camera" Position="-40,160,100" LookDirection="40,-160,-100" UpDirection="0,1,0" /> </Viewport3D.Camera> <ModelVisual3D x:Name="mainBox"> <ModelVisual3D.Transform> <Transform3DGroup> <TranslateTransform3D> <TranslateTransform3D.OffsetY>10</TranslateTransform3D.OffsetY> </TranslateTransform3D> <ScaleTransform3D> <ScaleTransform3D.ScaleZ>3</ScaleTransform3D.ScaleZ> </ScaleTransform3D> </Transform3DGroup> </ModelVisual3D.Transform> <ModelVisual3D.Content> <Model3DGroup> <DirectionalLight Color="White" Direction="-1,-1,-3" /> <GeometryModel3D > <GeometryModel3D.Geometry> <MeshGeometry3D Positions="1.000000,1.000000,-1.000000 1.000000,-1.000000,-1.000000 -1.000000,-1.000000,-1.000000 -1.000000,1.000000,-1.000000 1.000000,0.999999,1.000000 -1.000000,1.000000,1.000000 -1.000000,-1.000000,1.000000 0.999999,-1.000001,1.000000 1.000000,1.000000,-1.000000 1.000000,0.999999,1.000000 0.999999,-1.000001,1.000000 1.000000,-1.000000,-1.000000 1.000000,-1.000000,-1.000000 0.999999,-1.000001,1.000000 -1.000000,-1.000000,1.000000 -1.000000,-1.000000,-1.000000 -1.000000,-1.000000,-1.000000 -1.000000,-1.000000,1.000000 -1.000000,1.000000,1.000000 -1.000000,1.000000,-1.000000 1.000000,0.999999,1.000000 1.000000,1.000000,-1.000000 -1.000000,1.000000,-1.000000 -1.000000,1.000000,1.000000" TriangleIndices="0,1,3 1,2,3 4,5,7 5,6,7 8,9,11 9,10,11 12,13,15 13,14,15 16,17,19 17,18,19 20,21,23 21,22,23" Normals="0.000000,0.000000,-1.000000 0.000000,0.000000,-1.000000 0.000000,0.000000,-1.000000 0.000000,0.000000,-1.000000 0.000000,-0.000000,1.000000 0.000000,-0.000000,1.000000 0.000000,-0.000000,1.000000 0.000000,-0.000000,1.000000 1.000000,-0.000000,0.000000 1.000000,-0.000000,0.000000 1.000000,-0.000000,0.000000 1.000000,-0.000000,0.000000 -0.000000,-1.000000,-0.000000 -0.000000,-1.000000,-0.000000 -0.000000,-1.000000,-0.000000 -0.000000,-1.000000,-0.000000 -1.000000,0.000000,-0.000000 -1.000000,0.000000,-0.000000 -1.000000,0.000000,-0.000000 -1.000000,0.000000,-0.000000 0.000000,1.000000,0.000000 0.000000,1.000000,0.000000 0.000000,1.000000,0.000000 0.000000,1.000000,0.000000"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush="blue"/> </GeometryModel3D.Material> </GeometryModel3D> <Model3DGroup.Transform> <Transform3DGroup> <Transform3DGroup.Children> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0.0935395359992981"/> <ScaleTransform3D ScaleX="12.5608325004577637" ScaleY="12.5608322620391846" ScaleZ="12.5608325004577637"/> </Transform3DGroup.Children> </Transform3DGroup> </Model3DGroup.Transform> </Model3DGroup> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D>
上面的代碼中,照相機對象(camera)有一個以X,Y,Z表示的位置屬性。X從左至右增長。Y從下往上增長。Z隨着屏幕所在平面,向靠近用戶的方向增長。該對象還有一個觀看方向,該方向和位置相反。在這裏咱們告訴照相機日後面的0,0,0座標點看。最後,UpDirection表示照相機的朝向,在本例中,照相機向上朝向Y軸方向。
立方體使用一系列8個位置的值來繪製,每個點都是三維座標點。而後經過這些點繪製三角形切片來造成立方體的表面。在這之上咱們添加一個Material對象而後將其顏色改成藍色。而後給一個縮放變形使這個立方體看起來大一點。最後咱們給這個立方體一些定向光源來產生一些3D的效果。
在後臺代碼中,咱們只須要配置KienctSensor對象來支持骨骼追蹤,以下代碼所示。彩色影像和深度值影像在該項目中不須要用到。
KinectSensor _kinectSensor; public MainWindow() { InitializeComponent(); this.Unloaded += delegate { _kinectSensor.DepthStream.Disable(); _kinectSensor.SkeletonStream.Disable(); }; this.Loaded += delegate { _kinectSensor = KinectSensor.KinectSensors[0]; _kinectSensor.SkeletonFrameReady += SkeletonFrameReady; _kinectSensor.DepthFrameReady += DepthFrameReady; _kinectSensor.SkeletonStream.Enable(); _kinectSensor.DepthStream.Enable(); _kinectSensor.Start(); }; }
爲了可以產生全息圖的效果,咱們將會圍繞立方體來移動照相機的位置,而不是對立方體自己進行旋轉。首先咱們須要肯定Kinect中是否有用戶正在處於追蹤狀態。若是有一個,咱們就忽略其餘的。咱們獲取追蹤到的骨骼數據並提取X,Y,Z座標信息。由於Kinect提供的位置信息的單位是米,而咱們的3D立方體不是的,因此須要將這些位置點信息進行必定的轉換纔可以產生3D效果。基於這些位置座標,咱們大概以正在追中的骨骼爲中心來移動照相機。咱們也用這些座標,並將這些座標翻轉,使得照相機可以連續的指向0,0,0這個原點。
void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { float x=0, y=0, z = 0; //get angle of skeleton using (var frame = e.OpenSkeletonFrame()) { if (frame == null || frame.SkeletonArrayLength == 0) return; var skeletons = new Skeleton[frame.SkeletonArrayLength]; frame.CopySkeletonDataTo(skeletons); for (int s = 0; s < skeletons.Length; s++) { if (skeletons[s].TrackingState == SkeletonTrackingState.Tracked) { border.BorderBrush = new SolidColorBrush(Colors.Red); var skeleton = skeletons[s]; x = skeleton.Position.X * 60; z = skeleton.Position.Z * 120; y = skeleton.Position.Y; break; } else { border.BorderBrush = new SolidColorBrush(Colors.Black); } } } if (Math.Abs(x) > 0) { camera.Position = new System.Windows.Media.Media3D.Point3D(x, y , z); camera.LookDirection = new System.Windows.Media.Media3D.Vector3D(-x, -y , -z); } }
有意思的是,這樣產生全息圖影像的效果比許多複雜的3D對象產生的全息圖要好。3D立方體能夠很容易的轉換爲矩形,以下圖所示,只須要簡單的將立方體在Z方向上進行拉伸便可。這個矩形朝向遊戲者拉伸。咱們能夠簡單的將立方體的UI這部分代碼複製,而後建立一個新的modelVisual3D對象來產生新的矩形。使用變換來將這些矩形放X,Y軸上的不一樣位置,並給予不一樣的顏色。由於照相機是後臺代碼惟一須要關注的對象,在前臺添加一個矩形對象及變換並不會影響代碼的運行。
與Kinect相關的一些類庫和工具在之後的幾年,估計會更加多和Kienct SDK一塊兒工做。固然,最重要的令人着迷的類庫及工具備FAAST,Utility3D以及Microsoft Robotics Developer Studio。
FAAST(Flexible Action and Articulated Skeleton Toolkit)是一箇中間件類庫用來鏈接Kinect的手勢交互界面和傳統的人際交互界面。這個類庫由南加利福利亞大學的創新技術研究院編寫和維護。最初FAAST是使用OpenNI結合Kinect來編寫的一個手勢識別類庫。這個類庫真正優秀的地方在於它可以將大多數內建手勢映射到其餘API中,甚至可以將手勢映射到鍵盤點擊中來。這使得一些Kinect技術愛好者(hacker)可以使用這一工具來使用Kinect玩一些視頻遊戲,包括一些例如《使命召喚》(Call of Duty)的第一人稱射擊遊戲,以及一些像《第二人生》(Second Life)和《魔獸世界》(World of Warcraft)這樣的網絡遊戲。目前FAAST只有針對OpenNI版本,可是據最新的消息,FAAST如今正在針對KienctSDK進行開發,能夠在其官網http://projects.ict.usc.edu/mxr/faast瞭解更多的相關信息。
Utility3D是一個工具,可以使得傳統開發3D遊戲的複雜工做變得相對簡單,他有免費版和專業版之分。使用Uitltiy3D編寫的遊戲可以輕鬆的跨多種平臺,包括web,windows,ios, iPhone,ipad,Android,Xbox,Playstation以及Wii。它也支持第三方插件,包括一些爲Kinect開發的插件,使得開發者可以使用Kinect做爲傳感器來做爲Windows遊戲的輸入設備。能夠在Unity3D的官網查看更多信息http://unity3d.com。
Microsoft Robotics Developer Studio是微軟針對機器人的開發平臺,其最新發布的RDS4.0版本也集成了Kinect傳感器。除了可以訪問Kinect提供的功能,Kinect也支持一系列特定的基於Kinect的機器人平臺,他們能夠利用Kinect傳感器進行障礙物避讓。更多的有關Microsoft Robotics Developer Studio的信息能夠訪問http://www.microsoft.com/robotics。
本文接上文,介紹了Kinect進階開發中須要瞭解一些類庫和項目。在上文的基礎上,本文接着介紹了近距離探測中如何探測運動,如何獲取並保存產生的影像數據;而後將會介紹如何進行臉部識別,以及介紹全息圖(Holograme)的一些知識,最後介紹了一些值得關注的類庫和項目。本文中的幾個例子可能對計算機性能有點要求,由於從Kinect獲取數據以及實時圖像處理是一個比較耗費資源的工做,我是09年的買的筆記本,運行上面的例子基本比較卡,你們能夠試着在好一點的機器上運行,就像以前所講的,本文例子着重的是功能和方法的展現,因此注重代碼的可讀性,最求性能的同窗能夠試着優化一下。
本文全部示例代碼點擊此處下載,但願以上內容對於您擴寬Kinect開發視野有所幫助!