niedziela, 10 maja 2009

Detekcja twarzy

Detekcja twarzy jest obecnie powszechnie wykorzystywana (np. w ustawianiu ostrości aparatów cyfrowych). Gdy dowiemy się gdzie na obrazie znajdują się twarze, możemy wykonać wiele ciekawych operacji. Najpierw jednak musimy je znaleźć :) Za proces ten w OpenCV odpowiadna jedna funkcja... Kod:

CvCapture* vid = cvCreateCameraCapture(0);
cvNamedWindow("detekcja twarzy", CV_WINDOW_AUTOSIZE);
cvQueryFrame(vid);
double fps = 25;
int odstep_miedzy_klatkami = 1000 / fps;

// kolory do zaznaczania twarzy
static CvScalar kolory[] = {
{{19.0,69.0,139.0,0.0}},
{{63.0,133.0,205.0,0.0}},
{{96.0,164.0,244.0,0.0}},
};

// deklarujemy pamiec na obliczenia
CvMemStorage * storage = cvCreateMemStorage(0);

// tworzymy klasyfikator
// jako agrument musimy podac siezke do pliku z efektem treningu klasyfikatora
CvHaarClassifierCascade * haar = (CvHaarClassifierCascade*) cvLoad("/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt.xml");

while (true)
{
IplImage* ramka = cvQueryFrame(vid);

if (ramka == 0)
break;

// czyscimy bufor
cvClearMemStorage(storage);

// skala
double skala = 1.5;

// przygotowujemy obrazy posrednie
IplImage *temp = cvCreateImage(cvSize(ramka->width, ramka->height), 8, 1);
IplImage *temp2 = cvCreateImage(cvSize(cvRound(ramka->width / skala), cvRound(ramka->height / skala)), 8, 1);

// zamieniamy kolory na skale szarosci
cvConvertImage(ramka, temp, CV_BGR2GRAY);

// zmniejszamy czarno-bialy obraz
cvResize(temp, temp2, CV_INTER_LINEAR);

// szukamy twarzy w danej ramce
CvSeq *wynik = cvHaarDetectObjects(temp2, haar, storage, 1.1, 3, CV_HAAR_DO_CANNY_PRUNING, cvSize(30,30));

// iterujemy po wszystkich wynikach
for (int i = 0; i < (wynik ? wynik->total : 0); i++)
{
// pobieramy prostokat z pozycja wskazujaca twarz
CvRect * twarz = (CvRect*) cvGetSeqElem(wynik, i);


// ustalamy dwa punkty po przekatnej prostokata
CvPoint punkt1 = cvPoint(cvRound(twarz->x * skala),cvRound(twarz->y * skala));
CvPoint punkt2 = cvPoint(cvRound((twarz->x + twarz->width)* skala),cvRound((twarz->y + twarz->height) * skala));

// rysujemy prostokat zaznaczajacy twarz na obrazie
cvRectangle(ramka,punkt1,punkt2,kolory[i%3],2);

}


cvShowImage("detekcja twarzy", ramka);

cvReleaseImage(&temp);
cvReleaseImage(&temp2);

int c = cvWaitKey(odstep_miedzy_klatkami);
if (c == 'k')
break;

}

cvReleaseHaarClassifierCascade(&haar);
cvDestroyAllWindows();
cvReleaseCapture(&vid);


Powyższy kod to modyfikacja z jednego z wcześniejszych wipsów. Na uzyskany z kamery obraz dokładane są prostokąty zaznaczające twarze.

Parę słów komentarza do kodu: pierwsza sprawa to plik xml z danymi z treningu. Plik ten zawiera dane do klasyfikatora cech Haara. Same klasyfikatory to dość rozbudowana dziedzina związana ze sztuczną inteligencją. Nas interesuje to, że klasyfikator zbudowany w oparciu o dany plik pozwoli nam wykryć twarze skierowane frontalnie do kamery. Użycie innego pliku pozwoli nam wykryć np. twarze uchwycone z profilu czy ludzką postać. W kolejnych wersjach biblioteki ma pojawić się ich więcej. Można je znaleźć w odpowiednim katalogu OpenCV.

Przygotowanie obrazu przed detekcją. Detekcja twarzy odbywa się w różnej skali, co pozwala znaleźć twarze ludzi zarówno bliżej jak i dalej obiektywu. Sam proces jest dość złożony. Podanie do wykrywania mniejszego obrazu powoduje znaczne przyspieszenie detekcji, przy niewielkim spadku jego skuteczności (jednak z utratą możliwości wykrywania mocno oddalonych twarzy). Podobnie jest z zamianą kolorów na skalę szarości.

Najważniejszym aspektem w detekcji frontalnych twarzy są oczy. Kiedy jedno oko jest przysłonięte, twarz w większości przypadków nie powinna zostać wykryta. Można też pobawić się w drugą stronę i "spreparować" twarz jak na filmiku ;-)



Zachęcam do modyfikacji parametrów. U mnie, niestety, szybkość nie powala, ale nad tym jeszcze popracuję :)

