找回密碼
         注冊(cè)會(huì)員
        搜索附件  

        201204061859385914.png

         

        Kinect for Windows SDK開發(fā)入門(六):骨骼追蹤基礎(chǔ) 上:
        Kinect產(chǎn)生的景深數(shù)據(jù)作用有限,要利用Kinect創(chuàng)建真正意義上交互,有趣和難忘的應(yīng)用,還需要除了深度數(shù)據(jù)之外的其他數(shù)據(jù)。這就是骨骼追蹤技術(shù)的初衷,骨骼追蹤技術(shù)通過(guò)處理景深數(shù)據(jù)來(lái)建立人體各個(gè)關(guān)節(jié)的坐標(biāo),骨骼追蹤能夠確定人體的各個(gè)部分,如那部分是手,頭部,以及身體。骨骼追蹤產(chǎn)生X,Y,Z數(shù)據(jù)來(lái)確定這些骨骼點(diǎn)。在,我們討論了景深圖像處理的一些技術(shù)。骨骼追蹤系統(tǒng)采用的景深圖像處理技術(shù)使用更復(fù)雜的算法如矩陣變換,機(jī)器學(xué)習(xí)及其他方式來(lái)確定骨骼點(diǎn)的坐標(biāo)。
              本文首先用一個(gè)例子展示骨骼追蹤系統(tǒng)涉及的主要對(duì)象,然后在此基礎(chǔ)上詳細(xì)討論骨骼追蹤中所涉及的對(duì)象模型。
           1. 獲取骨骼數(shù)據(jù)
               本節(jié)將會(huì)創(chuàng)建一個(gè)應(yīng)用來(lái)將獲取到的骨骼數(shù)據(jù)繪制到UI界面上來(lái)。在開始編碼前,首先來(lái)看看一些基本的對(duì)象以及如何從這些對(duì)象中如何獲取骨骼數(shù)據(jù)。在進(jìn)行數(shù)據(jù)處理之前了解數(shù)據(jù)的格式也很有必要。這個(gè)例子很簡(jiǎn)單明了,只需要骨骼數(shù)據(jù)對(duì)象然后將獲取到的數(shù)據(jù)繪制出來(lái)。
              彩色影像數(shù)據(jù),景深數(shù)據(jù)分別來(lái)自ColorImageSteam和DepthImageStream,同樣地,骨骼數(shù)據(jù)來(lái)自SkeletonStream。訪問(wèn)骨骼數(shù)據(jù)和訪問(wèn)彩色影像數(shù)據(jù)、景深數(shù)據(jù)一樣,也有事件模式和 “拉”模式兩種方式。在本例中我們采用基于事件的方式,因?yàn)檫@種方式簡(jiǎn)單,代碼量少,并且是一種很普通基本的方法。KinectSensor對(duì)象有一個(gè)名為SkeletonFrameReady事件。當(dāng)SkeletonStream中有新的骨骼數(shù)據(jù)產(chǎn)生時(shí)就會(huì)觸發(fā)該事件。通過(guò)AllFramesReady事件也可以獲取骨骼數(shù)據(jù)。在下一節(jié)中,我們將會(huì)詳細(xì)討論骨骼追蹤對(duì)象模型,現(xiàn)在我們只展示如何從SkeletonStream流中獲取骨骼數(shù)據(jù)。SkeletonStream產(chǎn)生的每一幀數(shù)據(jù)都是一個(gè)骨骼對(duì)象集合。每一個(gè)骨骼對(duì)象包含有描述骨骼位置以及骨骼關(guān)節(jié)的數(shù)據(jù)。每一個(gè)關(guān)節(jié)有一個(gè)唯一標(biāo)示符如頭(head)、肩(shoulder)、肘(dlbow)等信息和3D向量數(shù)據(jù)。
              現(xiàn)在來(lái)寫代碼。首先創(chuàng)建一個(gè)新的wpf工程文件,添加Microsoft.Kinect.dll。添加基本查找和初始化傳感器的代碼,這些代碼參考之前的文章。在開始啟動(dòng)傳感器之前,初始化SkeletonStream數(shù)據(jù)流,并注冊(cè)KinectSensor對(duì)象的SkeletonFrameReady事件,這個(gè)例子沒(méi)有使用彩色攝像機(jī)和紅外攝像機(jī)產(chǎn)生的數(shù)據(jù),所以不需要初始化這些數(shù)據(jù)流。UI界面采用默認(rèn)的,將Grid的名稱改為L(zhǎng)ayoutRoot,之后就再Grid里面繪制。代碼如下:<Window x:Class="KinectSkeletonTracking.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 x:Name="LayoutRoot" Background="White">
                
            </Grid>
        </Window>
          后臺(tái)邏輯代碼如下:private KinectSensor kinectDevice;
        private readonly Brush[] skeletonBrushes;//繪圖筆刷
        private Skeleton[] frameSkeletons;

        public MainWindow()
        {
            InitializeComponent();
            skeletonBrushes = new Brush[] { Brushes.Black, Brushes.Crimson, Brushes.Indigo, Brushes.DodgerBlue, Brushes.Purple, Brushes.Pink };
            KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
            this.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected);

        }

        public KinectSensor KinectDevice
        {
            get { return this.kinectDevice; }
            set
            {
                if (this.kinectDevice != value)
                {
                    //Uninitialize
                    if (this.kinectDevice != null)
                    {
                        this.kinectDevice.Stop();
                        this.kinectDevice.SkeletonFrameReady -= KinectDevice_SkeletonFrameReady;
                        this.kinectDevice.SkeletonStream.Disable();

                        this.frameSkeletons = null;
                    }

                    this.kinectDevice = value;

                    //Initialize
                    if (this.kinectDevice != null)
                    {
                        if (this.kinectDevice.Status == KinectStatus.Connected)
                        {
                            this.kinectDevice.SkeletonStream.Enable();
                            this.frameSkeletons = new Skeleton[this.kinectDevice.SkeletonStream.FrameSkeletonArrayLength];
                            this.kinectDevice.SkeletonFrameReady += KinectDevice_SkeletonFrameReady;

                            this.kinectDevice.Start();
                        }
                    }
                }
            }
        }

        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;
            }
        }
              以上代碼中,值得注意的是frameSkeletons數(shù)組以及該數(shù)組如何在流初始化時(shí)進(jìn)行內(nèi)存分配的。Kinect能夠追蹤到的骨骼數(shù)量是一個(gè)常量。這使得我們?cè)谡麄€(gè)應(yīng)用程序中能夠一次性的為數(shù)組分配內(nèi)存。為了方便,Kinect SDK在SkeletonStream對(duì)象中定義了一個(gè)能夠追蹤到的骨骼個(gè)數(shù)常量FrameSkeletonArrayLength,使用這個(gè)常量可以方便的對(duì)數(shù)組進(jìn)行初始化。代碼中也定義了一個(gè)筆刷數(shù)組,這些筆刷在繪制骨骼時(shí)對(duì)多個(gè)游戲者可以使用不同的顏色進(jìn)行繪制。也可以將筆刷數(shù)組中的顏色設(shè)置為自己喜歡的顏色。
               下面的代碼展示了SkeletonFrameReady事件的響應(yīng)方法,每一次事件被激發(fā)時(shí),通過(guò)調(diào)用事件參數(shù)的OpenSkeletonFrame方法就能夠獲取當(dāng)前的骨骼數(shù)據(jù)幀。剩余的代碼遍歷骨骼數(shù)據(jù)幀的Skeleton數(shù)組frameSkeletons,在UI界面通過(guò)關(guān)節(jié)點(diǎn)將骨骼連接起來(lái),用一條直線代表一根骨骼。UI界面簡(jiǎn)單,將Grid元素作為根結(jié)點(diǎn),并將其背景設(shè)置為白色。private void KinectDevice_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            using (SkeletonFrame frame = e.OpenSkeletonFrame())
            {
                if (frame != null)
                {
                    Polyline figure;
                    Brush userBrush;
                    Skeleton skeleton;

                    LayoutRoot.Children.Clear();
                    frame.CopySkeletonDataTo(this.frameSkeletons);


                    for (int i = 0; i < this.frameSkeletons.Length; i++)
                    {
                        skeleton = this.frameSkeletons;

                        if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                        {
                            userBrush = this.skeletonBrushes[i % this.skeletonBrushes.Length];

                            //繪制頭和軀干
                            figure = CreateFigure(skeleton, userBrush, new[] { JointType.Head, JointType.ShoulderCenter, JointType.ShoulderLeft, JointType.Spine,
                                                                        JointType.ShoulderRight, JointType.ShoulderCenter, JointType.HipCenter
                                                                        });
                            LayoutRoot.Children.Add(figure);

                            figure = CreateFigure(skeleton, userBrush, new[] { JointType.HipLeft, JointType.HipRight });
                            LayoutRoot.Children.Add(figure);

                            //繪制作腿
                            figure = CreateFigure(skeleton, userBrush, new[] { JointType.HipCenter, JointType.HipLeft, JointType.KneeLeft, JointType.AnkleLeft, JointType.FootLeft });
                            LayoutRoot.Children.Add(figure);

                            //繪制右腿
                            figure = CreateFigure(skeleton, userBrush, new[] { JointType.HipCenter, JointType.HipRight, JointType.KneeRight, JointType.AnkleRight, JointType.FootRight });
                            LayoutRoot.Children.Add(figure);

                            //繪制左臂
                            figure = CreateFigure(skeleton, userBrush, new[] { JointType.ShoulderLeft, JointType.ElbowLeft, JointType.WristLeft, JointType.HandLeft });
                            LayoutRoot.Children.Add(figure);

                            //繪制右臂
                            figure = CreateFigure(skeleton, userBrush, new[] { JointType.ShoulderRight, JointType.ElbowRight, JointType.WristRight, JointType.HandRight });
                            LayoutRoot.Children.Add(figure);
                        }
                    }
                }
            }
        }
              循環(huán)遍歷frameSkeletons對(duì)象,每一次處理一個(gè)骨骼,在處理之前需要判斷是否是一個(gè)追蹤好的骨骼,可以使用Skeleton對(duì)象的TrackingState屬性來(lái)判斷,只有骨骼追蹤引擎追蹤到的骨骼我們才進(jìn)行繪制,忽略哪些不是游戲者的骨骼信息即過(guò)濾掉那些TrackingState不等于SkeletonTrackingState.Tracked的骨骼數(shù)據(jù)。Kinect能夠探測(cè)到6個(gè)游戲者,但是同時(shí)只能夠追蹤到2個(gè)游戲者的骨骼關(guān)節(jié)位置信息。在后面我們將會(huì)詳細(xì)討論TrackingState這一屬性。
               處理骨骼數(shù)據(jù)相對(duì)簡(jiǎn)單,首先,我們根Kinect追蹤到的游戲者的編號(hào),選擇一種顏色筆刷。然后利用這只筆刷繪制曲線。CreateFigure方法為每一根骨骼繪制一條直線。GetJointPoint方法在繪制骨骼曲線中很關(guān)鍵。該方法以關(guān)節(jié)點(diǎn)的三維坐標(biāo)作為參數(shù),然后調(diào)用KinectSensor對(duì)象的MapSkeletonPointToDepth方法將骨骼坐標(biāo)轉(zhuǎn)換到深度影像坐標(biāo)上去。后面我們將會(huì)討論為什么需要這樣轉(zhuǎn)換以及如何定義坐標(biāo)系統(tǒng)。現(xiàn)在我們只需要知道的是,骨骼坐標(biāo)系和深度坐標(biāo)及彩色影像坐標(biāo)系不一樣,甚至和UI界面上的坐標(biāo)系不一樣。在開發(fā)Kinect應(yīng)用程序中,從一個(gè)坐標(biāo)系轉(zhuǎn)換到另外一個(gè)坐標(biāo)系這樣的操作非常常見(jiàn),GetJointPoint方法的目的就是將骨骼關(guān)節(jié)點(diǎn)的三維坐標(biāo)轉(zhuǎn)換到UI繪圖坐標(biāo)系統(tǒng),返回該骨骼關(guān)節(jié)點(diǎn)在UI上的位置。下面的代碼展示了CreateFigure和GetJointPoint這兩個(gè)方法。private Polyline CreateFigure(Skeleton skeleton, Brush brush, JointType[] joints)
        {
            Polyline figure = new Polyline();

            figure.StrokeThickness = 8;
            figure.Stroke = brush;

            for (int i = 0; i < joints.Length; i++)
            {
                figure.Points.Add(GetJointPoint(skeleton.Joints[joints]));
            }

            return figure;
        }

        private Point GetJointPoint(Joint joint)
        {

            DepthImagePoint point = this.KinectDevice.MapSkeletonPointToDepth(joint.Position, this.KinectDevice.DepthStream.Format);

            point.X *= (int)this.LayoutRoot.ActualWidth / KinectDevice.DepthStream.FrameWidth;
            point.Y *= (int)this.LayoutRoot.ActualHeight / KinectDevice.DepthStream.FrameHeight;

            return new Point(point.X, point.Y);
        }
              值得注意的是,骨骼關(guān)節(jié)點(diǎn)的三維坐標(biāo)中我們舍棄了Z值,只用了X,Y值。Kinect好不容易為我們提供了每一個(gè)節(jié)點(diǎn)的深度數(shù)據(jù)(Z值)而我們卻沒(méi)有使用,這看起來(lái)顯得很浪費(fèi)。其實(shí)不是這樣的,我們使用了節(jié)點(diǎn)的Z值,只是沒(méi)有直接使用,沒(méi)有在UI界面上展現(xiàn)出來(lái)而已。在坐標(biāo)空間轉(zhuǎn)換中是需要深度數(shù)據(jù)的。可以試試在GetJointPoint方法中,將joint的Position中的Z值改為0,然后再調(diào)用MapSkeletonPointToDepth方法,你會(huì)發(fā)現(xiàn)返回的對(duì)象中x和y值均為0,可以試試,將圖像以Z值進(jìn)行等比縮放,可以發(fā)現(xiàn)圖像的大小是和Z值(深度)成反的。也就是說(shuō),深度值越小,圖像越大,即人物離Kinect越近,骨骼數(shù)據(jù)越大。
               運(yùn)行程序,會(huì)得到如下骨骼圖像,這個(gè)是手握鍵盤準(zhǔn)備截圖的姿勢(shì)。一開始可能需要調(diào)整一些Form窗體的大小。程序會(huì)為每一個(gè)游戲者以一種顏色繪制骨骼圖像,可以試著在Kinect前面移動(dòng),可以看到骨骼圖像的變化,也可以走進(jìn)然后走出圖像以觀察顏色的變化。仔細(xì)觀察有時(shí)候可以看到繪圖出現(xiàn)了一些奇怪的圖案,在討論完骨骼追蹤相關(guān)的API之后,就會(huì)明白這些現(xiàn)象出現(xiàn)的原因了。
           
          


           2. 骨骼對(duì)象模型
              Kinect SDK中骨骼追蹤有一些和其他對(duì)象不一樣的對(duì)象結(jié)構(gòu)和枚舉。在SDK中骨骼追蹤相關(guān)的內(nèi)容幾乎占據(jù)了三分之一的內(nèi)容,可見(jiàn)Kinect中骨骼追蹤技術(shù)的重要性。下圖展示了骨骼追蹤系統(tǒng)中涉及到的一些主要的對(duì)象模型。有四個(gè)最主要的對(duì)象,他們是SkeletonStream,SkeletonFrame,Skeleton和Joint。下面將詳細(xì)介紹這四個(gè)對(duì)象。
          


           2.1 SkeletonStream對(duì)象
              SkeletonStream對(duì)象產(chǎn)生SkeletonFrame。從SkeletonStream獲取骨骼幀數(shù)據(jù)和從ColorStream及DepthStream中獲取數(shù)據(jù)類似。可以注冊(cè)SkeletonFrameReady事件或者AllFramesReady事件通過(guò)事件模型來(lái)獲取數(shù)據(jù),或者是使用OpenNextFrame方法通過(guò)“拉”模型來(lái)獲取數(shù)據(jù)。不能對(duì)同一個(gè)SkeletonStream同時(shí)使用這兩種模式。如果注冊(cè)了SkeletonFrameReady事件然后又調(diào)用OpenNextFrame方法將會(huì)返回一個(gè)InvalidOperationException異常。
           
          SkeletonStream的啟動(dòng)和關(guān)閉
              除非啟動(dòng)了SkeletonStream對(duì)象,否則,不會(huì)產(chǎn)生任何數(shù)據(jù),默認(rèn)情況下,SkeletonStream對(duì)象是關(guān)閉的。要使SkeletonStream產(chǎn)生數(shù)據(jù),必須調(diào)用對(duì)象的Enabled方法。相反,調(diào)用Disable方法能夠使SkeletonStream對(duì)象暫停產(chǎn)生數(shù)據(jù)。SkeletonStream有一個(gè)IsEnabled方法來(lái)描述當(dāng)前SkeletonStream對(duì)象的狀態(tài)。只有SkeletonStream對(duì)象啟動(dòng)了,KinectSensor對(duì)象的SkeletonFrameReady事件才能被激活。如果要使用“拉”模式來(lái)獲取數(shù)據(jù)SkeletonStream也必須啟動(dòng)后才能調(diào)用OpenNextFrame方法。否則也會(huì)拋出InvalidOperationException異常。
              一般地在應(yīng)用程序的聲明周期中,一旦啟動(dòng)了SkeletonStream對(duì)象,一般會(huì)保持啟動(dòng)狀態(tài)。但是在有些情況下,我們希望關(guān)閉SkeletonStream對(duì)象。比如在應(yīng)用程序中使用多個(gè)Kinect傳感器時(shí)。只有一個(gè)Kinect傳感器能夠產(chǎn)生骨骼數(shù)據(jù),這也意味著,即使使用多個(gè)Kinect傳感器,同時(shí)也只能追蹤到兩個(gè)游戲者的骨骼數(shù)據(jù)信息。在應(yīng)用程序執(zhí)行的過(guò)程中,有可能會(huì)關(guān)閉某一個(gè)Kinect傳感器的SkeletonStream對(duì)象而開啟另一個(gè)Kinect傳感器的SkeletonStream對(duì)象。
              另一個(gè)有可能關(guān)閉骨骼數(shù)據(jù)產(chǎn)生的原因是出于性能方面的考慮,骨骼數(shù)據(jù)處理是很耗費(fèi)計(jì)算性能的操作。打開骨骼追蹤是可以觀察的到CPU的占用率明顯增加。當(dāng)不需要骨骼數(shù)據(jù)時(shí),關(guān)閉骨骼追蹤很有必要。例如,在有些游戲場(chǎng)景中可能在展現(xiàn)一些動(dòng)畫效果或者播放視頻,在這個(gè)動(dòng)畫效果或者視頻播放時(shí),停止骨骼追蹤可能可以使得游戲更加流暢。
              當(dāng)然關(guān)閉SkeletonStream也有一些副作用。當(dāng)SkeletonStream的狀態(tài)發(fā)生改變時(shí),所有的數(shù)據(jù)產(chǎn)生都會(huì)停止和從新開始。SkeletonStream的狀態(tài)改變會(huì)使傳感器重新初始化,將TimeStamp和FrameNumber重置為0。在傳感器重新初始化時(shí)也有幾毫秒的延遲。
           
          平滑化
              在前面的例子中,會(huì)注意到,骨骼運(yùn)動(dòng)會(huì)呈現(xiàn)出跳躍式的變化。有幾個(gè)原因會(huì)導(dǎo)致出現(xiàn)這一問(wèn)題,可能是應(yīng)用程序的性能,游戲者的動(dòng)作不夠連貫,也有可能是Kinect硬件的性能問(wèn)題。骨骼關(guān)節(jié)點(diǎn)的相對(duì)位置可能在幀與幀之間變動(dòng)很大,這回對(duì)應(yīng)用程序產(chǎn)生一些負(fù)面的影像。除了會(huì)影像用戶體驗(yàn)和不愉快意外,也可能會(huì)導(dǎo)致用戶的形象或者手的顫動(dòng)抽搐而使用戶感到迷惑。
              SkeletonStream對(duì)象有一種方法能夠解決這個(gè)問(wèn)題。他通過(guò)將骨骼關(guān)節(jié)點(diǎn)的坐標(biāo)標(biāo)準(zhǔn)化來(lái)減少幀與幀之間的關(guān)節(jié)點(diǎn)位置差異。當(dāng)初始化SkeletonStream對(duì)象調(diào)用重載的Enable方法時(shí)可以傳入一個(gè)TransformSmoothParameters參數(shù)。SkeletonStream對(duì)象有兩個(gè)與平滑有關(guān)只讀屬性:IsSmoothingEnabled和SmoothParameters。當(dāng)調(diào)用Enable方法傳入了TransformSmoothParameters是IsSmoothingEnabled返回true而當(dāng)使用默認(rèn)的不帶參數(shù)的Enable方法初始化時(shí),IsSmoothingEnabled對(duì)象返回false。SmoothParameters屬性用來(lái)存儲(chǔ)定義平滑參數(shù)。TransformSmoothParameters這個(gè)結(jié)構(gòu)定義了一些屬性:
        修正值(Correction)屬性,接受一個(gè)從0-1的浮點(diǎn)型。值越小,修正越多。
        抖動(dòng)半徑(JitterRadius)屬性,設(shè)置修正的半徑,如果關(guān)節(jié)點(diǎn)“抖動(dòng)”超過(guò)了設(shè)置的這個(gè)半徑,將會(huì)被糾正到這個(gè)半徑之內(nèi)。該屬性為浮點(diǎn)型,單位為米。
        最大偏離半徑(MaxDeviationRadius)屬性,用來(lái)和抖動(dòng)半徑一起來(lái)設(shè)置抖動(dòng)半徑的最大邊界。任何超過(guò)這一半徑的點(diǎn)都不會(huì)認(rèn)為是抖動(dòng)產(chǎn)生的,而被認(rèn)定為是一個(gè)新的點(diǎn)。該屬性為浮點(diǎn)型,單位為米。
        預(yù)測(cè)幀大小(Prediction)屬性,返回用來(lái)進(jìn)行平滑需要的骨骼幀的數(shù)目。
        平滑值(Smoothing)屬性,設(shè)置處理骨骼數(shù)據(jù)幀時(shí)的平滑量,接受一個(gè)0-1的浮點(diǎn)值,值越大,平滑的越多。0表示不進(jìn)行平滑。
              對(duì)骨骼關(guān)節(jié)點(diǎn)進(jìn)行平滑處理會(huì)產(chǎn)生性能開銷。平滑處理的越多,性能消耗越大。設(shè)置平滑參數(shù)沒(méi)有經(jīng)驗(yàn)可以遵循。需要不斷的測(cè)試和調(diào)試已達(dá)到最好的性能和效果。在程序運(yùn)行的不同階段,可能需要設(shè)置不同的平滑參數(shù)。
          Note:SDK使用(Holt Double Exponential Smoothing)來(lái)對(duì)減少關(guān)節(jié)點(diǎn)的抖動(dòng)。指數(shù)平滑數(shù)據(jù)處理與時(shí)間有關(guān)。骨骼數(shù)據(jù)是時(shí)間序列數(shù)據(jù),因?yàn)楣趋酪鏁?huì)以某一時(shí)間間隔不斷產(chǎn)生一幀一幀的骨骼數(shù)據(jù)。平滑處理使用統(tǒng)計(jì)方法進(jìn)行滑動(dòng)平均,這樣能夠減少時(shí)間序列數(shù)據(jù)中的噪聲和極值。類似的處理方法最開始被用于金融市場(chǎng)和經(jīng)濟(jì)數(shù)據(jù)的預(yù)測(cè)。
           
          骨骼追蹤對(duì)象選擇
              默認(rèn)情況下,骨骼追蹤引擎會(huì)對(duì)視野內(nèi)的所有活動(dòng)的游戲者進(jìn)行追蹤。但只會(huì)選擇兩個(gè)可能的游戲者產(chǎn)生骨骼數(shù)據(jù),大多數(shù)情況下,這個(gè)選擇過(guò)程不確定。如果要自己選擇追蹤對(duì)象,需要使用AppChoosesSkeletons屬性和ChooseSkeletons方法。 默認(rèn)情況下AppChoosesSkeleton屬性為false,骨骼追蹤引擎追蹤所有可能的最多兩個(gè)游戲者。要手動(dòng)選擇追蹤者,需要將AppChoosesSkeleton設(shè)置為true,并調(diào)用ChooseSkeletons方法,傳入TrackingIDs已表明需要追蹤那個(gè)對(duì)象。ChooseSkeletons方法接受一個(gè),兩個(gè)或者0個(gè)TrackingIDs。當(dāng)ChooseSkeletons方法傳入0個(gè)參數(shù)時(shí),引擎停止追蹤骨骼信息。有一些需要注意的地方:
        如果調(diào)用ChooseSkeletons方法時(shí)AppChoosesSkeletons的屬性為false,就會(huì)引發(fā)InvalidOperationExcepthion的異常。
        如果在SkeletonStream開啟前,經(jīng)AppChoosesSkeletons設(shè)置為true,只有手動(dòng)調(diào)用ChooseSkeleton方法后才會(huì)開始骨骼追蹤。
        在AppChoosesSkeletons設(shè)置為 true之前,骨骼引擎自動(dòng)選擇追蹤的游戲者,并且繼續(xù)保持這些該游戲者的追蹤,直到用戶手動(dòng)指定需要追蹤的游戲者。如果自動(dòng)選擇追蹤的游戲者離開場(chǎng)景,骨骼引擎不會(huì)自動(dòng)更換追蹤者。
        將AppChoosesSkeletons沖新設(shè)置為false后,骨骼引擎會(huì)繼續(xù)對(duì)之前手動(dòng)設(shè)置的游戲者進(jìn)行追蹤,直到這些游戲者離開視野。當(dāng)游戲這離開視野時(shí)骨骼引擎才會(huì)選擇其他的可能的游戲者進(jìn)行追蹤。 2.2 SkeletonFrame
              SkeletonStream產(chǎn)生SkeletonFrame對(duì)象。可以使用事件模型從事件參數(shù)中調(diào)用OpenSkeletonFrame方法來(lái)獲取SkeletonFrame對(duì)象,或者采用”拉”模型調(diào)用SkeletonStream的OpenNextFrame來(lái)獲取SkeletonFrame對(duì)象。SkeletonFrame對(duì)象會(huì)存儲(chǔ)骨骼數(shù)據(jù)一段時(shí)間。同以通過(guò)調(diào)用SkeletonFrame對(duì)象的CopySkeletonDataTo方法將其保存的數(shù)據(jù)拷貝到骨骼對(duì)象數(shù)組中。SkeletonFrame對(duì)象有一個(gè)SkeletonArrayLength的屬性,這個(gè)屬性表示追蹤到的骨骼信息的個(gè)數(shù)。
           
          時(shí)間標(biāo)記字段
              SkeletonFrame的FrameNumber和Timestamp字段表示當(dāng)前記錄中的幀序列信息。FrameNumber是景深數(shù)據(jù)幀中的用來(lái)產(chǎn)生骨骼數(shù)據(jù)幀的幀編號(hào)。幀編號(hào)通常是不連續(xù)的,但是之后的幀編號(hào)一定比之前的要大。骨骼追蹤引擎在追蹤過(guò)程中可能會(huì)忽略某一幀深度數(shù)據(jù),這跟應(yīng)用程序的性能和每秒產(chǎn)生的幀數(shù)有關(guān)。例如,在基于事件獲取骨骼幀信息中,如果事件中處理幀數(shù)據(jù)的時(shí)間過(guò)長(zhǎng)就會(huì)導(dǎo)致這一幀數(shù)據(jù)還沒(méi)有處理完就產(chǎn)生了新的數(shù)據(jù),那么這些新的數(shù)據(jù)就有可能被忽略了。如果采用“拉”模型獲取幀數(shù)據(jù),那么取決于應(yīng)用程序設(shè)置的骨骼引擎產(chǎn)生數(shù)據(jù)的頻率,即取決于深度影像數(shù)據(jù)產(chǎn)生骨骼數(shù)據(jù)的頻率。
          Timestap字段記錄字Kinect傳感器初始化以來(lái)經(jīng)過(guò)的累計(jì)毫秒時(shí)間。不用擔(dān)心FrameNumber或者Timestamp字段會(huì)超出上限。FrameNumber是一個(gè)32位的整型,Timestamp是64位整型。如果應(yīng)用程序以每秒30幀的速度產(chǎn)生數(shù)據(jù),應(yīng)用程序需要運(yùn)行2.25年才會(huì)達(dá)到FrameNumber的限,此時(shí)Timestamp離上限還很遠(yuǎn)。另外在Kinect傳感器每一次初始化時(shí),這兩個(gè)字段都會(huì)初始化為0。可以認(rèn)為FrameNumber和Timestamp這兩個(gè)值是唯一的。
          這兩個(gè)字段在分析處理幀序列數(shù)據(jù)時(shí)很重要,比如進(jìn)行關(guān)節(jié)點(diǎn)值的平滑,手勢(shì)識(shí)別操作等。在多數(shù)情況下,我們通常會(huì)處理幀時(shí)間序列數(shù)據(jù),這兩個(gè)字段就顯得很有用。目前SDK中并沒(méi)有包含手勢(shì)識(shí)別引擎。在未來(lái)SDK中加入手勢(shì)引擎之前,我們需要自己編寫算法來(lái)對(duì)幀時(shí)間序列進(jìn)行處理來(lái)識(shí)別手勢(shì),這樣就會(huì)大量依賴這兩個(gè)字段。
           
          幀描述信息
              FloorClipPlane字段是一個(gè)有四個(gè)元素的元組Tuple<int,int,int,int>,每一個(gè)都是Ax+By+Cz+D=0地面平面(floor plane)表達(dá)式里面的系數(shù)項(xiàng)。元組中第一個(gè)元素表示A,即x前面的系數(shù),一次類推,最后一個(gè)表示常數(shù)項(xiàng),通常為負(fù)數(shù),是Kinect距離地面高度。在可能的情況下SDK會(huì)利用圖像處理技術(shù)來(lái)確定這些系數(shù)。但是有時(shí)候這些系數(shù)不肯能能夠確定下來(lái),可能需要預(yù)估。當(dāng)?shù)孛娌荒艽_定時(shí)FloorClipPlane中的所有元素均為0. 2.3 Skeleton
              Skeleton類定義了一系列字段來(lái)描述骨骼信息,包括描述骨骼的位置以及骨骼中關(guān)節(jié)可能的位置信息。骨骼數(shù)據(jù)可以通過(guò)調(diào)用SkeletonFrame對(duì)象的CopySkeletonDataTo方法獲得Skeleton數(shù)組。CopySkeletonDataTo方法有一些不可預(yù)料的行為,可能會(huì)影響內(nèi)存使用和其引用的骨骼數(shù)組對(duì)象。產(chǎn)生的每一個(gè)骨骼數(shù)組對(duì)象數(shù)組都是唯一的。以下面代碼為例:Skeleton[] skeletonA = new Skeleton[frame.SkeletonArrayLength];
        Skeleton[] skeletonB = new Skeleton[frame.SkeletonArrayLength];

        frame.CopySkeletonDataTo(skeletonA);
        frame.CopySkeletonDataTo(skeletonB);

        Boolean resultA = skeletonA[0] == skeletonB[0];//false
        Boolean resultB = skeletonA[0].TrackingId == skeletonB[0].TrackingId;//true

           
          上面的代碼可以看出,使用CopySkeletonDataTo是深拷貝對(duì)象,會(huì)產(chǎn)生兩個(gè)不同的Skeleton數(shù)組對(duì)象。
           
          TrackingID
              骨骼追蹤引擎對(duì)于每一個(gè)追蹤到的游戲者的骨骼信息都有一個(gè)唯一編號(hào)。這個(gè)值是整型,他會(huì)隨著新的追蹤到的游戲者的產(chǎn)生添加增長(zhǎng)。和之前幀序號(hào)一樣,這個(gè)值并不是連續(xù)增長(zhǎng)的,但是能保證的是后面追蹤到的對(duì)象的編號(hào)要比之前的編號(hào)大。另外,這個(gè)編號(hào)的產(chǎn)生是不確定的。如果骨骼追蹤引擎失去了對(duì)游戲者的追蹤,比如說(shuō)游戲者離開了Kinect的視野,那么這個(gè)對(duì)應(yīng)的唯一編號(hào)就會(huì)過(guò)期。當(dāng)Kinect追蹤到了一個(gè)新的游戲者,他會(huì)為其分配一個(gè)新的唯一編號(hào),編號(hào)值為0表示這個(gè)骨骼信息不是游戲者的,他在集合中僅僅是一個(gè)占位符。應(yīng)用程序使用TrackingID來(lái)指定需要骨骼追蹤引擎追蹤那個(gè)游戲者。調(diào)用SkeletonStream對(duì)象的ChooseSkeleton能以初始化對(duì)指定游戲這的追蹤。
           
          TrackingState
              該字段表示當(dāng)前的骨骼數(shù)據(jù)的狀態(tài)。下表展示了SkeletonTrackingState枚舉的可能值機(jī)器含義:
          


           
          Position
              Position一個(gè)SkeletonPoint類型的字段,代表所有骨骼的中間點(diǎn)。身體的中間點(diǎn)和脊柱關(guān)節(jié)的位置相當(dāng)。改字段提供了一個(gè)最快且最簡(jiǎn)單的所有視野范圍內(nèi)的游戲者位置的信息,而不管其是否在追蹤狀態(tài)中。在一些應(yīng)用中,如果不用關(guān)心骨骼中具體的關(guān)節(jié)點(diǎn)的位置信息,那么該字段對(duì)于確定游戲者的位置狀態(tài)已經(jīng)足夠。該字段對(duì)于手動(dòng)選擇要追蹤的游戲者(SkeletonStream.ChooseSkeleton)也是一個(gè)參考。例如,應(yīng)用程序可能需要追蹤距離Kinect最近的且處于追蹤狀態(tài)的游戲者,那么該字段就可以用來(lái)過(guò)濾掉其他的游戲者。
          ClippedEdges
              ClippedEdges字段用來(lái)描述追蹤者的身體哪部分位于Kinect的視野范圍外。他大體上提供了一個(gè)追蹤這的位置信息。使用這一屬性可以通過(guò)程序調(diào)整Kinect攝像頭的俯仰角或者提示游戲者讓其返回到視野中來(lái)。該字段類型為FrameEdges,他是一個(gè)枚舉并且有一個(gè)FlagsAtrribute自定義屬性修飾。這意味著ClippedEdges字段可以一個(gè)或者多個(gè)FrameEdges值。下面列出了FrameEdges的所有可能的值。
           
          


              當(dāng)游戲者身體的某一部分超出Kinect視場(chǎng)范圍時(shí),就需要對(duì)骨骼追蹤產(chǎn)生的數(shù)據(jù)進(jìn)行某些改進(jìn),因?yàn)槟承┎课坏臄?shù)據(jù)可能追蹤不到或者不準(zhǔn)確。最簡(jiǎn)單的解決辦法就是提示游戲者身體超出了Kinect的某一邊界范圍讓游戲者回到視場(chǎng)中來(lái)。例如,有時(shí)候應(yīng)用程序可能不關(guān)心游戲者超出Kinect視場(chǎng)下邊界的情況,但是如果超出了左邊界或者右邊界時(shí)就會(huì)對(duì)應(yīng)用產(chǎn)生影響,這是可以針對(duì)性的給游戲者一些提示。另一個(gè)解決辦法是調(diào)整Kinect設(shè)備的物理位置。Kinect底座上面有一個(gè)小的馬達(dá)能夠調(diào)整Kinect的俯仰角度。俯仰角度可以通過(guò)更改KinectSensor對(duì)象的ElevationAnagle屬性來(lái)進(jìn)行調(diào)整。如果應(yīng)用程序?qū)τ谟螒蛘吣_部動(dòng)作比較關(guān)注,那么通過(guò)程序調(diào)整Kinect的俯仰角能夠決絕腳部超出視場(chǎng)下界的情況。
              ElevationAnagle以度為單位。KinectSensor的MaxElevationAngle和MinElevationAngle確定了可以調(diào)整角度的上下界。任何將ElevationAngle設(shè)置超出上下界的操作將會(huì)掏出ArgumentOutOfRangeExcepthion異常。微軟建議不要過(guò)于頻繁重復(fù)的調(diào)整俯仰角以免損壞馬達(dá)。為了使得開發(fā)這少犯錯(cuò)誤和保護(hù)馬達(dá),SDK限制了每秒能調(diào)整的俯仰角的值。SDK限制了在連續(xù)15次調(diào)整之后要暫停20秒。
           
          Joints
           
          每一個(gè)骨骼對(duì)象都有一個(gè)Joints字段。該字段是一個(gè)JointsCollection類型,它存儲(chǔ)了一些列的Joint結(jié)構(gòu)來(lái)描述骨骼中可追蹤的關(guān)節(jié)點(diǎn)(如head,hands,elbow等等)。應(yīng)用程序使用JointsCollection索引獲取特定的關(guān)節(jié)點(diǎn),并通過(guò)節(jié)點(diǎn)的JointType枚舉來(lái)過(guò)濾指定的關(guān)節(jié)點(diǎn)。即使Kinect視場(chǎng)中沒(méi)有游戲者Joints對(duì)象也被填充。
           2.4 Joint
          骨骼追蹤引擎能夠跟蹤和獲取每個(gè)用戶的近20個(gè)點(diǎn)或者關(guān)節(jié)點(diǎn)信息。追蹤的數(shù)據(jù)以關(guān)節(jié)點(diǎn)數(shù)據(jù)展現(xiàn),它有三個(gè)屬性。JointType屬性是一個(gè)枚舉類型。下圖描述了可追蹤的所有關(guān)節(jié)點(diǎn)。
           
          


           
               每一個(gè)關(guān)節(jié)點(diǎn)都有類型為SkeletonPoint的Position屬性,他通過(guò)X,Y,Z三個(gè)值來(lái)描述關(guān)節(jié)點(diǎn)的控件位置。X,Y值是相對(duì)于骨骼平面空間的位置,他和深度影像,彩色影像的空間坐標(biāo)系不一樣。KinectSnesor對(duì)象有一些列的坐標(biāo)轉(zhuǎn)換方法,可以將骨骼坐標(biāo)點(diǎn)轉(zhuǎn)換到對(duì)應(yīng)的深度數(shù)據(jù)影像中去。最后每一個(gè)Skeleton對(duì)象還有一個(gè)JointTrackingState屬性,他描述了該關(guān)節(jié)點(diǎn)的跟蹤狀態(tài)及方式,下面列出了所有的可能值。
           
          


           3. 結(jié)語(yǔ)
              本文首先通過(guò)一個(gè)例子展示骨骼追蹤系統(tǒng)所涉及的主要對(duì)象,并將骨骼數(shù)據(jù)在UI界面上進(jìn)行了繪制,在此基礎(chǔ)上詳細(xì)介紹了骨骼追蹤對(duì)象模型中涉及到的主要對(duì)象,方法和屬性。SDK中骨骼追蹤占了大概三分之一的內(nèi)容,所以熟悉這些對(duì)象對(duì)于開發(fā)基于Kinect應(yīng)用程序至關(guān)重要。
        201204061859385914.png

        QQ|手機(jī)版|MCU資訊論壇 ( 京ICP備18035221號(hào)-2 )|網(wǎng)站地圖

        GMT+8, 2025-4-30 22:05 , Processed in 0.115761 second(s), 8 queries , Redis On.

        Powered by Discuz! X3.5

        © 2001-2025 Discuz! Team.

        国产精品多p对白交换绿帽| 国产精品高清在线观看| 国产精品毛片一区二区| 精品欧美| 亚洲精品成人无限看| 国产精品毛片一区二区三区| 日韩精品在线视频| 无码精品蜜桃一区二区三区WW| 亚洲国产精品无码久久一区二区 | 亚洲精品欧美精品日韩精品| 午夜精品久久久久久中宇| 久久91精品久久91综合| 全国精品一区二区在线观看| 久久亚洲精精品中文字幕| 成人精品一区二区三区免费看 | 日韩精品一区二区三区四区 | 精品精品国产自在久久高清| 久久亚洲AV永久无码精品| 无码国内精品久久人妻| 伊人久久大香线蕉精品| 日本不卡一区二区| 精品亚洲综合在线第一区| 在线精品动漫一区二区无广告| 国产成人精品999在线观看| 久久久久这里只有精品| 精品国产sm捆绑最大网免费站| 国产精品户外野外| 日韩人妻精品无码一区二区三区 | 国产精品亚洲精品日韩已满| 精品久久久久久久久久中文字幕| 亚洲无线观看国产精品| 99久久99久久精品国产片果冻| 色婷婷影院你懂的| 亚洲日本精品一区二区| 91大神在线电影| 国产精品伦理久久久久久| 久久久无码人妻精品无码| 精品国产91久久久久久久a | 99re66热这里只有精品| 日韩精品一区二区三区在线观看| 国产AV国片精品|