Ara 182018
 
4.845 kez okundu

OpenCV ile Yüz Tanıma Bölüm 3-2

Önceki yazımda veri setimizi nasıl oluşturacağımızı anlatmıştım.

Bu kez veri setimizi Keras kullanarak eğiteceğiz. Sonra da yeni resimleri bu eğitilmiş veri setini kullanarak sınıflandıracağız.

3. ADIM – EĞİTİM

Projemize örnek olarak aldığımız çalışmada Adrian, Keras modellemesi için SmallerVGGNet sınıfını kullanıyor. Ben onun yerine daha basit bir model kullandım (vgg_like). Eğer isterseniz yazının sonundaki Referanslar bölümünde yer alan linkleri kullanarak SmallerVGGNet kodlarına ulaşabilirsiniz.

Lafı uzatmadan eğitim betiğimize geçelim. Betiğimizin adı: train.py

İşin içine eğitim girince bir sürü kütüphane ve modüle ihtiyaç duyuyoruz. Kullanacağımız kütüphaneler hakkında kabaca da olsa, bilgi sahibi olduğunuzu umuyorum. Yoksa bu yazıyı 1000-1500 kelimelik bir hacme sığdırmak olanaksız hale gelirdi.

İlk 27 satırımızda matplotlib, keras, sklearn, imutils, numpy, random, pickle, cv2 ve os kütüphanelerimizi içe aktarıyoruz. smallervggnet kütüphanesini de belki kullanırsınız diye listeden çıkarmadım.

Modeli daha basit tutmak amacıyla vgg_like() fonksiyonunu tanımladım. (VGGNet, 2014 yılındaki Imagenet Büyük Ölçekli Görsel Tanıma Yarışmasında çok iyi performans gösteren bir yapay sinir ağıdır. VGG (Visual Geometry Group) Oxford Üniversitesi Mühendislik Bilimleri Bölümünün bir alt birimidir.)

Eğitim için Doğrusal(Sequential) bir model kullanıyoruz – satır 30
Görsellerimiz 96×96 boyutlu ve 3 renk kanalına sahip.
Modelimize ilk önce bir Conv2D katmanı uyguluyoruz – satır 31
İkinci katmanımız yine Conv2D – satır 32
Üçüncü katmanda MaxPooling2D var – satır 33
34. satırdaki Dropout ile %25’lik giriş bilgisini devre dışı bırakıyoruz (overfitting engelleme).

Benzer işlemleri bir kez daha tekrarlıyoruz. Ama boyutlamalar biraz farklı. satır 36-39

Son aşamada modelimize önce Flatten(), sonra Dense() uyguluyoruz. satır 41-42
Dropout oranımız %50 satır 43
Son işlemimiz her zamanki gibi ‘softmax’ satır 44
45.satırda modelimizi geri döndürüyoruz.

47. satırda işlem başlangıç zamanını t0 olarak not ediyoruz. Bu değişkeni belirli aşamalarda geçen süreyi belirtmek için kullanacağız.

Satır 48-51’de sabitlerimizi tanımlıyoruz.

data ve labels boş birer listedir. satır 53-54

58. satırda görsel dosya yollarımızı bir listede topluyoruz (imagePaths).

imagePaths listesinin satırlarını karıştırırken, sonraki denemelerde de aynı rasgele değerleri elde edebilmek için seed değerini 42 olarak belirliyoruz. satır 60 (Bu değer herhangi bir başka sayı da olabilir. Önemli olan hep aynı başlatma değerini kullanmak.)

63. satırda görsel dosyalarını işlemeye başlıyoruz.
Dosyayı okuyoruz – satır 64
İmajı 96×96 olarak yeniden boyutlandırıyoruz – satır 65
İmajı bir dizi (array) haline dönüştürüyoruz – satır 66
Ve imaj dizisini data listesine ekliyoruz – satır 67

Dosya yolundan etiket bilgisini (sanatçı_kodu) ayrıştırıyoruz – satır 70
Etiket bilgisini labels listesine ekliyoruz – satır 71

data listesini elemanları 0 veya 1 olacak şekilde bir numpy dizisine çeviriyoruz – satır 75
Aynı şekilde labels listesini de bir numpy dizisi haline getiriyoruz – satır 76

Satır 81-82’de etiketleri sayısallaştırıyoruz.

Satır 85-86: data ve labels dizi elemanlarının %20’sini test, geri kalanını eğitim için ayırıyoruz.

Veri sayımız kısıtlı olduğu için data çoğullama (augmentation) işlemi yaptırıyoruz – satır 89-91 (Çoğullama sırasında döndürme, kaydırma, yükseklik değiştirme, kesme, büyütme ve yatay aynalama (flip) işlemleri yapılacak.)

Modelimizi vgg_like() fonksiyonu ile oluşturuyoruz – satır 99

model.summary(), model katmanlarımızın değişimini ayrıntılı bir şekilde açıklıyor. Bu özeti hem ekrana, hem de dosyaya kaydediyoruz. – satır 102-105

Optimizasyon işlemi için Adam da yaygın bir şekilde kullanılıyor. Benim yaptığım denemelerde SGD daha iyi sonuçlar almamı sağladı. Her ikisini de deneyebilirsiniz. Satır 108

110. satırda modelimizi derliyoruz.

Eğitim işlemleri (satır 116-120) için bir kaç dakika bekliyoruz.

Eğitim tamamlandığında, önce modelimizi (satır 124), sonra da etiketlerimizi (satır 128-130) kaydediyoruz.

Son aşama olarak, eğitim işlemlerimiz sırasında hesaplanan kayıp ve doğruluk değerlerini grafiğe döküyor ve plot.png adıyla kaydediyoruz. – satır 135-146

OpenCV ile Yüz Tanıma Bölüm 3-2

Artık sınıflandırma işlemlerimize geçebiliriz. Ama daha önce model özetimize kısaca bir göz atsak iyi olacak:

