Kinect SDK 初探(下)-b

  • 3392
  • 0
  • 2012-08-29

摘要:Kinect SDK 初探(下)-b

  • ShapeGame

 

這一個範例是我覺得最有興趣的一個, 因為它很像我一直想做的人和場景物件互動的東西

這個Solution包含了3個Project

 

  • Microsoft.Kinect.Toolkit之前一直看到, 就是用來負責捉Kinect Sensor和處理它斷線的機制
  • Microsoft.Samples.Kinect.WpfViewers 方便專案把Color、Depth或其他的Viewer整合的元件庫
  • ShapeGame 這個方案的主程式

 

這邊先岔個題, Microsoft.Kinect.Toolkit用了很多MultiThreading的程式來處理硬體的溝通

因為硬體的driver通常會在不同的Thread, 一不小心就會死鎖了

 

  • CallbackLock
  • ContextEventWrapper
  • RelayCommand
  • ThreadSafeCollection

 

先來看看 CallbackLock這個東西

internal class CallbackLock : IDisposable

它在建立的時候 Monitor.Enter(lockObject); (System.Threading.Monitor是這麼解釋的 

您也可以使用 Monitor 來確保其他執行緒都無法存取由鎖定擁有者執行之應用程式碼的區段Section)

它在Dispose的時候會執行 Monitor.Exit(lockObject);

我需要了解的是

 

  1. Callback Lock什麼時候會被Dispose, 而且
  2. Callback Lock 和直接 lock(object) 有什麼不同

跑了一下Breakpoint之後, 以這個程式碼為例

                using (var callbackLock = new CallbackLock(lockObject))
                {
                    // Check again in case someone else started while
                    // we were waiting on the lock.
                    if (!isStarted)
                    {
                        isStarted = true;

                        KinectSensor.KinectSensors.StatusChanged += KinectSensorsOnStatusChanged;

                        TryFindAndStartKinect(callbackLock);
                    }
                }

使用using{} 在結束的時候, 會呼叫CallbackLock的Dispose(如果不用using 會怎樣?)寫個程式來試試...

第二個問題我自己的回答是, 可以在Dispose時呼叫LockExitEventHandler

 

public sealed class ThreadSafeCollection<T> : IList<T> 就是把LIST包起來, 在做每件事情時, 先用lock鎖定section...其實很單純

其他的ThreadSafe機制礙於我的功力不夠, 無法一一參透, 就先回神到Kinect本身吧

 

  • Microsoft.Samples.Kinect.WpfViewers

 

它包括了很多常用的WPF物件, 不在話下

 

  • ShapeGame

 

MainWindow單純到嚇人 它只有四個東西 

 

  • playfield 一個canvas
  • KinectColorViewer (from Microsoft.Samples.Kinect.WpfViewers)
  • SenserChooserUI (from Microsoft.Kinect.Toolkit)
  • enableAec 一個checkbox

 

一切都是從 this.KinectSensorManager.KinectSensorChanged += this.KinectSensorChanged; 開始的

再來是 this.InitializeKinectServices(this.KinectSensorManager, args.NewValue);

sensor.SkeletonFrameReady += this.SkeletonsReady;

using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())

處理單個Joint的端點(頭、手、腳)

                                player.UpdateJointPosition(skeleton.Joints, JointType.Head);
                                player.UpdateJointPosition(skeleton.Joints, JointType.HandLeft);
                                player.UpdateJointPosition(skeleton.Joints, JointType.HandRight);
                                player.UpdateJointPosition(skeleton.Joints, JointType.FootLeft);
                                player.UpdateJointPosition(skeleton.Joints, JointType.FootRight);

處理兩個Joints組合成一個Bone

                                // Hands and arms
                                player.UpdateBonePosition(skeleton.Joints, JointType.HandRight, JointType.WristRight);
                                player.UpdateBonePosition(skeleton.Joints, JointType.WristRight, JointType.ElbowRight);
                                player.UpdateBonePosition(skeleton.Joints, JointType.ElbowRight, JointType.ShoulderRight);

                                player.UpdateBonePosition(skeleton.Joints, JointType.HandLeft, JointType.WristLeft);
                                player.UpdateBonePosition(skeleton.Joints, JointType.WristLeft, JointType.ElbowLeft);
                                player.UpdateBonePosition(skeleton.Joints, JointType.ElbowLeft, JointType.ShoulderLeft);

                                // Head and Shoulders
                                player.UpdateBonePosition(skeleton.Joints, JointType.ShoulderCenter, JointType.Head);
                                player.UpdateBonePosition(skeleton.Joints, JointType.ShoulderLeft, JointType.ShoulderCenter);
                                player.UpdateBonePosition(skeleton.Joints, JointType.ShoulderCenter, JointType.ShoulderRight);

                                // Legs
                                player.UpdateBonePosition(skeleton.Joints, JointType.HipLeft, JointType.KneeLeft);
                                player.UpdateBonePosition(skeleton.Joints, JointType.KneeLeft, JointType.AnkleLeft);
                                player.UpdateBonePosition(skeleton.Joints, JointType.AnkleLeft, JointType.FootLeft);

                                player.UpdateBonePosition(skeleton.Joints, JointType.HipRight, JointType.KneeRight);
                                player.UpdateBonePosition(skeleton.Joints, JointType.KneeRight, JointType.AnkleRight);
                                player.UpdateBonePosition(skeleton.Joints, JointType.AnkleRight, JointType.FootRight);

                                player.UpdateBonePosition(skeleton.Joints, JointType.HipLeft, JointType.HipCenter);
                                player.UpdateBonePosition(skeleton.Joints, JointType.HipCenter, JointType.HipRight);

                                // Spine
                                player.UpdateBonePosition(skeleton.Joints, JointType.HipCenter, JointType.ShoulderCenter);

來看看player.cs

        public void UpdateBonePosition(Microsoft.Kinect.JointCollection joints, JointType j1, JointType j2)
        {
            var seg = new Segment(
                (joints[j1].Position.X * this.playerScale) + this.playerCenter.X,
                this.playerCenter.Y - (joints[j1].Position.Y * this.playerScale),
                (joints[j2].Position.X * this.playerScale) + this.playerCenter.X,
                this.playerCenter.Y - (joints[j2].Position.Y * this.playerScale))
                { Radius = Math.Max(3.0, this.playerBounds.Height * BoneSize) / 2 };
            this.UpdateSegmentPosition(j1, j2, seg);
        }

        public void UpdateJointPosition(Microsoft.Kinect.JointCollection joints, JointType j)
        {
            var seg = new Segment(
                (joints[j].Position.X * this.playerScale) + this.playerCenter.X,
                this.playerCenter.Y - (joints[j].Position.Y * this.playerScale))
                { Radius = this.playerBounds.Height * ((j == JointType.Head) ? HeadSize : HandSize) / 2 };
            this.UpdateSegmentPosition(j, j, seg);
        }

JointType 列表(from metadata)

namespace Microsoft.Kinect
    public enum JointType
    {
        HipCenter = 0,
        Spine = 1,
        ShoulderCenter = 2,
        Head = 3,
        ShoulderLeft = 4,
        ElbowLeft = 5,
        WristLeft = 6,
        HandLeft = 7,
        ShoulderRight = 8,
        ElbowRight = 9,
        WristRight = 10,
        HandRight = 11,
        HipLeft = 12,
        KneeLeft = 13,
        AnkleLeft = 14,
        FootLeft = 15,
        HipRight = 16,
        KneeRight = 17,
        AnkleRight = 18,
        FootRight = 19,
    }
}

private void HandleGameTimer(int param)可以說是整個程式的核心

        private void HandleGameTimer(int param)
        {
            // Every so often, notify what our actual framerate is
            if ((this.frameCount % 100) == 0)
            {
                this.myFallingThings.SetFramerate(1000.0 / this.actualFrameTime);
            }

            // Advance animations, and do hit testing.
            for (int i = 0; i < NumIntraFrames; ++i)
            {
                foreach (var pair in this.players)
                {
                    HitType hit = this.myFallingThings.LookForHits(pair.Value.Segments, pair.Value.GetId());
                    if ((hit & HitType.Squeezed) != 0)
                    {
                        this.squeezeSound.Play();
                    }
                    else if ((hit & HitType.Popped) != 0)
                    {
                        this.popSound.Play();
                    }
                    else if ((hit & HitType.Hand) != 0)
                    {
                        this.hitSound.Play();
                    }
                }

                this.myFallingThings.AdvanceFrame();
            }

            // Draw new Wpf scene by adding all objects to canvas
            playfield.Children.Clear();
            this.myFallingThings.DrawFrame(this.playfield.Children);
            foreach (var player in this.players)
            {
                player.Value.Draw(playfield.Children);
            }

            BannerText.Draw(playfield.Children);
            FlyingText.Draw(playfield.Children);

            this.CheckPlayers();
        }

 

這三件事為遊戲的主軸

  • myFallingThings.LookForHits
  • myFallingThings.AdvanceFrame
  • myFallingThings.DrawFrame

