poniedziałek, 8 czerwca 2009

Inpainting


Czasami, z różnych powodów, na obrazie pojawiają się wady, które możemy łatwo dostrzec. Wady takie, mogą przyczynić się do pogorszenia wyników (np. wykrycie nadmiarowych krawędzi). W OpenCV możemy takie wady usunąć (musimy jednak je określić...). Za wszystko odpowiedzialna jest funkcja cvInpaint.
void cvInpaint( const CvArr* src, const CvArr* inpaint_mask, CvArr* dst, double inpaintRange, int flags );

Obrazy wejsciowy i źródłowy muszą być tego samego typu i rozmiaru. Maska musi posiadać ten sam rozmiar co obraz, ponadto musi posiadać 8-bitową głębię i 1 kanał. inpaintRange określa w jakim promień obszaru przetwarzania dla danego piksela. Za duży może powodować uwzględnianie pikseli dosyć odległych i dla małych wad powodować rozmywanie obrazu. Flaga oznacza wybór metody. Są dwie do wyboru: CV_INPAINT_NS oraz CV_INPAINT_TELEA.

Poniższe obrazy przedstawiają obraz oryginalny, a dalej obrazy z wadą oraz obrazy o po naprawianiu i różnica pomiędzy obrazem oryginalnym i naprawionym. Jak będzie można zauważyć, dla małej wady wynik jest zadowalający. Dla większych, już nie jest tak dobrze. Niemniej, może być to pomocne, gdy chcemy usunać wadę wpływającą niekorzystnie na wynik programu.

Obraz oryginalny

Obraz z cienką krzywą
Obraz oryginalny

Po naprawie

Różnica


Obraz z grubą krzywą
Obraz oryginalny

Po naprawie

Różnica


Obraz z półprzezroczystą plamą
Obraz oryginalny

Po naprawie

Różnica


Przykład wykorzystania. Jest to program z poprzedniego wpisu (o myszce), tym razem rysujący większe punkty (okresla to zmienna promien). Po naciśnięciu klawisza "i", w miejscach zaznaczonych myszką, wykonany zostanie inpaintig. Z linii poleceń należy przekazać ścieżkę do pliku z obrazem.
bool wcisniety = false;
CvScalar kolor;
char* nazwa = "Rysowanie";
IplImage* maska; // maska okreslajaca piksele do zmiany
double promien = 3.0; // promien rysowanych kolek

void obsluga_myszki(int e, int x, int y, int fl, void* par)
{
IplImage * obraz = (IplImage*) par;

// zmiany zapisujemy na obrazie i w masce
switch (e)
{
case CV_EVENT_LBUTTONDOWN:
cvCircle(obraz, cvPoint(x, y), promien, kolor, -1);
cvCircle(maska, cvPoint(x, y), promien, kolor, -1);
wcisniety = true;
break;
case CV_EVENT_LBUTTONUP:
wcisniety = false;
break;
case CV_EVENT_MOUSEMOVE:
if (wcisniety)
{
cvCircle(obraz, cvPoint(x, y), promien, kolor, -1);
cvCircle(maska, cvPoint(x, y), promien, kolor, -1);
}
break;
}
}

void inpaint(IplImage * obraz)
{
IplImage* naprawiony = cvCreateImage(cvGetSize(obraz), obraz->depth, obraz->nChannels);

// funkcja naprawiajaca obraz
cvInpaint(obraz, maska, naprawiony, 3, CV_INPAINT_TELEA);

cvReleaseImage(&obraz);
obraz = cvCloneImage(naprawiony);
cvReleaseImage(&naprawiony);
}

void program_gl(char * sciezka)
{
cvNamedWindow(nazwa, CV_WINDOW_AUTOSIZE);
IplImage* wys = cvLoadImage(sciezka, CV_LOAD_IMAGE_ANYCOLOR);
// maska zawsze musi byc 8-bitowa i 1-kanalowa
maska = cvCreateImage(cvGetSize(wys), 8, 1);
cvZero(maska);
kolor = cvScalarAll(1.0);
cvSetMouseCallback(nazwa, obsluga_myszki, wys);

while (true)
{
cvShowImage(nazwa, wys);
int key = cvWaitKey(10);
if (key == 'k')
break;
if (key == 'i')
{
inpaint(wys);
cvZero(maska);
}
}

cvReleaseImage(&wys);
cvReleaseImage(&maska);
cvDestroyWindow(nazwa);

}

/*
* Inpainting
* autor: ratixu.blogspot.com
*/
int main(int argc, char** argv)
{
program_gl(argv[1]);
return (EXIT_SUCCESS);
}

wtorek, 2 czerwca 2009

Mysz w OpenCV


Kolejny post niezwiązany stricte z wizją komputerową, ale opisujący przydatną w pracy nad programem/algorytmem funkcjonalność. W OpenCV brakuje zaawansowanych elementów GUI, biblioteka ma inny cel. Jednak interakcja pozwala na znaczne przyspieszenie testowania programu. OpenCV udostępnia kilka podstawowych operacji w części zwanej HighGUI (związany z nią jest plik highgui.h). Jedną z nich jest tworzenie okien. Dzisiaj opiszę inną: obsługę myszy.

Obsługa myszki rozwiązana jest czymś na wzór "callbacku". Aby jej użyć musimy napisać funkcję o określonym nagłówku (ważna jest tylko kolejność i typ parametrów):
void obsluga_myszki(int zdarzenie, int x, int y, int flagi, void* parametry);

Proste jej użycie pokzauje poniższy przykład:
// czy przycisk jest wcisniety
bool wcisniety = false;
// kolor ktorym rysujemy
CvScalar kolor;
// nazwa okna
char* nazwa = "Rysowanie";

