Kinect ile Uzaya Çıkıyoruz

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

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir