Detekcja twarzy w .NET z użyciem biblioteki EmguCV oraz klasyfikatora kaskad cech Haaropodobnych

W XXI wieku nowe kierunki w interpretacji świata przez komputery wyznaczają między innymi zaawansowane metody analizy obrazu. Nie ulega wątpliwości, że tzw. computer vision, czyli widzenie komputerowe znajduje szerokie spektrum zastosowań w obszarach takich jak robotyka, sztuczna inteligencja czy nawet bezpieczeństwo ludzi oraz mienia. Oczywiście realizacja tych zagadnień nie jest aktualnie już wyzwaniem nie do wykonania. Jak można się domyśleć, ile technologii, tyle realizacji. Właściwie należy przyjąć za pewnik na samym początku – realizacji jest wiele dla każdej z technologii. I tak w samym .NET możemy wskazać kilka ciekawych rozwiązań. Można wskazać bibliotekę AForge.NET, ale także kilka wrapperów znanej z C++ biblioteki OpenCV, jakimi są choćby OpenCVSharp oraz EmguCV.

Do rozważań w kontekście tego wpisu posłużę się tą ostatnią. Głównie dlatego, że oferuje dużo bardziej precyzyjne nazewnictwo komponentów przy tym samym potencjale funkcjonalnym, co jest decydujące przy tworzeniu dobrego kodu. Jednakże, czym dokładnie jest wrapper pewnej technologii? Otóż, jest to technologia, która opakowuje coś czymś. W tym przypadku rozwiązania zdefiniowane w bibliotece OpenCV są opakowane wywołaniami języka C# ze stajni firmy Microsoft.

Mając te podstawowe informacje, można swobodnie przejść do konkretnego zagadnienia wskazanego w temacie, a zatem detekcji twarzy z użyciem .NET. Z racji wąskiego zakresu tematu, pomijam zagadnienia związane z konfiguracją połączenia z kamerą i sposobu pobrania obrazu czy wczytania obrazu z dysku, które są zadaniami względnie trywialnymi i nie dotyczą stricte detekcji.

Biblioteka EmguCV w intuicyjny sposób pozwala na dokonanie detekcji kształtów poprzez wykorzystanie mechanizmu matematycznego, jakim jest dekompozycja falek Haar’a (od nazwiska węgierskiego matematyka – Alfreda Haar’a), a docelowo stworzenie klasyfikatora kaskad złożonych z cech Haaropodobnych, które pozwalają na precyzyjne wskazanie żądanych obiektów na obrazie.
Postanowiłem przybliżyć realizację tego mechanizmu, ponieważ same wywołania programistyczne są intuicyjne, a zrozumienie tego, co właściwie kryje się pod tworzonym kodem wydaje się o wiele bardziej przejmujące.

Wychodząc od ogółu i zakładając, że operujemy już na konkretnej klatce filmu czy pojedynczym zdjęciu, bo de facto do procesowania pobieramy zawsze dokładnie jeden obraz (o ile nie jest wykrywany ruch, gdzie siłą rzeczy następuje konieczność wskazania przynajmniej dwóch grafik), stawiamy sobie za cel detekcję twarzy. Tutaj ścieżki są dwie, ponieważ do wyznaczenia tzw. cech Haaropodobnych możemy przystąpić samodzielnie lub skorzystać z dobrze zdefiniowanych plików, które są dołączone do biblioteki OpenCV, a przez to możliwe do obsłużenia przez EmguCV.

Dla celów tego artykułu posłużę się zdefiniowanymi już klasyfikatorami kaskad cech Haaropodobnych i to tylko w ujęciu frontalnym, ponieważ przebiegi z profilu czy jakiekolwiek inne zadziałają dokładnie analogicznie. Warto mieć na uwadze, że ludzie co do zasady mają podobnie zorganizowane twarze, zatem o ile nie potrzebujemy klasyfikatora wytrenowanego w bardziej specyficznym kierunku, to te już przygotowane okażą się bardzo dobre. Dzięki temu uzyskujemy już na wejściu dokument, który definiuje uogólniony zestaw cech, wystarczających do zaklasyfikowania obiektu jako twarz, co w uproszczeniu zostało przedstawione jako seria grafik poniżej.

 

Rys. 1. Od lewej: zdjęcie przedstawiające twarz autora artykułu; zdjęcie pierwsze po zabiegu binaryzacji (lepiej widoczne dla człowieka zależności); poglądowy układ najbardziej charakterystycznych kaskad cech Haar’a; zdjęcie przedstawiające twarz z nałożoną maską układu najbardziej charakterystycznych kaskad cech Haar’a

Charakterystyczne jasne i ciemne prostokąty są właśnie wynikami wyznaczenia najbardziej charakterystycznych cech Haaropodobnych na podstawie podanego obrazu wejściowego (poprzez sumowanie jaśniejszych i ciemniejszych pikseli w regionach), co w kontekście plików dołączonych do biblioteki jest po prostu wynikiem trenowania klasyfikatora na ogromnej ilości zdjęć, które rzeczywiście przedstawiały twarz oraz takich, które tego nie zapewniały.

Podczas analizy obrazu, po odnalezieniu na nim zestawu najbardziej charakterystycznych cech Haaropodobnych, klasyfikator stwierdzi, że na ujęciu jest obecna twarz. Warto uzupełnić to informacją, że kaskady cech tworzone są z wykorzystaniem algorytmu Adaboost, który zapewnia nadanie wag cechom i wytypowanie na tej podstawie najbardziej znaczących cech oraz umieszczenie ich na początku kaskad (uruchamianie w początkowych przebiegach processingu, warunkowo dopuszczając kolejne), co pozytywnie wpływa na złożoność analizy i czas ustalenia obecności twarzy na ujęciu. Pora więc przejść do właściwego sposobu analizy obrazu, by zrozumieć, w jaki sposób rzeczone wyszukiwanie cech przebiega.

Realizowana jest tu metoda Viola-Jonesa, wprowadzająca swego rodzaju wirtualne okno, które przemieszcza się po obrazie i wyszukuje zadanych cech. Okno to zachowuje się tak, że przesuwa się od lewego górnego rogu obrazu do prawego dolnego. Rozmiar okna jest zmieniany w kolejnych iteracjach (zgodnie z wartością parametru), by przeszukiwać w różnych skalach. Doskonale działanie tej metody przedstawił Adam Harvey w formie nagrania wideo, do którego referuję w formie uzupełnienia do opisywanych zagadnień.

Przykładowe wywołanie kodu w języku C# z użyciem biblioteki .NET będzie miało następującą postać:

private void WykryjTwarze(Image<Bgr, byte> obraz, List<Rectangle> listaTwarzy)
{
  using (var kaskadyTwarzy = new CascadeClassifier(RecognizerConfig.CascadeFaceFilePath))
  {
    using (var obrazWSkaliSzarosci = obraz.Convert<Gray, byte>())
    {
      obrazWSkaliSzarosci._EqualizeHist();
      var facesDetected = kaskadyTwarzy.DetectMultiScale(
        obrazWSkaliSzarosci,
        1.1,
        10,
        new Size(20, 20),
        Size.Empty);
      listaTwarzy.AddRange(facesDetected);
    }
  }
}

Listing 1. Przykładowa implementacja metody pozwalającej na detekcję twarzy z użyciem EmguCV

Przywołana wyżej metoda wykonuje pozornie niewiele, jednak zapewnia kompletną obsługę detekcji twarzy. EmguCV to potężne narzędzie, a przy tym rzeczywiście intuicyjne w obsłudze. W parametrach wyspecyfikowane są takie informacje jak obraz wejściowy oraz lista twarzy, która w tym przypadku jest po prostu zainicjalizowaną pustą listą zdefiniowaną poza metodą, a która agreguje odkryte twarze ze wszystkich przebiegów. Pierwszą czynnością, jaka jest wykonywana jest stworzenie uchwytu do pliku z klasyfikatorem kaskad cech Haar’a oraz interpretacja jego zawartości. Kolejny krok to konwersja podanego obrazu wejściowego do jego wersji w skali szarości.

Zapewnia to oszczędniejszy zapis i łatwiejszą analizę z zachowaniem cech obrazu, które są rzeczywiście użyteczne w kontekście detekcji twarzy. Następne wywołanie _EqualizeHist() ma za zadanie znormalizować jasność i wzmocnić kontrast obrazu, który będzie przekazany do analizy.
Ostatni krok to rozpoczęcie faktycznej detekcji na podanym obrazie w skali szarości o znormalizowanych parametrach. Poza obrazem, metodzie należy podać współczynnik zmiany skali wirtualnego okna (zapis 1.1 oznacza zwiększenie o 10% w kolejnych przebiegach) oraz minimalną ilość sąsiednich prostokątów, które w grupie mogą wskazywać na obecność twarzy (wpływa na precyzję processingu), a także parametry związane z minimalnym i maksymalnym rozmiarem okna (domyślnie pożądana wartość minimalna powinna odpowiadać tej, której użyto do trenowania klasyfikatora, a wartość maksymalna może być pominięty dzięki zapisowi Size.Empty).

W tym miejscu warto zaznaczyć, że wybór parametrów wywołania, a szczególnie parametrów opisujących zachowanie wirtualnego okna nie mogą być zupełnie dowolne, ponieważ zbyt duże okno może wykluczyć odnalezienie pożądanych elementów, natomiast zbyt małe może okazać się bezużyteczne funkcjonalnie, a także ogromnie obciążające dla jednostki obliczeniowej.

Jeśli w danej pozycji, okno trafi na twarz, pozycja ta zostaje dodana do listy po to chociaż, by umożliwić programiście wyliczenie jej elementów czy wykreślenie kształtu okalającego region z wykrytą twarzą. Ostatecznym elementem w pracy całego mechanizmu detekcji jest najczęściej właśnie zwrócenie informacji o odnalezionych na ujęciu twarzach. W praktyce oznacza to na przykład wyrysowanie kształtów zakreślających twarz na ujęciu.

Omawiana biblioteka EmguCV również i w tym aspekcie wspiera dewelopera, ponieważ pozwala na stosunkowo wygodną ekstrakcję regionu z twarzą, a to dlatego, że przechowuje informacje o całym obiekcie odkrytego prostokąta zawierającego obszar sklasyfikowany jako twarz. Pozwala też wyspecyfikować kolor obramowania oraz grubość linii.

private void RysujProstokaty(Image<Bgr, byte> obraz)
{
  foreach (var twarz in _twarze)
    obraz.Draw(twarz, new Bgr(Color.Blue), 2);
}

Listing 2. Fragment wywołania kodu, pozwalającego na wykreślenie kształtów w miejscach oznaczonych przez detektor jako twarz (_twarze to lista wykrytych prostokątów odpowiadających obszarom z twarzami na ujęciu)

Ostatnim etapem jest najczęściej naniesienie kształtów na obraz wejściowy w miejscach występowania twarzy oraz podpięcie tak zmodyfikowanego obrazu do interfejsu użytkownika, by w elegancki i wygodny sposób powiadomić użytkownika o wynikach pracy nad daną klatką. Najczęściej odbywa się to poprzez dodanie prostokąta, który swoimi krawędziami opisuje odkrytą twarz.

Rys. 2. Obraz wejściowy po analizie pod kątem detekcji twarzy z użyciem klasyfikatora kaskad cech Haar’a oraz wykorzystaniem biblioteki EmguCV w technologii .NET

Warto wspomnieć o aspektach optymalizacji pracy z analizą obrazu, który jest pobierany na żywo lub z zapisanego wcześniej strumienia wideo. Analiza obrazu może okazać się bardzo złożona w szczególności dla obrazów o dużej rozdzielczości w formatach zapisu bez kompresji. Stąd dobrym punktem rozważań może okazać się podjęcie decyzji o analizie klatek z pominięciem pewnej ich ilości. Rzadko wymagane będzie analizowanie każdej klatki filmu, a dzięki decyzji o analizie nie wszystkich z nich, można zdecydowanie zwiększyć wydajność tworzonego systemu. Równie ważne może okazać się wprowadzenie wielowątkowości do programu, ponieważ analiza obrazu często wiąże się z prezentacją wyników w czasie rzeczywistym, a długa analiza może odczuwalnie spowalniać lub zwieszać działanie programu. Dlatego istotne może okazać się pobranie dodatkowego wątku z puli dostępnych i zlecenie mu analizy obrazu w tle na podstawie asynchronicznych wywołań uzależnionych od zdarzenia pobrania nowej klatki. Z racji znacznego uproszczenia opisywanego przypadku, który zdegenerowany został do analizy pojedynczej grafiki – informacje te stanowią uzupełnienie i próbę wyznaczenia kierunku dalszych poszukiwań w obszarze detekcji twarzy z użyciem .NET.

Detekcja twarzy jako pewien obszar computer vision jest nadal tematem otwartym i wciąż podatnym na rozwój. Opisywane w tym artykule podejście, korzystające z klasyfikatora Haar’a i metody Viola-Jonesa, to jedna z najbardziej popularnych metod, choć dosyć już zaawansowana wiekiem, bowiem pochodzi z roku 2001. Biblioteki wywodzące się od OpenCV, które mają swoje implementacje również w innych językach (np. Python, Java, Ruby), zapewniają analizę obrazu na bardzo wysokim poziomie, czego świetnym dowodem jest wrapper EmguCV i wygoda pracy z nim. Należy jednak spodziewać się przełomowych zmian w obszarze widzenia komputerowego w niedalekiej przyszłości lub nawet dziś, ponieważ znakomity potencjał wykazują metody tzw. deep learningu, które są o tyle zdumiewające, że same wyznaczają sobie cechy charakterystyczne, redukując równocześnie ryzyko wyciągania błędnych wniosków z nieprecyzyjnie zadanych parametrów, co może niewątpliwie stanowić podstawę dla kolejnych rozważań w tematyce analizy obrazu i detekcji kształtów.

 


inż. Kamil Reszka

Full Stack Developer

Powrót