void obsluga_myszki(int e, int x, int y, int fl, void* par)
{
// rzutujemy parametr na wskaznik na obraz
IplImage * obraz = (IplImage*) par;

switch (e)
{
case CV_EVENT_LBUTTONDOWN:
cvSet2D(obraz, y, x, kolor);
wcisniety = true;
break;
case CV_EVENT_LBUTTONUP:
wcisniety = false;
break;
case CV_EVENT_MOUSEMOVE:
if (wcisniety)
{
cvSet2D(obraz, y, x, kolor);
}
break;
}
}

void program_gl()
{

// tworzymy okno
cvNamedWindow(nazwa, CV_WINDOW_AUTOSIZE);

// wyswietlany obraz
IplImage* wys = cvCreateImage(cvSize(500, 500), 8, 3);
cvZero(wys);

// ustawiamy funkcje obslugi myszy
// kolejno podajemy nazwe okna, nazwe funkcji obslugi
// nasze dane (jako wskaznik), ktore otrzymamy w funkcji
// obslugi w parametrze void* par
cvSetMouseCallback(nazwa,obsluga_myszki,wys);

while(true)
{

cvShowImage(nazwa,wys);

if(cvWaitKey(10) == 'k')
break;
}

cvReleaseImage(&wys);
cvDestroyWindow(nazwa);

}

/*
* Rysowanie
* autor: ratixu.blogspot.com
*/
int main(int argc, char** argv)
{
kolor = cvScalarAll(255.0);
program_gl();
return (EXIT_SUCCESS);
}

Po uruchomieniu możemy rysować białym kolorem na czarnym tle.

Oczywiście zastosowań jest tyle, ile wyobraźnia podpowie :)

czwartek, 28 maja 2009

XML i YAML w OpenCV

Czasu ostatnio niewiele (zaliczenia i te sprawy), a na blogu zastój. W OpenCV pracuję nad czymś "grubszym" dlatego od czasu do czasu zarzucę jakąś krótszą notką, żeby sprawić wrażenie, że blog jeszcze żyje :P

Dzisiaj będzie o XML/YAML. Najczęściej wykorzystujemy tego typu pliki do przechowywania parametrów lub ustawień programu. Zapis do pliku wygląda następująco:

// otwieramy plik do zapisu
CvFileStorage * fs = cvOpenFileStorage("conf.xml",0,CV_STORAGE_WRITE);

// zapisujemy dane
cvWriteInt(fs,"wysokosc", 480);
cvWriteInt(fs,"szerokosc", 640);

// zwalniamy zasoby
cvReleaseFileStorage(&fs);


i w ten sposób otrzymujemy plik conf.xml:



480
640



lub, zmieniając rozszerzenie, plik conf.yml:

%YAML:1.0
wysokosc: 480
szerokosc: 640


Odczyt danych wygląda podobnie do zapisu:
// otwieramy plik do odczytu
CvFileStorage * fs = cvOpenFileStorage("conf.xml", 0, CV_STORAGE_READ);

// odczytujemy dane
int wysokosc = cvReadIntByName(fs, 0, "wysokosc");
int szerokosc = cvReadIntByName(fs, 0, "szerokosc");

// zwalniamy zasoby
cvReleaseFileStorage(&fs);


To właściwie tyle z podstaw :) Oczywiście OpenCV daje więcej możliwości manipulowania danymi. Zamiast zapisywać wartości oddzielnie, możemy to zrobić w ciągu:
cvWriteInt(fs,"fps",30);
// zapisujemy strukture
cvStartWriteStruct(fs,"rozmiar",CV_NODE_SEQ);
cvWriteInt(fs,0, 480);
cvWriteInt(fs,0, 640);
cvEndWriteStruct(fs);


Otrzymamy pliki:


30

480 640


%YAML:1.0
fps: 30
rozmiar:
- 480
- 640


taką strukturę odczytujemy tak:

// otrzymujemy sekwencje - podobnie jak w detekcji twarzy
CvSeq* seq = cvGetFileNodeByName(fsr,0,"rozmiar")->data.seq;

int wysokosc = cvReadInt((CvFileNode*)cvGetSeqElem(seq,0));
int szerokosc = cvReadInt((CvFileNode*)cvGetSeqElem(seq,1));


Do plików XML/YAML możemy zapisywać także inne rzeczy:
CvMat* macierz = cvCreateMat(5,6,CV_32F);
cvZero(macierz);

// zapisujemy komentarz...
cvWriteComment(fs,"Komentarz",0);
// ...napis...
cvWriteString(fs,"napis","zapisany lancuch znakow");
// ...liczbe rzeczywista...
cvWriteReal(fs,"liczba",3.14);
// ...strukture (np. macierz)...
cvWrite(fs,"macierz",macierz);




"zapisany lancuch znakow"
3.1400000000000001

5
6
f
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.

%YAML:1.0
# Komentarz
napis: zapisany lancuch znakow
liczba: 3.1400000000000001
macierz: !!opencv-matrix
rows: 5
cols: 6
dt: f
data: [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0. ]


odczyt:
CvMat* macierz = (CvMat*) cvReadByName(fs,0,"macierz");
double liczba = cvReadRealByName(fs,0,"liczba",0.0);
const char* napis = cvReadStringByName(fs,0,"napis","domyslny");


Do plików tych można zapisywać także skomplikowane struktury drzewiaste. Wspomnę o tym gdy będę opisywał takie struktury :)

wtorek, 26 maja 2009

Linux Mint 7 wydany

Wydano najnowszą wersję Linuksa Mint o numerze 7 i nazwie "Gloria". Więcej informacji na oficjalnym blogu dystrybucji.

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ę :)