Monday, July 27, 2015

Example Environment ใน LaTeX

อยากได้ Example แบบมีตัวเลขให้ตามบทแบบนี้


ซึ่งพอไปค้นหาดูในคำสั่ง LaTeX แล้วก็ไม่เจอ อันที่ใกล้และง่ายที่สุดก็อาจจะใช้คำสั่ง

\newtheorem{example}{Example}[chapter]

ในส่วนของ preamble และพอจะใช้งานจริงก็จะต้องใช้

\begin{example}
...
\end{example}

แต่ก็จะพบว่าตัวอักษรจะเป็นตัวเอียงทั้งหมด ตามรูปแบบปกติของ theorem

ถ้าอยากให้เป็นตัวอักษรปกติ ก็จะต้องใช้ package "amsthm" ดังนี้ในส่วนของ preamble

\usepackage{amsthm}

\theoremstyle{definition}
\newtheorem{example}{Example}[chapter]

อย่างไรก็ตาม ผมอยากได้เส้นขีดแสดงว่า Example นี้สิ้นสุดแล้ว ซึ่งไม่รู้จะทำอย่างไรใน package ข้างต้น

เลยมาเลือกวิธีใหม่ โดยการกำหนด Environment ใหม่ซะเอง ซึ่งดัดแปลงมาจากหลายๆเว็บ (ของเค้าซับซ้อน ผมไม่เข้าใจ T_T) ได้แบบนี้ครับ วางโค้ดไว้ที่ส่วนของ preamble เหมือนเดิม

%=== example environment===
\newcounter{example}[chapter]
\renewcommand{\theexample}{\thechapter.\arabic{example}}
\newenvironment{example}{
 \refstepcounter{example}
 \noindent \textbf{Example~\theexample}
}
{
 \begin{center}
  ******************* 
 \end{center}
}
%==========================

เวลาใช้งานจริงก็ใช้เหมือนเดิมครับ คือ
\begin{example}
 The program xyz below shows how to create an animation.
 
 My program here ...
\end{example}
ลองทดสอบและปรับคำสั่งให้ได้รูปแบบตามใจชอบเลยครับ

อ้างอิง
https://en.wikibooks.org/wiki/LaTeX/Theorems

Wednesday, April 15, 2015

การประมวลผลภาพโดยการสะท้อน (Mirror) และการจับภาพจากวิดีโอเฟรมของ Kinect ตอนที่ 1

ลองมาทำ mirror effect ให้กับวิดีโอและทำปุ่มสำหรับบันทึกรูป ให้ได้ผลลัพธ์ประมาณนี้กันครับ

เมื่อคลิกที่ checkbox ที่มีข้อความว่า Reflect ผลลัพธ์ก็จะเปลี่ยนเป็น

นอกจากนี้เรายังสามารถกดปุ่ม Save เพื่อบันทึกรูปขณะนั้นได้

ส่วนของ UI
<Window x:Class="KinectMirrorSave.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 Orientation="Horizontal">
        <Image Name="kinectVideo" Height="480" Width="640" />
        <StackPanel Width="80">
            <CheckBox Content="Reflect" Name="cbReflect" Margin="10,20"/>
            <Button Name="bttSave" Content="Save" Margin="10,20" Click="onSave" HorizontalAlignment="Center" VerticalAlignment="Center"></Button>            
        </StackPanel>
    </StackPanel>
