Tuesday, April 14, 2015

การเพิ่มประสิทธิภาพในการประมวลผลภาพจาก Kinect ด้วย Unsafe build และ pointer

ตัวอย่างที่ผ่านมา เราทำการอ่านค่าภาพวิดีโอจาก Kinect มาเก็บไว้ในอาร์เรย์หนึ่งมิติ หลังจากนั้นเราจึงทำการเปลี่ยนค่าในอาร์เรย์แล้วนำไปสร้างเป็นบิตแมพเพื่อแสดงผล

การเข้าถึงข้อมูลภาพวิดีโอด้วยดัชนีของอาร์เรย์ทำได้ค่อนข้างง่ายและสะดวก แต่เมื่อมีภาพจำนวนมากและต้องประมวลผลทุกภาพ ก็จะต้องใช้พลังสูง โดยเฉพาะเมื่อใช้อาร์เรย์ เพราะ .NET framework จำเป็นต้องตรวจสอบว่าดัชนีของอาร์เรย์อยู่ในช่วงที่เป็นไปได้หรือไม่ตลอดเป็นต้น

หากต้องการให้การประมวลผลมีประสิทธิภาพมากขึ้น แลกกับความเสี่ยงในการทำให้โปรแกรมล้มเหลวหากเขียนโค้ดผิดพลาด คือปิดการตรวจสอบช่วงของอาร์เรย์ เราสามารถทำได้โดยการปรับรูปแบบการคอมไพล์ให้เป็นแบบ unsafe และใช้ pointer ในการเข้าถึงหน่วยความจำโดยตรง

ในการกำหนดให้การ build เป็นแบบ unsafe ทำได้ดังรูป


จากนั้นทำการบันทึกไฟล์นี้

ต่อไปเราก็จะมาปรับโค้ดใหม่ โดยแยกส่วนที่เป็นการแก้ไขอาร์เรย์ของภาพออกมาเป็นฟังก์ชันใหม่ ดังนี้

        unsafe void updateImage(byte[] colorData)
        {
        }
โดยการใช้คำสั่ง unsafe คือการระบุว่าโค้ดที่อยู่ใน block นี้จะมีส่วนที่ unsafe นั่นคือเปิดให้ใช้ pointer ในการแก้ไขหน่วยความจำได้โดยตรง และไม่ต้องตรวจสอบเงื่อนไขบางประการ

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

        unsafe void updateImage(byte[] colorData)
        {
            //fixed pixel data array in memory, otherwise it can be moved and be inaccessible by a pointer
            fixed (byte* pImage = colorData)
            {
            }
        }
โค้ัดที่เหลือของฟังก์ชันนี้ ก็จะมีประมาณนี้ครับ สังเกตการใช้พอยน์เตอร์ และการใช้สัญลักษณ์ * เพื่อเข้าถึงค่าที่พอยน์เตอร์ชี้อยู่
        unsafe void updateImage(byte[] colorData)
        {
            //fixed pixel data array in memory, otherwise it can be moved and be inaccessible by a pointer
            fixed (byte* pImage = colorData)
            {
                //in this block the data array in memory will be fixed
                //set a start pointer to the beginning of image data
                byte* pStart = pImage;
                //set an end pointer to the end of image data
                byte* pEnd = pImage + colorData.Length;

                int newValue=0;
                //loop through each byte of image data
                while (pStart != pEnd)
                {
                    //Each byte is B G R A
                    //blue
                    newValue = *pStart + blueOffset;
                    //clamping
                    if (newValue < 0)
                        newValue = 0;
                    else if (newValue > 255)
                        newValue = 255;
                    *pStart = (byte)newValue;

                    //green
                    //move to next byte
                    pStart++;
                    newValue = *pStart + greenOffset;
                    //clamping
                    if (newValue < 0)
                        newValue = 0;
                    else if (newValue > 255)
                        newValue = 255;
                    *pStart = (byte)newValue;

                    //red
                    //move to next byte
                    pStart++;
                    newValue = *pStart + redOffset;
                    //clamping
                    if (newValue < 0)
                        newValue = 0;
                    else if (newValue > 255)
                        newValue = 255;
                    *pStart = (byte)newValue;

                    //move to next 2 bytes ->alpha then blue again
                    pStart += 2;
                }
            }
        }
