Wykrywanie twarzy real-time w 15 liniach kodu w Python

wykrywanie twarzy computer vision

– Tato, a dlaczego Ci ludzie mają zamazane twarze w telewizji?

– Hmm…ponieważ zapewne nie zgodzili się na udostępnienie wizerunku.

– A dlaczego?

– Bo może boją się kogoś. A może nie są tak ładni jak Wy – zaśmiałem się złowieszczo.

– Tatusiu, nie strasz mnie – powiedziała ze spokojem Jagódka.

– Ne staś – krzyknęła Otylka powtarzając za Jagódką

Hmm… a czy nie mając doświadczenia w Computer Vision można prosto samemu coś takiego zrobić? Z wcześniejszego postu wiemy już czym jest wizja komputerowa i jakie ma zastosowania. Spróbujmy troszeczkę wejść głębiej i napisać pierwszy program, którego zadaniem będzie wykrywanie twarzy. Najprostszym sposobem będzie wykorzystanie biblioteki OpenCV.

Uwaga. Jeśli interesuje Cię sam kod w Python i nic więcej, to tutaj masz link do pliku w GitHub Albo możesz rzucić okiem na koniec wpisu.

Czym jest OpenCV?

OpenCV (Open Computer Vision) to biblioteka typu open source. OpenCV został zbudowany, aby zapewnić wspólną infrastrukturę dla aplikacji do wizji komputerowej i przyspieszyć wykorzystanie postrzegania maszyn w produktach komercyjnych.

opencv logo
http://opencv.org

Biblioteka ma ponad 2500! zoptymalizowanych algorytmów, które obejmują kompleksowy zestaw zarówno klasycznych, jak i najnowocześniejszych algorytmów wizji komputerowej i uczenia maszynowego.

Algorytmy te mogą być używane do:

  • wykrywania i rozpoznawania twarzy,
  • identyfikowania obiektów,
  • klasyfikowania ludzkich działań w filmach,
  • śledzenia ruchów kamery,
  • śledzenia ruchomych obiektów,
  • itd. itp.

Instalacja pakietu jest bardzo skomplikowana :). Należy zainstalować go wpisując:

pip install opencv-python

i już!

Czym jest klasyfikator kaskadowy Haar’a?

Zaimplementujemy nasz przypadek użycia za pomocą klasyfikatora Haar Cascade.

Klasyfikator kaskadowy Haar jest skutecznym podejściem do wykrywania obiektów, zaproponowanym przez Paula Violę i Michaela Jonesa w ich artykule zatytułowanym „Szybkie wykrywanie obiektów za pomocą wzmocnionej kaskady prostych funkcji” w 2001 roku.

Jest to podejście oparte na uczeniu maszynowym, w którym klasyfikator jest trenowany z wielu pozytywnych i negatywnych obrazów. Jest on następnie używany do wykrywania obiektów na innych obrazach.

Jak działają klasyfikatory Haar’a (krótka wersja)?

Idea wykrywanie obiektów opiera się na ekstrakcji cech z zadanego zdjęcia, gdzie każda cecha to różnica pomiędzy sumą pikseli pod białym prostokątem i sumą pikseli pod czarnym prostokątem.

Funkcja haar - wykrywanie twarzy
Link do tutoriala OpenCV

Tak przygotowane cechy następnie wykorzystuje się do trenowania modeli kaskadowych, gdzie dane wejściowe to próbka zdjęć zawierających lub nie zadany obiekt (np. twarze).

Następnie wybierane są cechy, które najdokładniej wychwytują pożądane obiekty. Cechy podzielone są na odpowiednie grupy, gdzie każda z grup to kolejny etap klasyfikacji. Wybrane okno zaklasyfikowane jest jako zawierające obiekt kiedy przechodzi pozytywnie przez wszystkie etapy.

Jak działają klasyfikatory Haar’a (dłuższa wersja)?

Sposób działania klasyfikatorów Haar’a można podzielić na cztery kroki.

Krok 1. Wybór „charakterystyk” Haar’a

Funkcja Haar uwzględnia sąsiednie obszary prostokątne w określonym miejscu w oknie wykrywania, sumuje intensywność pikseli w każdym regionie i oblicza różnicę między tymi sumami. Różnica ta jest następnie wykorzystywana do kategoryzacji podsekcji obrazu. Na przykład cechą charakterystyczną wszystkich twarzy jest to, że obszar oczu jest ciemniejszy niż obszar nosa pomiędzy nimi. Dlatego powszechną funkcją Haar do wykrywania twarzy jest zestaw trzech sąsiadujących prostokątów, które leżą na oczach i przerwie między nimi.

wykrywanie twarzy funkcja haara

Krok 2. Stworzenie zintegrowanych obrazów

Obraz zintegrowany lub tabela sumowanych obszarów został po stworzony w 1984 r. Niemnie jednak dopiero został wprowadzony do świata Computer Vision w 2001 przez Violę i Jonesa w ramach Viola-Jones Object Detection Framework.

Obraz zintegrowany służy jako szybki i skuteczny sposób obliczania sumy wartości (wartości pikseli) na danym obrazie – lub prostokątnym podzbiorze. Głównie wykorzystywany jest do obliczenia średniej intensywności na danym obrazie. Jeśli ktoś chce użyć obrazu zintegrowanego, zwykle rozsądnie jest upewnić się, że obraz jest najpierw w skali szarości.

Jeśli chcesz zgłębić bardziej sposób działania, to możesz więcej poczytać na przykład TUTAJ.

Krok 3. Wykorzystaniu Adaboost

Weźmy pod uwagę powyższy przykład. Prostokąt przesuwamy po całym zdjęciu. Spośród wszystkich obliczonych przez nas cech większość z nich jest nieistotna. Wybrana cecha polega na tym, że oczy są ciemniejsze niż grzbiet nosa. Natomiast to samo okna będące na policzku, ustach czy uszach nie ma znaczenia.

Jak zatem wybrać najlepsze cechy spośród setek tysięcy czy milionów? Robione to jest za pomocą algorytmu Adaboost. Odpowiada on za wybranie najlepszych cech, jak i szkolenie klasyfikatorów, które ich używają.

Algorytm Adaboost konstruuje „silny” klasyfikator jako liniową kombinację ważonych prostych „słabych” klasyfikatorów (jak działa uczenie ze wzmocnieniem opisywałem TUTAJ).

To wzmocnienie („boosting”) zapewnia możliwość wyszkolenia bardzo dokładnego klasyfikatora, biorąc średnią ważoną decyzji podjętych przez słabych uczniów.

Krok 4. Trening kaskadowego klasyfikatora

Ok, mamy zdjęcie. Ale gdyby nawet te setki tysięcy cech ograniczyć np. do 6.000 i nałożyć na każde okno o wymiarach naszego zdjęcia np. 24×24 piksele sprawdzając, czy widać twarz może okazać się dość nieefektywne i czasochłonne. Autorzy rozwiązania wpadli na super pomysł!

Na zdjęciu większość obszaru obrazu jest regionem innym niż twarz. Dlatego autorzy wymyślili prostą metodę sprawdzenia, czy okno nie jest obszarem twarzy. Jeśli nie jest, to należy ten region od razu odrzucić. Bez sensu go przetwarzać ponownie. Po prostu lepiej czas poświęcić na skupieniu się na regionie gdzie możne znajdować się twarz!

Tak wprowadzono koncepcję kaskady klasyfikatorów. Zamiast stosować wszystkie 6.000 funkcji w oknie, to zgrupowano je w różne etapy klasyfikatorów i stosowano jeden po drugim. Pierwsze etapy zawierają znacznie mniej funkcji (ale są to najmocniejsze funkcje). Jeśli okno zawiedzie w pierwszym etapie, odrzucamy je. Nie rozważamy pozostałych funkcji. Jeśli przejdzie pomyślnie, zostanie zastosowany drugi etap funkcji i proces jest kontynuowany. Okno, które przeszło przez wszystkie etapy, jest zakwalifikowane jako obszar twarzy.