</Window>
โค้ด (รายละเอียดจะอธิบายในตอนที่ 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;
using System.IO;

namespace KinectMirrorSave
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        KinectSensor myKinect;

        public MainWindow()
        {
            InitializeComponent();
        }

        //When a window loads
        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 display when video frame is ready*********
        //color data array
        byte[] colorData = null;
        WriteableBitmap imageBitmap = null;

        private 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
                if (cbReflect.IsChecked == true)
                {
                    //reflect a half left image to a half right
                    reflectImage(colorData, colorFrame.Width, colorFrame.Height);
                }

                //prepare a saved frame
                if (takePicture)
                {
                    //create a bitmap from the current frame
                    capBitmap = BitmapSource.Create(
                        colorFrame.Width, colorFrame.Height, 96, 96, PixelFormats.Bgr32, null,
                        colorData, colorFrame.Width * colorFrame.BytesPerPixel);
                    //switch a photo captured flag
                    takePicture = false;
                }

                //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 **************
        //Reflect the half left of the image to the half right
        private unsafe void reflectImage(byte[] colorData, int width, int height)
        {
            //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                
                //we use integer pointer to move by one pixel, faster than by one byte
                //however, we cannot get color data, OK for reflection                

                //loop through each row
                for (int row = 0; row < height; row++)
                {
                    //set a start pointer to the beginning of each row
                    int* pStart = (int*)pImage + (row * width);
                    //set an end pointer to the end of each row
                    int* pEnd = pStart + width - 1;
                    //loop through each column of the row
                    while (pStart < pEnd)
                    {
                        //assign right pixel to left pixel
                        *pEnd = *pStart;
                        //move start pointer to right
                        pStart++;
                        //move end pointer to left
                        pEnd--;
                    }
                }//end for                               
            }
        }//end unsafe method

        //*********** Click a save button *********************
        bool takePicture;
        BitmapSource capBitmap = null;      //bitmap for captured frame

        private void onSave(object sender, RoutedEventArgs e)
        {
            //set a flag to capture image in video frame event
            takePicture = true;
            // Configure save file dialog box
            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
            dlg.FileName = "capture"; // Default file name
            dlg.DefaultExt = ".jpg"; // Default file extension
            dlg.Filter = "Pictures (.jpg)|*.jpg"; // Filter files by extension

            //if select Dialog OK
            if (dlg.ShowDialog() == true)
            {
                // Save document
                string filename = dlg.FileName;
                using (FileStream stream = new FileStream(filename, FileMode.Create))
                {
                    //encode and save to JPG format
                    JpegBitmapEncoder encoder = new JpegBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(capBitmap));
                    encoder.Save(stream);
                }
            }
        }
    }
}

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;
                }
            }
        }
        //------------------------------------------------------------------------------
    }
}

Monday, April 13, 2015

การประมวลผลภาพจาก Kinect ตัวอย่างการปรับค่าสีด้วย slider

ลองมาสร้าง slider เพื่อใช้ปรับค่าสีกันครับ

ตัวอย่างก่อนปรับ

ตัวอย่างหลังปรับเพิ่มค่าสีน้ำเงิน

ก่อนอื่นก็ไปสร้างหน้าอินเตอร์เฟสก่อนครับ โดยไปปรับแก้ไฟล์ XAML ให้ได้ประมาณนี้
<Window x:Class="KinectCam.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
    <StackPanel Orientation="Horizontal">
        <Image Name="kinectVideo" Width="440"/>
        <Slider Name="BlueSlider" Background="Blue" Maximum="255" Minimum="-255" Orientation="Vertical" ValueChanged="BlueSlider_ValueChanged"/>
        <Slider Name="GreenSlider" Background="Green" Maximum="255" Minimum="-255" Orientation="Vertical" ValueChanged="GreenSlider_ValueChanged"/>
        <Slider Name="RedSlider" Background="Red" Maximum="255" Minimum="-255" Orientation="Vertical" ValueChanged="RedSlider_ValueChanged"/>
    </StackPanel>
</Window>

สังเกตว่าเรากำหนดให้ช่วงของ slider คือ -255 ถึง 255

หลักการค่อนข้างตรงไปตรงมาครับ เมื่อขยับ slider ก็ให้หาว่าค่าของ slider เป็นเท่าไหร่ สมมติเรียกว่าค่า offset

จากนั้นค่าของสีใหม่ = สีเดิม + offset
ถ้าค่านี้เกิน 255 ก็ให้เป็น 255 หรือถ้าต่ำกว่า 0 ก็ให้เป็น 0 ซึ่งเราเรียกวิธีการนี้ว่า Clamping หรือ Clipping ครับ

โค้ดทั้งหมดก็เพิ่มมานิดหน่อยจากของเดิมดังนี้ครับ
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);           
        }

        //color value
        private int blueOffset = 0;
        private int greenOffset = 0;
        private int redOffset = 0;

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

                //------------------------------------------------------
                //modify the pixel data
                //0=Blue,1=Green,2=Red,3=Alpha
                int newValue = 0;
                for (int i = 0; i < colorData.Length; i = i + 4)
                {
                    //blue
                    newValue = colorData[i] + blueOffset;
                    //clamping
                    if (newValue > 255)
                        newValue = 255;
                    if (newValue < 0)
                        newValue = 0;
                    colorData[i] = (byte)(newValue);

                    //green
                    newValue = colorData[i + 1] + greenOffset;
                    if (newValue > 255)
                        newValue = 255;
                    if (newValue < 0)
                        newValue = 0;
                    colorData[i + 1] = (byte)(newValue);

                    //red
                    newValue = colorData[i + 2] + redOffset;
                    if (newValue > 255)
                        newValue = 255;
                    if (newValue < 0)
                        newValue = 0;
                    colorData[i + 2] = (byte)(newValue);
                }
                //------------------------------------------------------

                //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;
            }
        }

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

การประมวลผลภาพวิดีโอจาก Kinect ตัวอย่างการสร้างภาพกลับสี (Negative)

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

โครงสร้างการเก็บข้อมูลพิกเซล ที่เราได้มาจาก Kinect ด้วยคำสั่ง colorFrame.CopyPixelDataTo(colorData); นั้น จะเป็นข้อมูลแบบไบต์ (byte) ที่เก็บในอาร์เรย์หนึ่งมิติ โดยหนึ่งพิกเซลจะประกอบไปด้วยอาร์เรย์สี่ช่อง แบบ BGRA นั่นคือ
0-Blue
1-Green
2-Red
3-Alpha
โดยเลข 0,1,2,... เป็น index ของอาร์เรย์ และวนไปเรื่อยๆ คือ 4-Blue, 5-Green, 6-Red, 7-Alpha...


แต่ละช่องสีก็จะถูกเก็บด้วยตัวแปรชนิดไบต์ นั่นคือ 1 ช่องสี = 1 byte = 8 bits และเก็บค่าช่วงสีได้ 2^8 = 256 ค่า (0-255) ดังนั้น หนึ่งพิกเซลจะใช้อาร์เรย์ 4 ช่อง = 4 bytes = 32 bits

ในที่นี้ เราจะลองมาเปลี่ยนค่าสีของพิกเซล โดยสร้างภาพกลับสี (Negative) กันครับ หลักการคือถ้าอ่านค่าสีพิกเซลได้เท่าใด ให้นำไปหักออกจาก 255 ก็จะได้ค่าสีที่อยู่ตรงข้ามครับ

สมมติว่ารูปปกติคือ

ตัวอย่างผลลัพธ์ที่ได้ก็จะเป็น

การแก้ไขก็จะแก้เฉพาะส่วนของ byte array ก่อนที่จะสร้างบิตแมพ ซึ่งจะแก้ในเฉพาะส่วนของฟังก์ชันที่เป็นผลของ ColorFrameReady ดังนี้ครับ

สังเกตว่าส่วนที่แก้ไขมีนิดเดียว อยู่ระหว่าเส้นประที่ขีดไว้ครับ
        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);

                //------------------------------------------------------
                //modify the pixel data
                //0=Blue,1=Green,2=Red,3=Alpha
                for (int i = 0; i < colorData.Length; i = i + 4)
                {
                    //image negative
                    //blue
                    colorData[i] = (byte)(255 - colorData[i]);
                    //green
                    colorData[i + 1] = (byte)(255 - colorData[i + 1]);
                    //red
                    colorData[i + 2] = (byte)(255 - colorData[i + 2]);
                }
                //------------------------------------------------------

                //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;
            }
        }