1 Mart 2014 Cumartesi

OpenCVsharp'a Giriş - Yüz Algılama

    Merhaba arkadaşlar, bu yazımızda OpenCV adlı  görüntü işleme kütüphanesinden ve bu kütüphanenin C# wrapper'ı OpenCVSharp'tan bahsedeceğiz. Sonrasında ise yüz algılama ile ilgili basit bir örnek yapacağız.

    Öncelikle OpenCV kütüphanesinden bahsedelim. OpenCV ilk versiyonu 1999 yılında İntel'in araştırma ekibi tarafından yayımlanmış açık kaynak kodlu ve BSD lisanslı bir görüntü işleme kütüphanesidir.İçinde birçok ileri düzey algoritmayı bulundurur ve çok kolay bir kullanımı vardır. (yapılan işlemlerin aslında ne kadar karmaşık olduğunu göz önüne alırsak.) OpenCV kullanarak C++ ve C dillerinde kolayca Haar cascade'leri kullanarak yüz tanıma, SURF-FAST gibi algoritma kullanarak nesne tanıma gerçekleştirebilirsiniz.

   Dediğimiz gibi C++ ve C dillerinde kullanıma hazır olan bu kütüphaneyi C# ve Python gibi dillerde kullanmak için wrapper denen, kütüphanenin fonksiyonlarını farklı diller kullanarak çağırmamıza yarayan ara kütüphanelere ihtiyacımız var. Bu noktada yardımımıza Emgu CV ve OpenCVSharp gibi wrapperlar koşuyor.

   Kütüphaneden bahsettiğimize göre örnek programımıza geçebiliriz. Öncelikle Visual Studio'da yeni bir C# projesi açalım. Projemizde opencvsharp kullanabilmek için ilk önce gerekli dllleri projemize eklememiz gerekiyor. Bunu bu adresten indirerek elle de yapabilirsiniz fakat biz bu makalede NuGet kullanarak otomatik olarak projemize gerekli dosyaları ekleyeceğiz.

   Projenizi oluşturduktan sonra araç çubuğundan Project > Manage NuGet Packages seçeneklerini seçerek NuGet paket ekleme penceresini açın. Online seçeneğini seçip arama kutusuna "opencvsharp" yazdığınızda karşınıza gelen sonuç aşağı yukarı böyle bir şey olacak. İşletim sisteminize göre 64 bit ve 32 bit sürümleri arasından seçim yapın ve "Install" a basın. (x86 32 bit anlamına gelmektedir.)




Kısa bir süre sonra projenizin Solution Explorer penceresinde eklenen dllleri görebililiyor olacaksınız.


Bu aşamada herhangi bir sorunla karşılaştıysanız yorumlarda belirtebilirsiniz. Wrapperımızı yükledik ve artık kod yazmaya geçebiliriz.


Her şeyden önce kodumuza kullanacağımız kütüphaneleri ekleyelim;
using OpenCvSharp;
using OpenCvSharp.MachineLearning;
using OpenCvSharp.Blob;
using OpenCvSharp.UserInterface;
using OpenCvSharp.CPlusPlus;
Kodumuzun en üstüne bu satırları ekliyoruz.



Formumuza resmi göstermek için kullanacağımız bir pictureBox ve kodu çalıştırmak için kullanacağımız bir buton ekleyelim.

Butonumuza çift tıklayarak click eventini oluşturalım. Buttonumuzun click eventi içinde:

Bitmap resim = (Bitmap)Bitmap.FromFile(@"C:\resim\resim1.jpg");

şeklinde bir bitmap nesnesi oluşturalım. Eğer parantez içine neden (Bitmap) yazdığımı merak ediyorsanız internetten C# Implicit Type Conversion şeklinde araştırabilirsiniz. Bu nesne yüz algılama yapacağımız resmi tutacak. O yüzden parametrelere işlem yapmak istediğiniz resmi yazın.

OpenCv kütüphanesi resimleri saklarken görüntü işleme ile ilgili fonksiyonları olan iplimage adlı bir nesne kullanır. Resmimizi bitmap olarak aldık ama işlem yapmak için ilk önce iplimage'e çevirmeliyiz. Bunun için;

IplImage resimipl = BitmapConverter.ToIplImage(resim);

diyerek resmimizi iplimage'e çevirelim. Resmi aldığımıza göre artık algılamaya geçebiliriz.



Yüz algılama yapacağımız, iplimage türünden değer geri döndüren ve parametrelerinde bir adet iplimage alan bir fonksiyon oluşturalım;