Conv2D katman çıkış boyutlarında 2 piksellik küçülme yaratıyor.
MaxPooling2D ise (2,2) oranları ile her iki yönde boyutları yarıya indiriyor.
Flatten işlemi katmanı tek boyutlu bir diziye dönüştürür.
Dropout input değerlerinden bir bölümünü işlem dışı bırakır. Böylece overfitting olasılığı azalır.

4. ADIM – SINIFLANDIRMA

Sınıflandırma işlemlerinde eğitim aşamasında kullanmadığımız görsellerden yararlanacağız. Her sanatçı için birer görsel seçtik ve bunları kontrol klasörünün altına kopyaladık.

İlk 7 satırda gerekli kütüphaneleri içeri aktarıyoruz.

imajlar listesi sınıflandıracağımız fotoğrafları tutuyor. satır 10

Sınıflandırma işlemlerini check_image() fonksiyonu ile yapacağız (satır 12). Her görsel ayrı ayrı işleme sokulacak.

Satır 13-16’da imajımızı 96×96 piksel boyutlarına küçültüyor ve modelimize uygun bir numpy dizisine dönüştürüyoruz.

Satır 19 ve 20’de eğitim sonunda kaydettiğimiz model ve etiket bilgilerini belleğe alıyoruz.

Modele göre bulunan tahmin ve olası doğruluk derecesini saptıyor; etiket değerini alıyoruz. satır 23-25

Sınıflandırma sonucunu genişliğini 400 pikselle sınırlandırdığımız imajımızın üzerine işliyoruz. satır 28-33

İşlenmiş imajı ekranda gösterip diske kaydettikten sonra bir tuşa basılmasını bekliyoruz. satır 35-38

Tuşa basıldığında belleği temizliyoruz: satır 39

check_image() fonksiyonumuzu her resim için ayrı ayrı çağıran döngümüz satır 42-45 arasında.

Elde ettiğim görüntüler aşağıda:

OpenCV ile Yüz Tanıma Bölüm 3-2

OpenCV ile Yüz Tanıma Bölüm 3-2

OpenCV ile Yüz Tanıma Bölüm 3-2

SmallerVGGNet kullanarak yaptığım denemelerde daha yüksek sayısal doğruluk oranlarına ulaştım ama, yapılan tahminlerin pek çoğu aslında hatalıydı. Bu, veri kümemizin çok dar olmasından kaynaklanmış olabilir.

Aynı eğitim setini yüz saptama fonksiyonlarıyla birlikte kullanarak grup fotoğraflarından çoklu yüz tanıma işlemleri gerçekleştirmek mümkün olabilir.

Açıklamalara böyle bir blog yazısında daha kapsamlı bir şekilde girmek mümkün olmuyor. Sorularınız varsa, yorum alanından iletebilirsiniz.

Bu tür çalışmalar çok geniş bir kapsama alanına sahip. Yapılan işlemler ve elde edilen sonuçlar deneysel nitelikte. Bu yüzden, bu tür konulara ilgi duyuyorsanız, siz de kendi modellerinizi geliştirin. Mümkünse veri kümeleri oluşturun. Deneyin. Yorumlayın.

Elbette hala öğrenecek çok şey var. Ama yapabileceğimiz şeyler de pek çok…

Önemli olan, hangi düzeyde olursak olalım, deneysel bakış açımızı yitirmemek ve hevesimizi kaybetmemek… Hata yapmak bizi korkutmasın. Hatalar, kullanmasını bilenler için en etkin yol göstericilerdir.

Yeni yazılarda buluşmak üzere…

Beni izlemeye devam edin.

Ahmet Aksoy

Referanslar:
https://github.com/kjaisingh/pokemon-classifier/blob/master/pyimagesearch/smallervggnet.py
https://www.pyimagesearch.com/2018/04/16/keras-and-convolutional-neural-networks-cnns/
https://hackernoon.com/learning-keras-by-implementing-vgg16-from-scratch-d036733f2d5
http://www.robots.ox.ac.uk/~vgg/research/very_deep/

Ara 172018
 
5.521 kez okundu

OpenCV ile Yüz Tanıma Bölüm 3-1

Bu yazımda 3 Yeşilçam emektarının fotoğraflarını içeren küçük bir veri setini oluşturmak için gerekli işlemleri ele alacağım. Eğitim ve sınıflandırma işlemleri ise bir sonraki yazımın konusu olacak.

OpenCV ile Yüz Tanıma Bölüm 3-1

Orijinal kodları yine PyimageSearch web sitesinden edinmek mümkün. Adrian, anime karakterleri kullanmış. Ben ise Yeşilçam karakterlerini tercih ettim. Ayrıca kodları biraz daha basitleştirdim ve gerekli yerlerini Türkçeleştirdim.

Veri seti için Google üzerinden ulaşabildiğim fotoğrafların sayısı hiç ummadığım kadar düşük çıktı. Oysa çok fazla fotoğrafa ulaşabileceğimi umuyordum. Bu proje için 3 sanatçımızın toplam 147 farklı fotoğrafına ulaşabildim. Sayının azlığına rağmen sınıflandırma değerleri fena değil.

Projemizde 4 ayrı betiğimiz var:
1- Dosyadaki linklerin okunup, ilgili imaj dosyalarının internetten indirilip kaydedilmesi
2- Kaydedilen dosyaların denetlenerek, içeriği aynı olan dosyaların teke düşürülmesi
3- Veri setinin eğitilmesi
4- Eğitilmiş veri seti bilgilerini kullanarak örnek dosyaların sınıflandırılması

VERİ SETİMİZİ OLUŞTURMAK

Sanatçılarımız: Türkan Şoray, Sadri Alışık ve Cüneyt Arkın. Amacım sadece çalışma yöntemini örneklemek olduğu için sayıyı daha fazla arttırmadım. Belki ileride, bu veri setini genişletip çok daha zengin ve işlevsel bir projeye dönüştürmek mümkün olabilir.

1. ADIM – VERİ SETİNİN OLUŞTURULMASI

