Bir bilgisayar mühendisi için programlama dili, öğrendiklerini sınadığı, deneyler yaptığı bir laboratuardır ve mühendisler deneylerini, kestiremedikleri sonuçları gözlemlemek için değil, öngördükleri sonuçları doğrulamak için yapar...

Bitmapleri .Net Çatısı Altında Hızlıca İşleme

Bir bitmapin piksellerini Bitmap.GetPixel() ve Bitmap.SetPixel() fonksiyonlarından yaklaşık 25 kat daha hızlı bir şekilde okuyup yazmak mümkün.

Bugünlerde, muhtemelen OpenCV'de implementasyonlarından en çok faydalanılan algoritmalardan birisi olan Viola-Jones nesne tespit algoritmasını (kademeli haar sınıflandırıcıları) VB.Net ile yazmaya çalışıyorum. Yöntemi uygulamaya çalışırken bazı noktaları yanlış anlamış olduğumu da farkettim. Bu çalışma o açıdan da faydalı oluyor benim için. Ayrıca hazırlamakta olduğum kütüphaneyi tamamlayabilirsem dökümantasyonunu da güzelce hazırlayıp bu sefer açık kaynak kodlu olarak dağıtmayı planlıyorum... [ -> HaarCascadeClassifier.dll ]

.Net'te, çok acelesi olmayan işlemlerde SetPixel() ve GetPixel() fonksiyonları işimizi iyi görüyor. Ama gerçek zamanlı işlemler için bu fonksiyonlar birer kaplumbağa. Kütüphanede bu fonksiyonları kullanırsam, onu gerçek zamanlı uygulamalarda kullanmak mümkün olmayacak.

Fazla miktarda verinin hızlıca işlenmesi gerektiği durumlarda belleğe yakınlığınız oldukça önemli. SetPixel() ve GetPixel() fonksiyonları programcıyı bellek üzerindeki hesaplardan kurtardığı için hızın pek önemli olmadığı durumlarda kullanışlı olsa da hız söz konusu olduğunda başka bir yol aramaya başlıyoruz. Bellek ile aramızdaki aracıları ortadan kaldırarak görüntülerin bellekteki verilerine direkt olarak erişebilmemizi sağlayacak bir yola ihtiyacımız var.

Çözüm .Net'in Bitmap sınıfında saklı. LockBits() fonksiyonu ile bitmapin verilerinin tutulduğu bellek alanını geçici bir alana kopyalayıp, oraya direkt erişim imkanı elde edebiliyoruz. Bu yol dikenli olsa da, SetPixel() ve GetPixel() fonksiyonlarına göre çok daha hızlı sonuç alabilmemizi sağlıyor ...

"Goruntu" isminde bir bitmapimiz olsun. İlk işimiz bitmapin, üzerinde çalışacağımız kısmını geçici bir bellek alanına kopyalamak ve bu alanın çöp toplayıcısı tarafından sahipsiz sanılıp temzilenmesini önlemek için onu kilitlemek. Bu işlerin tümünü LockBits() fonksiyonu hallediyor:

Dim BmpVerileri As BitmapData = Goruntu.LockBits(New Rectangle(0, 0, Width, Height), _
   ImageLockMode.ReadWrite, Goruntu.PixelFormat)

Bitmapin, üzerinde işlem yapacağımız bölgesini yeni Rectangle nesnesine girdiğimiz değerlerle istediğimiz gibi ayarlayabiliyoruz. Geçici bellek alanını sadece okuyacaksak ImageLockMode.ReadOnly, bu alana sadece veri yazacaksak ImageLockMode.WriteOnly ifadelerini kullanabiliyoruz. Son parametrede de bitmapimizin piksel formatını belirtiyoruz.

Çok sayıda piksel formatı var. Yazının devamında bitmaplerimizin "Format24bppRgb" formatını kullandığını varsayacağım. (Amacım olayın mantığını vermek, diğer formatlar da benzer şekilde ele alınabilir.) "Format24bppRgb", bitmapte piksel başına 24 bit kullanıldığını (bbp, bit per pixel) ve bu 24 bit ile renklerin r g b bileşenlerinin tutulduğunu ifade eder. Her bir renk bileşeni 1 bayt ile temsil edildiği için, renkleri elde etmek diğer formatların bir kısmına göre biraz daha kolay olacak.

Verileri bellekte geçici bir alana kopyalayıp kilitledik. Kopyalanan veriler hafızada scan line (tarama satırı) veya stride (adım) denen satırlar halinde tutulur. Satır sayısı, bitmapin yüksekliği kadardır. 24 bitlik bir bitmapin tarama satırlarının genişliğini de hesaplayalım; her piksel 3 bayt ile gösterildiğine göre tarama satırının genişliği 3*BitmapinGenişliği ifadesine eşit olmalı. Ama bu her zaman doğru değil. Burada şöyle bir sıkıntımız var:

32 bitlik bir işlemci, her seferinde 1 DWORD (4 bayt=32 bit) kadar veri işler. GDI+, işlemcinin verimli olarak kullanılabilmesi için veri bloklarını hafızada 4 baytın katları halinde tutar. (.Net, Drawing sınıfında GDI+'ı kullanır.) Dolayısıyla tarama satırlarının genişliği, her zaman 4'ün katıdır. Ancak, nasıl ki yazı yazarken bir heceyi bölüp alt satıra geçmiyorsak, GDI+ da pikselin baytlarının bir kısmını bir satıra, diğer kısmını sonraki satıra yazmaz. Bir pikselin tüm baytları mutlaka aynı tarama satırı içinde yer alır. Bu nedenlerden dolayı 3*Genişlik sayısının 4'ün katı olmaması halinde, tarama satırlarına boş baytlar yerleştirilerek satır genişlikleri 4'ün katına tamamlanır.

BitmapData sınıfının "Scan0" üyesi kilitlediğimiz verinin ilk baytının işaretçisini, "Stride" üyesi tarama satırının genişliğini verir. Bir işaretçinin işaret ettiği bellek alanından veri okumak için .NET'in Marshal sınıfının üyelerini kullanabiliyoruz:

Dim BaytSayisi As Integer = Math.Abs(BmpVerileri.Stride) * BmpVerileri.Height
Dim Isaretci As IntPtr = BmpVerileri.Scan0
Dim Renkler(BaytSayisi - 1) As Byte
Marshal.Copy(Isaretci , Renkler, 0, BaytSayisi)

Bu arada bahsetmeyi unttum; stride, negatif çıkabilir. Bu durum satırların ters dizildiğini, yani görüntünün ters saklandığını gösterir. Stride'ın mutlak değerini bu yüzden aldık.

Bitmapin verilerini yönetilmeyen bellek alanından, üzerinde işlem yapabileceğimiz dizimize aktardık. Tarama satırlarının genişliğinin 4'ün katı olabilmesi için gerektiğinde boş baytlarla genişletildiğini de öğrendik. O halde görüntüyü işlemek için verileri gezmeye başlamadan önce, son olarak her bir tarama satırına kaç boş bayt yerleştirildiğini hesaplamamız gerekiyor:

BosBaytSayisi = BmpVerileri.Stride - BmpVerileri.Width * 3

Genişliğin 3 katı, 4'ün katı ise bu, veriler arasında ekstradan konmuş hiçbir bayta rastlamayacağımız anlamına geliyor. Aksi halde, verileri dolaşırken bitmap genişliğinin 3 katına her ulaştığımızda bulunduğumuz konumdan, hesapladığımız boş bayt sayısı kadar ileriye atlayıp oradan devam etmemiz gerekir.

Tüm bunları göz önünde bulundurarak verilerimizi işlemeye başlayabiliriz. İşlemleri tamamladığımızda, UnlockBits() fonksiyonu ile, geçici bellek alanımızın bitmapimize yazılmasını sağlayıp, alanın kilidini kaldırmayı unutmuyoruz:

Marshal.Copy(Renkler, 0, Isaretci, BaytSayisi)
Goruntu.UnlockBits(BmpVerileri)

Kafa karışıklığını gidermek ve bu zahmete değip değmeyeceğini görmek adına her iki yöntemi de kullanarak 24 bitlik bir bitmapi griye dönüştüren bir örnek yapalım ve işlem sürelerini karşılaştıralım.

GetPixel(), SetPixel():

Dim Goruntu As New Bitmap("D:\Visual Basic\Basic .Net\YuzTanima\YuzTanima\Test\lise3-640x480.jpg")

Dim BaslamaZ As DateTime = Now

For i As Integer = 0 To Goruntu.Width - 1
    For j = 0 To Goruntu.Height - 1
        Dim Renk As Color = Goruntu.GetPixel(i, j)
        Dim Gri As Integer = Renk.R * 0.299 + Renk.G * 0.587 + Renk.B * 0.114 ' Matlabteki gibi
        Goruntu.SetPixel(i, j, Color.FromArgb(Gri, Gri, Gri))
    Next
Next

MessageBox.Show("İşlem " & (Now - BaslamaZ).TotalMilliseconds & " milisaniye sürdü.")

Daha hızlı sonuç vermesini beklediğimiz yöntem:

Dim Goruntu As New Bitmap("D:\Visual Basic\Basic .Net\YuzTanima\YuzTanima\Test\lise3-640x480.jpg")

Dim BaslamaZ As DateTime = Now

Dim BmpVerileri As BitmapData = Goruntu.LockBits(New Rectangle(0, 0, Goruntu.Width, _
    Goruntu.Height), ImageLockMode.ReadWrite, Goruntu.PixelFormat)
Dim TaramaSatiriG As Integer = Math.Abs(BmpVerileri.Stride)
Dim BosBaytSayisi = TaramaSatiriG - BmpVerileri.Width * 3
Dim BosluksuzTSGenisligi As Integer = TaramaSatiriG - BosBaytSayisi
Dim Isaretci As IntPtr = BmpVerileri.Scan0
Dim BaytSayisi As Integer = TaramaSatiriG * BmpVerileri.Height
Dim Renkler(BaytSayisi - 1) As Byte
Marshal.Copy(Isaretci, Renkler, 0, BaytSayisi)

Dim i As Integer = 0
Dim PozKontrol As Integer = 0 ' Boş baytlara gelip gelmediğimizi kontrol etmek için
While i < BaytSayisi
    Dim GriDeger As Integer = 0.114 * Renkler(i)
    i = i + 1

    GriDeger = GriDeger + 0.587 * Renkler(i)
    i = i + 1

    GriDeger = GriDeger + 0.299 * Renkler(i)
    i = i + 1

    Renkler(i - 3) = GriDeger
    Renkler(i - 2) = GriDeger
    Renkler(i - 1) = GriDeger

    PozKontrol = PozKontrol + 3 ' 3 bayt (1 piksel) okuduğumuz için pozisyonumuzu 3 arttırıyoruz
    If PozKontrol = BosluksuzTSGenisligi Then ' Boş baytları atlama zamanımız gelmişse
        PozKontrol = 0
        i = i + BosBaytSayisi
    End If
End While

Marshal.Copy(Renkler, 0, Isaretci, BaytSayisi)
Goruntu.UnlockBits(BmpVerileri)

MessageBox.Show("İşlem " & (Now - BaslamaZ).TotalMilliseconds & " milisaniye sürdü.")

640x480 boyutlarındaki renkli görüntünün griye dönüşümü, ilk kod ile 1016.0581, ikinci kod ile 41.0023 milisaniyede tamamlandı. Bu da ikinci kodun yaklaşık 25 kat daha hızlı sonuç verdiğini gösteriyor...

Sayfayı
Yayın tarihi: 31 Temmuz 2012 Salı, 17:47
Anahtar kelimeler: pikselleri hızlıca okuma, görüntü işleme hızını arttırma

Yorum Gönder

 
Yorumunuzu -1. yoruma yanıt olarak gönderiyorsunuz. Yanıtlamayı iptal etmek için buraya tıklayabilirsiniz.

 

Yorumlar (4)

Hüseyin
Yanıtla
12 Aralık 2012 Çarşamba, 13:08
#1

Merhaba,

Öncelikle sitenizdeki bilgiler için çok teşekkür ediyorum. Bu güzel bilgileri paylaşmanız beni çok sevindirdi. Hele ki bu konularda yapılan çalışmaların sürekli olarak mathlab gibi hazır programlara yaptırılmasının nedenini bir türlü anlayamadığım bir zamanda çok keyif aldım okurken.
Görüntü işleme ile profesyönel olmasa da amatör olarak ilgilenmiyorum. Ancak kendimce birşeyler yapmayı da seviyorum. Bu güne kadar VB 6.0 kullandım ancak hız ve tabi işlem olarak çok çok yavaş kalıyor ve çok zorlar oldu. C# a geçmek içinde çok bir zaman bulamadım ancak siz sanırım son çalışmalarınızı VB.net ile yapıyorsunuz. Bu konuda C# ın bir üstünlüğü varmı acaba ? veya OpenCV gibi kütüphaneleri Vb.net ile kontrol etmek C# ta olduğu gibi kolay mı ?

Hüseyin Atasoy
Yanıtla
06 Mart 2013 Çarşamba, 14:20
#2

C# ile yazabileceğiniz herşeyi vb.net ile yazabilirsiniz. Bunun tersi de doğru. Arkaplanda aynı şeyler dönüyor ama dil farklı. Yani biri diğerinden üstün değil.
Ama gördüğüm kadarıyla C# daha popüler.

Ferhat
Yanıtla
03 Ocak 2015 Cumartesi, 01:24
#3

Hüseyin bey, çok mühendis ve programcı gördüm, sizin gibi paylaşımcı ve bilgilisini görmedim. Burada anlattıklarınızı şuanda çok büyük bir projede kullanıyorum. Gerçekten resim işleme işi çok kısa sürede bitiyor. Allah her zaman yar ve yardımcınız olsun.

Hüseyin Atasoy
Yanıtla
04 Ocak 2015 Pazar, 21:32
#4

İyi dilekleriniz için teşekkür ederim, işinize yaramasına sevindim.

 
 
Sayfa 41 sorgu ile 0.02 saniyede oluşturuldu.
Atasoy Blog v4 © 2008-2017 Hüseyin Atasoy