private IplImage yuzAlgila(IplImage resim) {}


Bu fonksiyonun içine suratların etrafına çizeceğimiz çemberin rengini belirleyen bir CvColor nesnesi oluşturalım.


CvColor surat = new CvColor(0, 0, 255); //mavi

Parametrelere yazdığımız (0,0,255) değerleri mavi renktir. R - G - B değerlerini değiştirerek rengi değiştirebilirsiniz.

Fonksiyonumuz içerisinde aşağıdaki değerleri tanımlayalım.


const double olcek = 3.0;   //hizli islem icin resmin kac kat kucultulecegi
const double ScaleFactor = 2.5;
const int MinNeighbors = 1;
Buradaki ölçek değişkeni resmin kaç kat küçültüleceğini belirtiyor. İşlem yapmadan önce resmi küçülterek tanıma için gerekli süreyi azaltıyoruz. Bu gerçek zamanlı çalışan uygulamalar için kritik önem taşıyor.

ScaleFactor ve MinNeighbors değişkenleri ise Haar yöntemi için gerekli bazı değişkenler. Bu makalede açıklamaya çalışırsak gereksiz uzar ve konumuzun dışına çıkmış oluruz. Burada sadece basit bir örnek ile başlangıç yapmaya çalışıyoruz :)

using (IplImage kucukresim = new IplImage(new CvSize(Cv.Round(resim.Width / olcek), Cv.Round(resim.Height / olcek)), BitDepth.U8, 1))


            {


                using (IplImage gri = new IplImage(resim.Size, BitDepth.U8, 1))   //renksizlestir


                {


                    Cv.CvtColor(resim, gri, ColorConversion.BgrToGray);


                    Cv.Resize(gri, kucukresim, Interpolation.Linear);


                    Cv.EqualizeHist(kucukresim, kucukresim);


                }




                IplImage outImage = Cv.Clone(resim);




                //asil tespit kismina geldik


                using (CvHaarClassifierCascade cascade = CvHaarClassifierCascade.FromFile(Environment.CurrentDirectory + "\\haarcascades\\haarcascade_frontalface_alt2.xml"))


                using (CvMemStorage storage = new CvMemStorage())


                {


                    storage.Clear();




                    Stopwatch watch = Stopwatch.StartNew();


                    CvSeq<CvAvgComp> faces = Cv.HaarDetectObjects(kucukresim, cascade, storage, ScaleFactor, MinNeighbors, 0, new CvSize(30, 30));


                    watch.Stop();


                    Console.WriteLine("tespit suresi = {0} milisaniye", watch.ElapsedMilliseconds);


                    tespitsuresi = (watch.ElapsedMilliseconds).ToString();






                    for (int i = 0; i < faces.Total; i++)


                    {


                        CvRect r = faces[i].Value.Rect;


                        CvPoint center = new CvPoint


                        {


                            X = Cv.Round((r.X + r.Width * 0.5) * olcek),


                            Y = Cv.Round((r.Y + r.Height * 0.5) * olcek)


                        };


                        int radius = Cv.Round((r.Width + r.Height) * 0.25 * olcek);


                        resim.Circle(center, radius, surat, 3, LineType.AntiAlias, 0);


                    }


                }




                return resim;


            }
Kodumuzu açıklayalım: başta using içerisinde kullanacağımız şeyleri belirtiyoruz,çünkü wrapperımızın otomatik çöp toplama özelliği yok, yani nesnelerle işimiz bittiğinde nesneler otomatik olarak silinmiyor, bellekte kalmaya devam ediyor. Bu OutOfMemory hatalarına yol açabilir. using bloğunun  bittiği yerde using parantezlerinin içine yazılan değişkenler otomatik olarak dispose edilir (bellekten silinir).

İkinci using bloğunun içinde resmi küçültüyor ve gri tonlamaya çeviriyoruz. EqualizeHist() fonksiyonu resmin histogramını düzenleyerek ışıklandırmanın etkilerini azaltıyor. Bu sayede daha iyi sonuçlar elde ediyoruz.

Bir alt kısımda ise yüz tanıma işlemi gerçekleşiyor ve yüzlerin bulunduğu koordinatlar daha sonra işaretleyip kullanıcıya göstermek için alınıyor. Bu örnekte kullandığımız haar cascade'ini google aracılığıyla bulup programınızın exe'sinin bulunduğu konuma atmalısınız.


Kodun en sonundaki for döngüsünde küçük resim üzerinde tespit edilen suratların koordinatları ile büyük resim üzerinde suratların etrafına çember çiziliyor. Butonumuza bu fonksiyonu çağırmak için kodu ekleyerek programımızı bitirelim.

pictureBox1.Image = BitmapConverter.ToBitmap(yuzAlgila(resimipl));


Kodumuzun son hali şöyle olmalı;

Çalıştırmayı deneyelim;


Kodumuz çalışıyor! Bu aşamaya kadar gelebildiyseniz tebrikler! 

Bir sonraki yazımda SURF algoritmasını kullanarak nesne tespit etmeyi anlatacağım. Okuduğunuz için teşekkürler.

8 Aralık 2013 Pazar

Resim İşleme - Şablon tabanlı algoritmalar

Resim İşleme - Şablon tabanlı algoritmalar


Resmin kenarlarını tespit ederken belirli şablonlar kullanarak daha sağlıklı sonuçlar elde edebiliriz. Bu yazımızda Sobel kenar bulma algoritmasını kullanacağız. Bu algoritma da bir önceki yazımızda kullandığımız yöntemle aynı mantıkta çalışıyor fakat daha iyi sonuçlar vermesi için biraz değiştirilmiş.

Sobel şablonu aşağıdaki gibi tanımlanmış:





Üzerinde çalışılan resmin her pikseli için iki şablon da pikselin etrafındaki 3x3'lük bir alana uygulanıyor. Çevredeki piksellerin parlaklık değerleri matriste karşılık gelen değerlerle çarpılıp toplanıyor.







Formülde T, ya  Sx (x ekseni için) ya da Sy (y ekseni için) oluyor. Sonrasında da lx ve ly'nin mutlak değerleri toplanıyor.






En son olarak da l belirlenen eşik ile karşılaştırılıp büyük çıkarsa kenar olarak işaretleniyor.



Bunun gibi bir başka şablon metododu da Kirsch metodu, o da bununla aynı mantıkla çalışmakla beraber 2 yerine 8 adet şablon kullanıyor: sol-sağ, aşağı-yukarı, sol üst-sol alt, sağ üst-sağ alt. Resmin her pikseli için 8 şablon uygulanıyor ve aralarından en yüksek olanı eşik değeri ile karşılaştırılıyor.

Kod:

C# dilinde örnek kod:


           public Bitmap sinirbul(Bitmap resim)
            {
                Bitmap ret = new Bitmap(resim.Width, resim.Height);
                for (int i = 1; i < resim.Width - 1; i++)
                {
                    for (int j = 1; j < resim.Height - 1; j++)
                    {
                        Color cr = resim.GetPixel(i + 1, j);
                        Color cl = resim.GetPixel(i - 1, j);
                        Color cu = resim.GetPixel(i, j - 1);
                        Color cd = resim.GetPixel(i, j + 1);
                        Color cld = resim.GetPixel(i - 1, j + 1);
                        Color clu = resim.GetPixel(i - 1, j - 1);
                        Color crd = resim.GetPixel(i + 1, j + 1);
                        Color cru = resim.GetPixel(i + 1, j - 1);
                        int power = getMaxD(cr.R, cl.R, cu.R, cd.R, cld.R, clu.R, cru.R, crd.R);
                        if (power > 50)
                            ret.SetPixel(i, j, Color.Yellow);
                        else
                            ret.SetPixel(i, j, Color.Black);
                    }
                }
                return ret;
            }
            private int getD(int cr, int cl, int cu, int cd, int cld, int clu, int cru, int crd, int[,] matrix)
            {
                return Math.Abs(matrix[0, 0] * clu + matrix[0, 1] * cu + matrix[0, 2] * cru
                   + matrix[1, 0] * cl + matrix[1, 2] * cr
                      + matrix[2, 0] * cld + matrix[2, 1] * cd + matrix[2, 2] * crd);
            }
            private int getMaxD(int cr, int cl, int cu, int cd, int cld, int clu, int cru, int crd)
            {
                int max = int.MinValue;
                for (int i = 0; i < templates.Count; i++)
                {
                    int newVal = getD(cr, cl, cu, cd, cld, clu, cru, crd, templates[i]);
                    if (newVal > max)
                        max = newVal;
                }
                return max;
            }

        
        
        
        private List<int[,]> templates = new List<int[,]> 
{
   new int[,] {{ -3, -3, 5 }, { -3, 0, 5 }, { -3, -3, 5 } },
   new int[,] {{ -3, 5, 5 }, { -3, 0, 5 }, { -3, -3, -3 } },
   new int[,] {{ 5, 5, 5 }, { -3, 0, -3 }, { -3, -3, -3 } },
   new int[,] {{ 5, 5, -3 }, { 5, 0, -3 }, { -3, -3, -3 } },
   new int[,] {{ 5, -3, -3 }, { 5, 0, -3 }, { 5, -3, -3 } },
   new int[,] {{ -3, -3, -3 }, { 5, 0, -3 }, { 5, 5, -3 } },
   new int[,] {{ -3, -3, -3 }, { -3, 0, -3 }, { 5, 5, 5 } },
   new int[,] {{ -3, -3, -3 }, { -3, 0, 5 }, { -3, 5, 5 } }
};

Hazır program : http://sdrv.ms/1bQoZxX

Sonuç:




Bir başka yazıda görüşmek üzere.

11 Kasım 2013 Pazartesi

Resim işleme - Komşu pikseller yardımı ile kenar bulma algoritması

Resmin Kenarlarını Bulma Yöntemi

Merhabalar, bu gün projelerinizde kullanabileceğiniz bir kenar bulma metodunu anlatacağım.

Metodun Çalışma Mantığı

Siyah beyaz bir resimde kenar bulma işlemi piksellerdeki ani parlaklık değişimlerinden yola çıkarak yapılır. Beyaz bir arkaplan üzerinde siyah bir kare resmi hayal edelim. Karenin sol kenarının sol tarafındaki piksel beyaz, bu pikselin sağ tarafındaki piksel ise siyah olacak. Böyle bir durumda bu pikseli bir kenar olarak işaretleyebiliriz.


Resimde yukarıda anlattığımız metod ile kenarları belirlenmiş ve kenar pikselleri kırmızı olarak işaretlenmiş kareyi görüyorsunuz.


Renkli resimlerde de parlaklık farkı yerine piksellerin komşularından herhangi veya her kanaldaki (R,G,B renk kanallarından bahsediyorum) renk farklılığından yola çıkarak kenar algılama işlemini gerçekleştirebiliriz.

Matematiksel Kısım (basitleştirilmiş halde)


Kenarları tespit etmek için piksellerin komşu piksellere göre farklılık seviyesini kullanıyoruz.  (x,y) şeklinde koordinatları verilmiş olan pikselimizin parlaklığına A(x,y) dersek değişimi hesaplamak için formülümüz şöyle olur.;








Formülde pikselin dört bir tarafındaki komşularının parlaklığını baz alarak pikselin kenar olup olmadığına karar veriyoruz (x+1,y pikselin sağ komşusu, x-1,y  pikselin sol komşusu x,y+1  pikselin  alt komşusu, x,y-1  pikselin üst komşusu).


Ortaya çıkan G değeri, değişim derecemiz oluyor. Bu derece bizim belirlediğimiz belirli bir eşikten yüksekteyse söz konusu pikseli kenar olarak işaretliyoruz. Bu eşik resmin netliğine göre el ile ayarlandığında en sağlıklı sonuç elde ediliyor.


Programlama Kısmı


Aşağıda C# dilinde yazılmış, resmin RGB kanallarından red (kırmızı) kanalı üzerinde yukarıdaki yöntemi kullanarak çalışan bir fonksiyon var. Kodda eşiği 14 değerine sabitledim fakat ihtiyacınıza göre değiştirebilirsiniz.

private Bitmap sinirbul(Bitmap resim)
{
Bitmap ret = new Bitmap(resim.Width, resim.Height);
for (int i = 1; i < resim.Width - 1; i++)
{
for (int j = 1; j < resim.Height - 1; j++)
{
Color cr = resim.GetPixel(i + 1, j);
Color cl = resim.GetPixel(i - 1, j);
Color cu = resim.GetPixel(i, j - 1);
Color cd = resim.GetPixel(i, j + 1);
int dx = cr.R - cl.R;
int dy = cd.R - cu.R;
double power = Math.Sqrt(dx * dx / 4 + dy * dy / 4);
if (power > 14)
ret.SetPixel(i, j, Color.Yellow);
else
ret.SetPixel(i, j, Color.Black);
}
}
  return ret;
}

Sonuç 


Bir sonraki yazımda daha gelişmiş bir algoritma kullanarak daha etkili sonuçlar elde edeceğiz :) beklemede kalın.