İnternet üzerinden ulaştığım görsel linklerini, “linkler.txt” isimli bir metin dosyasına kaydettim. Bu dosyayı linkler.txt adresinden indirebilir veya tamamen kendiniz oluşturabilirsiniz.
Dosya yapısı şöyle:
sanatçı_kodu,link1
sanatçı_kodu,link2

Sanatçı kodları aynı zamanda data klasörünün içindeki klasörlerin de adlarıdır. O nedenle kodlarda Türkçe karakter kullanmadım. Kodlar ve klasör isimleri şu şekildedir:
c_arkin
s_alisik
t_soray

Betik kodlarını kısa tutmak için data klasörünü ve onun altındaki klasörleri kendim oluşturdum.

Çalışma klasörümüzün adı “yesilcam”. Alt dizin yapısı da şu şekilde:
yesilcam
|–data
| |–c_arkin
| |–s_alisik
| |–t_soray
|–kontrol
|–__pycache__

Önce ilk betiğimizi (resim_indir.py) inceleyelim:

skimage kütüphanesi, imaj dosyalarımızı doğrudan internet üzerinden okuyabilmek için gerekli. Ancak bu kütüphane RGB renk sistemini kullanıyor. Bu yüzden 22 nolu satır ile renk sistemini OpenCV’nin kullandığı BGR sistemine çeviriyoruz.

Linkleri tutan dosyamızın adı “linkler.txt”.

Sorunsuz okunan dosya sayısını tutmak için ok, sorunluları saymak içinse hata değişkenlerini kullanıyoruz.

kac sözlük değişkeni, her sanatçı için dosya isimlerini ayrı ayrı sayılaştırmamızı sağlıyor.

Satırları tek tek okuyoruz. # ile başlayan veya boyu 3 karakterden kısa olan satırları atlıyoruz.

İşlenecek satırları “,” ile parçalıyoruz. İlk parçada sanatçı kodu bulunuyor. Normalde ikinci parçanın link adresini tutması lazım. Ancak, sayıları az olsa da, bazı link adreslerinde “,” işaretinin kullanıldığına rastlayabiliriz. Bu nedenle -eğer varsa- 3. parçayı 2. parçaya ekleyerek doğru adresi elde ediyoruz.

17. satırda başlattığımız try bloğu, hatalı okumalardan kaçınmamızı sağlıyor.

kim değişkeni sanatçı kodunu, url ise imaj adresini saklıyor.

kac anahtarları içinde kim stringi yoksa, ilk kez tanımlanacak demektir. Başlangıç değeri olarak 1 atıyoruz.

21. satırda dosyamızı internet üzerinden okuyor; 2. satırda renk sistemini BGR haline çeviriyor ve 23. satırda okuduğumuz imajı görselleştiriyoruz.

24. satırdaki tuş kontrolü, gerektiğinde, Esc tuşuna basarak döngüyü kırabilmemizi sağlıyor. Bu satır aynı zamanda görselleştirme işlemleri için de gerekli.

27-36 satırlarını kullanarak dosyayı data klasörü altındaki sanatçı klasörüne (kim) kaydediyoruz. Dosya isimlendirmesinde bir çakışma olması ihtimalini dikkate alıyor, ama bu sayının 100’den fazla olmasına izin vermiyoruz. (Dikkat! Eğer kendi veri setinizi oluşturacaksanız, kullanacağınız link sayısını dikkate almak için bu sınırı değiştirmeniz gerekebilir.)

41. satırda son işlem olarak bilgisayar belleğini temizliyoruz.

İmajlar farklı boyutlarda olduğu için işlenmeleri farklı süreler gerektirebiliyor.

(Not: Çalışmalarımı Pycharm üzerinde yapıyor ve çalıştırıyorum. Ancak bu betiği terminal üzerinden çalıştırmak zorunda kaldım. Aynı durum sizin başınıza da gelebilir. Betiği çalıştırmak için Pycharm’ın terminal modunu kullanabilirsiniz.)

2. ADIM – İMAJ TEKRARLARININ ÖNLENMESİ

Bu betiğimizde doğrudan md5 hesaplamak yerine imagehash kütüphanesindeki dhash() metodunu uyguluyoruz. Bu metod hash hesabını imajları belli bir boyuta indirip gri renge çevirdikten sonra yapıyor. Yeniden boyutlandırma biraz zaman alıyor olsa da sonuçlar daha güvenilir.

İkinci betiğimizde os, PIL ve imagehash kütüphanelerini kullanıyoruz.

hash() fonksiyonumuzda okuma işlemini PIL.Image modülü ile gerçekleştiriyor ve elde edilen imaja imagehash.dhash() işlemini uyguluyoruz. (Doğrudan md5 hesabı yaptığımızda birbirine çok benzer bazı imajlar gözden kaçabiliyor.)

Tekrarlanan imaj dosyaları tekrar_sil() fonksiyonunun içinde temizleniyor.

19. satırda path değişkenimizle aktarılan klasör yolu bir döngü yapısı içinde kök, klasör ve dosya bileşenlerine ayrıştırılıyor ve her bir dosya için hash() değeri hesaplanıp, hash_dict sözlük değişkeninde saklanıyor. Eğer eşdeğer hash değeri (key) bulunursa, aynı anahtarın karşılığına mükerrer dosyanın adı ekleniyor. Böylece len(hash_dict[k]) değeri birden büyük olan kayıtlar, mükerrer dosyaları tutuyor.

25-30 satırları mükerrer dosyaların silinmesini sağlıyor.

34. satırda data klasörünü işleme sokuyoruz. Böylece onun altındaki tüm klasör ve dosyaları taramış oluyoruz.

Sizi daha fazla yormamak ve veri setini hazırlamanız zaman tanımak için bu yazıyı burada kesiyorum. Bir sonraki yazımda, oluşturduğumuz veri setini önce eğiteceğiz, sonra da eğitim setinde yer almayan kontrol görsellerinin kime ait olduğunu sorgulayacağız.

Beni izlemeye devam edin.

Ahmet Aksoy

Referanslar:

