poniedziałek, 24 sierpnia 2009

Detekcja skóry

Dzisiaj wybiegnę trochę do przodu i pokażę detekcję skóry (będzie w zbliżającej się wersji OpenCV). Jak w wielu przypadkach, detekcję skóry przeprowadza się jedną funkcją. Na początku tworzymy odpowiedni obiekt
CvAdaptiveSkinDetector sd();

Konstruktor może przyjąć dwa parametry, poniżej wartości domyślne
CvAdaptiveSkinDetector sd(1, CvAdaptiveSkinDetector::MORPHING_METHOD_NONE);

Pierwszy z nich mówi o skali obrazu wyjściowego (np. 2 powoduje, że wynik ma dwukrotnie mniejszą szerokość i wysokość od obrazu przetwarzanego). Drugi to technika morfologiczna. Do wyboru mamy
  • MORPHING_METHOD_NONE - brak
  • MORPHING_METHOD_ERODE - erozja
  • MORPHING_METHOD_ERODE_ERODE - podwójna erozja
  • MORPHING_METHOD_ERODE_DILATE - otwarcie

Morfologia może być przydatna, gdyż całkiem dobrze redukuje szum w postaci pojedynczych porozrzucanych pikseli (zakładamy, że takie nie stanowią obszaru skóry). Analizę obrazu przeprowadzamy funkcją process
void process(IplImage *inputBGRImage, IplImage *outputHueMask);

Pierwszy parametr to obraz wejściowy, a drugi wyjściowy. Co uzyskujemy? Jest to obraz na którym wszystkie wartości powyżej zera spełniają kilka zależności i które są prawdopodobnie pikselami skóry. Tego typu detekcja opiera się na analizie kolorów obrazu (często w HSL/HSV wspieranym wstępną analizą w RGB). Powoduje to, że na wynik niekorzystnie wpływa np. niejednolite oświetlenie. Zainteresowanych techniką tego typu algorytmów odsyłam np. tutaj lub do googla, gdzie pod hasłem "skin detection" znajdziemy wiele artykułów na ten temat. Poniżej wynik działania przykładowego programu adaptiveskindetector_sample.cpp
Obraz oryginalny

Obraz wynikowy

Jak widać, działanie nie jest idealne. Przyznam, że ogólnie jestem zawiedziony "skutecznością" metody, ale może jeszcze coś poprawią :) Fajnie byłoby porównać działanie innych algorytmów znalezionych w sieci. Może coś tam skrobnę ale nie ukrywam, że ma to na razie niski priorytet :) Uaktualniłem przykład bloby.cpp na githubie, wybierając kanał "3" można się pobawić w detekcję skóry.

czwartek, 20 sierpnia 2009

Detekcja blobów

Dzisiaj o obszarach spójnych (ang. blob). Detekcja blobów jest istotną częścią wielu algorytmów wizyjnych (zwłaszcza ich początkowej fazy). Czym jest blob? Trudno na to pytanie jednoznacznie odpowiedzieć... Można powiedzieć, że jest to obszar obrazu odróżniający się pewną cechą od otoczenia. W większości przypadków będzie to jego jasność. Generalnie obraz cyfrowy jest zbiorem liczb, więc może to być nawet coś w stylu czasu od ostatniej zmiany wartości piksela. Spójrzmy na przykładowe bloby:

Są to proste przypadki. Pierwszy z lewej ma stały rozkład wartości, przypadek idealny, świetnie odróżnia się od otoczenia :). Dalej jest już gorzej, środkowy przypadek nie ma stałej wartości, jednak maksimum jest w środku, a wartości spadają równomiernie w każdym kierunku wraz z oddalaniem od środka. Ostatni nie ma ani stałej wartości, ani równomiernego jej spadku/wzrostu z oddalaniem od środka. Bloby to właśnie takie "plamy" na obrazie, mają, jak już wspomniałem, dość duże znaczenie (blobami mogą być np. gwiazdy na obrazie nieba, czy samochody na zdjęciu lotniczym). Często mają nieregularny kształt, co utrudnia znajdowanie ich granic.
Do detekcji blobów wydzielono specjalną część z OpenCV o nazwie cvBlobsLib. Właściwie do zapoznania się z jej podstawami wystarczy przeczytać przykładowy kod. Ten wpis wybiega niewiele poza niego :) Na początku poszukujemy blobów przy pomocy obiektu CBlobResult
CBlobResult bloby = CBlobResult(obraz, NULL, 0);

W konstruktorze podajemy kolejno przetwarzany obraz, maskę i wartość tła. Dalej możemy bloby przefiltrować, odrzucając je na podstawie ich cech, np.
bloby.Filter(bloby, B_EXCLUDE, CBlobGetArea(), B_LESS, min_pow);
bloby.Filter(bloby, B_EXCLUDE, CBlobGetArea(), B_GREATER, max_pow);

W powyższym kodzie odrzucamy bloby, których powierzchnia nie mieści się pomiędzy wartościami min_pow i max_pow. Ilość znalezionych blobów łatwo uzyskać
bloby.GetNumBlobs();

Bloba o pobieramy w następujący sposób
CBlob *aktualny_blob = bloby.GetBlob(0);

Z ciekawszych rzeczy, możemy uzyskać "przylądki" bloba :)
aktualny_blob->MinX(); // lewy
aktualny_blob->MinY(); // gorny
aktualny_blob->MaxX(); // prawy
aktualny_blob->MaxY(); // dolny

Możemy także zmienić wszystkie jego piksele na określoną wartość
aktualny_blob->FillBlob(obraz, kolor);

CvBlobLib nie jest obszerna, wszystkie możliwe parametry czy funkcje łatwo znaleźć przeglądając kody źródłowe lub autouzupełnianie kodu w IDE :) Napisałem niewielki program do zabawy parametrami. Powstał też film pokazujący jego działanie, ale po jego obejrzeniu stwierdziłem, że jest nudny (trwa 2 min.) i ostatecznie go nie umieszczę. Poniżej zrzut ekranu
oraz działanie dla obrazka z trzema przykładowymi blobami z początku wpisu

Najlepiej popróbować samemu. Założyłem repozytorium, gdzie będę wrzucał gotowe do użycia kody. Plik z tego wpisu nazywa się bloby.cpp. Miłej zabawy :)