13 komentarzy:

pozycjonowanie olsztyn pisze...

Znalazłem chwilę aby napisać pierwszy program z wykorzystaniem OpenCV i Twojego przykładu. Nie było łatwo bo postawiłem sobie ambitne zadanie napisania aplikacji w delphi, a że nikt jeszcze nie napisał wrapper-a dla delphi dla openCV wiec musiałam sam go napisać. Jak sf.net zacznie działać o zapachnę jako nowy projekt, może komuś sie przyda.

W każdym bać razie wykrywanie twarzy mam już rozpracowane ;)

OK, ale jak teraz bym chciał wykrywać trójkąty ? Albo kwadraty na obrazie ? Albo samochody ;)

Łukasz pisze...

Wykrywanie innych rzeczy to kwestia klasyfikatora ;-) Można je samemu wytrenować, jak mi się uda to na pewno opiszę :) Samochodzy mają być w następnej wersji OpenCV.

Ostatnio spędziłem sporo czasu na wypróbowaniu bibliotek wizyjnych w Ruby. Niestety, jedyne co udało mi się postawić to... OpenCV :) Projekt jest jeszcze w powijakach, ale może uda mu się rozwinąć.

pozycjonowanie olsztyn pisze...

Teoretycznie jezyk programowania nie powinien mieć znaczenia, jednak przy przenoszeniu kodu z C do Delphi nie jest tak łatwo.

synonimek pisze...

Jaką kamerę najlepiej kupić aby bez problemu współpracowała z openCV?
Każda która działa w windows będzie działać z openCV ?

Łukasz pisze...

Teoretycznie powinna. Niestety nie wiem jak na windowsie, ale na linuksie mam 2 kamery: zew. i wbudowana. Na poczatku nie potrafilem jednej uruchomic ( w sensie, nie dzialala na skypie itp) i w OpenCV tez nie dzialala. Gdy juz mi sie udalo to zaczela tez dzialac w OpenCV. Na windowsie powinno byc podobnie. Jak juz ja uruchomisz w jakims programie to powinna zadzialac.

Anonimowy pisze...

Jak utworzyć w nowy oknie sama twarz? Tzn. to co jest w prostokącie?

Łukasz pisze...

Można np. ustawić ROI dla obrazka. Ponizej przyklad (dla jednej twarzy):

...
// szukamy twarzy w danej ramce
CvSeq *wynik = cvHaarDetectObjects(temp2, haar, storage, 1.1, 3, CV_HAAR_DO_CANNY_PRUNING, cvSize(30, 30));

if (wynik->total > 0)
{
CvRect * twarz = (CvRect*) cvGetSeqElem(wynik, 0);
CvRect roiTwarzy = cvRect(cvRound(twarz->x * skala), cvRound(twarz->y * skala), cvRound(twarz->width * skala), cvRound(twarz->height * skala));
cvSetImageROI(ramka, roiTwarzy);
}

cvShowImage("detekcja twarzy", ramka);

cvResetImageROI(ramka);
cvReleaseImage(&temp);
cvReleaseImage(&temp2);
...

Jeżeli chce się wszystkie twarze wyświetlać w kolejno tworzonych oknach, można to zrobić analogicznie kopiując do struktur IplImage (funkcja cvClone) ramkę z ustawianą odpowiednio dla danej twarzy ROI. Należy pamiętać o resetowaniu ROI przed kolejną analizą.

Anonimowy pisze...

dzieki wielkie :)

Anonimowy pisze...

Cześć.
Dużo pracy robisz na tym blogu
bardzo dobry pomysł.

Mam pytanie
Czy do utworzenia (wytrenowania) klasyfikatora używa się narzędzi w opencv czy potrzebne są zewnętrzne biblioteki narzędzia ?

Łukasz pisze...

Witam,

na blogu zastój jakiego jeszcze wcześniej nie było... Myślałem nawet nad jego usunięciem, ale archiwum postów pewnie się komuś jeszcze przydaje :)

Wracając do pytania, do trenowania używa się aplikacji z OpenCV. Nazywa się ona haartraining. Sam proces nie należy do prostych. Jego dość dokładny przebieg jest opisany tutaj: http://note.sonots.com/SciSoftware/haartraining.html Niestety, sam tego nie testowałem, więc nie wiem na ile jest to poprawna metoda.

Grizley pisze...

Długo nie mogłem znaleść wpisu do komentarza .
dzięki za link . tego właśnie szukałem.
(będę zaglądał na bloga)
griz.

Unknown pisze...

mam problem, moze ktoś zna rozwiązanie libgcc_s_dw2-1.dll missing

nie wiem może coś źle zrobiłem przy instalacji MinGW?
nie moge sobie poradzić :(

prosze o podpowiedź.

Agnieszka pisze...

mam pytanie, w jaki sposób zaciemnić wszystko poza narysowaną ramką? Dodam, ze ramkę mam okrągłą a nie prostokątną. jest w ogóle taka możliwość?