niedziela, 19 kwietnia 2009

OpenCV cz. 4. Splot

Splot (ang. convolution) jest jedną z najczęściej używanych operacji w wizji (widzeniu) komputerowym. Po definicję matematyczną splotu odsyłam do wikipedii. Nie należy się zrażać widząc całki. W przetwarzaniu obrazów cyfrowych mamy doczynienia z funkcjami dyskretnymi, więc całki jako takie nie mają racji bytu (dotyczy to też wielu innych matematycznych narzędzi). Więc czym jest splot? Można to określić jako modyfikację jednej funkcji inną. U nas będzie to modyfikacja obrazu pewną maską. W ten sposób zmodyfikujemy obraz, aby ułatwić sobie dalszą z nim pracę. Jak to działa w praktyce?

Powyżej widzimy maskę i wycinek obrazka. Ciemniejszym kolorem zaznaczono odpowiednio środek maski i aktualnie przetwarzany piksel. "Nakładamy" maskę tak aby zaciemnione pola się nakryły i wykonujemy mnożenie pikseli obrazka z pikselami maski które się nad nimi znajdują:
0.1 * 32 + 0.1 * 12 + 0.1 * 54 + 0.1 * 43 + 0.2 * 32 + 0.1 * 32 + 0.1 * 12 + 0.1 * 73 + 0.1 * 43 = 36.5.
Wynik zaokrąglamy, otrzymujemy 36. Wartość tą wstawiamy w obraz wynikowy na pozycji odpowiadającej aktualnie przetwarzanemu pikselowi. Tak uzyskany obraz wynikowy nazywany jest potocznie splotem.

Poniżej przedstawione zdjęcie (tablica z parku Vigelanda w Oslo):
przetwarzamy, w wersji w odcieniach szarości, wcześniej pokazaną maską
0.1 0.1 0.1
0.1 0.2 0.1
0.1 0.1 0.1

i otrzymujemy:
Jak można zauważyć (lub nie można ;-) ), uzyskany obraz jest wygładzony. Omawiana maska jest tzw. filtrem wygładzającym. Jest to przykładowe wykorzystanie splotu. Samych filtrów wygładzających jest sporo. Często maski prezentuje się w takiej formie:
1 1 1
1 2 1
1 1 1

dodając przed maską współczynnik skali, w tym przypadku wynosi on 1/10. Ok, czas na kod:
// otwieramy obraz na ktorym bedziemy operowac
IplImage* obraz = cvLoadImage("/home/lukasz/blog/vigeland.png", CV_LOAD_IMAGE_GRAYSCALE);

// tworzymy maske
CvMat* maska = cvCreateMat(3,3,CV_32F);

// nasza maska wyglada nastepujaco
// 0.1 0.1 0.1
// 0.1 0.2 0.1
// 0.1 0.1 0.1
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
cvSet2D(maska,j,i,cvScalarAll(0.1));

cvSet2D(maska,1,1,cvScalarAll(0.2));

// splot
// kolejno przekazujemy: obraz zrodlowy, obraz docelowy, maska i opcjonalnie
// srodek maski
// Przekazywanie jako obrazu zrodlowego i docelowego tej samej struktury nie
// zawsze jest wskazane. Wspomne o tym w odpowiednim momencie.
cvFilter2D(obraz,obraz,maska);

cvNamedWindow("splot", CV_WINDOW_AUTOSIZE);
cvShowImage("splot", obraz);
while (true)
{
if (cvWaitKey(100) == 'k') break;
}

// zapisujemy obraz wynikowy
cvSaveImage("/home/lukasz/blog/splot.png",obraz);

