Saturday, October 24, 2015

การเลือกตรวจจับ Skeleton เฉพาะบางคนด้วย Kinect

Kinect เวอร์ชันแรกสามารถตรวจจับ Skeleton ได้สูงสุดถึง 6 คนพร้อมกัน แต่ตรวจจับข้อต่อได้เพียง 2 คน อีก 4 คนจะได้แค่ตำแหน่งดังรูป

ที่มาของรูป Kinect for Windows | Human Interface Guidelines v1.8

แต่ละคนก็จะมี TrackingID เป็นตัวเลขสุ่มจำนวนเต็มที่มากกว่า 0 และค่านี้จะคงที่ตลอดถ้าคนนั้นๆยังถูกตรวจจับได้ (อยู่ในเฟรมและไม่มีอะไรบัง) แต่ถ้าเกิดการตรวจจับไม่ได้ เช่น ออกไปจากเฟรมแล้วกลับเข้ามาใหม่ Kinect ก็จะสุ่ม TrackingID ให้คนนั้นใหม่อีกที

อย่างไรก็ตาม ในบางครั้งเราต้องการตรวจจับแค่คนเดียว เช่น ในการใช้ซอฟต์แวร์หรือเล่นเกมคนเดียว ก็สามารถทำได้ครับ โดยใช้คำสั่ง
1. KinectSensor.SkeletonStream.AppChoosesSkeletons = true;
พร้อมๆกับ
2.  KinectSensor.SkeletonStream.ChooseSkeletons(trackingID);

คราวนี้ Kinect ก็จะ track คนที่มี trackingID นี้ตลอด ไม่สนคนอื่นแม้ว่าจะอยู่ในเฟรมด้วย

สำหรับตัวอย่างคราวนี้ เราจะตั้งเงื่อนไขว่า

  • จะ track เฉพาะคนแรกที่กล้องเจอเท่านั้น (ตอนแรกควรจะมีคนเดียวอยู่ในเฟรม)
  • ต่อมาแม้ว่าจะมีคนเข้ามาในเฟรมเพิ่มก็จะไม่ track
  • คนที่ถูก track จะไม่ออกจากเฟรมเสมอ และต้องไม่ถูกบดบังจนไม่สามารถ track ได้
หมายเหตุ จากการทดลองพบว่า
  • Kinect จะส่ง skeleton มาหกชุดเสมอ (เป็นอาร์เรย์) คนที่ถูก track จะอยู่ในตำแหน่งสุ่ม 0-5 ของอาร์เรย์ ตำแหน่งในอาร์เรย์ไม่เกี่ยวอะไรเลยกับ trackingID
  • จะมี skeleton เดียวเท่านั้นที่เราสั่งให้ track ที่มีค่า TrackingState เป็น Tracked ส่วน skeleton อื่นจะไม่ใช่สถานะนี้
อ้างอิงเนื้อหาจาก https://msdn.microsoft.com/en-us/library/jj131025.aspx

โค้ด UI
<Window x:Class="SingleDectection.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" Closing="Window_Closing">
    <StackPanel>
        <Canvas Height="480" Width="640">
            <Image Name="kinectVideo"  Height="480" Width="640"></Image>
            <Canvas Name="skeletonCanvas" Height="480" Width="640"></Canvas>
        </Canvas>
        <TextBlock Name="textID" Text="Tracking ID" HorizontalAlignment="Center" FontSize="22" />
    </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 SingleDectection
{    
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        //======== Window Loaded and Closing ========
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            startKinect();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            stopKinect();
        }

        //=============== Start and Stop Kinect =========================
        void startKinect()
        {
            // Check to see if a Kinect is available
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("No Kinects detected", "Camera Viewer");
                Application.Current.Shutdown();
                return;
            }

            // Start the Kinect and enable both video and skeleton streams
            try
            {
                // Get the first Kinect on the computer
                myKinect = KinectSensor.KinectSensors[0];
                myKinect.ColorStream.Enable();
                myKinect.SkeletonStream.Enable();                
                myKinect.Start();
            }
            catch
            {
                MessageBox.Show("Kinect initialise failed", "Camera Viewer");
                Application.Current.Shutdown();
                return;
            }

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

        void stopKinect()
        {
            if (myKinect != null)
                myKinect.Stop();
        }

        //================================== video =============================
        byte[] colorData = null;
        WriteableBitmap colorImageBitmap = null;

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

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

                colorFrame.CopyPixelDataTo(colorData);

                if (colorImageBitmap == null)
                {
                    colorImageBitmap = new WriteableBitmap(
                        colorFrame.Width,
                        colorFrame.Height,
                        96,  // DpiX
                        96,  // DpiY
                        PixelFormats.Bgr32,
                        null);
                }

                colorImageBitmap.WritePixels(
                    new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height),
                    colorData, // video data
                    colorFrame.Width * colorFrame.BytesPerPixel, // stride,
                    0   // offset into the array - start at 0
                    );

                kinectVideo.Source = colorImageBitmap;
            }
        }

        //================================== skeleton ===========================
        //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);
        }

        //Track a skeleton by ID
        int personID = 0;

        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;

            //Objective: to track only the first person
            //Assumption: First person must be alone in the screen and be tracked at the beginning to get his/her ID
            //Assumption: That first person must not leave the screen or be obscured, otherwise Kinect will dispose his/her ID

            //textID.Text = "Person ID = " + personID;

            //if not get person ID yet
            if (personID == 0)
            {
                //textID.Text = "Skeleton size = " + skeletons.Length;
                //find ID of the first tracked skeleton
                foreach (Skeleton skeleton in skeletons)
                {
                    if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                    {
                        personID = skeleton.TrackingId;                                                
                        //allow track on specific skeleton (not all)
                        myKinect.SkeletonStream.AppChoosesSkeletons = true;
                        //track only this skeleton
                        myKinect.SkeletonStream.ChooseSkeletons(personID);
                        //neglect other skeletons
                        break;
                    }
                }
            }
            else
            {
                //draw the tracked skeleton only
                //the skeletonFrameReady event will always return six skeletons, though we track only one
                //the tracked skeleton will be randomly in the skeleton array, no matter what its tracking ID is                            
                //while tracked, the tracking ID is constant                

                //Draw only the tracked skeleton
                foreach (Skeleton skeleton in skeletons)
                {
                    if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                    {
                        //Debug info
                        textID.Text = "Tracking ID, Current=" + skeleton.TrackingId + ": Before=" + personID;   //must be equal to personID

                        //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]);

                        //neglect other not tracked skeletons
                        break;
                    }
                }
                
            }//end if-else get personID
        }//end skeleton frame ready method

    }//end class
}

การวาด Skeleton จาก Kinect ตอนที่ 4: วาดซ้อนทับกับภาพวิดีโอ

ก่อนหน้านี้เราได้ลองวาด Skeleton แบบเต็มตัว และตรวจสอบว่าข้อต่อถูกตรวจจับหรือไม่ ตอนนี้เรามาลองวาด Skeleton ซ้อนทับกับภาพวิดีโอกันดูครับ

หลักการก็ตรงไปตรงมา คือส่วนของ UI ก็จะวาง canvas เป็นพื้นหลัง ทับด้วย Image เพื่อแสดงผลวิดีโอ และ ถูกทับอีกทีด้วย canvas เพื่อวาด skeleton

เมื่อซ้อนกันตามลำดับนี้ ภาพ Skeleton ก็จะอยู่บน ภาพวิดีโอ โดยอัตโนมัติครับ

รูปผลลัพธ์ก็จะคล้ายๆกับแบบนี้ครับ

ที่มา: Kinect for Windows | Human Interface Guidelines v1.8


โค้ด UI
<Window x:Class="SkeletonVideo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Skeleton and Video" SizeToContent="WidthAndHeight" Loaded="Window_Loaded" Closing="Window_Closing">
    <Canvas Height="480" Width="640">
        <Image Name="kinectVideo" Height="480" Width="640"/>
        <Canvas Name="skeletonCanvas" Height="480" Width="640"/>
    </Canvas>
</Window>


ส่วนของโค้ด ก็จะอ่านทั้งภาพวิดีโอและ skeleton จากนั้นก็วาดลงไปบน UI ที่เกี่ยวข้อง

โค้ด 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 SkeletonVideo
{
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        //======== Window Loaded and Closing ========
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            startKinect();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            stopKinect();
        }
        
        //=============== Start and Stop Kinect =========================
        void startKinect()
        {
            // Check to see if a Kinect is available
            if (KinectSensor.KinectSensors.Count == 0)
            {
                MessageBox.Show("No Kinects detected", "Camera Viewer");
                Application.Current.Shutdown();
                return;
            }

            // Start the Kinect and enable both video and skeleton streams
            try
            {
                // Get the first Kinect on the computer
                myKinect = KinectSensor.KinectSensors[0];
                myKinect.ColorStream.Enable();
                myKinect.SkeletonStream.Enable();
                myKinect.Start();
            }
            catch
            {
                MessageBox.Show("Kinect initialise failed", "Camera Viewer");
                Application.Current.Shutdown();
                return;
            }

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

        void stopKinect()
        {
            if (myKinect != null)
                myKinect.Stop();
        }

        //================================== video =============================
        byte[] colorData = null;
        WriteableBitmap colorImageBitmap = null;

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

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

                colorFrame.CopyPixelDataTo(colorData);

                if (colorImageBitmap == null)
                {
                    colorImageBitmap = new WriteableBitmap(
                        colorFrame.Width,
                        colorFrame.Height,
                        96,  // DpiX
                        96,  // DpiY
                        PixelFormats.Bgr32,
                        null);
                }

                colorImageBitmap.WritePixels(
                    new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height),
                    colorData, // video data
                    colorFrame.Width * colorFrame.BytesPerPixel, // stride,
                    0   // offset into the array - start at 0
                    );

                kinectVideo.Source = colorImageBitmap;
            }
        }

        //================================== skeleton ===========================
        //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);
        }

        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 class
}

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
}