https://www.pyimagesearch.com/

Ara 092018
 
15.327 kez okundu

OpenCV ile Yüz Tanıma Bölüm 2

OpenCV Yüz Tanıma 2

Bu bölümde 3 ayrı betik kullanıyoruz:
1- veri toplama: 01_face_dataset.py
2- eğitim: 02_face_training.py
3- uygulama: 03_face_recognition.py

Orijinal kodlara https://www.hackster.io/mjrobot/real-time-face-recognition-an-end-to-end-project-a10826 adresinden ulaşabilirsiniz.

Ben veri kümesini yeniden oluşturdum. Türkçe karakterlerin görüntülenebilmesi için gerekli düzenlemeyi yaptım ve bazı mesajları Türkçeleştirdim.

İlk betikte input() komutunu # ile devre dışı bıraktım. Yeni bir yüz tanıtırken face_id sayısını doğrudan tanımladım. Örneğimizde 4 yüz tanımlı. Her yüz için 30 kare dataset klasörüne kaydedildi. Siz de kendi verilerinizi kaydetmeli ve eğitmelisiniz.

İlk betiğimizde cv2 ve os kütüphanelerini kullanıyoruz.

Görüntüleri webcam üzerinden elde edeceğiz. Görüntülerimizin eni 640, yüksekliği 480 piksel olarak tanımlandı (satır 5 ve 6).

7 nolu satırda cepheden yüz sınıflandırıcısını tanımlıyoruz.

face_id yüz görüntülerini eğiteceğimiz kullanıcı numarasını belirtiyoruz. Eğer input() satırını kullanmak isterseniz 9.satırın başındaki # işaretini silip, 10. satırın başına # eklemelisiniz.

count değişkenini görüntü sayısını takip etmek için kullanacağız.

Kayıt işlemlerimiz 15. satırdaki sonsuz while döngüsüyle başlıyor.

16. satırda kamera görüntüsünü alıyoruz. ret değişkeni işlevsizdir. Bu değişken video dosyasından okuma yaparken işimize yarıyor.

Görüntü karesini griye çevirip içindeki yüzleri saptıyoruz. (Satır 18 ve 19.)

Aslında her karede sadece 1 kişiye ait tek bir yüz bulunmalı. Yine de burada bir döngü kullanıyoruz. Aslında döngüdeki enbüyük dikdörtgeni seçsek daha doğru olurdu. Ama kodu gereksiz yere uzatmamak için bu tür ayrıntılara girmiyoruz.

Yakalanan her yüz için count değerini 1 arttırıyor (satır 22) ve yüz görüntüsünü dataset klasörüne kaydediyoruz. Kaydedilen görüntünün boyutları yüzün boyutları ile aynı. Yani webcam tarafından yakalanan görüntünün tümünü kullanmıyoruz. (Satır 24)

25.satırda kamera görüntüsünü (tamamını) ekrana yansıtıyoruz.

k değişkeni ile 100 milisaniye içinde bir tuşa basılıp basılmadığını kontrol ediyoruz.

Eğer ‘Esc’ tuşuna basıldıysa (sayısal karşılığı 27 olan tuş) döngüyü kırıyor, basılmadıysa bu kez count değerinin 30’u aşıp aşmadığını denetliyoruz. Sayı 30’u aştıysa döngüyü yine kırıyoruz. Çünkü her kişi için 30 görüntü kaydediyoruz.

Son iki satırda kamerayı serbest bırakıyor ve belleği temizliyoruz.

“dataset” klasörünün içine bakarsanız, verdiğiniz kullanıcı numarasına karşılık gelen 30 adet resim göreceksiniz.

Kullanıcı sayısını (yüz görüntülerini kaydedeceğiniz kişi sayısını) istediğiniz kadar arttırabilirsiniz. (Elbette en az bir kişi olmalı!)

Verilerimizi tamamladığımızda işimiz bitmiyor. Şimdi bu veri kümesini eğitmemiz lazım. İkinci betiğimizin işlevi bu!

İkinci -eğitim- betiğimizde opencv ve os yanısıra numpy ve pillow kütüphanelerini kullanıyoruz.

Veri klasörümüzün adı ‘dataset’. Bu bilgiyi path değişkeninde tutuyoruz.

Veri kümemizin eğitimini cv2.face.LBPHFaceRecognizer_create() komutuyla oluşturduğumuz recognizer (satır 7) üzerinden yapacağız.

Görüntülerdeki yüz alanlarını ayırmak için yine ‘haarcascade_frontalface_default.xml’ filtresinden yararlanacağız. Filtre değişkenimizin adı: detector. (satır 8)

Görüntü etiketlerimizi almak için getImagesAndLabels() fonksiyonunu kullanıyoruz.

ImagePaths listesi, path klasörü içinde yer alan dosyaların listesidir.

faceSamples listesi yüz görüntülerini, ids listesi ise her bir görüntünün etiketini tutacak.

imagePaths listesindeki her bir elemanı (dosya adını) kullanarak ilgili resmi okuyup griye dönüştürüyor ve PIL_img değişkenine atıyoruz (satır 15). Sonra bu görüntüleri 8 bitlik pozitif tamsayılardan oluşan bir numpy dizisi (array) haline dönüştürüyoruz (satır 16).

17 numaralı satırda, dosya adından kişi numarasını ayırıp, bir tam sayı olarak id değişkenine atıyoruz.

img_numpy görüntü numpy dizisinde bulunan yüz alanlarını bulup faces listesine atıyoruz. Yüzleri içeren listeyi döngüye sokup her bir görüntüyü faceSamples, ona karşılık gelen etiketi ise ids listesine ekliyoruz.

Fonksiyonumuz faceSamples ve ids listelerini döndürüyor.

24 numaralı satırda faces ve ids listeleri oluşturuluyor.

25 nolu satırda ise veri kümemizi eğitiyoruz.

Eğitilmiş veriseti bilgileri 27. satırda trainer klasörü içindeki trainer.yml dosyasına kaydediliyor. Bu dosyayı son betiğimizdeki görüntüleri sınıflandırırken kullanacağız.