Obraz warty więcej niż słowa

Całą koncepcje super obrazuje ten film:

Niesamowite video stworzone przez Adama Harveya

I wszystko powinno być jasne 🙂

Wykrywanie twarzy w OpenCV w Python

Zakładam, że macie działające środowisko do Pythona.

Import

W pierwszym kroku wczytajmy biblioteki oraz przygotowane już klasyfikatory kaskadowe. Zwróćcie uwagę ile ich jest. Jeśli potrzebujecie, możecie wykorzystać do wykrycia całego ciała czy uśmiechu 🙂

Wszystkie wytrenowane modele możecie zobaczyć w zainstalowanym u Was pakiecie lub na Gitlab:

import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt2.xml')

Dla uproszczenia skopiowałem model do folderu gdzie uruchamiam plik w jupyter notebook.

Przechwycenie video z kamery

Teraz musimy przechwytywać strumień z naszej kamery video. OpenCV zapewnia bardzo prosty interfejs do tego. Ja przechwycę po prostu wideo z wbudowanej kamery w moim laptopie.

Aby przechwycić wideo należy utworzyć obiekt VideoCapture. Argumentem może być indeks urządzenia lub nazwa pliku wideo. Indeks urządzenia to tylko liczba określająca, która kamera. W moim przypadku mam jedną kamerkę więc przekazuje wartość 0.

cap = cv2.VideoCapture(0)

cap.release()
cv2.destroyAllWindows()

Na koniec nie zapomnijcie uwolnić przechwytywania i usunięcie okien jeśli już jakieś będą się wyświetlać.

Teraz do powyższego kodu dodajmy pętle by wyświetlać przechwycony obraz jeden do jeden. Obraz zapisujemy w zmiennej img. Dodatkowo definiujemy by po wciśnięciu przycisku q pętla się zatrzymała.

cap = cv2.VideoCapture(0)
while(True):
    ret, img = cap.read()
    cv2.imshow('img',img)
    if cv2.waitKey(1) & 0xFF == ord('q'): 
        break
        
cap.release()
cv2.destroyAllWindows()

Zwróćcie uwagę na jeden ważny warunek: cv2.waitKey(1). Tutaj definiujemy ile milisekund ma oczekiwać. Jak już podłączymy jakieś przeliczenia to obraz będzie się przycinał w zależności od sprzętu na jakim pracujecie. Zwiększając ten parametr obraz będzie bardziej płynny. Potem zwiększę go odpowiednio by zachować na moim komputerze płynność.

Teraz powinien być taki efekt:

Konwersja na odcienie szarości

Mamy już obraz przechwycony. Teraz przekonwertujmy go na skalę szarości. Po co? Są dwa powody.

Ogólnie obrazy, które widzimy, mają postać kanału RGB (czerwony, zielony, niebieski). Szary kanał jest łatwy do przetworzenia i jest mniej intensywny obliczeniowo, ponieważ zawiera tylko 1 kanał czarno-biały. Dodatkowo wczytane modele działają znacznie lepiej na odcieniach szarości.

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

Wykrywanie twarzy

Mamy przechwycony obraz w odcieniu szarości. Teraz spróbujmy zlokalizować położenie twarzy na tym obrazie. Wykorzystamy wbudowaną funkcję detectMultiScale z załadowanego naszego obiektu face_cascade, która pomoże znaleźć wszystkie lokalizacje twarzy. Można to zrobić w ten sposób:

faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, 
                                      minNeighbors=5, minSize=(50,50))

Wywołując ją warto podać cztery parametry:

  • image – pierwszy (jedyny obligo) to nasz obraz
  • scaleFactor – wytrenowany model Haar Cascade ma zdefiniowany rozmiar twarzy, która jest wykrywana na obrazie. Jednak przeskalowując obraz wejściowy, możesz zmienić rozmiar większej twarzy na mniejszą, dzięki czemu algorytm może ją wykryć. Ustalenie tego parametru na wartość scaleFactor = 1.05 oznacza zmniejszenie rozmiaru wejściowego o 5%, co zwiększa szansę na wykrycie twarzy przez model. Im większa wartość tym jakość wykrytych obiektów jest wyższa.
  • minNeighbors – minimalna liczba sąsiadujących obszarów, które zaklasyfikowano jako posiadający porządany obiekt. Ten parametr wpływa na jakość wykrytych twarzy. Wyższa wartość powoduje mniej wykrywalności, ale o wyższej jakości.
  • minSize – minimalny rozmiar obszaru posiadający pożądany obiekt.

Jeśli nie chcecie nie musicie definiować tych parametrów tylko przyjąć domyślne. Natomiast warto byście się pobawili nimi i zobaczyli jak wpłynie na detekcje.

Rysowanie ramek 🙂

Z powyższego kroku funkcja detectMultiScale zwraca 4 wartości – współrzędną x, współrzędną y, szerokość (w) i wysokość (h) wykrytych cechy twarzy. Na podstawie tych 4 wartości narysujemy prostokąt wokół twarzy.

for (x,y,w,h) in faces:
    cv2.rectangle(img,(x,y),(x+w,y+h),(190,0,0),2)

Kolor określiłem jako RGB=(190,0,0). Dodatkowo gdybycie chcieli wyłapywac większe twarze możecie odpowiednio pododawać jeszcze kilka pikseli do narysowanych prostokątów.

Wykrywanie twarzy w 15 liniach kodu

Zbierzmy wszystko w całość:

import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt2.xml')

cap = cv2.VideoCapture(0)

while(True):
    ret, img = cap.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5)

    for (x,y,w,h) in faces:
        cv2.rectangle(img,(x,y),(x+w,y+h),(190,0,0),2)

    cv2.imshow('img',img)
    if cv2.waitKey(30) & 0xFF == ord('q'): 
        break

cap.release()
cv2.destroyAllWindows()

a oto efekt:

Automatyczne rozmazywanie twarzy

Teraz ogranicza Was już tylko wyobraźnia co zrobicie z zdjęciem twarzy. Wykrywanie twarzy mamy już rozpracowane. Możecie w prosty sposób napisać mechanizm do zamazywania twarzy. Wystarczy do powyższego kodu zamiast prostokąta wstawić funkcję blur do rozmazywania i to wszystko 🙂

for (x,y,w,h) in faces:        
    face = img[y:y+h, x:x+w]
    face = cv2.blur(face,((w // 5),(h // 5)))
    img[y:y+h, x:x+w] = face

Wykrywanie twarzy, oczu i uśmiechów

Również możecie wczytać inne modele, które zostały udostępnione do chociażby zaznaczenia oczy czy uśmiechów.

eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
smile_cascade = cv2.CascadeClassifier('haarcascade_smile.xml')

cap = cv2.VideoCapture(0)

while(True):
    ret, img = cap.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5)

    for (x,y,w,h) in faces:
        cv2.rectangle(img,(x,y),(x+w,y+h),(190,0,0),2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = img[y:y+h, x:x+w]
        
        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,190,0),2)
            
        smile = smile_cascade.detectMultiScale(roi_gray,1.5,15)
        for (sx,sy,sw,sh) in smile:
            cv2.rectangle(roi_color,(sx,sy),(sx+sw,sy+sh),(0,0,190),2)

    cv2.imshow('img',img)
    if cv2.waitKey(30) & 0xFF == ord('q'): 
        break

cap.release()
cv2.destroyAllWindows()

Podsumowanie

Mając takie narzędzia ogranicza nas jedynie wyobraźnia co z nimi zrobimy. Z miesiąca na miesiąc przybywają nowe rozwiązania, które w prosty sposób można wykorzystać do najróżniejszych pomysłów.

PS. Tutaj kod na GitHub!

.

One Comment on “Wykrywanie twarzy real-time w 15 liniach kodu w Python”

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *