Kinect ile İskelet, Derinlik ve Yazı İşlemleri
Selamlar;
Microsoft Xbox 360 için Project Natal adında bir proje ile kameradan görüntü yakalayan, çeşitli pixellerle tarama yaparak her bir noktanın derinlik bilgisinin alabilen, vicudumuzdaki 20 farklı bölgenin kordinatlarını yakalayabilen, üzerindeki mikrofon ile speech recognition yapabilen ve açı kontrolü ile görüntü takip edebilen Kinect adında bir cihaz tasarladı. Kinect SDK ile biz de bu aleti aşağıda görüldüğü gibi pc’den usb bağlantısı ile kullanabilmekteyiz.
Kinect’in 3 adet sensörü var. Soldaki sensör lazer projeksiyon olarak kullanılır. Kinect’in ortasında bulunan sensör ise 640×480 çözünürlüğünde 30fps bir VGA kameradır. Capture edilen görüntüyü, saniyede 30 poz resim olarak iletir. Burada herhangi bir video akışı yoktur. Herşey resim üzerine kurgulanmıştır. Sağdaki sensör kızılötesidir. Bu ışınların gidiş – geliş süresini 320×240 çözünürlüğünde hesaplıyarak tüm pixellerin kordinatlarını bulur. Kinect firmware’i ise bu dataları kullanarak iskelet yapısına göre 20 farklı bölgenin kordinatlarını çıkartır. Firmware kaydedilen 200 poz ile sensörlerde görünmeyen iskelete yapısının diğer kısımlarının kordinatlarını yani X,Y,Z sini hesaplıya biliyor. Yani tahmin eder.
Cihazın alt tarafında gizli bir DC motor vardır. Bu motor, sensörün +/- 28 derece yukarı ve aşağı hareket ederek görüntünün alınmasına yardımcı olur.Bunun ile ilgili örneği aşağıda yapıcağız. Ayrıca Kinect’in hemen altında boydan boya uzanan mikrofon, ses iletimini sağlamaktadır.
- Uygulama geliştirmek için Microsoft Kineck SDK‘yı indirmek gerekmektedir.
- Yazı yazabilmek için gerekli olan metodlar için Coding4Fun Kinect Toolkit‘i indirmek gerekmektedir.
Uygulama KinectSDK-v1.8 ve Coding4Fun Kinect Toolkit 1.7 – use K4W v1.7 ile Visula Studio 2013’de yazılmıştır.
Öncelikle bir WPF projesi yaratılır. Ve referance’lara aşağıdaki DLL’ler eklenir:
- Microsoft.Kinect
- Coding4Fun.Kinect.Wpf
- Coding4Fun.Kinect.WinForm
Öncelikle görsel olan MainWindow.xaml sayfasını yaratalım. İlgili nesnelerin resim karşılıkları aşağıda görüldüğü gibidir.
Aşağıdaki kodlarda görüldüğü gibi öncelikle 3 tane Ellipse yaratılmıştır.Bunlar başı, sağ eli ve sol eli temsil etmektedir. Sol yukarıda ismi txtMessage olan bir sistem mesaj text’i yaratılmıştır. Ellerin durumlarına göre çeşitli mesajlar vermektedir. Hemen altta ellerin durumuna göre belirlenecek resim objesi yaratılır. ID’si imgKinect’dir. Birtane slider tanımlanmıştır. Slider’ın hareketinde aşağısında yaratılan txtAngle isimli text, ilgili değerler ile dolar. Onun altında bntAngle adında kinecti belirlenen açıya göre hareket ettirecek methodu çağıran button vardır. Renkli image halimizi gösteren yukarıda sağdaki imgVideo isimli image nesnesi yaratılır. Yukarıda solda derinlik bilgisinin gösterildiği imgDept isimli image nesne yaratılır. Hemen altta spelling isminde bir TextBlock yaratılır. Görevi üzerinde belli bir süre durulan harfin değeri bu textblock içine yazılır. Coding4Fun sayesinde custom bir Bbutton isimli bir hoverbutton yaratılır. Üzerinde b harfinin gözükmesini sağlıyan bbutton2 isimli bir label yaratılır. Üzerine gelince üzerindeki b değeri spelling isimli textblock’una doldurulur. Bir de o harfinin gösterildiği Obutton isimli yeni bir hoverButtonu ve hemen altında o harifinin gözükmesi için O contentli label1 isimli yeni bir label yaratılır.
MainWindow.xaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<Window x:Class="KinectSkeletonApplication3.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Controls="clr-namespace:Coding4Fun.Kinect.Wpf.Controls;assembly=Coding4Fun.Kinect.Wpf" Title="MainWindow" Height="600" Width="800"> <Grid> <Canvas Background="Transparent"> <Ellipse Fill="Fuchsia" Height="34" Width="37" Name="leftHand" /> <Ellipse Fill="Fuchsia" Height="34" Width="37" Name="rightHand" Panel.ZIndex="99999" /> <Ellipse Fill="Red" Height="20" Width="20" Name="Head" Stroke="White" /> <TextBlock Canvas.Left="466" Canvas.Top="14" Height="96" Name="txtMessage" Text="System Hazır" Width="286" FontSize="28" Foreground="#FF4B4BB5" FontWeight="Bold" /> <Image Canvas.Left="466" Canvas.Top="116" Height="150" Name="imgKinect" Stretch="Fill" Width="200" Source="/KinectSkeletonApplication3;component/Images/assasin.jpg" /> <Slider Canvas.Left="475" Canvas.Top="355" Height="21" Name="slider1" Width="119" Maximum="27" Minimum="-27" ValueChanged="slider1_ValueChanged" /> <TextBox Canvas.Left="592" Canvas.Top="340" Height="40" Name="txtAngle" Width="74" FontSize="24" FontWeight="Bold" Text="0" /> <Button Canvas.Left="404" Canvas.Top="352" Content="Ver Ayarı!" Height="23" Name="btnAngle" Width="75" Click="btnAngle_Click" /> <Image Canvas.Left="542" Canvas.Top="405" Height="150" Name="imgVideo" Stretch="Fill" Width="200" /> <Image Canvas.Left="336" Canvas.Top="405" Height="150" Name="imgDepth" Stretch="Fill" Width="200" /> <TextBlock Canvas.Left="404" Canvas.Top="381" Height="23" Name="spelling" Text="" Width="75" FontWeight="Bold" FontSize="15" /> <Controls:HoverButton x:Name="Bbutton" ImageSize="100" ImageSource="/Resources/YellowButton-Hover.png" ActiveImageSource="/Resources/YellowButton-Active.png" TimeInterval="3000" Canvas.Left="404" Canvas.Top="203" Height="63" Width="53"/> <Label Canvas.Left="415" Canvas.Top="203" Content="b" FontFamily="Cooper" FontSize="40" FontWeight="Bold" Height="63" Name="bbutton2" Width="53" /> <Controls:HoverButton x:Name="Obutton" ImageSize="100" ImageSource="/Resources/YellowButton-Hover.png" ActiveImageSource="/Resources/YellowButton-Active.png" TimeInterval="3000" Canvas.Left="325" Canvas.Top="203" Height="63" Width="53"/> <Label Canvas.Left="336" Canvas.Top="203" Content="o" FontFamily="Cooper" FontSize="40" FontWeight="Bold" Height="63" Name="label1" Width="53" /> </Canvas> </Grid> </Window> |
Şimdi öncelikle kinect sensor’ünü tanımlayalım. Makinaya birden fazla kinect sensor bağlanabilmektedir.
public KinectSensor sensor = KinectSensor.KinectSensors[0];
Kullanılacak değişkenler aşağıdadır:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public KinectSensor sensor = KinectSensor.KinectSensors[0]; public Skeleton[] skeletonData; private short[] depthPixelData; private byte[] depthFrame32; private WriteableBitmap outputBitmap; private static double _topBoundary; private static double _bottomBoundary; private static double _leftBoundary; private static double _rightBoundary; private static double _itemLeft; private static double _itemTop; [DllImport("user32")] public static extern int SetCursorPos(int x, int y); private const int MOUSEEVENTF_MOVE = 0X0001; private const int MOUSEEVENTF_LEFTDOWN = 0X0002; private const int MOUSEEVENTF_LEFTUP = 0X0004; private const int MOUSEEVENTF_RIGHTDOWN = 0X008; [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); |
Kinect’de aşağıda görüldüğü gibi 3 farklı stream özelliği vardır.
Öncelikle ColorImageStream’i ele alalım. Ve ilkin kendimizi görelim:)
1 2 3 4 |
public MainWindow() { sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30); } |
Yukarıda görldüğü gibi ColorImageStream’i MainWindow class’ının constructer’ında önceden tanımlanan sensor’e 640*480 çözünürlükte aktif edildi. Daha sonra aşağıda görüldüğü gibi kinect’in ColorFrameReady event’ine runtime_VideoFrameReady methodu bağlandı. Burada sn’de 30 kare şeklinde gelen image data 96*96 boyutlandırarak belli bir format’da imgVideo’ya ya basıldı. Böylece yapılan hareketler renkli olarak artık ekaranda görülebildi.
1 2 3 4 |
void MainWindow_Loaded(object sender, RoutedEventArgs e) { sensor.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(runtime_VideoFrameReady); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
void runtime_VideoFrameReady(object sender, ColorImageFrameReadyEventArgs e) { using (ColorImageFrame colorFrame = e.OpenColorImageFrame()) { if (colorFrame != null) { //Using standard SDK this.colorPixelData = new byte[colorFrame.PixelDataLength]; colorFrame.CopyPixelDataTo(this.colorPixelData); this.outputImage = new WriteableBitmap( colorFrame.Width, colorFrame.Height, 96, // DpiX 96, // DpiY PixelFormats.Bgr32, null); this.outputImage.WritePixels( new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height), this.colorPixelData, colorFrame.Width * 4, 0); this.imgVideo.Source = this.outputImage; //Using Coding4Fun Kinect Toolkit //kinectColorImage.Source = imageFrame.ToBitmapSource(); } } } |
Şimdi gelelim SkeletonStream’e. MainWindow constructer’ında kinect sensöre SkeletonStream ilgili Joint Filterlar ile active edilir.
Joint Filterlar:
- Smoothing: 0 ile 1 arasında gelen iskeletin pozisyonunu görüntüsünü yumuşatarak daha az keskin çizgiler olmasını sağlar. Bu da algılamayı arttırır.
- Correction: 0 ile 1 arasında olan düşük değerlerin daha yavaş yüksek değerlerde daha hızlı düzeltme yaparak hata riskini artırır. Yavaş durumda raw data daha fazla smooth olur.
- Prediction: Hızlı hareketlerde belli bir frame önceden tahmin edilerek, yani tamamlanarak harekette akıcılık sağlanır.
- JitterRadius: Belli bir yarı çaptaki titreme sönümlendirilir.
- MaxDeviationRadius: Belirlenen çapta gelen raw data’da bozulan frameler toplanır ve düzenlenir.
1 2 3 4 5 6 7 8 9 10 11 |
public MainWindow() { sensor.SkeletonStream.Enable(new TransformSmoothParameters() { Smoothing = 0.5f, Correction = 0.5f, Prediction = 0.5f, JitterRadius = 0.05f, MaxDeviationRadius = 0.04f }); } |
Aşağıda görüldüğü gibi kinect sensörün SkeletonFrameReady event’ine runtime_SkeletonFrameReady method’u bağlanmıştır.
1 2 3 4 |
void MainWindow_Loaded(object sender, RoutedEventArgs e) { sensor.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(runtime_SkeletonFrameReady); } |
Şimdi gelelim hareket sırasında sağ,sol el ve başımızın takibine.Öncelikle aşağıdaki runtime_SkeletonFrameReady methodunu incleyelim.
Gelen skeletonFrame’in null olup olmamasına bakılır. Gelen frame’in dataları çekildikten sonra takip edilenler yani Tracked olanlara göre filitrelenme yapılır. Amaç vicutta belirlenen bölgelerden yakalanan dataları çekmektir.
Gelen datadan sağ el, sol el ve başın joint’i yani datası çekilir. Daha sonra designer’da belirlenen sağ el, sol el ve head imageleri bu joint’lerin kodinatlarına göre konumlandırılırlar. Ayrıca ellerin ve başın kordinatlarına göre çeşitli seneryolara bakılarak getHandPosition() methodunda gerekli işlemler yapılır. Son olarak B ve O harflerine sağ el ile basılıp basılmadığı CheckButton() methodu ile bakılır. Şimdi ilgili methodları aşağıda detaylı olarak inceleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void runtime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame()) { if (skeletonFrame != null) { Skeleton[] skeletonData = new Skeleton[skeletonFrame.SkeletonArrayLength]; skeletonFrame.CopySkeletonDataTo(skeletonData); Skeleton playerSkeleton = (from s in skeletonData where s.TrackingState == SkeletonTrackingState.Tracked select s).FirstOrDefault(); if (playerSkeleton != null) { Joint rightHandJ = playerSkeleton.Joints[JointType.HandRight]; Joint leftHandJ = playerSkeleton.Joints[JointType.HandLeft]; Joint headJ = playerSkeleton.Joints[JointType.Head]; SetEllipsePosition(Head, headJ); SetEllipsePosition(leftHand, leftHandJ); SetEllipsePosition(rightHand, rightHandJ); getHandPosition(headJ, rightHandJ, leftHandJ); } } } CheckButton(Bbutton, rightHand); CheckButton(Obutton, rightHand); } |
MainWindow() constructer’ında designer’daki sağ ve sol el ellipse’lerine hand.png resmi aşağıdaki gibi atanır.
1 2 3 4 |
ImageBrush myhandbrush = new ImageBrush(); myhandbrush.ImageSource = new BitmapImage(new Uri("hand.png", UriKind.Relative)); rightHand.Fill = myhandbrush; leftHand.Fill = myhandbrush; |
Aşağıdaki SetEllipsePosition() methodunda belirlenen elips yani baş, sağ el veya sol el gelen joint kordinatına göre belli oranlara göre ki bunu ScaleVector() methodu ile yapıyoruz , yukardan ve soldan olan boşluğu Canvas class’ı ile setlenir. Böylece ellerin ve kafanın hareketi anlık olarak ilgili imagelarda izlenebilecektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
private void SetEllipsePosition(Ellipse ellipse, Joint joint) { SkeletonPoint point = new SkeletonPoint(); point.X = ScaleVector(640, joint.Position.X); point.Y = ScaleVector(480, -joint.Position.Y); point.Z = joint.Position.Z; Joint updatedJoint = new Joint(); updatedJoint.TrackingState = JointTrackingState.Tracked; updatedJoint.Position = point; Canvas.SetLeft(ellipse, updatedJoint.Position.X); Canvas.SetTop(ellipse, updatedJoint.Position.Y); } private float ScaleVector(int length, float position) { float value = (((((float)length) / 1f) / 2f) * position) + (length / 2); if (value > length) { return (float)length; } if (value < 0f) { return 0f; } return value; } |
Şimdi de ellerin ve başın belli kordinatlarına göre belirlediğimiz seneryoları getHandPosition() methodu ile nasıl gerçekleştirdiğimize bakalım.
- Eğer sağ el başın yukarısına çıkarsa ImageSource’un resmi değişir ve alttan soldaki resim gibi olur. Değil ise alttan sağdaki resim gibi olur. Ayrıca mesaj olarak Elin Başının Üstünde ibaresi gözükür.
- Eğer sağ elin X kordinatı sol elin x kordinatından küçük ise image alttaki gibi olur. Ve mesaj olarak Rap Mann ibaresi gözükür.
- Eğer sol elin y kordinatı başın y kordinatından büyük olursa image hareket ettirilir ve kordinatları da sağ elin kordinatlarına bağlı olarak değişir. Kısaca sol eli başın üstünde tuttuğumuz sürece sağ elimizle resmi istediğimiz yöne hareket ettirebiliriz. Sol elimizi aşağıya indirdiğimizde resmi sabitlenmiş olur. Aşağıda dikkat edilirse nesnelere yeni kordinat set etmek için Canvas.SetTop() ve Canvas.SetLeft() methodları kullanılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
private void getHandPosition(Joint Head, Joint rightHand, Joint leftHand) { if (rightHand.Position.Y > Head.Position.Y) { changeImage(@"\Images\AspNet.jpg"); txtMessage.Text = "Elin Başının Üstünde"; } else { changeImage(@"\Images\assasin.jpg"); txtMessage.Text = "System Hazır"; //+ ScaleVector(100, rightHand.Position.Z).ToString(); } if (rightHand.Position.X < leftHand.Position.X) { changeImage(@"\Images\avatar.jpg"); txtMessage.Text = "Rap Mannn"; } if (leftHand.Position.Y > Head.Position.Y) { SkeletonPoint point = new SkeletonPoint(); point.X = ScaleVector(640, rightHand.Position.X); point.Y = ScaleVector(480, -rightHand.Position.Y); point.Z = rightHand.Position.Z; rightHand.Position = point; Canvas.SetLeft(imgKinect, rightHand.Position.X); Canvas.SetTop(imgKinect, rightHand.Position.Y); } } public void changeImage(string urlSrc) { Uri url = new Uri(urlSrc, UriKind.Relative); ImageSource src = new BitmapImage(url); imgKinect.Source = src; } |
Yukarıdaki durumlarda da gördüğü gibi vicut hareketleri ile çeşitli komutlar verilebilmekte ve sonuçları anlık olarak görülebilmektedir. Şimdi birazda yazı yazma konusu üzerinde çalışalım. Designer’da 2 tane hower button vardı. SkeletonFrameReady eventinde yandaki kodlar ile ilgili buttonların üstünde olunup olunmadığına bakılır CheckButton(Bbutton, rightHand); CheckButton(Obutton, rightHand); İlgili method aşağıda tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 |
private static void CheckButton(HoverButton button, Ellipse thumbStick) { if (IsItemMidpointInContainer(button, thumbStick)) { button.Hovering(); } else { button.Release(); } } |
IsItemMidpointInContainer() methodu ile rightHand’in bu Bbutton ve Obutton sınırları içinde olup olmadığına bakılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static bool IsItemMidpointInContainer(FrameworkElement container, FrameworkElement target) { FindValues(container, target); if (_itemTop < _topBoundary || _bottomBoundary < _itemTop) { //Midpoint of target is outside of top or bottom return false; } if (_itemLeft < _leftBoundary || _rightBoundary < _itemLeft) { //Midpoint of target is outside of left or right return false; } return true; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static void FindValues(FrameworkElement container, FrameworkElement target) { var containerTopLeft = container.PointToScreen(new System.Windows.Point()); var itemTopLeft = target.PointToScreen(new System.Windows.Point()); _topBoundary = containerTopLeft.Y; _bottomBoundary = _topBoundary + container.ActualHeight; _leftBoundary = containerTopLeft.X; _rightBoundary = _leftBoundary + container.ActualWidth; //use midpoint of item (width or height divided by 2) _itemLeft = itemTopLeft.X + (target.ActualWidth / 2); _itemTop = itemTopLeft.Y + (target.ActualHeight / 2); } |
Eğer rightHand bu hower buttonlarının herhangi birinin üstünde ise ufak bir animasyon ile üzerindeki karakter spelling textbox’ına yazılır. İlgili kod aşağıdadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public MainWindow() { InitializeComponent(); Bbutton.Click += new RoutedEventHandler(Bbutton_Clicked); Obutton.Click += new RoutedEventHandler(Obutton_Clicked); } void Bbutton_Clicked(object sender, RoutedEventArgs e) { spelling.Text = spelling.Text + "b"; } void Obutton_Clicked(object sender, RoutedEventArgs e) { spelling.Text = spelling.Text + "o"; } |
Son olarak kinnect’de DepthImageStream özelliğini inceleyelim. Yani derinlik işlemleri. MainWindow constructer’ında sensor’e DepthStream.Enable özelliğine izin verilir. Daha sonra da DepthFrameReady event’ine runtime_DepthFrameReady() methodu bağlanır.İlgili methodun kodları aşağıdadır.
1 2 3 4 5 6 7 8 9 10 |
public MainWindow() { sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30); } void MainWindow_Loaded(object sender, RoutedEventArgs e) { sensor.DepthFrameReady += new EventHandler<DepthImageFrameReadyEventArgs>(runtime_DepthFrameReady); sensor.Start(); } |
Öncelikle gelen DeptImageFrame dataları byte dizisine’e çevrilir. Dept Frame’in ilgili değişkenleri sayfanın tepesinde kullanılacak değişkenlar bölümünde tanımlanmıştır. Daha sonra bu byte dizisi bitmap’e çevrilir ve imgDepth’in source’una verilir. Derinlik bilgisini mesafeye göre oluşan renk farkından anlayabiliriz. Mesela yukarıda kollar yakına gelmiştir rengi beyazdır. Gövdenin diğer kısmı biraz daha uzakta ve rengi yeşildir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
private void runtime_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) { using (DepthImageFrame depthFrame = e.OpenDepthImageFrame()) { if (depthFrame != null) { this.depthPixelData = new short[depthFrame.PixelDataLength]; this.depthFrame32 = new byte[depthFrame.Width*depthFrame.Height*4]; depthFrame.CopyPixelDataTo(this.depthPixelData); byte[] convertedDepthBits = this.ConvertDepthFrame(this.depthPixelData,((KinectSensor) sender).DepthStream); this.outputBitmap = new WriteableBitmap( depthFrame.Width, depthFrame.Height, 96, 96,PixelFormats.Bgr32, null); this.outputBitmap.WritePixels( new Int32Rect(0, 0, depthFrame.Width, depthFrame.Height), convertedDepthBits, depthFrame.Width*4, 0); this.imgDepth.Source = this.outputBitmap; //Using Coding4Fun Kinect Toolkit ////turn raw data into an array of distances; //var depthArray = depthFrame.ToDepthArray(); ////image //kinectDepthImage.Source = depthFrame.ToBitmapSource(); } // Gelen görüntüyü Image elementinde göster //PlanarImage imageData = e.ImageFrame.Image; //imgDepth.Source = e.ImageFrame.ToBitmapSource(); } } private static readonly int[] IntensityShiftByPlayerR = { 1, 2, 0, 2, 0, 0, 2, 0 }; private static readonly int[] IntensityShiftByPlayerG = { 1, 2, 2, 0, 2, 0, 0, 1 }; private static readonly int[] IntensityShiftByPlayerB = { 1, 0, 2, 2, 0, 2, 0, 2 }; private const int RedIndex = 2; private const int GreenIndex = 1; private const int BlueIndex = 0; // Converts a 16-bit grayscale depth frame which includes player indexes into a 32-bit frame // private byte[] ConvertDepthFrame(short[] depthFrame, DepthImageStream depthStream) { int tooNearDepth = depthStream.TooNearDepth; int tooFarDepth = depthStream.TooFarDepth; int unknownDepth = depthStream.UnknownDepth; for (int i16 = 0, i32 = 0; i16 < depthFrame.Length && i32 < this.depthFrame32.Length; i16++, i32 += 4) { int player = depthFrame[i16] & DepthImageFrame.PlayerIndexBitmask; int realDepth = depthFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth; // transform 13-bit depth information into an 8-bit intensity appropriate // for display (we disregard information in most significant bit) byte intensity = (byte)(~(realDepth >> 4)); if (player == 0 && realDepth == 0) { this.depthFrame32[i32 + RedIndex] = 255; this.depthFrame32[i32 + GreenIndex] = 255; this.depthFrame32[i32 + BlueIndex] = 255; } else if (player == 0 && realDepth == tooFarDepth) { this.depthFrame32[i32 + RedIndex] = 66; this.depthFrame32[i32 + GreenIndex] = 0; this.depthFrame32[i32 + BlueIndex] = 66; } else if (player == 0 && realDepth == unknownDepth) { this.depthFrame32[i32 + RedIndex] = 66; this.depthFrame32[i32 + GreenIndex] = 66; this.depthFrame32[i32 + BlueIndex] = 33; } else { this.depthFrame32[i32 + RedIndex] = (byte)(intensity >> IntensityShiftByPlayerR[player]); this.depthFrame32[i32 + GreenIndex] = (byte)(intensity >> IntensityShiftByPlayerG[player]); this.depthFrame32[i32 + BlueIndex] = (byte)(intensity >> IntensityShiftByPlayerB[player]); } } return this.depthFrame32; } |
Kinectin açısını değiştirmek için bir tane slider konmuştur.Bu slider’ın değeri txtAngle’a yazılmaktadır . Seçilen değer bntAngle butonuna basılınca aşağıdaki kodda da görüldüğü gibi kinect sensörün açısına setlenir ve cihaz girilen değere göre yukarı veya aşağı doğru hareket eder.
1 2 3 4 5 6 7 8 9 10 11 12 |
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { txtAngle.Text = ((int)e.NewValue).ToString(); } private void btnAngle_Click(object sender, RoutedEventArgs e) { if (sensor.ElevationAngle != (int)slider1.Value) { sensor.ElevationAngle = (int)slider1.Value; } } |
MainWindow.xaml (Tamamı):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using Microsoft.Kinect; using System.Runtime.InteropServices; using Coding4Fun.Kinect.Wpf.Controls; using System.Drawing; using System.Drawing.Imaging; namespace KinectSkeletonApplication3 { public partial class MainWindow : Window { //Instantiate the Kinect runtime. Required to initialize the device. //IMPORTANT NOTE: You can pass the device ID here, in case more than one Kinect device is connected. public KinectSensor sensor = KinectSensor.KinectSensors[0]; public Skeleton[] skeletonData; private short[] depthPixelData; private byte[] depthFrame32; private WriteableBitmap outputBitmap; private static double _topBoundary; private static double _bottomBoundary; private static double _leftBoundary; private static double _rightBoundary; private static double _itemLeft; private static double _itemTop; [DllImport("user32")] public static extern int SetCursorPos(int x, int y); private const int MOUSEEVENTF_MOVE = 0X0001; private const int MOUSEEVENTF_LEFTDOWN = 0X0002; private const int MOUSEEVENTF_LEFTUP = 0X0004; private const int MOUSEEVENTF_RIGHTDOWN = 0X008; [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); public MainWindow() { sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30); sensor.SkeletonStream.Enable(new TransformSmoothParameters() { Smoothing = 0.5f, Correction = 0.5f, Prediction = 0.5f, JitterRadius = 0.05f, MaxDeviationRadius = 0.04f }); sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30); InitializeComponent(); Bbutton.Click += new RoutedEventHandler(Bbutton_Clicked); Obutton.Click += new RoutedEventHandler(Obutton_Clicked); //Runtime initialization is handled when the window is opened. When the window //is closed, the runtime MUST be unitialized. this.Loaded += new RoutedEventHandler(MainWindow_Loaded); this.Unloaded += new RoutedEventHandler(MainWindow_Unloaded); ImageBrush myhandbrush = new ImageBrush(); myhandbrush.ImageSource = new BitmapImage(new Uri("hand.png", UriKind.Relative)); rightHand.Fill = myhandbrush; leftHand.Fill = myhandbrush; } private static void CheckButton(HoverButton button, Ellipse thumbStick) { if (IsItemMidpointInContainer(button, thumbStick)) { button.Hovering(); } else { button.Release(); } } public static bool IsItemMidpointInContainer(FrameworkElement container, FrameworkElement target) { FindValues(container, target); if (_itemTop < _topBoundary || _bottomBoundary < _itemTop) { //Midpoint of target is outside of top or bottom return false; } if (_itemLeft < _leftBoundary || _rightBoundary < _itemLeft) { //Midpoint of target is outside of left or right return false; } return true; } void Bbutton_Clicked(object sender, RoutedEventArgs e) { spelling.Text = spelling.Text + "b"; } void Obutton_Clicked(object sender, RoutedEventArgs e) { spelling.Text = spelling.Text + "o"; } private static void FindValues(FrameworkElement container, FrameworkElement target) { var containerTopLeft = container.PointToScreen(new System.Windows.Point()); var itemTopLeft = target.PointToScreen(new System.Windows.Point()); _topBoundary = containerTopLeft.Y; _bottomBoundary = _topBoundary + container.ActualHeight; _leftBoundary = containerTopLeft.X; _rightBoundary = _leftBoundary + container.ActualWidth; //use midpoint of item (width or height divided by 2) _itemLeft = itemTopLeft.X + (target.ActualWidth / 2); _itemTop = itemTopLeft.Y + (target.ActualHeight / 2); } void runtime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame()) { if (skeletonFrame != null) { Skeleton[] skeletonData = new Skeleton[skeletonFrame.SkeletonArrayLength]; skeletonFrame.CopySkeletonDataTo(skeletonData); Skeleton playerSkeleton = (from s in skeletonData where s.TrackingState == SkeletonTrackingState.Tracked select s).FirstOrDefault(); if (playerSkeleton != null) { Joint rightHandJ = playerSkeleton.Joints[JointType.HandRight]; Joint leftHandJ = playerSkeleton.Joints[JointType.HandLeft]; Joint headJ = playerSkeleton.Joints[JointType.Head]; SetEllipsePosition(Head, headJ); SetEllipsePosition(leftHand, leftHandJ); SetEllipsePosition(rightHand, rightHandJ); getHandPosition(headJ, rightHandJ, leftHandJ); } } } CheckButton(Bbutton, rightHand); CheckButton(Obutton, rightHand); } public void changeImage(string urlSrc) { Uri url = new Uri(urlSrc, UriKind.Relative); ImageSource src = new BitmapImage(url); imgKinect.Source = src; } private void getHandPosition(Joint Head, Joint rightHand, Joint leftHand) { if (rightHand.Position.Y > Head.Position.Y) { changeImage(@"\Images\AspNet.jpg"); txtMessage.Text = "Elin Başının Üstünde"; } else { changeImage(@"\Images\assasin.jpg"); txtMessage.Text = "System Hazır"; //+ ScaleVector(100, rightHand.Position.Z).ToString(); } if (rightHand.Position.X < leftHand.Position.X) { changeImage(@"\Images\avatar.jpg"); txtMessage.Text = "Rap Mannn"; } if (leftHand.Position.Y > Head.Position.Y) { SkeletonPoint point = new SkeletonPoint(); point.X = ScaleVector(640, rightHand.Position.X); point.Y = ScaleVector(480, -rightHand.Position.Y); point.Z = rightHand.Position.Z; rightHand.Position = point; Canvas.SetLeft(imgKinect, rightHand.Position.X); Canvas.SetTop(imgKinect, rightHand.Position.Y); } } private void SetEllipsePosition(Ellipse ellipse, Joint joint) { SkeletonPoint point = new SkeletonPoint(); point.X = ScaleVector(640, joint.Position.X); point.Y = ScaleVector(480, -joint.Position.Y); point.Z = joint.Position.Z; Joint updatedJoint = new Joint(); updatedJoint.TrackingState = JointTrackingState.Tracked; updatedJoint.Position = point; Canvas.SetLeft(ellipse, updatedJoint.Position.X); Canvas.SetTop(ellipse, updatedJoint.Position.Y); } private float ScaleVector(int length, float position) { float value = (((((float)length) / 1f) / 2f) * position) + (length / 2); if (value > length) { return (float)length; } if (value < 0f) { return 0f; } return value; } void MainWindow_Unloaded(object sender, RoutedEventArgs e) { sensor.Stop(); sensor.ColorFrameReady -= new EventHandler<ColorImageFrameReadyEventArgs>(runtime_VideoFrameReady); sensor.SkeletonFrameReady -= new EventHandler<SkeletonFrameReadyEventArgs>(runtime_SkeletonFrameReady); sensor.DepthFrameReady -= new EventHandler<DepthImageFrameReadyEventArgs>(runtime_DepthFrameReady); } void MainWindow_Loaded(object sender, RoutedEventArgs e) { sensor.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(runtime_VideoFrameReady); sensor.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(runtime_SkeletonFrameReady); sensor.DepthFrameReady += new EventHandler<DepthImageFrameReadyEventArgs>(runtime_DepthFrameReady); sensor.Start(); } private byte[] colorPixelData; private WriteableBitmap outputImage; void runtime_VideoFrameReady(object sender, ColorImageFrameReadyEventArgs e) { using (ColorImageFrame colorFrame = e.OpenColorImageFrame()) { if (colorFrame != null) { //Using standard SDK this.colorPixelData = new byte[colorFrame.PixelDataLength]; colorFrame.CopyPixelDataTo(this.colorPixelData); this.outputImage = new WriteableBitmap( colorFrame.Width, colorFrame.Height, 96, // DpiX 96, // DpiY PixelFormats.Bgr32, null); this.outputImage.WritePixels( new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height), this.colorPixelData, colorFrame.Width * 4, 0); this.imgVideo.Source = this.outputImage; //Using Coding4Fun Kinect Toolkit //kinectColorImage.Source = imageFrame.ToBitmapSource(); } } } private void runtime_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) { using (DepthImageFrame depthFrame = e.OpenDepthImageFrame()) { if (depthFrame != null) { this.depthPixelData = new short[depthFrame.PixelDataLength]; this.depthFrame32 = new byte[depthFrame.Width*depthFrame.Height*4]; depthFrame.CopyPixelDataTo(this.depthPixelData); byte[] convertedDepthBits = this.ConvertDepthFrame(this.depthPixelData,((KinectSensor) sender).DepthStream); this.outputBitmap = new WriteableBitmap( depthFrame.Width, depthFrame.Height, 96, 96,PixelFormats.Bgr32, null); this.outputBitmap.WritePixels( new Int32Rect(0, 0, depthFrame.Width, depthFrame.Height), convertedDepthBits, depthFrame.Width*4, 0); this.imgDepth.Source = this.outputBitmap; //Using Coding4Fun Kinect Toolkit ////turn raw data into an array of distances; //var depthArray = depthFrame.ToDepthArray(); ////image //kinectDepthImage.Source = depthFrame.ToBitmapSource(); } // Gelen görüntüyü Image elementinde göster //PlanarImage imageData = e.ImageFrame.Image; //imgDepth.Source = e.ImageFrame.ToBitmapSource(); } } private static readonly int[] IntensityShiftByPlayerR = { 1, 2, 0, 2, 0, 0, 2, 0 }; private static readonly int[] IntensityShiftByPlayerG = { 1, 2, 2, 0, 2, 0, 0, 1 }; private static readonly int[] IntensityShiftByPlayerB = { 1, 0, 2, 2, 0, 2, 0, 2 }; private const int RedIndex = 2; private const int GreenIndex = 1; private const int BlueIndex = 0; // Converts a 16-bit grayscale depth frame which includes player indexes into a 32-bit frame // private byte[] ConvertDepthFrame(short[] depthFrame, DepthImageStream depthStream) { int tooNearDepth = depthStream.TooNearDepth; int tooFarDepth = depthStream.TooFarDepth; int unknownDepth = depthStream.UnknownDepth; for (int i16 = 0, i32 = 0; i16 < depthFrame.Length && i32 < this.depthFrame32.Length; i16++, i32 += 4) { int player = depthFrame[i16] & DepthImageFrame.PlayerIndexBitmask; int realDepth = depthFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth; // transform 13-bit depth information into an 8-bit intensity appropriate // for display (we disregard information in most significant bit) byte intensity = (byte)(~(realDepth >> 4)); if (player == 0 && realDepth == 0) { this.depthFrame32[i32 + RedIndex] = 255; this.depthFrame32[i32 + GreenIndex] = 255; this.depthFrame32[i32 + BlueIndex] = 255; } else if (player == 0 && realDepth == tooFarDepth) { this.depthFrame32[i32 + RedIndex] = 66; this.depthFrame32[i32 + GreenIndex] = 0; this.depthFrame32[i32 + BlueIndex] = 66; } else if (player == 0 && realDepth == unknownDepth) { this.depthFrame32[i32 + RedIndex] = 66; this.depthFrame32[i32 + GreenIndex] = 66; this.depthFrame32[i32 + BlueIndex] = 33; } else { this.depthFrame32[i32 + RedIndex] = (byte)(intensity >> IntensityShiftByPlayerR[player]); this.depthFrame32[i32 + GreenIndex] = (byte)(intensity >> IntensityShiftByPlayerG[player]); this.depthFrame32[i32 + BlueIndex] = (byte)(intensity >> IntensityShiftByPlayerB[player]); } } return this.depthFrame32; } private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { txtAngle.Text = ((int)e.NewValue).ToString(); } private void btnAngle_Click(object sender, RoutedEventArgs e) { if (sensor.ElevationAngle != (int)slider1.Value) { sensor.ElevationAngle = (int)slider1.Value; } } } } |
Böylece kinect ile belli harfler ile yazı yazıldı. Vicut hareketlerini Skeleton Frame ile takip edilip buna göre çeşitli komutlar verildi ve sonuçları incelendi. Video frame ile renkli olarak kullanıcı ekranda gösterildi. Depth Frame ile nesnelerin derinlik bilgisi alındı. RGB renk kodları bu derinliğin uzaklığına göre farklılaştırıldı. Son olarak da kinect sensör açısı bir slider ile istenen değere göre set edilip, sensörün yukarı aşağı hareketi sağlandı.
Geldik bir makalenin daha sonuna.
Yeni bir makalede görüşmek üzere hoşçakalın.
Source Code:http://www.borakasmer.com/projects/KinectBlog.rar
Source:
- https://digitalerr0r.net/2011/06/21/kinect-fundamentals-3-getting-data-from-the-depth-sensor/
- https://docs.microsoft.com/en-us/archive/blogs/ampuri/kinect-for-windows-sdk-color-stream
- https://stackoverflow.com/questions/10192476/kinect-sideways-skeleton-tracking
- https://channel9.msdn.com/Series/KinectQuickstart/Skeletal-Tracking-Fundamentals
- https://stackoverflow.com/questions/22367937/need-help-updating-old-kinect-sdk-code?rq=1
- https://www.codeproject.com/Questions/706059/Kinect-depth-in-windows-application-form
Bora Bey Merhabalar,
yazınızda firmware ekli 200 poz ile vücudun görüş alanına girmeyen kısımları olsada tam iskeletin tahmin edilebildiğinden bahsetmişsiniz.Peki bu 200 poz arttırılabilir mi ? Ya da firmware harici tüm iskeleti tahmin etmek için kullanacağımız dataset varmıdır ?Teşekkürler.
Bora bey profesyonel destek veriyor musunuz.
6 yıl sonra gelen yorum :)