Filtry

Ostatnio miałem okazje trochę pobawić się w pisanie filtrów używanych w grafice, m.in. filtru okienkowego oraz medianowego co okazało się całkiem niezłym fun’em, szczególnie jeżeli chodzi o rezultat ich działania. Swoją implementacje filtrów wykonałem w C++ Builder’e i na koniec posta postaram się dołączyć co nieco kodu ,ale nie będzie to całość, z tego względu, że imo całość nie jest tak dojrzała(co nie przeszkodziło im uzyskać max pkt. za to zadanie na uczelni :P) aby ujrzeć światło dzienne :P.
Cała filozofia filtru okienkowego polega na wybraniu rozmiaru okna (najpopularniejsza wartością jest 3×3) czyli ilości pixeli branych pod uwagę podczas filtrowania oraz odpowiedniemu dobraniu dla nich współczynników.
Grid
Tablica pixeli. Przykład okna filtrującego o rozmiarze 3×3.
Powyższy rysunek prezentuje zastosowanie okna o rozmiarze 3×3 gdzie pixel zaznaczony na fieletowo jest pixelem wyróżnionym. Iterując po tablicy pixeli musimy sprawić, aby każdy pixel w pewnym momencie iteracji był pixelem wyróżnionym, czyli jest to nic innego jak poruszanie się po niej od pozycje x = 0,y =0 do x = image_width -1 , y = image_height -1.
Przenosząc te informacje na kod :

	for(int x = 0;x < bmp->Width;++x)
	{
	  for(int y = 0;y < bmp->Height;++y)
	  {
	      //loopy odpowiedzialne za operowanie na oknie filtrujacym
	      for(int j = -(windowSize/2); j < windowSize - 1; ++j)
	       {
		for(int i = -(windowSize/2); i < windowSize - 1;++i)
		{
                         //pobranie pixela z uwzględnieniem
                        //wysokości i szerokości bitmap’y
		color = getProperPixel(bmp,x+i,y+j);
		( … )

Warto tutaj zauważyć , że indexy “i” oraz „j” , są tak dobrane tak iż pixel wyróżniony znajduje się w środku okna, czyli od razu mamy wniosek taki, że aby móc wyznaczyć środek okna jego wielkość musi być liczbą nie parzystą >1 (bynajmniej nie spotkałem się z zastosowaniem innych wartości, jeżeli ktoś miał taka przyjemność to piszcie).
No tak ,ale nie powiedzieliśmy sobie jeszcze co tak naprawdę oznacza to „wyróżnienie”. Oznacza ono tyle iż wartość tego pixela zostania zmieniona na podstawie banalnego algorytmu , o którym za chwilę. UWAGA!!! Nie zmieniamy wartości pixeli w obrazie źródłowym , a więc należy sobie przygotować kolejny obiekt reprezentujący nasz image po filtrowaniu, który będzie podlegał modyfikacją. Ok., teraz jak przedstawia się sam algorytm wyliczający nową wartość dla pixela wyróżnionego.
Zapis matematyczny dla okna o wymiarach 3×3:
formulka_1
oraz
formulka_1
oraz w przypadku kiedy suma współczynników równa się 0.
Myślę, że sprostowania wymaga jedynie oznaczenie W(i,j) – jest to oczywiście tablica współczynników okna filtrującego.
Rzućmy teraz okiem jak ten zapis przedstawia się w kodzie(wielkość okna dowolna ze wspomnianego wyżej przedziału):

(   …   )
for(int j = -(windowSize/2); j < windowSize - 1; ++j)
{
  for(int i = -(windowSize/2); i < windowSize - 1;++i)
   {
      //pobierz odpowiedni pixel
      tmpColor = getProperPixel(SrcPixels, x+i , y+j );
      r =  GetRValue(tmpColor)  * window[index];
      g = GetGValue(tmpColor)  * window[index];
      b = GetBValue(tmpColor)  * window[index];
      totalR +=   r;
      totalG +=  g;
      totalB +=  b;
      index++;
      }
}
//zsumowanie wartości wszystkich współczynników
suma = accumulate(window.begin(),window.end(),0);
if(suma == 0)
    suma = 1;
totalR /= suma;
totalG /= suma;
totalB /= suma;
totalR = min(255, max(0, totalR));
totalG = min(255, max(0, totalG));
totalB = min(255, max(0, totalB));
//modyfikacja wartości pixela w obrazie docelowym
DstPixels->Canvas->Pixels[x][y] = (TColor)RGB(totalR,totalG,totalB);
//zeruj index pozycji w oknie współczynników
index = 0;
//zeruj wartości
totalR = 0;
totalG = 0;
totalB = 0;

To czego nie objął wzór matematyczny ,a co raczej jest oczywiste to wykonanie całego algorytmy dla każdego kanału RGB osobno. Oczywiście można zastosować np. konwersje z formatu RGB na YUV i tam jedynie manipulować wartości Y , a następnie już przy samej aktualizacji obrazu docelowego zamienić YUV na RGB, ale w tym zapisie chodziło mi o prostotę.
Ok. czas na przykładowy interface aplikacji:
interface
,aż razi swoją prostotą ;].
Teraz cała zabawa polega na tym, aby dobrać odpowiednie współczynniki dla okna filtrującego.
Przyjrzyjmy się rezultatom działania filtrów z różnych kategorii (jako modela wybrałem myślę wszystkim dobrze znaną postać Dexter’a 😀 ):
Filtr dolnoprzepustowy
dolno
Filtr górnoprzepustowy
gorno
Filtry krawędziowe
krawedziowe1
krawedziowe2
Filtr konturowy
konturowe
Myślę, że efekty całkiem ciekawe;).
Przejdźmy teraz do filtru medianowego. Jego zasada działania jest nieco odmienna choć nadal będziemy korzystać z okna lecz w tym wypadku nie definiujemy żadnych wartości współczynników. Myślę ,że kawałek kodu wyjaśni sprawę:

void CFilters::medianFilter(Graphics::TBitmap *bmp,
Graphics::TBitmap *bmpRez,
int windowSize)
{
	vector pixels;
	TColor color;
	//glowny loop dla siatki pixeli wyznaczajacy tzw pixel wyrozniony
	for(int x = 0;x < bmp->Width;++x)
	{
	  for(int y = 0;y < bmp->Height;++y)
	  {
		//loopy odpowiedzialne za operowanie na oknie filtrujacym
		for(int j = -(windowSize/2); j < windowSize - 1; ++j)
		{
			for(int i = -(windowSize/2); i < windowSize - 1;++i)
			{
				  color = getProperPixel(bmp,x+i,y+j);
				  pixels.push_back(color);
			}
		}
	sort(pixels.begin(),pixels.end());
	bmpRez->Canvas->Pixels[x][y] = (TColor)pixels[pixels.size()/2];
	pixels.clear();
	  }
	}
}

Tak ja wspomniałem powyżej nadal posługujemy się oknem lecz w tym wypadku tylko i wyłącznie w celu zgromadzenia wartości pixeli „pokrytych” przez okno. Wartość pixela wyróżnionego zastanie zastąpiona poprzez wartość środkową zgromadzonych pixeli , stąd widoczne sortowanie w kodzie:
sort(pixels.begin(),pixels.end());
oraz wskazanie elementu środkowego:
(TColor)pixels[pixels.size()/2];
(Np. dla filtru o rozmiarze okna 3×3 elementem środkowym będzie element o indexe 5).
Oczywiście , żeby móc obserwować efekty działania filtra medianowego należy wcześniej dodać „szum„.Oto kod prostego „zaszumiacza” 😀 :

 //glowny loop dla siatki pixeli wyznaczajacy tzw pixel wyrozniony
for(int x = 1;x Width - 5;++x)
{
  for(int y = 1;y Height - 5;++y)
  {
 bmp->Canvas->Pixels[x][y] = bmp->Canvas->Pixels[x+rand()%5][y+rand()%5];
  }
}

i efekt jego działania:
szum
Czas na przyjrzenie się działaniu tego filtra:
wielkość okna 3×3
med1
wielkość okna 9×9
med2
W razie jakiś pytań ,ciekawie „przefiltrowanych fotek” ,sugestii piszcie ;).
Obiecany kod – > Filters.zip.

This entry was posted in Aplikacja, Grafika and tagged , , , , . Bookmark the permalink.

4 Responses to Filtry

  1. piwowarczyk says:

    Obrazek we wzorze jest ten sam – zapomniałeś zmienić cyferkę w adresie 😉

    • Icewall says:

      Dzięki za info 😉
      Podczas przenoszenia postów z wordpress.com, coś musiało się sypnąć po drodze.
      Poprawione 😉

  2. kasia says:

    możesz udostępnić ten program? chciałabym zobaczyć jak on działa, ponieważ mam do napisania coś podobnego,
    pozdrawiam

    • Icewall says:

      @kasia
      Niezbedny kod do napisania takiej aplikacji zostal zalaczony do postu, a gotowcow nie udostepniam. W razie jakis pytan zapraszam na gg lub @.

Leave a Reply

Your email address will not be published. Required fields are marked *