可惜的是 它寫的很不物件導向, 像是程序式程式時代的程序員寫法(好繞舌)

 

再來找碰撞偵測的部份 myFallingThings.LookForHits中

            foreach (var pair in segments)
            {
                for (int i = 0; i < this.things.Count; i++)
                {

 

骨頭加上物件的列舉, 等於是 (segments X things)的檢查碰撞次數

Thing.Hit應該是核心中的核心

            public bool Hit(Segment seg, ref System.Windows.Point hitCenter, ref double lineHitLocation)
            {
                double minDxSquared = this.Size + seg.Radius;
                minDxSquared *= minDxSquared;

                // See if falling thing hit this body segment
                if (seg.IsCircle())
                {
                    if (SquaredDistance(this.Center.X, this.Center.Y, seg.X1, seg.Y1) <= minDxSquared)
                    {
                        hitCenter.X = seg.X1;
                        hitCenter.Y = seg.Y1;
                        lineHitLocation = 0;
                        return true;
                    }
                }
                else
                {
                    double sqrLineSize = SquaredDistance(seg.X1, seg.Y1, seg.X2, seg.Y2);
                    if (sqrLineSize < 0.5)
                    {
                        // if less than 1/2 pixel apart, just check dx to an endpoint
                        return SquaredDistance(this.Center.X, this.Center.Y, seg.X1, seg.Y1) < minDxSquared;
                    }

                    // Find dx from center to line
                    double u = ((this.Center.X - seg.X1) * (seg.X2 - seg.X1)) + (((this.Center.Y - seg.Y1) * (seg.Y2 - seg.Y1)) / sqrLineSize);
                    if ((u >= 0) && (u <= 1.0))
                    {   // Tangent within line endpoints, see if we're close enough
                        double intersectX = seg.X1 + ((seg.X2 - seg.X1) * u);
                        double intersectY = seg.Y1 + ((seg.Y2 - seg.Y1) * u);

                        if (SquaredDistance(this.Center.X, this.Center.Y, intersectX, intersectY) < minDxSquared)
                        {
                            lineHitLocation = u;
                            hitCenter.X = intersectX;
                            hitCenter.Y = intersectY;
                            return true;
                        }
                    }
                    else
                    {
                        // See how close we are to an endpoint
                        if (u < 0)
                        {
                            if (SquaredDistance(this.Center.X, this.Center.Y, seg.X1, seg.Y1) < minDxSquared)
                            {
                                lineHitLocation = 0;
                                hitCenter.X = seg.X1;
                                hitCenter.Y = seg.Y1;
                                return true;
                            }
                        }
                        else
                        {
                            if (SquaredDistance(this.Center.X, this.Center.Y, seg.X2, seg.Y2) < minDxSquared)
                            {
                                lineHitLocation = 1;
                                hitCenter.X = seg.X2;
                                hitCenter.Y = seg.Y2;
                                return true;
                            }
                        }
                    }

                    return false;
                }

                return false;
            }

基本上它就是分成點和線兩種根據距離公式得知是否碰到物體

到這裏以為事情已經完全清楚明白了, 但是又想到了一個如果自己來做一定會遇到的問題, Thing的位置單位和人的位置單位為什麼會一樣呢?

經過追查之後, 整理出它的脈絡

 

            this.playerBounds.X = 0;
            this.playerBounds.Width = this.playfield.ActualWidth;
            this.playerBounds.Y = this.playfield.ActualHeight * 0.2;
            this.playerBounds.Height = this.playfield.ActualHeight * 0.75;
 
  player.Value.SetBounds(this.playerBounds);
 
            this.playerBounds = r;
            this.playerCenter.X = (this.playerBounds.Left + this.playerBounds.Right) / 2;
            this.playerCenter.Y = (this.playerBounds.Top + this.playerBounds.Bottom) / 2;
            this.playerScale = Math.Min(this.playerBounds.Width, this.playerBounds.Height / 2);
 

所以UpdateBonePosition和UpdateJointPosition就會把目前的值Mapping到該Rect中

問題還是沒有解決crying 我們並不知道skeletonFrame.CopySkeletonDataTo(this.skeletonData);中

SkeletonData.Joint進來的單位是什麼....

根據最後結果來判斷, 它應該也是以判讀後的人型範圍取一個框, 再將每一個Joint對中間的相對位置求出

查了Document並沒有明顯看到說明

只能以這個推論來安慰自己已經得到答案

 

Rz

 

 

 

  Rz     should work (hard)