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
}

No comments:

Post a Comment