Kinect SDK : Havuz vs Olay

kinect_thumb

   Windows için Kinect SDK Beta sürümüyle birlikte gelen programlama kılavuzunu okuduysanız derinlik, video ve iskelet akışlarından bilgi almak için iki yöntem önerildiğini biliyor olmalısınız; Havuz ve Olay. Bu makalemde, bu iki yöntem arasındaki farklara değinerek daha önceden Olay bildirimleri yöntemi üzerinden örneklediğim Kinect ile Uzaya Çıkıyoruz makalemdeki kodumu havuz yöntemiyle nasıl yazabileceğimizi sizlerle paylaşıyor olacağım.

   Doğrudan kodlamaya başlamadan önce isterseniz her iki yöntemi tanıyalım:

Olay (event) Yöntemi   : Kinect tarafından derinlik, video ve iskelet verisi oluştuğunda bir olay ile bildirilmesi temeline dayanan bu yöntem bize daha esnek olarak uygulama geliştirme imkanı sunmaktadır. Kullanıma hazır bir veri oluşması durumunda çalışma-zamanı Runtime.DepthFrameReady ve/veya Runtime.ImageFrameReady olayları tetiklenecektir. Uygulama içerisinde ihtiyacımız olan veriye göre bu iki olay bildiriminden ihtiyacımız olanı dinlememiz yeterli olacaktır.

Havuz (pooling) yöntemi : Derinlik, video ve iskelet akışlarını işlemede en basit yöntem olan havuz yönteminde verinin oluşması sırasında SDK’dan bir olay bildirimi beklemek yerine, belirli aralıklarla verinin oluşup oluşmadığı kontrol edilmekte. Oluşan veri programsal olarak DepthStream.GetNextFrame ve VideoStream.GetNextFrame fonksiyonları yardımıyla teslim alınıp işleninceye kadar bir havuzda bekleyecektir. Donanımsal kısıtlar nedeniyle oluşan bir verinin sonsuza kadar havuzda hazır tutulamayacak olması nedeniyle, ilgili veri akışı başlatılırken havuzun boyutu (havuzda aynı anda tutulacak veri sayısı) belirlenmekte ve bu sayı aşıldığında eski veriler havuzdan silinmektedir.

   Yukarıda özetle ne olduklarını paylaştığım her iki yöntemin de kendilerine göre artıları ve eksileri mevcut. Olay bildirimi üzerinden hareket etmek her zaman için biz managed kod geliştiren yazılımcılar için daha cazip olacaktır. Hali hazırda geliştirdiğimiz pek çok uygulamada zaten kullanıyor olduğumuz bu yöntem bize daha esnek hareket etme şansı sunacaktır. Öte yandan havuz yönteminde, durmaksızın beslenen ve işlemci zamanını kullanan bir veri akışı ve olay bildirimleri yerine, hazırda bekleyen veriyi ihtiyacımız olduğunda okuyarak işlem yapma imkanı sunacaktır.

   İki yöntemden hangisi seçeceğimiz konusunda belirleyici olan bir başka nokta da yapmak istediğimiz işlem olacaktır. Kinect ile Uzaya Çıkıyoruz başlıklı makalemi hatırlayacak olursanız video ve derinlik akışlarını bir arada kullanmıştık. Teorik olarak problemsiz çalışacak olan bu kod düşük işlemci kapasitesine sahip ya da aynı anda yoğun işlemci zamanı kullanımı olan durumlarda senkronizasyon problemi yaratacaktır. Özellikle hızlı hareket ettiğimizde bu problem daha bariz şekilde görülecektir. Bu problemin ortaya çıkış nedenlerinden birisi de Kinect’in hareket ve video sensörünün aynı saat frekansında çalışmıyor olmasıdır. Alternatif olarak Çerçeve numarası (FrameNumber) değerinin kullanımı akıllara gelse de henüz beta seviyesindeki Windows için Kinect SDK’de aynı çerçeve numarasına sahip iki akışın senkron olacağının bir garantisi bulunmamakta. Bu durumda olay bildirimi üzerinden bu tarz uygulama geliştirmeyi zorlaştırmakta ve havuz yöntemi alternatifini akıllara getirmekte.

   Yukarıda bahsettiğim senaryoda, olay bildirimleri yerine havuz üzerinden işlemleri gerçekleştirmek daha senkron bir oluşturma konusunda bizlere daha fazla kontrol sağlaması dışında ortaya çıkan kod da daha anlaşılır olacaktır.

    Aradaki farkın daha rahat anlaşılması ve havuz yöntemi ile nasıl kod geliştirileceğinin pekişmesi adına, aşağıda Kinect ile Uzaya Çıkıyoruz başlıklı makalemde paylaştığım uygulamayı havuz yöntemi ile baştan geliştirilmiş haliyle paylaşıyorum;

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Research.Kinect.Nui;

namespace Com.Enterprisecoding.MerhabaKinect {
    public partial class MainWindow : Window {
        private Runtime kinect;

        private Queue<ImageFrame> VideoKuyrugu = new Queue<ImageFrame>();
        private DispatcherTimer zamanlayici = new DispatcherTimer();

        public MainWindow() {
            InitializeComponent();

            kinect = new Runtime();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e) {
            kinect.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);

            kinect.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
            kinect.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);

            zamanlayici.Interval = new TimeSpan(0, 0, 0, 0, 20);
            zamanlayici.Tick += zamanlayici_Tick;
            zamanlayici.Start();

            var derinlikBilgisi = kinect.DepthStream.GetNextFrame(0);
            var videoBilgisi = kinect.VideoStream.GetNextFrame(0);
        }

        private void zamanlayici_Tick(object sender, EventArgs e) {
            var derinlikBilgisi = kinect.DepthStream.GetNextFrame(0);
            var videoBilgisi = kinect.VideoStream.GetNextFrame(0);

            if (videoBilgisi != null) {
                VideoKuyrugu.Enqueue(videoBilgisi);
            }

            if (derinlikBilgisi != null) {
                var enUygunVideoBilgisi = EnUygunVideoBilgisiniBul(derinlikBilgisi.Timestamp);

                if (enUygunVideoBilgisi != null) {
                    GoruntuyuIsle(derinlikBilgisi, enUygunVideoBilgisi);
                }
            }
        }

        private void GoruntuyuIsle(ImageFrame derinlikBilgisi, ImageFrame videoBilgisi) {
            var genislik = derinlikBilgisi.Image.Width;
            var yukseklik = derinlikBilgisi.Image.Height;

            var derinlikBitleri = derinlikBilgisi.Image.Bits;
            var videoBitleri = videoBilgisi.Image.Bits;

            var islenmisGoruntu = new byte[genislik * yukseklik << 2];

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

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

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

                        int xKordinati;
                        int yKordinati;

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

                        var videoPikselIndeksi = (xKordinati + (yKordinati * videoBilgisi.Image.Width)) << 2;

                        islenmisGoruntu[pikselIndeksi] = videoBitleri[videoPikselIndeksi];
                        islenmisGoruntu[pikselIndeksi + 1] = videoBitleri[videoPikselIndeksi + 1];
                        islenmisGoruntu[pikselIndeksi + 2] = videoBitleri[videoPikselIndeksi + 2];
                        islenmisGoruntu[pikselIndeksi + 3] = (byte)255;
                    }
                }
            }

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

        private ImageFrame EnUygunVideoBilgisiniBul(long timestamp) {
            if (VideoKuyrugu.Count == 0) { return null; }

            var enUygunVideoBilgisi = VideoKuyrugu.Dequeue();

            var enUygunZamanFarki = Math.Abs(enUygunVideoBilgisi.Timestamp - timestamp);

            if (VideoKuyrugu.Count > 0) {
                var videoBilgisi = VideoKuyrugu.Peek();
                var zamanFarki = Math.Abs(videoBilgisi.Timestamp - timestamp);

                if (zamanFarki < enUygunZamanFarki) {
                    enUygunVideoBilgisi = VideoKuyrugu.Dequeue();
                }
            }

            return enUygunVideoBilgisi;
        }


        private void Window_Closed(object sender, System.EventArgs e) {
            kinect.Uninitialize();
        }
    }
}

Her iki kodu ayrı ayrı çalıştırdığımızda havuz yöntemiyle daha senkron bir görüntü elde edildiğini sizlerde görebilirsiniz. Şüphesiz ki aynı yöntem üzerinden alternatif implemantasyonlarla daha performanslı kod yazılması da söz konusu olabilir.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, 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# konusundan Microsoft tarafından dört 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