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