ส่วนโค้ดเต็มทั้งหมดของโปรแกรมนี้ เพื่อให้ได้ผลลัพธ์เหมือนตอนที่แล้วมีดังนี้ครับ
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 KinectCam
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;
        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();
            }

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

            //Video event handler
            myKinect.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(myKinect_ColorFrameReady);           
        }

        //------------------------- Update color value from slider ----------------------------------
        //color value
        private int blueOffset = 0;
        private int greenOffset = 0;
        private int redOffset = 0;

        private void BlueSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            //get slider value
            blueOffset = (int)BlueSlider.Value;
        }

        private void GreenSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            //get slider value
            greenOffset = (int)GreenSlider.Value;
        }

        private void RedSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            //get slider value
            redOffset = (int)RedSlider.Value;
        }
        //--------------------------------------------------------------------------------------------

        //************************ Update display when video frame is ready********************* 
        //color data array
        byte[] colorData = null;
        WriteableBitmap imageBitmap = null;
        void myKinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                //if get new video frame
                if (colorFrame == null)
                    return;
                //create an array of pixel data
                if(colorData==null)
                    colorData = new byte[colorFrame.PixelDataLength];
                //extract pixel data from the frame to the array
                colorFrame.CopyPixelDataTo(colorData);

                //call unsafe method to modify the pixel data
                updateImage(colorData);

                //For the first video frame, no bitmap, create a writable bitmap of video size
                if (imageBitmap == null)
                {
                    imageBitmap = new WriteableBitmap(
                        colorFrame.Width,
                        colorFrame.Height,
                        96,     //dpiX
                        96,     //dpiY
                        PixelFormats.Bgr32,
                        null    //palette:none
                        );
                }
                //For other video frames, write video data to bitmap
                imageBitmap.WritePixels(
                    new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height),
                    colorData,  //video data
                    colorFrame.Width * colorFrame.BytesPerPixel,      //stride
                    0   //offset to the array
                    );
                //set bitmap to image view
                kinectVideo.Source = imageBitmap;
            }
        }
        //**********************************************************************************

        //unsafe method to modify pixel data
        //------------------------------------------------------------------------------
        unsafe void updateImage(byte[] colorData)
        {
            //fixed pixel data array in memory, otherwise it can be moved and be inaccessible by a pointer
            fixed (byte* pImage = colorData)
            {
                //in this block the data array in memory will be fixed
                //set a start pointer to the beginning of image data
                byte* pStart = pImage;
                //set an end pointer to the end of image data
                byte* pEnd = pImage + colorData.Length;

                int newValue=0;
                //loop through each byte of image data
                while (pStart != pEnd)
                {
                    //Each byte is B G R A
                    //blue
                    newValue = *pStart + blueOffset;
                    //clamping
                    if (newValue < 0)
                        newValue = 0;
                    else if (newValue > 255)
                        newValue = 255;
                    *pStart = (byte)newValue;

                    //green
                    //move to next byte
                    pStart++;
                    newValue = *pStart + greenOffset;
                    //clamping
                    if (newValue < 0)
                        newValue = 0;
                    else if (newValue > 255)
                        newValue = 255;
                    *pStart = (byte)newValue;

                    //red
                    //move to next byte
                    pStart++;
                    newValue = *pStart + redOffset;
                    //clamping
                    if (newValue < 0)
                        newValue = 0;
                    else if (newValue > 255)
                        newValue = 255;
                    *pStart = (byte)newValue;

                    //move to next 2 bytes ->alpha then blue again
                    pStart += 2;
                }
            }
        }
        //------------------------------------------------------------------------------
    }
}

No comments:

Post a Comment