Home TürkçeC# Kinect ile Uzaya Çıkıyoruz

Kinect ile Uzaya Çıkıyoruz

by Fatih Boy
0 comment

   Kinect ile birlikte sahip olduğumuz yetenekler biz yazılımcıların hayal gücü ile birleşince yapılabileceklerde sınırlar kalkıyor. Öyle ki; Kinect istersek bizi uzaya bile çıkartabilir… Bu makalemde, adım adım ilerlediğimiz Kinect programlamada, video görüntüsünden aldığımız ve sadece oyunculara ait olan görüntüyü nasıl bir ardalan üzerinde gösterebileceğimizi göreceğiz.

Uygulamamızda kullanacağımız uzay ardalanı

   Hatırlarsanız, Kinect makale serisinin bir önceki makalesinde işlediğimiz derinlik bilgisini analiz ederek nasıl sadece oyuncuya ait derinlik bilgisini gösterebileceğimizi öğrenmiştik. Bu yazımda ise konuyu biraz daha eğlenceli hale getirebilmek ve pratikte bu işlemi ne şekilde kullanabileceğimizi görebilmek adına video akışından gelen görüntüden sadece oyuncuya ait olanları alarak bir ardalan üzerine yerleştiriyor olacağım.

   Hatırlarsanız Kinect’te video ve derinlik bilgisi iki farklı akış üzerinden iletilmekteydi. Bu durum, bir önceki makalemizde derinlik akışı üzerinden yaptığımız işlemleri video akışıyla birleştirme gereğini doğuruyor; çünkü video akışıyla bize herhangi bir derinlik bilgisi sunulmamakta. Temel mantığımız önceki makalemizde yaptığımız gibi oyuncuya ait olan pikselleri derinlik akışından tespit etmek, ardından bu piksellerin video akışındaki karşılıklarını bularak benzer şekilde video akışındaki istenmeyen pikselleri temizlemek. Son olarak da, oluşan oyuncu görüntüsünü bir ardalan üzerine yerleştireceğiz.

   Video akışında yer alan piksel verisi sadece renk bilgisini barındırıyor olması nedeniyle maalesef ki oyuncu tespitinde kullanılamamakta; bu durumda her bir derinlik akışı verisi elimize ulaştığında bu bilgiyi video akışında kullanmak üzere saklamalı, video akış verisi geldiğinde de iki bilgiyi işleyerek oyuncu görüntüsünü elde etmeliyiz. Bu amaçla, ana ekran sınıfımız içerisinde derinlikVerisi adıyla bir byte dizisi değişkeni oluşturuyoruz;

private byte[] derinlikVerisi;

   Ardından, ana formumuz yüklenirken Kinect çalışma zamanını ilklendirip video ve derinlik akışlarını dinlemeye başlıyoruz;

private void Window_Loaded(object sender, RoutedEventArgs e) {
    kinect.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
 
    kinect.VideoFrameReady += kinect_VideoFrameReady;
    kinect.DepthFrameReady += kinect_DepthFrameReady;
 
    kinect.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
    kinect.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
}

   ve her bir derinlik akışıyla geren derinlik bilgisini sonradan kullanmak üzere saklıyoruz;

private void kinect_DepthFrameReady(object sender, ImageFrameReadyEventArgs e) {
    derinlikVerisi = e.ImageFrame.Image.Bits;
}

   Bu noktadan sonra geriye video akışıyla gelen veriyi işlemek kalıyor. Temelde yapacaklarımız bir önceki makalemde paylaştığım oyuncu görüntüsüne ulaşmak ile aynı;

private void kinect_VideoFrameReady(object sender, ImageFrameReadyEventArgs e) {
    if (derinlikVerisi == null) return;

    var genislik = 320;
    var yukseklik = 240;

    var videoGoruntusu = e.ImageFrame.Image.Bits;
    var bitmap = new byte[genislik * yukseklik << 2]; // genislik * yukseklik * 4

    lock (derinlikVerisi) {
        for (int y = 0, veriIndeksi = 0; y < yukseklik; y++) {
            var yukseklikOfseti = y * genislik;

            for (var x = 0; x < genislik; x++, veriIndeksi += 2) {
                int oyuncu = derinlikVerisi[veriIndeksi] & 0x07;

                if (oyuncu > 0) {
                    var pikselIndeksi = ((genislik - x - 1) + yukseklikOfseti) * 4;
                    
                    
                    // Bulunan pikselin video akışındaki
                    // karşılığı üzerinde işlem yapılmalı
                }
            }
        } 
    }

    derinlikVideo.Source = BitmapSource.Create(genislik, yukseklik, 96, 96, PixelFormats.Bgra32, null, bitmap, (genislik * PixelFormats.Bgra32.BitsPerPixel) >> 3);
}

   Ayrılan nokta ise oyuncuya ait olan her bir pikselin video görüntüsündeki karşılığının kullanılacak olmasıdır. Şanslıyız ki bu karışık hesaplamaları yapabilmek için Kinect SDK bize GetColorPixelCoordinatesFromDepthPixel adıyla bir fonksiyon sunmakta. Çözünürlük, ham derinlik bilgisi ve derinlik akışındaki x/y koordinatı gibi bilgilerin verilmesi sonrası bu fonksiyon bize video akışındaki ilgili piksel koordinatlarını verecektir;

kinect.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(e.ImageFrame.Resolution,
                        e.ImageFrame.ViewArea, x, y,
                        (short)(derinlikVerisi[veriIndeksi] | (derinlikVerisi[veriIndeksi + 1] << 8)),
                        out xKordinati,
                        out yKordinati);

   Bu işlem ardında son olarak, bu koordinatlara denk gelen video pikselini nihayi görüntümüze kopyalamalıyız;

var videoPikselIndeksi = (xKordinati + (yKordinati * e.ImageFrame.Image.Width)) << 2; //(xKordinati + (yKordinati * e.ImageFrame.Image.Width)) *4
 
bitmap[pikselIndeksi] = videoGoruntusu[videoPikselIndeksi];
bitmap[pikselIndeksi + 1] = videoGoruntusu[videoPikselIndeksi + 1];
bitmap[pikselIndeksi + 2] = videoGoruntusu[videoPikselIndeksi + 2];
bitmap[pikselIndeksi + 3] = (byte)255;

   Bu işlemi oyuncuya denk gelen her bir piksel için yapmamız sonrasında önceki makalemizde de olduğu gibi nihai verimizi görüntüye çevirebiliriz;

derinlikVideo.Source = BitmapSource.Create(genislik, yukseklik, 96, 96, PixelFormats.Bgra32, null, bitmap, (genislik * PixelFormats.Bgra32.BitsPerPixel) >> 3);

   Toparladığımızda kinect_VideoFrameReady fonksiyonumuz aşağıdaki gibi olacaktır;

private void kinect_VideoFrameReady(object sender, ImageFrameReadyEventArgs e) {
    if (derinlikVerisi == null) return;

    var genislik = 320;
    var yukseklik = 240;

    var videoGoruntusu = e.ImageFrame.Image.Bits;
    var bitmap = new byte[genislik * yukseklik << 2]; // genislik * yukseklik * 4

    lock (derinlikVerisi) {
        for (int y = 0, veriIndeksi = 0; y < yukseklik; y++) {
            var yukseklikOfseti = y * genislik;

            for (var x = 0; x < genislik; x++, veriIndeksi += 2) {
                int oyuncu = derinlikVerisi[veriIndeksi] & 0x07;

                if (oyuncu > 0) {
                    var pikselIndeksi = ((genislik - x - 1) + yukseklikOfseti) * 4;
                    int xKordinati;
                    int yKordinati;

                    kinect.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(e.ImageFrame.Resolution,
                        e.ImageFrame.ViewArea, x, y,
                        (short)(derinlikVerisi[veriIndeksi] | (derinlikVerisi[veriIndeksi + 1] << 8)),
                        out xKordinati,
                        out yKordinati);

                    var videoPikselIndeksi = (xKordinati + (yKordinati * e.ImageFrame.Image.Width)) << 2; //(xKordinati + (yKordinati * e.ImageFrame.Image.Width)) *4

                    bitmap[pikselIndeksi] = videoGoruntusu[videoPikselIndeksi];
                    bitmap[pikselIndeksi + 1] = videoGoruntusu[videoPikselIndeksi + 1];
                    bitmap[pikselIndeksi + 2] = videoGoruntusu[videoPikselIndeksi + 2];
                    bitmap[pikselIndeksi + 3] = (byte)255;
                }
            }
        }
    }

    derinlikVideo.Source = BitmapSource.Create(genislik, yukseklik, 96, 96, PixelFormats.Bgra32, null, bitmap, (genislik * PixelFormats.Bgra32.BitsPerPixel) >> 3);
}

   Tüm bunların ardından, elde ettiğimiz görüntümüzün arkasına aşağıdaki şekilde bir ardalan yerleştirebiliriz;

<Window x:Class="Com.Enterprisecoding.MerhabaKinect.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Merhaba Kinect" Height="580" Width="710" Loaded="Window_Loaded" Closed="Window_Closed">
    <Grid>
        <Grid.Background>
            <ImageBrush ImageSource="/MerhabaKinect;component/ardalan.jpeg" />
        </Grid.Background>
        <Image Name="derinlikVideo" Stretch="Fill" />
    </Grid>
</Window>

Uygulamamızı çalıştırmamız sonrasında Kinect sensörleri önüne geçitiğimizde uzaya çıkmış olacağız

   Her ne kadar bu işlemler ardından istediğimiz görüntüyü elde ediyor olsak da; kodumuz yoğun şekilde büyük piksel grupları ile işlem yapacağından görüntüde kaymalar olabilir. Bunu en aza indirmek için ise yapılan işlemleri yatayda işlemcilere dağıtmak en doğrusu olacaktır. Aşağıda aynı mantığı C# 4.0 ve TPL (Task Parallel Library) ile birlikte gelen Parallel.For fonksiyonu yardımıyla paralelde işleyerek hızlandırdığım halini bulabilirsiniz;

private void kinect_VideoFrameReady(object sender, ImageFrameReadyEventArgs e) {
    if (derinlikVerisi == null) return;

    var genislik = 320;
    var yukseklik = 240;

    var videoGoruntusu = e.ImageFrame.Image.Bits;
    var bitmap = new byte[genislik * yukseklik << 2]; // genislik * yukseklik * 4

    lock (derinlikVerisi) {
        Parallel.For(0, yukseklik * genislik, xy => {
            var x = xy % genislik;
            var y = (int)(xy / genislik);

            int veriIndeksi = (y * genislik + x) << 1; // (y * genislik + x) * 2

            int oyuncu = derinlikVerisi[veriIndeksi] & 0x07;

            if (oyuncu > 0) {
                var yukseklikOfseti = y * genislik;
                var hamDerinlikBilgisi = (short)(derinlikVerisi[veriIndeksi] | (derinlikVerisi[veriIndeksi + 1] << 8));

                var sonucPikselIndeksi = ((genislik - x - 1) + yukseklikOfseti) << 2; // ((genislik - x - 1) + yukseklikOfseti) * 4

                int xKordinati;
                int yKordinati;

                kinect.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(
                    e.ImageFrame.Resolution, e.ImageFrame.ViewArea, x, y, hamDerinlikBilgisi,
                    out xKordinati,
                    out yKordinati);

                var videoPikselIndeksi = (xKordinati + (yKordinati * e.ImageFrame.Image.Width)) << 2; //(xKordinati + (yKordinati * e.ImageFrame.Image.Width)) *4

                bitmap[sonucPikselIndeksi] = videoGoruntusu[videoPikselIndeksi];
                bitmap[sonucPikselIndeksi + 1] = videoGoruntusu[videoPikselIndeksi + 1];
                bitmap[sonucPikselIndeksi + 2] = videoGoruntusu[videoPikselIndeksi + 2];
                bitmap[sonucPikselIndeksi + 3] = (byte)255;
            }
        });
    }

    derinlikVideo.Source = BitmapSource.Create(genislik, yukseklik, 96, 96, PixelFormats.Bgra32, null, bitmap, (genislik * PixelFormats.Bgra32.BitsPerPixel) >> 3);
}

Şu Yazıları da Sevebilirsiniz

Leave a Comment

* Bu formu kullanarak, verilerinizin bu web sitesi tarafından saklanması ve kullanılmasını kabul ediyorsunuz.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Bu web sitesi deneyiminizi geliştirmek için çerezleri kullanır. Bunu kabul ettiğinizi varsayacağız, ancak isterseniz vazgeçebilirsiniz. Kabul Et Daha Fazla Bilgi