Son betiğimiz şöyle:

Kamera görüntülerindeki yüzleri yakalayıp kime ait olduklarını belirten betiğimiz bu. Kullanacağımız kütüphaneler opencv, numpy ve pillow.

print_utf8_text() fonksiyonunu bir önceki yazımda anlatmıştım. Türkçe karakterleri bu fonksiyon aracılığıyla bastırıyoruz.

Yüz tanıma nesnemiz recognizer‘a trainer.yml dosyası aracılığıyla eğitilmiş verisetimizi yüklüyoruz (satır 18 ve 19).

Canlı kamera görüntülerindeki yüzleri de yine haarcascade_frontalface_default.xml filtresi aracılığıyla yakalayacağız. Filtre değişkenimiz faceCascade (satır 20 ve 21).

22 nolu satırda orijinal betikte kullanılan font tanımlanıyor. Biz bu fontu da kullanacağız.

Kişi numaraları id değikeninde tutulacak. (Satır 24)

Etiketleri (isimleri) ise names listesiyle tanımlıyoruz.

Web kamerasının görüntüsünü cam değişkenine aktarıyoruz ve görüntü boyutlarını 1000 ve 800 piksel olarak ayarlıyoruz. (Bu değerleri 640 ve 480 olarak da bırakabilirdik.)

minW ve minH değişkenleri yakalanan yüz dikdörtgenlerinin boyutu için alt sınır değerleridir (satır 33 ve 34). Bu değerleri kendi ortamınıza göre değiştirebilirsiniz.

35 nolu satırda sonsuz döngümüz başlıyor.

36. satırda kamera görüntüsünü okuyor, 38. satırda bu görüntüyü grileştiriyoruz.

40 nolu satırda görüntü karesindeki tüm yüzleri yakalıyor; 46. satırda ise bu yüz dikdörtgenlerini bir döngü içinde işleme sokuyoruz.

recognizer.predict() metodu id ve confidence değerlerini döndürüyor. id kişi numarası; confidence ise yapılan saptamanın tahmini doğruluk oranıdır (satır 48).

id ve confidence değişkenlerini print için uygun hale getiriyoruz (satır 50-55).

58 nolu satırda kişi ismini UTF-8 kullanarak (Türkçe karakterleri doğru şekilde yansıtarak), 60 nolu satırda ise belirtilen kişinin tahmini doğruluk oranını standart font ile görüntüye işliyoruz.

Görüntünün ekrana yansıtılması 62 nolu satırda.

Döngümüz yine Esc veya q tuşlarından birine basıldığında kırılıyor.

Döngü kırıldığında web kamerayı sonlandıyor ve belleği temizliyoruz (satır 68-69).

Bu betiğin çalışmasını gösteren örnek video aşağıda.

Bu yazı, açıklamaları olabildiğince kısa tutsam da biraz uzunca oldu. Umarım yeterince açıklayıcıdır. Sorularınız için yorum alanını kullanabilirsiniz.

Bir sonraki yazımda yine yüz tanıma olacak. Ancak veri kümemizi oluşturmak ve eğitmek için farklı yöntemler kullanacağız.

Beni izlemeye devam edin.

Ahmet Aksoy

Ara 062018
 
12.640 kez okundu

OpenCV ile Yüz Tanıma Bölüm 1

OpenCV ile Yüz Tanıma Bölüm 1

Yüz tanıma biraz geniş kapsamlı bir konu. Öncelikle incelediğimiz görselde bulunan olası “yüz”leri, daha sonra da gerekiyorsa bu yüzlerin “kim”e ait olduğunu saptamak sözkonusu. Ya da yüzlerin üzerindeki yüz, göz, kaş, burun, kulak gibi elemanların (facial features) belirlenmesi… O yüzden bu konuyu tek bir yazıyla değil, ardışık birkaç yazıyla ele alacağım.

İlk bölümde bir fotoğraf karesinde kaç adet “yüz” bulunduğunu saptayacağız.

Çeşitli beden parçalarını filtre etmek amacıyla kullanılan en yaygın araçlardan biri “haar cascade” sınıflandırıcılarıdır. Yazının sonunda bu “.xml” uzantılı dosyalara ulaşabileceğiniz çeşitli linkler bulacaksınız. Hatta isterseniz, kendi sınıflandırıcılarınızı bile eğitebilirsiniz. (Ben şimdilik bu maceraya kalkışmıyorum. Elde yeterince hazır kaynak var.)

Ele alacağımız “face_count.py” betiğindeki temel filtremiz “haarcascade_frontalface_default.xml”. Bu filtre genellikle çok verimli çalışıyor. Ancak imaj boyutları yeterince büyük değilse bazı hatalı (false positive) sonuçlar döndürebiliyor. Bu sorunu kısmen de olsa aşabilmek için “it_contains_eyes()” isimli bir fonksiyon tanımladım. Bu fonksiyon saptanan yüz alanının içinde göz, burun veya dudak olup olmadığını araştırıyor. Bulamazsa, yüz bulgusunun hatalı olduğunu geriye “False” döndürerek bildiriyor.

Ben 3 ayrı fotoğrafı döngü içinde test ettirdim. Siz de kendi istediğiniz fotoğrafları kullanabilirsiniz.

Orijinal koda ilave ettiğim bir değişiklik de UTF-8 fontlarını destekleyen Pillow kütüphanesini kullanarak hazırladığım print_utf8_text() fonksiyonudur. Böylece Türkçe karakterleri bastırmak da mümkün. OpenCV’nin putText() fonksiyonu sadece ASCII fontları destekliyor. Bu nedenle Türkçe karakterler basılamıyor.

Kodların içinde İngilizce açıklamalar kullandım. Çünkü bu seri tamamlandığında github üzerinden ve İngilizce olarak paylaşmayı düşünüyorum. İngilizce bilmeyen arkadaşlar zaten buradaki yazılardan aynı açıklamaları fazlasıyla edinebilecekler.

Gelelim kodlarımızın açıklamalarına:

Betiğimizde numpy ve cv2 yanında PIL kütüphanesini de kullanacağız. Böylece UTF-8 destekli FreeType fontları üzerinden Türkçe karakterli mesajları da basabileceğiz.

“haarcascade” dosyalarını “Cascades” isimli klasörün altında topladım. Bu betikte 4 farklı sınıflandırıcı kullanacağız. Sırasıyla yüz (cepheden), göz, gülümseme (dudak) ve burun.

“nocheck” değişkeni True olursa, fazladan göz, dudak ve burun kontrolü yapılmaması için it_contains_eyes() fonksiyonu işlem yapmadan True döndürerek devre dışı kalacaktır. Bu değer False olursa, ek kontrollerin yapılmasına izin veriliyor demektir.

OpenCV string basımı için putText() metodunu kullanıyor. Bu metodun kullanabildiği HERSHEY fontları sadece ASCII karakterleri destekliyor. Eğer sadece İngilizce veya ASCII karakterler içeren mesajlar kullanacaksanız print_text() fonksiyonu size yetecektir.

OpenCV doğrudan UTF-8 desteklemediği için Pillow kütüphanesini devreye sokarak FreeType fontlarını kullanabiliyoruz.
Örnekte ‘FreeSerif.ttf’ fontunu kullandım. Siz bir başka fontu tercih edebilirsiniz.
ImageFont.truetype() metodu ile font adını ve boyutunu belirliyoruz.
OpenCV ve Pillow farklı imaj sistemleri kullanıyor. Image.fromarray() metodunu kullanarak OpenCV formatlı imajı Pillow sistemine uygun biçime dönüştürüyoruz. Sonra da bu imajı bir Draw() nesnesi haline çevirip, text mesajımızı da bu nesnenin draw.text() metoduyla görüntünün üzerine işliyoruz.
Son aşamada işlenmiş görüntüyü yine bir numpy.array() biçimine dönüştürerek OpenCV’nin kullandığı formata aktarıyoruz. Fonksiyonumuz, üzerine text mesajı işlenmiş görüntüyü geri döndürüyor.

Eğer no_check değişkenimiz True değerine sahipse, it_contains_eys() fonksiyonumuz başka bir işlem yapmadan True değeri döndürür. Böylece ek kontrol yapılmaz.
Aksi halde önce roi_gray tanımlanır. Bu, “yüz” olarak tesbit edilen dikdörtgen yüzeydir.
İlk kontrol olarak bu alanın içindeki olası “göz”ler araştırılır. Parametreler deneyseldir. Çalışmanız için en uygun değerleri deneyerek bulmanız gerekebilir.
41-45 satırları arasında göz sayısını buluyor, bu sayı sıfırdan büyükse True değeri döndürerek geri dönüyoruz. Aynı satırları if len(eyes)>0: return True olarak kısaltmak da mümkün.
Diğer smiles (gülümseme-dudak) ve noses (burun) kontrolleri de aynı mantıkla çalışıyor.
Bu fonksiyonun temel amacı şudur: Bir yüzün içinde en azından 2 göz, bir dudak veya bir burun olmalıdır. Eğer hiçbiri yoksa, yüz hatalı saptanmıştır.

Ben bu betikte 3 ayrı imaj kullandım. İlk başta scaleFactor=1.5, minNeighbors=5, minSize(20,20) kullandım. Daha sonra deneme-yanılma yöntemiyle yukarıdaki değerlere ulaştım. Siz de deneyin.
Döngü içinde isimlerini verdiğim imaj dosyaları sırayla okunup image değişkenine atanıyor. Buradan da görüntünün gri bir kopyası oluşturuluyor. Gri görüntüler üzerinde sayısal işlem yapmak çok daha hızlı sonuçlar veriyor.
faceCascade.detectMultiScale(grayImage) metodu bize imaj içinde bulduğu yüz koordinatlarını veriyor (sol üst köşe x, sol üst köşe y, genişlik ve yükseklik). Konsol ekranına type(faces) değerini bastırıyoruz.

Eğer faces içinde hiç bilgi yoksa, görüntü üzerinde hiç yüz bulunamadı demektir. Bunu “Yüz bulunamadı” mesajıyla raporluyoruz.
Eğer faces içinde yüz koordinatları mevcutsa, -bilgi için- bulunan koordinatlar ile eleman boyutunu ve bu bilgiyi kullanarak ek filtre uygulanmamış yüz sayısını konsol ekranına bastırıyoruz.
Bulunan dikdörtgenlere it_contains_eyes() fonksiyonu ile ek denetimleri uyguluyoruz. Denetim atlanıyorsa veya aranan ek bilgiler bulunduysa o dikdörtgenin kenarlarını görüntünün üzerine çiziyor ve bulunan yüz sayısını tutan count değişkeninin değerini bir arttırıyoruz.
Tüm yüzler tarandıktan sonra “Belirlenen yüz sayısı: ” mesajımızın kolay görünebilir olması için altına beyaz bir zemin bastırıyoruz. (satır 88)
90-92 satırlarında bulunan yüz sayısını ifade eden mesajımız görüntünün üzerine işleniyor ve 94 numaralı satırda sonucu görüntülüyoruz.
Eğer UTF-8 yerine sadece ASCII fontlar kullanacaksanız 89 nolu satırı aktif hale getirip 90-92 satırlarını devre dışı bırakmanız yeterlidir.
Döngünün devam etmesi için herhangi bir tuşa basmamız gerek.
Tuşa bastığımızda ortalığı temizliyor ve varsa bir sonraki imaja geçiyoruz. Yeni imaj yoksa döngüyü ve betiği sonlandırıyoruz.

Tüm kodlar bir bütün olarak aşağıda:

Betiğin çalışmasını aşağıdaki videodan izleyebilirsiniz:

Bir sonraki yazımda kişi yüzlerini ismen tanıyan bir sistemi nasıl eğiteceğimizi ele alacağım.

Ahmet Aksoy

https://techtutorialsx.com/2017/05/02/python-opencv-face-detection-and-counting/
https://github.com/voidstellar/haar-cascade-files
https://github.com/opencv/opencv/tree/master/data/haarcascades
http://alereimondo.no-ip.org/OpenCV/34
https://www.instructables.com/id/Create-OpenCV-Image-Classifiers-Using-Python/
https://medium.com/@a5730051/train-dataset-to-xml-file-for-cascade-classifier-opencv-43a692b74bfe

Ara 032018
 
7.077 kez okundu

OpenCV ile Portakalın Peşinde

OpenCV ile Portakalın Peşinde

Bu yazımda bir önceki yazımda ele aldığım kodlardan yararlanacak, sadece bir kaç küçük değişiklik yapacağım.
Bu kez çember içine alarak işaretlediğimiz nesnenin (portakalın) merkez noktasını izlenebilir hale getireceğiz. İzlemeyi, en son ardışık 50 merkez noktasını çizgilerle birbirine birleştirerek yapacağız.

Bu betikte 2 kütüphaneye daha gereksinim var: collections ve numpy.

collections kütüphanesinin deque sınıfı her iki ucundan eleman eklenebilen ve önceden belirtilmişse maksimum eleman saısı sabit kalan nesneler üretir. Numpy kütüphanesini ise izleme çizgilerinin kalınlığını hesaplarken karekök almada kullanacağız. Onun yerine math kütüphanesini de kullanabilirdik ama, bu tür çalışmalarda numpy‘yi el altında tutmamızda büyük yarar var.

Bu kez kaynak kodları bir bütün halinde vereceğim. Sadece önceki örnekle karşılaştırdığımızda değişen satırları açıklamam yeterli olacak.

Kodlar aşağıda. Açıklamaları da onun altına ekledim.

3 ve 4 numaralı satırlarımızda yeni kütüphaneleri içe aktarıyoruz.

7. satırdaki WIDTH değerini 800 yaptım. Çünkü bu kez sadece bir pencereyi görselleştireceğiz.

8. satırda NO_OF_POINTS = 50 satırını ekledim. Bu sabit, merkez noktalarından kaç tanesini izleyeceğimizi belirliyor. Sayıyı azaltırsak izleme çizgilerimiz daha kısa, çoğaltırsak daha uzun olacaktır.

9. satırdaki ONLY_MAX artık kullanılmayacak. Çünkü sadece tek bir nesneyi (en büyük olanı) izleyeceğiz.

36. satıra kadar bir değişiklik yok. Burada frame isimli çerçevenin görüntüsünü cv2.GaussianBlur() metoduyla yumuşatıyoruz. Bu sayede görüntülerdeki renk dağılımları çok keskin hatlar oluşturmuyor. Aslında bizim örneğimizde bu metodu kullanmasak da olur.

46. satırda center değişkenini tanımlıyoruz. Bu değişken her görüntü karesinde yakalanan en büyük çemberin merkez noktasını tutacak.

49. satırdaki cmax değişeni en büyük alana sahip kontur değerlerini tutuyor.

51. satırda cmax’ı içine alan en küçük çembere ait merkez ve yarıçap bilgilerini buluyoruz.

52 ve 53. satırlarda şeklimizin ağırlık merkezini hesaplatıyoruz. Aslında bunun yerine center = (int(x),int(y)) kullansak da pek bir şey değişmeyecektir. Deneyebilirsiniz.

57-63 arası satırlarda yeni merkez noktamızı pts’ye ilk eleman olarak ekliyoruz. Böylece tüm elemanlar birer sıra sağ kayıyor ve en sondaki eleman siliniyor.

62. satırdaki thickness hesabı, çizgi kalınlığının, konumuna bağlı olarak değişmesini sağlıyor. Olayı basitleştirmek istersek onun yerine thickness=3 gibi bir satır kullanabiliriz. Elbette bu durumda çizgilerimizin kalınlığı sabit olur.

Son değişikliğimiz, 67.satırı devre dışı bırakmak. Böylece mask çerçevesini görünmez yapıyoruz.

Videoda kodların çalışır halini görebilirsiniz.

Bu yazıyı daha fazla uzatmayacağım. Çünkü ana fikirde bir önceki örneğimize göre önemli bir değişiklik yok. Ama istersek eğlenmek için bazı değişklikler yapabiliriz.

Örneğin ben cizgi kalınlıklarını biraz abartıp, rengini de kırmızı yaptım. Aşağıdaki videoda bu değişikliği görebilirsiniz. Siz de pts (deque) eleman sayısını azaltabilir veya çoğaltabilir, çizgi renkleriyle oynayabilirsiniz. Ya da renk aralıklarını değiştirerek farklı nesneleri izleyebilir veya web kamera yerine bir video dosyası kullanabilirsiniz (elinizde uygun bir video varsa).

Bir sonraki yazımda yüz tanıma konusunu yeniden ele alacağım.

Beni izlemeye devam edin.

Ahmet Aksoy

Ara 022018
 
6.777 kez okundu

OpenCV ile Portakallar ve Elmalar

OpenCV ile Portakallar ve Elmalar
Bu yazıda işleyeceğim kodların orijinal halini yine PyImageSearch sitesinde bulabilirsiniz. “ball tracking pyimagesearch” sözcüklerini Google’da aratırsanız, konuyla ilgili sayfalara kolayca ulaşırsınız.

Orijinal kodlarda izlediğimiz yuvarlağın (renkli topun) merkez noktasına ait koordinatlardaki değişimler görsel olarak izlenebiliyor. Benim aşağıda vereceğim kodlarda ise merkez noktası yerine, belirlediğimiz renk aralığının grileştirilerek maskelenmiş izdüşümünü ikinci çerçeveden izleyeceğiz.

(Not: Bir sonraki yazımda merkez noktalarının izlenmesi konusunu da ele alacağım. Ama dilerseniz bu kodları geliştirmeyi şimdiden bir ödev olarak üstlenebilirsiniz.)

OpenCV ile nesneleri izlemede kullanılan en basit yöntem, HSV renk sistemine uygun bir renk aralığını kullanmaktır. Çünkü HSV renk sistemi bu tür bir çalışmaya RGB veya BGR renk sistemlerine kıyasla çok daha yatkındır. Eğer renk sistemleriyle ilgili daha çok ayrıntıya ulaşmak isterseniz, internetteki pek çok kaynak size yardımcı olacaktır.

HSV terimi (Hue Saturation Value) sözcüklerinin ilk harflerini birleştirerek oluşturulmuştur. Hue renk tonlarını, saturation doygunluk oranını ve value parlaklık düzeyini ifade eder. OpenCV kütüphanesinde hue değerleri 0-179, diğerleri 0-255 arası değerler alır.

Şimdi kodlarımızı incelemeye başlayalım:

Örnek kodumuz için sadece cv2 ve imutils kütüphanelerini kullanacağız.

Web kamera görüntülerini kullanmak istiyorsak video_file string değeri boş olmalıdır. Eğer buraya bir video dosyasının erişim adresini yazarsak, web kamera yerine o videonun görüntüleri kullanılır.

WIDTH sabiti, pencerelerimizin genişliğini belirler. İstediğimiz gibi değiştirebiliriz.

ONLY_MAX sabiti False olarak tanımlanırsa, bulunan tüm sınır çizgileri (kontur) dikkate alınır. Bu değer True olarak verilirse, sadece alanı en büyük olan nesnenin çevre çizgisi çizilir.

Renk aralıkları için farklı opsiyonlar tanımladım. Kodu çalıştırmadan önce istediğiniz aralığı seçebilirsiniz. Örneğin YELLOW_RANGE aralığını kullanırsanız alt HSV değerler (10,100,100), üst değerler ise (40,255,255) olur.

video_file değişkenimizin uzunluğu sıfıra eşitse, içinde bir dosya adı yok demektir. Bu durumda web kamerayı etkinleştiriyoruz. İlk web kameranın karşılığı “0”dır. İkinci bir kamera tanımlarsanız, değeri “1” olur.

Örneğimizde yanyana 2 farklı pencere açacağız: frame ve mask. “mask” penceresi, “frame” penceresinin tam sağında yer alacak. Eğer bu yerleştirmeyi yapmazsak, pencereler üst üste açılacak; ikisini birden izleyebilmek için fare ile yerleşimi düzenlemek zorunda kalacaktık. (Not: cv2.moveWindow() parametreleri için kendi bilgisayarınızda biraz farklı değerler vermeniz gerekebilir.)

Ön tanımlarımızı yaptıktan ve pencere yerleşimlerini belirledikten sonra asıl işlemlerin içinde yer aldığı sonsuz döngüyü başlatıyoruz.

kamera.read() komutu, web kamera veya video dosyasından hangisini belirlediysek oradan bir çerçeveyi okuyor. Eğer ok değeri False ise ve dosya adı tanımlanmış ise okunacak çerçeve kalmadı demektir. Bu yüzden break komutuyla döngüyü sonlandırırız. Aksi halde işlenmesi gereken bir çerçeve okundu demektir.

Çerçeve boyutlarımızı WIDTH sabitine göre yeniden düzenledikten sonra frame isimli çerçevemizi HSV renk sistemine göre dönüştürüp, hsv isimli değişkene atarız. “frame” isimli çerçevemizin renk sistemi BGR’dir.

hsv isimli çerçeveden colorLower ve colorUpper renk değerlerine göre filtrelenen yeni çerçeveyi bu kez mask değişkenine atarız. Bu çerçeveye önce erode(), sonra da dilate() metodlarını uyguladıktan sonra bir de kopyasını alıp, çerçeve içinde yer alan nesne kenar çizgilerini (konturları) belirlemede kullanmak üzere mask_copy değişkenine atarız. (Not: erode() ve dilate() metodlarını kullanmadan önce mask çerçevemize GaussianBlur() metodu uygulayarak görüntüyü yumuşatmak daha güzel bir görsel etki yaratabilir. Bir sonraki yazımda bu metodu da örnekleyeceğim.)

contours listesi çerçevemizde yer alan tüm konturları içerir.

Eğer kontur listesi boş değilse, hepsini tek tek işleme sokarız.

ONLY_MAX sabitimiz True olarak tanımlandıysa her çerçevede sadece bir tek (en büyük) kontur belirlenir. Aksi halde yarıçapı 40 pikselden büyük olan tüm çemberleri kullanacağız demektir.

Konturlarımızı circle() metoduyla frame isimli çerçevemize işledikten sonra hem orijinal frame isimli çerçevemizi, hem de mask çerçevemizi görüntüleriz.

waitKey() metodumuz 4 milisaniyelik bir süre içinde basılacak tuşları dinler. Eğer ‘q’ veya ‘Esc’ tuşlarından birine basılmışsa, döngünün kırılmasını sağlar.

Döngü kırıldığında kamera nesnesini serbest bırakır ve açılmış tüm pencereleri kapatarak belleği temizleriz.

Örnek kodumuzun tamamı aşağıda:

Videoda kodların nasıl çalıştığı örnekleniyor. Tenis topu yerine elma ve portakal kullandım. Siz de isterseniz sarı renkler içeren başka nesneler kullanabilirsiniz.

mask çerçevesini görselleştirmek yerine sadece asıl çerçevedeki alanın veya alanların çember ile işaretlenmesini kullanabilir; ya da yakaladığımız nesnenin merkez koordinatlarındaki değişimi izlenebilir hale getirebiliriz.

Bu işlem için kendiniz doğrudan uğraşabilir ya da PyImageSearch sitesindeki örnekten veya benzer başka örneklerden yararlanabilirsiniz. (Bir sonraki yazımda bu konuyu ele alacağım.)

Yeni yazılarımdan anında haberdar olmak isterseniz sitemize abone olun.

Ahmet Aksoy