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.
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:
oraz
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:
,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
Filtr górnoprzepustowy
Filtry krawędziowe
Filtr konturowy
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:
Czas na przyjrzenie się działaniu tego filtra:
wielkość okna 3×3
wielkość okna 9×9
W razie jakiś pytań ,ciekawie „przefiltrowanych fotek” ,sugestii piszcie ;).
Obiecany kod – > Filters.zip.
Obrazek we wzorze jest ten sam – zapomniałeś zmienić cyferkę w adresie 😉
Dzięki za info 😉
Podczas przenoszenia postów z wordpress.com, coś musiało się sypnąć po drodze.
Poprawione 😉
możesz udostępnić ten program? chciałabym zobaczyć jak on działa, ponieważ mam do napisania coś podobnego,
pozdrawiam
@kasia
Niezbedny kod do napisania takiej aplikacji zostal zalaczony do postu, a gotowcow nie udostepniam. W razie jakis pytan zapraszam na gg lub @.