Przejdź do treści

Wykrywanie twarzy real-time w 15 liniach kodu w Python

– 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'): 
    # uwaga! jeśli w kodzie widzisz "amp;" po znaku and to usuń ten kawałek.
    # WordPress sam to dopisuje :( 
        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'): 
    # uwaga! jeśli w kodzie widzisz "amp;" po znaku and to usuń ten kawałek.
    # WordPress sam to dopisuje :( 
        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'): 
    # uwaga! jeśli w kodzie widzisz "amp;" po znaku and to usuń ten kawałek.
    # WordPress sam to dopisuje :( 
        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!

9 komentarzy do “Wykrywanie twarzy real-time w 15 liniach kodu w Python”

  1. Przepraszam ,ale źle skopiował się błąd, poprawny to:
    ubdate_TV()
    Traceback (most recent call last):
    File „”, line 1, in
    ubdate_TV()
    File „C:\Users\Michał\Desktop\programowanie\pyphon\projekty\dzwięki\MY_PHONE-SELF\My_phone_camera.py”, line 24, in ubdate_TV
    faces = face_cascade.detectMultiScale(image=gray, scaleFactor=1.05, minNeighbors=5)
    cv2.error: OpenCV(4.7.0) D:\a\opencv-python\opencv-python\opencv\modules\objdetect\src\cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'cv::CascadeClassifier::detectMultiScale’

    1. hmm…bardzo możliwe że to kwestia dobrania odpowiednich wersji pakietów. W najnowszych wpisach pilnuję już tego, by zawsze pokazywać jakiej wersji używałem by było łatwo każdemu zaimplementować samemu kod bez problemów techniczych. Niestety we wcześniejszych wpisach tego nie robiłem. Cóż, uczymy się na własnych błędach 😉 Mam nadzieję że przy nowszych wpisach takich problemów już nie będzie.

  2. Robiłem po kolei przykłady, by poznać bibliotekę cv2,lecz mam pewien błąd i w ogóle nie wiem o co chodzi.
    Mój program to:
    import sys
    import numpy as np
    import cv2

    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_alt.xml’)
    eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml’)
    smile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_smile.xml’)
    cap = cv2.VideoCapture(0)
    def ubdate_TV():
    ret, img = cap.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(image=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)
    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(’camera’,img)
    cap.release()
    cv2.destroyAllWindows()

    Gdy włączam program mam taki błąd:

    Traceback (most recent call last):
    File „”, line 1, in
    ubdate_TV()
    File „C:\Users\Michał\Desktop\programowanie\pyphon\projekty\dzwięki\MY_PHONE-SELF\My_phone_camera.py”, line 24, in ubdate_TV
    faces = face_cascade.detectMultiScale(image=gray, scaleFactor=1.05, minNeighbors=5)
    cv2.error: OpenCV(4.7.0) D:\a\opencv-python\opencv-python\opencv\modules\objdetect\src\cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'cv::CascadeClassifier::detectMultiScale’

    Jeśli to ma znaczenie to mam Windowsa.

    1. Wiesz co, niestety w starszych artykulach nie zamieszczalem informacji o wersji bibliotek. Zapewne trzeba byloby poszukac konkretnej wersji której używałem. Można też pewnie znaleźć nowsze artykuły z tym podstawowym problemem dla aktualnych wersji i pewnie szybko by Ci zadziałało. Jakbyś miał problemy daj znać to pomoge Ci poszukac 🙂

    1. & 0xFF – usunąłem i udało się odpalić pierwsze okienko wideo.

      Niestety przy następnych przykładach już wysypuje błąd związany z:
      faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5)

      error: OpenCV(4.4.0) /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/pip-req-build-wv7rsg8n/opencv/modules/objdetect/src/cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'detectMultiScale’

      1. nalezy zmienić linię w ten sposób i bedzie ok

        face_cascade = cv2.CascadeClassifier(
        cv2.data.haarcascades + „haarcascade_frontalface_alt2.xml”
        )

    2. Cześć Jarek!

      Dzięki za komentarz odnośnie „amp;”. Przepraszam za błąd. Nie byłem go świadomy 🙁
      Wystarczy usunąć kawałek „amp;” i wszystko działa jak należy. Wówczas naciskając „q” wyłączysz kamerkę.

      Problem leży po stronie WordPress. Zamienia on znak do znaku & dopisuje automatycznie „amp;”
      Tutaj opisane jest błąd nie jest poprawiony od 2011 🙁
      https://stackoverflow.com/questions/44887191/how-to-keep-wordpress-from-changing-into-html-amp

      Dodałem informacje do kodu na stronie by inni czytelnicy też nie mieli więcej z tym problemu 🙂

      Odnośnie drugiego problemu, o którym piszesz, to miałem w wcześniejszej wersji podobny problem z „detectMultiScale” jeśli plik nie był w tym samym folderze co notebook. Pod postem masz link do kodu. Pobierz bezpośrednio z GIT’a pliki to powinno być ok 🙂
      Jak nie wyślij mi maila to postaram się pomóc 😉

      pozdrawiam Mirek

  3. Pingback: Wykrywanie kolorów w OpenCV. Stwórz samemu "płaszcz niewidkę"! - Mirosław Mamczur

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Spodobało Ci się? Udostępnij ten post w social mediach! ❤️❤️❤️