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:
1 2 3 4 5 6 7 8 |
# original code from: https://techtutorialsx.com/2017/05/02/python-opencv-face-detection-and-counting/ # face_count.py # faces also checked for the existence of eyes, lips and nose # Instead of ascii fonts, utf-8 fonts are used import numpy as np import cv2 from PIL import Image, ImageDraw, ImageFont |
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.
9 10 11 12 13 14 |
faceCascade = cv2.CascadeClassifier('Cascades/haarcascade_frontalface_default.xml') eyeCascade = cv2.CascadeClassifier('Cascades/haarcascade_eye.xml') smileCascade = cv2.CascadeClassifier('Cascades/haarcascade_smile.xml') noseCascade = cv2.CascadeClassifier('Cascades/haarcascade_mcs_nose.xml') no_check = False |
“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.
16 17 18 |
def print_text(image): # only english characters cv2.putText(image, "Belirlenen yüz sayısı: " + str(faces.shape[0]), (0, image.shape[0] - 10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1) |
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.
20 21 22 23 24 25 26 27 28 |
def print_utf8_text(image, text, color): # utf-8 characters fontName = 'FreeSerif.ttf' # 'FreeSansBold.ttf' # 'FreeMono.ttf' 'FreeSerifBold.ttf' font = ImageFont.truetype(fontName, 18) # select font img_pil = Image.fromarray(image) # convert image to pillow mode draw = ImageDraw.Draw(img_pil) # prepare image draw.text((0, image.shape[0] - 30), text, font=font, fill=(color[0], color[1], color[2], 0)) # b,g,r,a image = np.array(img_pil) # convert image to cv2 mode (numpy.array()) return image |
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.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# Check whether face contains any of eyes, lips and nose def it_contains_eyes(frame, gray, x, y, w, h): if no_check: return True roi_gray = gray[y:y + h, x:x + w] eyes = eyeCascade.detectMultiScale( roi_gray, scaleFactor=1.05, minNeighbors=3, minSize=(20, 20) ) count = 0 for (ex, ey, ew, eh) in eyes: count += 1 if count > 1: return True smiles = smileCascade.detectMultiScale( roi_gray, scaleFactor=1.5, minNeighbors=5, minSize=(3, 3) ) if len(smiles) > 0: return True noses = noseCascade.detectMultiScale( roi_gray, scaleFactor=1.5, minNeighbors=5, minSize=(3, 3) ) if len(noses) > 0: return True return False |
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.
65 66 67 68 69 70 71 72 73 |
imageNames = ['toplu.jpg', 'maviboncuk.jpg', 'mt_aday.jpg'] for imageName in imageNames: image = cv2.imread(imageName) grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = faceCascade.detectMultiScale(grayImage) print(type(faces)) |
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.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
if len(faces) == 0: print("Yüz bulunamadı") else: print(faces) print(faces.shape) print("Belirlenen yüz sayısı: " + str(faces.shape[0])) say = 0 for (x, y, w, h) in faces: if it_contains_eyes(image, grayImage, x, y, w, h): say += 1 cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 1) cv2.rectangle(image, ((0, image.shape[0] - 25)), (270, image.shape[0]), (255, 255, 255), -1) # print_text() text = "Belirlenen yüz sayısı: " + str(say) # , (0, image.shape[0] - 10) color = (0, 0, 0) image = print_utf8_text(image, text, color) cv2.imshow('Imaj', image) cv2.waitKey(0) cv2.destroyAllWindows() |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# original code from: https://techtutorialsx.com/2017/05/02/python-opencv-face-detection-and-counting/ # face_count.py # faces also checked for the existence of eyes, lips and nose # Instead of ascii fonts, utf-8 fonts are used import numpy as np import cv2 from PIL import Image, ImageDraw, ImageFont faceCascade = cv2.CascadeClassifier('Cascades/haarcascade_frontalface_default.xml') eyeCascade = cv2.CascadeClassifier('Cascades/haarcascade_eye.xml') smileCascade = cv2.CascadeClassifier('Cascades/haarcascade_smile.xml') noseCascade = cv2.CascadeClassifier('Cascades/haarcascade_mcs_nose.xml') no_check = False def print_text(image): # only english characters cv2.putText(image, "Belirlenen yüz sayısı: " + str(faces.shape[0]), (0, image.shape[0] - 10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 0, 0), 1) def print_utf8_text(image, text, color): # utf-8 characters fontName = 'FreeSerif.ttf' # 'FreeSansBold.ttf' # 'FreeMono.ttf' 'FreeSerifBold.ttf' font = ImageFont.truetype(fontName, 18) # select font img_pil = Image.fromarray(image) # convert image to pillow mode draw = ImageDraw.Draw(img_pil) # prepare image draw.text((0, image.shape[0] - 30), text, font=font, fill=(color[0], color[1], color[2], 0)) # b,g,r,a image = np.array(img_pil) # convert image to cv2 mode (numpy.array()) return image # Check whether face contains any of eyes, lips and nose def it_contains_eyes(frame, gray, x, y, w, h): if no_check: return True roi_gray = gray[y:y + h, x:x + w] eyes = eyeCascade.detectMultiScale( roi_gray, scaleFactor=1.05, minNeighbors=3, minSize=(20, 20) ) count = 0 for (ex, ey, ew, eh) in eyes: count += 1 if count > 1: return True smiles = smileCascade.detectMultiScale( roi_gray, scaleFactor=1.5, minNeighbors=5, minSize=(3, 3) ) if len(smiles) > 0: return True noses = noseCascade.detectMultiScale( roi_gray, scaleFactor=1.5, minNeighbors=5, minSize=(3, 3) ) if len(noses) > 0: return True return False imageNames = ['toplu.jpg', 'maviboncuk.jpg', 'mt_aday.jpg'] for imageName in imageNames: image = cv2.imread(imageName) grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = faceCascade.detectMultiScale(grayImage) print(type(faces)) if len(faces) == 0: print("Yüz bulunamadı") else: print(faces) print(faces.shape) print("Belirlenen yüz sayısı: " + str(faces.shape[0])) say = 0 for (x, y, w, h) in faces: if it_contains_eyes(image, grayImage, x, y, w, h): say += 1 cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 1) cv2.rectangle(image, ((0, image.shape[0] - 25)), (270, image.shape[0]), (255, 255, 255), -1) # print_text() text = "Belirlenen yüz sayısı: " + str(say) # , (0, image.shape[0] - 10) color = (0, 0, 0) image = print_utf8_text(image, text, color) cv2.imshow('Imaj', image) cv2.waitKey(0) cv2.destroyAllWindows() |
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
Merhaba ahmet hocam, öncelikle makalelerinizi her zaman heyecanla okuyorum ve uyguluyorum.
bu denemem de şu tarz bir hata aldım.
Output :
$ python3.7 count_face.py
[[378 252 48 48]
[840 251 56 56]
[479 356 39 39]
[526 46 60 60]
[177 139 41 41]
[204 117 54 54]
[697 173 30 30]
[436 192 56 56]
[253 209 41 41]]
(9, 4)
Belirlenen yüz sayısı: 9
Traceback (most recent call last):
File “count_face.py”, line 80, in
if it_contains_eyes(image, grayImage, x, y, w, h):
File “count_face.py”, line 55, in it_contains_eyes
minSize=(3, 3)
cv2.error: OpenCV(4.0.1) /tmp/opencv-20190105-31032-o160to/opencv-4.0.1/modules/objdetect/src/cascadedetect.cpp:1658: error: (-215:Assertion failed) !empty() in function ‘detectMultiScale’
tam olarak hatayı ve çözümü anlayamadım. smile, eyes, noses alanlarında ki detectMultiScale değerlerini de deneyerek gittim ama sonuç aynı.
şimdiden teşekkürler.
Merhaba,
Anladığım kadarıyla OpenCV 4.0.1 kullanıyorsunuz. Ben test ettiğim kodlarda OpenCV 3.4.3 veya OpenCV 3.4.4 kullandım. Sürüm farkı nedeniyle böyle bir sonuç alıyor olabilirsiniz.
Ayrıca sizin hata mesajı aldığınız 80 nolu satır, orijinal kodlarda 85 nolu satır. Acaba kodlarda atlanmış bir şey olabilir mi?
Kodları çalıştırırken kullandığınız resmi bana ilettiğiniz takdirde deneyip, bende de hata verip vermediğini görebiliriz. En azından sürüm farklılığı olasılığını elemiş oluruz.
Evet, opencv 4.0.1 python 3.7 kullanıyorum. satır farklılığı yorum satırlarını dahil etmediğimden kaynaklı. yakın zaman da belirttiğiniz sürüm ile deneyeceğim.
Kullandığım kod : paste.ofcode(.)org/UZBdkAvQdPiKCLhXJxpD3B
Kodda bir sorun yok. Bu durumda ya OpenCV 4.0.1 ile veya kullandığınız resimlerle ilgili bir uyumsuzluk olabilir.
Resimlerin sırasını değiştirdiğinizde de sorun devam ediyorsa, geriye sürüm uyumsuzluğu kalacak.
—————————————————————————
error Traceback (most recent call last)
in ()
72 cv2.imshow(‘image’, grayImage)
73
—> 74 faces = faceCascade.detectMultiScale(grayImage)
75
76 print(type(faces))
error: OpenCV(4.1.0) C:\projects\opencv-python\opencv\modules\objdetect\src\cascadedetect.cpp:1658: error: (-215:Assertion failed) !empty() in function ‘cv::CascadeClassifier::detectMultiScale’
merhaba , kodu oldugu gibi çalıştırırken bu hatayı alıyoruz çözemedik yardımcı olur musunuz?
grayImage dosyası boş gibi görünüyor. İmaj dosyasının ve bağlantılı olarak grayImage’ın var olduğundan emin olun.
cv2.error: OpenCV(4.1.0) C:\projects\opencv-python\opencv\modules\core\src\alloc.cpp:55: error: (-4:Insufficient memory) Failed to allocate 1035772416 bytes in function ‘cv::OutOfMemoryError’
yüksek kalitede fotoğraflar yükleyince hafıza hatası alıyorum generator ve iterator kullanmama rağmen çözüme ulaşamadım yardımcı ola bilirmisiniz ?
Bellek yetersizliği mesajı almışsınız. Yüksek çözünürlüklü fotoğraf kullanmak pek bir işinize yaramaz. Çünkü eğitim sırasında bu fotoğraflar uygun boyuta küçültülür. Dolayısıyla yüksek çözünürlüklü fotoğraf kullanmak gerekli değildir.
Buna rağmen bellek yetersizliğinin bir başka nedeni olabilir. Yanlışlıkla aynı resmi tekrar tekrar yüklemek gibi bir durum oluşmadığından emin olun.
merhaba hocam kodu direk kopyalayıp yapıştırdım ama bu hatayı verdi .Araştırdım ama çözüm bulamadım.
line 72, in
faces = faceCascade.detectMultiScale(grayImage)
error: OpenCV(3.4.2) C:\Miniconda3\conda-bld\opencv-suite_1534379934306\work\modules\objdetect\src\cascadedetect.cpp:1698: error: (-215:Assertion failed) !empty() in function ‘cv::CascadeClassifier::detectMultiScale’