cvDestroyAllWindows();
cvReleaseImage(&obraz);
cvReleaseMat(&maska);
Splot służy nie tylko do wygładzania. Jedyne czego nam potrzeba to odpowiednie maski! Zobaczmy filtr Sobela. Maska wygląda nastepująco:
-1 -2 -1
0  0  0
1  2  1
w odpowienie miejsce w kodzie wstawiamy:
cvSet2D(maska,2,0,cvScalarAll(1.0));
cvSet2D(maska,2,1,cvScalarAll(2.0));
cvSet2D(maska,2,2,cvScalarAll(1.0));
cvSet2D(maska,1,1,cvScalarAll(0.0));
cvSet2D(maska,1,1,cvScalarAll(0.0));
cvSet2D(maska,1,1,cvScalarAll(0.0));
cvSet2D(maska,0,0,cvScalarAll(-1.0));
cvSet2D(maska,0,1,cvScalarAll(-2.0));
cvSet2D(maska,0,2,cvScalarAll(-1.0));
i otrzymujemy: Filtr sobela realizuje coś na wzór pierwszej pochodnej. Przedstawiona maska operuje wzdłuż osi pionowej. Tego typu filtry wykorzystuje się np. do wykrywania krawędzi na obrazie. Transponując macierz, otrzymamy filtr dla osi poziomej:
-1  0 -1
-2  0 -2
-1  0 -1
Ten sam efekt można uzyskać też inaczej, zdając się na bilbiotekę:
// otwieramy obraz na ktorym będziemy operowac
IplImage* obraz = cvLoadImage("/home/lukasz/blog/vigeland.png", CV_LOAD_IMAGE_GRAYSCALE);

// tworzymy maske
CvMat* maska = cvCreateMat(3,3,CV_32F);

// stosujemy filtr sobela rzedu 0 dla poziomu i 1 dla pionu
cvSobel(obraz,obraz,0,1);

cvNamedWindow("splot", CV_WINDOW_AUTOSIZE);
cvShowImage("splot", obraz);
while (true)
{
if (cvWaitKey(100) == 'k') break;
}

// zapisujemy obraz wynikowy
cvSaveImage("/home/lukasz/blog/splot.png",obraz);

cvDestroyAllWindows();
cvReleaseImage(&obraz);
Wynikiem jest: Wynik działania powinien być w miarę podobny. Czasami rozbieżności wynikają z faktu, że dla danego filtru istnieje więcej niż jedna maska. Na przyszłość polecam oczywiście metodę cvSobel :) Splot jest bardzo często jedną z początkowych operacji w przetwarzaniu wizyjnym. Zobaczmy laplasjan:
// otwieramy obraz na ktorym bedziemy operowac
IplImage* obraz = cvLoadImage("/home/lukasz/blog/vigeland.png", CV_LOAD_IMAGE_GRAYSCALE);

// tworzymy macierz na wynik
CvMat* lap = cvCreateMat(obraz->height,obraz->width,CV_32F);
// uzycie operatora Laplace'a 
cvLaplace(obraz,lap);

cvNamedWindow("splot", CV_WINDOW_AUTOSIZE);
cvShowImage("splot", lap);
while (true)
{
if (cvWaitKey(100) == 'k') break;
}

// zapisujemy obraz wynikowy
cvSaveImage("/home/lukasz/blog/splot.png",lap);

cvDestroyAllWindows();
cvReleaseMat(&lap);
Chciałem tu zwrócić uwagę na dwie sprawy. Po pierwsze, do wyświetlania wyniku posłużyliśmy się tym razem strukturą CvMat, jak widać nie ma z tym problemu, o czym pisałem już kiedyś (nie jest to oczywiście bezwarunkowa operacja). Druga to właśnie fakt, że do cvLaplace nie mogliśmy podać jako źródła i wyniku tego samego obrazu. cvLaplace wymaga by jako wynik podać strukturę przechowującą 16-bitowe liczby ze znakiem lub 32-bitowe bez znaku. Nasz obraz wczytany z pliku nie spełnia tych wymogów i jego użycie jako źródła i przeznaczenia spowoduje wystąpienie błędu. Po wykonaniu otrzymujemy: Uzyskany wynik przedstawia drugą pochodną cząstkową zsumowaną w pionie i poziomie. Nie może być traktowany jako obraz, gdyż nim nie jest (występują tam np. wartości ujemne). Dla laplasjanu także istnieją maski, np.
1   1   1   1  1
1   1   1   1  1
1   1  -24  1  1
1   1   1   1  1
1   1   1   1  1
Wykonując ją uzyskamy: Podobnie jak w przypadku Sobela, istnieje kilka masek dla laplasjanu. Chciałem zademonstrować sposób działania splotu, bo być może przyjdzie nam korzystać z naszej lub innej z góry zadanej maski. Sam problem wykonania splotu jest bardziej złożony, niż może się wydawać na pierwszy rzut oka (co zrobić np. z pikselami na krawędziach obrazu?). Jednak mając odpowiednią maskę możemy się zdać na bibliotekę :)

Brak komentarzy: