Friday, October 23, 2015

การวาด Skeleton จาก Kinect ตอนที่ 3: วาดทั้งตัวแบบระบุสถานะ

ตอนที่แล้วเราสามารถวาด Skeleton ได้ทั้งตัวแล้ว แต่สังเกตว่าจะมีบางตำแหน่งที่เส้นดูแปลกๆ ทั้งนี้อาจจะเนื่องมาจาก Kinect ไม่สามารถตรวจจับข้อต่อได้

ดังนั้น เพื่อให้การแสดงผลสะท้อนถึงสถานะการตรวจจับข้อต่อ เราจะลองใช้สีในการจำแนก เช่น สีเขียวหมายถึงจับข้อต่อได้ (Tracked) และสีแดงคือตรวจจับไม่ได้แต่พอคาดการณ์ได้ (Inferrred) และถ้าตรวจจับไม่ได้เลย (NotTracked) ก็จะไม่วาดเส้นครับ

ผลลัพธ์ก็ประมาณนี้ สังเกตว่าช่วงขวาเป็นสีแดงเกือบทั้งหมด เพราะกล้องอยู่สูงและผมยืนใกล้กล้องมากครับ


โค้ด C# เปลี่ยนเฉพาะส่วนของฟังก์ชันที่ใช้ในการวาดเส้นครับ
        //drawing color
        Brush brushRed = new SolidColorBrush(Colors.Red);
        Brush brushGreen = new SolidColorBrush(Colors.Green);

        //method to draw a line between joints
        void drawLine(Joint j1, Joint j2)
        {           
            //if any joint is not tracked
            if (j1.TrackingState == JointTrackingState.NotTracked || j2.TrackingState == JointTrackingState.NotTracked)
            {
                //do not draw
                return;
            }

            //line properties
            Line bone = new Line();
            //line thickness            
            bone.StrokeThickness = 5;

            //if any joint is inferred
            if (j1.TrackingState == JointTrackingState.Inferred || j2.TrackingState == JointTrackingState.Inferred)
            {
                //set color to red
                bone.Stroke = brushRed;
            }
            else if (j1.TrackingState == JointTrackingState.Tracked && j2.TrackingState == JointTrackingState.Tracked)
            {
                //both joints are tracked, set color to green
                bone.Stroke = brushGreen;
            }
           
            //map joint position to display location
            //starting point
            ColorImagePoint j1p = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(j1.Position, ColorImageFormat.RgbResolution640x480Fps30);
            bone.X1 = j1p.X;
            bone.Y1 = j1p.Y;
            //ending point
            ColorImagePoint j2p = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(j2.Position, ColorImageFormat.RgbResolution640x480Fps30);
            bone.X2 = j2p.X;
            bone.Y2 = j2p.Y;

            //add line to canvas
            skeletonCanvas.Children.Add(bone);
        }

การวาด Skeleton จาก Kinect ตอนที่ 2: วาดทั้งตัว

เมื่อรู้ตำแหน่งของข้อต่อแล้ว เราสามารถวาดโครงกระดูกทั้งตัวโดยใช้ลากเส้นตรงเชื่อมข้อต่อเข้าด้วยกัน ซึ่งข้อต่อต่างๆที่ Kinect จับได้มีดังนี้

ที่มาของรูป https://msdn.microsoft.com/en-us/library/jj131025.aspx

เพื่อให้สะดวก เราจะเขียนฟังก์ชันการวาดรูปแยกออกมาต่างหาก แล้วส่งข้อต่อต่างๆไปให้ฟังก์ชันนี้วาด พอวาดเสร็จเฟรมนึงก็ลบทิ้งแล้ววาดใหม่

ผลลัพธ์ที่ได้เมื่อ Kinect ตรวจจับคนได้



โค้ดก็จะมีเพิ่มเติมมาในส่วนของฟังก์ชัน และการเรียกใช้ฟังก์ชันนี้เพื่อวาดเส้นตรงระหว่างข้อต่อต่างๆ ดังนี้ครับ

โค้ด UI
<Window x:Class="SkeletonFull.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Skeleton Tracking" SizeToContent="WidthAndHeight" Loaded="Window_Loaded">
    <Canvas Name="skeletonCanvas" HorizontalAlignment="Center" Height="480" Width="640"/>
</Window>

โค้ด C#
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;

namespace SkeletonFull
{
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Check to see if a Kinect is available
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("No Kinects detected", "Camera Viewer");
                Application.Current.Shutdown();
            }

            // Get the first Kinect on the computer
            myKinect = KinectSensor.KinectSensors[0];

            // Start the Kinect running and select the depth camera
            try
            {
                myKinect.SkeletonStream.Enable();
                myKinect.Start();
            }
            catch
            {
                MessageBox.Show("Kinect initialise failed", "Camera Viewer");
                Application.Current.Shutdown();
            }

            // connect a handler to the event that fires when new frames are available
            myKinect.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(myKinect_SkeletonFrameReady);
        }

        //drawing color
        Brush brushColor = new SolidColorBrush(Colors.Red);
        //method to draw a line between joints
        void drawLine(Joint j1, Joint j2)
        {
            Line bone = new Line();
            //drawing color and thickness
            bone.Stroke = brushColor;
            bone.StrokeThickness = 5;

            //map joint position to display location
            //starting point
            ColorImagePoint j1p = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(j1.Position, ColorImageFormat.RgbResolution640x480Fps30);
            bone.X1 = j1p.X;
            bone.Y1 = j1p.Y;
            //ending point
            ColorImagePoint j2p = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(j2.Position, ColorImageFormat.RgbResolution640x480Fps30);
            bone.X2 = j2p.X;
            bone.Y2 = j2p.Y;

            //add line to canvas
            skeletonCanvas.Children.Add(bone);
        }

        private void myKinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            // Remove the old skeleton
            skeletonCanvas.Children.Clear();

            Skeleton[] skeletons = null;

            using (SkeletonFrame frame = e.OpenSkeletonFrame())
            {
                if (frame != null)
                {
                    skeletons = new Skeleton[frame.SkeletonArrayLength];
                    frame.CopySkeletonDataTo(skeletons);
                }
            }

            if (skeletons == null) 
                return;

            foreach (Skeleton skeleton in skeletons)
            {
                if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                {
                    //draw head to neck
                    drawLine(skeleton.Joints[JointType.Head], skeleton.Joints[JointType.ShoulderCenter]);
                    //draw neck to spine
                    drawLine(skeleton.Joints[JointType.ShoulderCenter], skeleton.Joints[JointType.Spine]);
                    //draw spine to hip center
                    drawLine(skeleton.Joints[JointType.Spine], skeleton.Joints[JointType.HipCenter]);

                    //draw left arm
                    drawLine(skeleton.Joints[JointType.ShoulderCenter], skeleton.Joints[JointType.ShoulderLeft]);
                    drawLine(skeleton.Joints[JointType.ShoulderLeft], skeleton.Joints[JointType.ElbowLeft]);
                    drawLine(skeleton.Joints[JointType.ElbowLeft], skeleton.Joints[JointType.WristLeft]);
                    drawLine(skeleton.Joints[JointType.WristLeft], skeleton.Joints[JointType.HandLeft]);

                    //draw right arm
                    drawLine(skeleton.Joints[JointType.ShoulderCenter], skeleton.Joints[JointType.ShoulderRight]);
                    drawLine(skeleton.Joints[JointType.ShoulderRight], skeleton.Joints[JointType.ElbowRight]);
                    drawLine(skeleton.Joints[JointType.ElbowRight], skeleton.Joints[JointType.WristRight]);
                    drawLine(skeleton.Joints[JointType.WristRight], skeleton.Joints[JointType.HandRight]);

                    //draw left leg
                    drawLine(skeleton.Joints[JointType.HipCenter], skeleton.Joints[JointType.HipLeft]);
                    drawLine(skeleton.Joints[JointType.HipLeft], skeleton.Joints[JointType.KneeLeft]);
                    drawLine(skeleton.Joints[JointType.KneeLeft], skeleton.Joints[JointType.AnkleLeft]);
                    drawLine(skeleton.Joints[JointType.AnkleLeft], skeleton.Joints[JointType.FootLeft]);

                    //draw right leg
                    drawLine(skeleton.Joints[JointType.HipCenter], skeleton.Joints[JointType.HipRight]);
                    drawLine(skeleton.Joints[JointType.HipRight], skeleton.Joints[JointType.KneeRight]);
                    drawLine(skeleton.Joints[JointType.KneeRight], skeleton.Joints[JointType.AnkleRight]);
                    drawLine(skeleton.Joints[JointType.AnkleRight], skeleton.Joints[JointType.FootRight]);
                }
            }//end foreach
        }//end method
    }//end class
}

การวาด Skeleton จาก Kinect ตอนที่ 1: วาดแนวสันหลัง

เมื่อเราสามารถตรวจจับตำแหน่งของ Skeleton และข้อต่อได้แล้ว เราสามารถแสดงผลค่าดังกล่าวออกมาเป็นภาพได้โดยการวาดเส้น

สำหรับการวาดเส้นตรงก็ต้องอาศัยจุดสองจุด คือจุดเริ่มต้นกับจุดสิ้นสุด ซึ่งในที่นี้ เราจะลองลากเส้นเชื่อมระหว่างหัวและสะโพก ซึ่งก็คือแนวกระดูกสันหลังนั่นเอง

ความยากอยู่ตรงที่ ตำแหน่งของข้อต่อต่างๆที่ได้จาก Kinect จะวัดเทียบกับแกนของ Kinect เอง (จุดกำเนิดอยู่ที่ตัว Kinect) และตำแหน่งของข้อต่อก็จะอยู่ในหน่วยของเมตร แต่ การแสดงผลบนหน้าจอจะเป็นพิกัดของหน้าต่าง (จุดกำเนิดอยู่มุมบนซ้าย) และขนาดของหน้าจอก็มักจะเท่ากับขนาดของภาพวีดิโอ เช่น 640x480

ดังนั้น จะต้องมีกระบวนการทำ Coordinate Mapping ซึ่งจริงๆแล้วก็ทำได้ไม่ยาก (อาศัยความรู้เรื่อง Computer Graphics) อย่างไรก็ตาม ตัว Kinect SDK เองก็มีฟังก์ชันสำเร็จรูปเพื่อทำงานนี้มาให้เช่นกัน คือ KinectSensor.CoordinateMapper.MapSkeletonPointToColorPoint() หรือ ถ้าเป็นคำสั่งเก่าจะใช้ KinectSensor.MapSkeletonPointToColor()

ตัวอย่างเช่น ถ้าเราต้องการทำ Mapping จากพิกัดของศีรษะใน Kinect ไปยังหน้าต่างขนาด 640x480 พิกเซล ก็จะใช้คำสั่ง
ColorImagePoint headPoint = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(headJoint.Position, ColorImageFormat.RgbResolution640x480Fps30);

ลองมาดูตัวอย่างกันครับ

ผลลัพธ์ที่ต้องการ (เส้นแนวกระดูกสันหลังที่เชื่อมต่อระหว่างหัวกับสะโพก)

โค้ด UI
<Window x:Class="SkeletonDrawing.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" SizeToContent="WidthAndHeight" Loaded="Window_Loaded">
    <Canvas Name="skeletonCanvas" HorizontalAlignment="Center" Height="480" Width="640"/>
</Window>

โค้ด C#
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;

namespace SkeletonDrawing
{
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Check to see if a Kinect is available
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("No Kinects detected", "Camera Viewer");
                Application.Current.Shutdown();
                return;
            }

            // Get the first Kinect on the computer
            myKinect = KinectSensor.KinectSensors[0];

            // Start the Kinect running and select the depth camera
            try
            {
                myKinect.SkeletonStream.Enable();
                myKinect.Start();
            }
            catch
            {
                MessageBox.Show("Kinect initialise failed", "Camera Viewer");
                Application.Current.Shutdown();
            }

            // connect a handler to the event that fires when new frames are available

            myKinect.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(myKinect_SkeletonFrameReady);
        }

        private void myKinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            // Remove the old skeleton (if exists)
            skeletonCanvas.Children.Clear();

            Skeleton[] skeletons = null;

            //copy skeleton data to skeleton array
            using (SkeletonFrame frame = e.OpenSkeletonFrame())
            {
                if (frame != null)
                {
                    skeletons = new Skeleton[frame.SkeletonArrayLength];
                    frame.CopySkeletonDataTo(skeletons);
                }
            }

            //if no skeleton data
            if (skeletons == null) 
                return;

            //for each skeleton
            foreach (Skeleton skeleton in skeletons)
            {
                //if skeleton is tracked
                if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                {
                    //Head joint
                    Joint headJoint = skeleton.Joints[JointType.Head];
                    //Hip joint
                    Joint hipJoint = skeleton.Joints[JointType.HipCenter];

                    //Map head position to canvas
                    //ColorImagePoint headPoint = myKinect.MapSkeletonPointToColor(headJoint.Position, ColorImageFormat.RgbResolution640x480Fps30);
                    ColorImagePoint headPoint = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(headJoint.Position, ColorImageFormat.RgbResolution640x480Fps30);
                    //Map hip position to canvas
                    //ColorImagePoint hipPoint = myKinect.MapSkeletonPointToColor(hipCenter.Position, ColorImageFormat.RgbResolution640x480Fps30);
                    ColorImagePoint hipPoint = myKinect.CoordinateMapper.MapSkeletonPointToColorPoint(hipJoint.Position, ColorImageFormat.RgbResolution640x480Fps30);

                    //Prepare a line
                    Line backBone = new Line();
                    backBone.Stroke = new SolidColorBrush(Colors.Red);
                    backBone.StrokeThickness = 5;

                    //Draw a line from head to hip
                    backBone.X1 = headPoint.X;
                    backBone.Y1 = headPoint.Y;
                    backBone.X2 = hipPoint.X;
                    backBone.Y2 = hipPoint.Y;

                    //Add this line to canvas
                    skeletonCanvas.Children.Add(backBone);
                }
            }//end for
        }//end method
    }//end class
}

Thursday, October 22, 2015

การปรับองศาการก้มเงย (tilt) กล้อง Kinect

Kinect มีมอเตอร์ที่ฐานของกล้องเพื่อใช้ปรับมุมก้มเงยได้ โดยถ้ากล้องอยู่ในแนวราบ มุมปกติจะประมาณ 0 องศา และสามารถปรับเพิ่มมุมให้เงยขึ้นได้มากที่สุดไม่เกินค่า KinectSensor.MaxElevationAngle (ประมาณ +27 องศา) และปรับลดมุมให้ก้มลงได้ต่ำสุดไม่น้อยกว่าค่า KinectSensor.MinElevationAngle (ประมาณ -27 องศา)

การปรับมุมก้มเงยสามารถทำได้ง่ายๆ โดยการปรับค่า KinectSensor.ElevationAngle แต่มีข้อควรระวังว่า

  • ไม่ควรปรับเกิน 1 ครั้งต่อวินาที
  • ถ้าปรับต่อเนื่องกัน 15 ครั้ง ต้องพักมอเตอร์อย่างน้อย 20 วินาที มิฉะนั้นแล้วกล้องจะล็อคมอเตอร์ไปช่วงหนึ่ง
ทั้งนี้เนื่องมาจากมอเตอร์ของ Kinect ไม่ได้ออกแบบมาสำหรับการปรับบ่อยๆ ซึ่งอาจทำให้มอเตอร์เสื่อมหรือเสียหายได้

ดังนั้น การปรับมุมก้มเงยควรจะทำเมื่อมีความจำเป็น เช่น ผู้ใช้อยู่ในระยะที่กล้องตรวจจับได้ไม่ดี และไม่สามารถขอให้ผู้ใช้เคลื่อนย้ายตำแหน่งได้

ตัวอย่างผลลัพธ์ที่เราจะเขียนโค้ดกันครับ


เมื่อกดปุ่ม Down กล้องก็จะก้มลง


โค้ด UI
<Window x:Class="AdjustSensorAngle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" SizeToContent="WidthAndHeight" Loaded="Window_Loaded" >
    <StackPanel>
        <Image Name="kinectVideo" Width="320" Height="240"/>
        <TextBlock Name="txtAngle" Text="Current Angle" HorizontalAlignment="Center" FontSize="22" />
        <StackPanel Orientation="Horizontal" Margin="10" HorizontalAlignment="Center">           
            <Button Content="Up" Name="buttonUp" FontSize="22" MinWidth="100" Click="buttonUp_Click" />
            <Button Content="Down" Name="buttonDown" FontSize="22" MinWidth="100" Margin="10,0,0,0" Click="buttonDown_Click" />  
        </StackPanel>        
    </StackPanel>
</Window>
โค้ด C#
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;

namespace AdjustSensorAngle
{
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("No Kinects detected", "Camera Viewer");
                Application.Current.Shutdown();
            }

            try
            {
                myKinect = KinectSensor.KinectSensors[0];

                myKinect.ColorStream.Enable();

                myKinect.Start();
            }
            catch
            {
                MessageBox.Show("Kinect initialise failed", "Camera Viewer");
                Application.Current.Shutdown();
            }

            //current angle
            txtAngle.Text = "Current angle = " + myKinect.ElevationAngle;

            myKinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(myKinect_ColorFrameReady);
        }//end Window_Loaded method

        private void myKinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                if (colorFrame == null) 
                    return;

                byte[] colorData = new byte[colorFrame.PixelDataLength];
                colorFrame.CopyPixelDataTo(colorData);

                kinectVideo.Source = BitmapSource.Create(
                                    colorFrame.Width, colorFrame.Height, // image dimensions
                                    96, 96,  // resolution - 96 dpi for video frames
                                    PixelFormats.Bgr32, // video format
                                    null,               // palette - none
                                    colorData,          // video data
                                    colorFrame.Width * colorFrame.BytesPerPixel // stride
                                    );
            }
        }//end ColorFrameReady method

        //============== Up and Down button click methods ===================
        private void buttonUp_Click(object sender, RoutedEventArgs e)
        {
            int angle = myKinect.ElevationAngle;
            angle += 5;
            if (angle <= myKinect.MaxElevationAngle)
            {
                //tilt the camera up 5 degrees
                myKinect.ElevationAngle = angle;
                txtAngle.Text = "Current angle = " + angle;
            }
        }

        private void buttonDown_Click(object sender, RoutedEventArgs e)
        {
            int angle = myKinect.ElevationAngle;
            angle -= 5;
            if (angle >= myKinect.MinElevationAngle)
            {
                //tilt the camera down 5 degrees
                myKinect.ElevationAngle = angle;
                txtAngle.Text = "Current angle = " + angle;
            }
        }
    }
}

คุณภาพและสถานะการตรวจจับ Skeleton และ ข้อต่อของ Kinect

ในการตรวจจับ Skeleton และข้อต่อ ของมนุษย์โดยใช้ Kinect สิ่งที่ควรคำนึงถึงตามลำดับคือ

1. สถานะในการตรวจจับ Skeleton (Skeleton tracking state)
เป็นสถานะที่บอกว่า Kinect ตรวจจับ Skeleton ได้หรือไม่ ซึ่งมีสามระดับคือ
  • ตรวจจับได้ (SkeletonTrackingState.Tracked หรือค่า 2)
  • ได้ตำแหน่งเท่านั้น (SkeletonTrackingState.PositionOnly หรือค่า 1) คือตรวจจับ Skeleton ไม่ได้ แต่ได้เฉพาะตำแหน่งโดยรวมของ Skeleton
  • ตรวจจับไม่ได้ (SkeletonTrackingState.NotTracked หรือค่า 0)

2. คุณภาพในการตรวจจับ Skeleton (Skeleton Information Quality)
การตรวจจับ skeleton จะมีประสิทธิภาพลดลง เมื่อเราอยู่นอกระยะที่กล้องสามารถตรวจจับได้ เช่น อยู่ใกล้หรือไกลเกินไป อยู่สูงหรือต่ำเกินไป หรือ อยู่ทางซ้ายหรือขวาของกล้องมากไป

Kinect จะมีการตรวจจับข้อมูลดังกล่าว โดยดูว่าเราอยู่ในตำแหน่งที่ตัดกับเฟรมของกล้องหรือไม่ โดยให้ผลลัพธ์ออกมาในตัวแปรที่ชื่อว่า ClippedEdges ซึ่งตัวแปรนี้จะเป็นผลรวมของสถานะ FrameEdges ที่มีค่าที่เป็นไปได้ดังนี้
0 หรือ 0000 ฐานสอง เท่ากับ สถานะปกติ ไม่อยู่ชิดขอบเฟรม (None)
1 หรือ 0001 ฐานสอง เท่ากับ อยู่ชิดขอบเฟรมด้านขวา (Right)
2 หรือ 0010 ฐานสอง เท่ากับ อยู่ชิดขอบเฟรมด้านซ้าย (Left)
4 หรือ 0100 ฐานสอง เท่ากับ อยู่ชิดขอบเฟรมด้านบน (Top)
8 หรือ 1000 ฐานสอง เท่ากับ อยู่ชิดขอบเฟรมด้านล่าง (Bottom)

สมมติว่า เราอยู่ชิดเฟรมไปทางด้านขวา และ อยู่ต่ำกว่าเฟรม ดังนั้น ผลรวมสถานะหรือค่า ClippedEdges ก็จะมีค่าเป็น 0001 & 1000 (ใช้เลขฐานสองมา bitwise and & กัน) ก็จะได้ผลลัพธ์เป็น 1001 หรือค่า 9

ในทางกลับกัน ถ้าเราต้องการบอกให้ผู้ใช้กล้องทราบว่า ควรจะขยับไปทางไหนเพื่อให้ไม่เลยหรือตัดกับเฟรมของกล้อง ก็เอาค่า ClippedEdges ไป bitwise and (&) กับทีละสถานะ เช่น ถ้ากล้องอ่านค่า ClippedEdges ได้ 9 (1001) จะได้ว่า
1001 & 0001 = 0001
1001 & 0010 = 0000
1001 & 0100 = 0000
1001 & 1000 = 1000

นั่นคือ ค่าที่ไม่เป็น 0 มีสองค่า คือค่า ขวา และ ล่าง ก็สามารถบอกผู้ใช้ให้ขยับไปทางซ้ายและขึ้นข้างบน เพื่อให้อยู่ในเฟรมของกล้อง

3. สถานะในการตรวจจับข้อต่อ (Joint Tracking State)
บางครั้ง Kinect ก็สามารถตรวจจับข้อต่อได้ชัดเจน หรือบางครั้งก็ไม่ได้ ขึ้นอยู่กับหลายปัจจัย ดังนั้น ตัวกล้องเองก็จะแสดงค่าสถานะ (JointTrackingState) ของแต่ละข้อต่ออยู่สามระดับคือ

  • ตรวจจับได้ (JointTrackingState.Tracked หรือค่า 2)
  • คาดการณ์ได้ (JointTrackingState.Inferred หรือค่า 1) คือตรวจจับข้อต่อไม่ได้ แต่พอคาดการณ์ได้ว่าข้อต่อนั้นอยู่ที่ไหน จากข้อต่อข้างเคียง สถานะนี้อาจเกิดขึ้นในกรณีที่ข้อต่อนั้นถูกบดบังด้วยวัตถุอื่นหรือด้วยเสื้อผ้าที่สวมใส่
  • ตรวจจับไม่ได้ (JointTrackingState.NotTracked หรือค่า 0)
โดยเราสามารถตรวจสอบได้จากตัวแปร TrackingState ว่ามีค่าเท่ากับค่าใดค่าหนึ่งในสามค่าข้างต้นหรือไม่

ในตัวอย่างนี้ เราจะมาลองเพิ่มโค้ดในการตรวจจับศีรษะ พร้อมๆกับแสดงคุณภาพและสถานะของการตรวจจับด้วย ผลลัพธ์ที่ได้คือ

กรณีหา Skeleton ไม่ได้

กรณีหา Skeleton ได้ และคุณภาพของ Skeleton และ Head Tracking ดี

กรณีหา Skeleton ได้ และคุณภาพของ Skeleton ดี แต่ว่า Head Tracking เป็นแบบ Inferred


กรณีหา Skeleton ได้ แต่ควรขยับตำแหน่ง และ Head Tracking ดี

โดยมีข้อจำกัดที่ว่า ผู้ทดสอบควรอยู่ห่างจาก Kinect ไม่ต่ำกว่าประมาณ 1.8 เมตร เพราะถ้าอยู่ใกล้กล้องเกินไป โปรแกรมอาจจะแสดงผลผิดพลาดได้

โค้ดของ UI
<Window x:Class="HeadDetection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Head Detection" SizeToContent="Height" Width="800" Loaded="Window_Loaded">
    <StackPanel>
        <TextBlock Name="TextHead" Text="Head Position" FontSize="54" HorizontalAlignment="Center"/>
        <TextBlock Name="TextQuality" Text="Quality" FontSize="36" HorizontalAlignment="Center"/>
    </StackPanel>
</Window>

โค้ด C#
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;

namespace HeadDetection
{
    public partial class MainWindow : Window
    {
        //variables
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //Check if there is any connecting Kinect
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("No Kinect detected!", "Error");
                //end this app
                Application.Current.Shutdown();
                return;
            }

            //Try to initialize Kinect
            try
            {
                //Get the first Kinect connected to this computer
                myKinect = KinectSensor.KinectSensors[0];
                //Enable the skeleton stream
                myKinect.SkeletonStream.Enable();
                //Start the sensor
                myKinect.Start();
            }
            catch
            {
                MessageBox.Show("Initialize Kinect failed!", "Error");
                //end this app
                Application.Current.Shutdown();
            }

            //Skeleton event handler
            myKinect.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(myKinect_SkeletonFrameReady);
        }

        private void myKinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            //array of skeleton data
            Skeleton[] skeletons = null;

            using (SkeletonFrame frame = e.OpenSkeletonFrame())
            {
                string message = "No Skeleton Data";
                string qualtiy = "";

                //if there is skeleton data
                if (frame != null)
                {
                    //create skeleton array
                    skeletons = new Skeleton[frame.SkeletonArrayLength];
                    //copy skeleton data to array
                    frame.CopySkeletonDataTo(skeletons);
                }

                //if no skeleton data
                if (skeletons == null)
                    return;

                //for each skeleton data in the skeleton array, there will be upto 6 possible skeletons (men)
                //only 2 skeletons are fully tracked, other 4 are for positions only (no joint info)
                foreach (Skeleton skeleton in skeletons)
                {
                    //============ 1. Check status of skeleon ==============
                    if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                    {
                        //===========2. check quality of skeleton==========
                        if(skeleton.ClippedEdges == FrameEdges.None)
                            qualtiy = "Good skeleton quality";
                        else
                        {
                            if((skeleton.ClippedEdges & FrameEdges.Right) != 0)
                                qualtiy += "Move left ";
                            if((skeleton.ClippedEdges & FrameEdges.Left) != 0)
                                qualtiy += "Move right ";
                            if((skeleton.ClippedEdges & FrameEdges.Top) != 0)
                                qualtiy += "Move down ";
                            if((skeleton.ClippedEdges & FrameEdges.Bottom) != 0)
                                qualtiy += "Move up ";
                        }

                        //get the head joint
                        Joint headJoint = skeleton.Joints[JointType.Head];
                        //get the head joint's position
                        SkeletonPoint headPosition = headJoint.Position;

                        //show the head position with one decimal or 0.1 meter
                        message = string.Format("Head: X:{0:0.0} Y:{1:0.0} Z:{2:0.0}",
                            headPosition.X,
                            headPosition.Y,
                            headPosition.Z);

                        //========3. check joint traking state (head only)======
                        if(headJoint.TrackingState == JointTrackingState.Inferred) 
                        {
                            message += " Inferred";
                        }
                    }
                }

                //show message on textblock
                TextHead.Text = message;
                TextQuality.Text = qualtiy;
            }
        }//end method

    }//end class
}