Prowadzący zajęcia projektowe: Rafał Petryniak (strona domowa zajęć projektowych).
Spis treści
Cel projektu
Zrównoleglenie procesu edycji grafiki rastrowej. W projekcie ukazano działanie filtrów graficznych z użyciem wieloplatformowej biblioteki EasyBMP utworzonej głównie z myślą o prostym odczytywaniu, zapisywaniu oraz modyfikowaniu bitmap.
Biblioteka programistyczna została zastosowana dla algorytmu sekwencyjnego oraz dla algorytmu przetwarzania równoległego. Dzięki zastosowaniu kompilacji warunkowej:
#if _USEMPI == 1 //(0 lub 1)
i skompilowaniu dla wersji sekwencyjnej (_USEMPI=0)
g++ main.cpp lib/EasyBMP.cpp -o sekwencja
ten sam kod programu znalazł zastosowanie dla obydwu zastosowań.
Proces przetwarzania bitmapy przedstawia się następująco:
Załadowanie do pamięci zadeklarowanego pliku jako argument z linii wywołania programu.
Zastosowanie filtru zadeklarowanego podczas kompilacji.
Utworzenie kopii macierzowej oryginalnej bitmapy.
Zastosowanie dwóch pętli pobierających oraz zapisujących przetworzone piksele w postaci macierzowej.
Zapisanie bitmapy do pliku wyjściowego.
Jako bibliotekę programistyczną do operowania na bitmapach wykorzystano poniższe filtry:
Filtr negatyw
Każdy piksel bitmapy zapisany w notacji RGB zostaje odwrócony. Wartość każdego z nich przedstawia się według wzoru 255 - aktualna_wartość.
obraz(i,j)->"Kolor" = 255 - obraz(i,j)->"Kolor";
void negatyw(BMP& obraz) { for( int j=0 ; j < obraz.TellHeight() ; j++) { for( int i=0 ; i < obraz.TellWidth() ; i++) { obraz(i,j)->Red = 255 - obraz(i,j)->Red; obraz(i,j)->Green = 255 - obraz(i,j)->Green; obraz(i,j)->Blue = 255 - obraz(i,j)->Blue; } } }
Filtr binaryzacja
W celu prawidłowego zastosowania tego filtru plik wejściowy powinien być uwtorzony w skali szarości. W zależności od potrzeb musimy dostroić próg binaryzacji (zakres 0-255). Algorytm pobiera wartość RGB piksela, po czym sprawdza czy jest większa od zdefiniowanej wartości progu. Jeśli jest większa przyjmuje 255, w przeciwnym wypadku 0.
obraz(i,j)->"Kolor" = (prog < obraz(i,j)->"Kolor") ?255:0;
void binaryzacja(BMP& obraz,int prog) { for( int j=0 ; j < obraz.TellHeight() ; j++) { for( int i=0 ; i < obraz.TellWidth() ; i++) { obraz(i,j)->Red = (prog < obraz(i,j)->Red )?255:0; obraz(i,j)->Green = (prog < obraz(i,j)->Green)?255:0; obraz(i,j)->Blue = (prog < obraz(i,j)->Blue )?255:0; } } }
Filtr Sobel'a
Algorytm wykrywania krawędzi jest zdecydowanie bardziej zaawansowany niż poprzednie. W celu poprawności ich wykrycia należy pobrać wartość R,G lub B siasiednich pikseli, w naszym przypadku 3x3. Aby nie pobierać danych spoza dostępnego zakesu (ujemne lub przekraczające obraz.TellHeight() czy też obraz.TellWidth()) zaimplementowane zostało pobieranie danych z krawędzi bocznych oraz pikseli narożnych dzięki warunkom if-else we wszystkich algorytmach wykorzystujących otoczenie przetwarzanego piksela.
p1 = obraz(i-1,j-1) ->Red; p2 = obraz(i,j-1) ->Red; p3 = obraz(i+1,j-1) ->Red; p4 = obraz(i-1,j) ->Red; p5 = obraz(i,j) ->Red; //aktualnie przetwarzany, pozostałe - otoczenie p6 = obraz(i+1,j) ->Red; p7 = obraz(i-1,j+1) ->Red; p8 = obraz(i,j+1) ->Red; p9 = obraz(i+1,j+1) ->Red;
Po pobraniu wartości otoczenia aktualnego piskela p5 obliczana jest wartość wagowa sum,
sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2);
a następnie przypisanie wartośći RGB.
void fsobel(BMP& obraz) { int p1,p2,p3,p4,p5,p6,p7,p8,p9; int sum=0, sum1, sum2; BMP img_temp(obraz); for( int j=0 ; j < obraz.TellHeight() ; j++) { for( int i=0 ; i < obraz.TellWidth() ; i++) { if (i == 0 && j == 0) { //lewy gorny p1 = obraz(i,j) ->Red; p2 = obraz(i+1,j) ->Red; p3 = obraz(i+2,j) ->Red; p4 = obraz(i,j+1) ->Red; p5 = obraz(i+1,j+1) ->Red; p6 = obraz(i+2,j+1) ->Red; p7 = obraz(i,j+2) ->Red; p8 = obraz(i+1,j+2) ->Red; p9 = obraz(i+2,j+2) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if ( 0 < i && i < obraz.TellWidth()-1 && j == 0) { //gora p1 = obraz(i-1,j) ->Red; p2 = obraz(i,j) ->Red; p3 = obraz(i+1,j) ->Red; p4 = obraz(i-1,j+1) ->Red; p5 = obraz(i,j+1) ->Red; p6 = obraz(i+1,j+1) ->Red; p7 = obraz(i-1,j+2) ->Red; p8 = obraz(i,j+2) ->Red; p9 = obraz(i+1,j+2) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if ( 0 < i && i < obraz.TellWidth() -1 && j == obraz.TellHeight()-1) { //dol p1 = obraz(i-1,j-2) ->Red; p2 = obraz(i,j-2) ->Red; p3 = obraz(i+1,j-2) ->Red; p4 = obraz(i-1,j-1) ->Red; p5 = obraz(i,j-1) ->Red; p6 = obraz(i+1,j-1) ->Red; p7 = obraz(i-1,j) ->Red; p8 = obraz(i,j) ->Red; p9 = obraz(i+1,j) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if (i == obraz.TellWidth() -1 && 0 < j && j < obraz.TellHeight() -1 ) { // prawy p1 = obraz(i-2,j-1) ->Red; p2 = obraz(i-1,j-1) ->Red; p3 = obraz(i,j-1) ->Red; p4 = obraz(i-2,j) ->Red; p5 = obraz(i-1,j) ->Red; p6 = obraz(i,j) ->Red; p7 = obraz(i-2,j+1) ->Red; p8 = obraz(i-1,j+1) ->Red; p9 = obraz(i,j+1) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if (i == 0 && 0 < j && j < obraz.TellHeight() -1 ) { //lewy p1 = obraz(i,j-1) ->Red; p2 = obraz(i+1,j-1) ->Red; p3 = obraz(i+2,j-1) ->Red; p4 = obraz(i,j) ->Red; p5 = obraz(i+1,j) ->Red; p6 = obraz(i+2,j) ->Red; p7 = obraz(i,j+1) ->Red; p8 = obraz(i+1,j+1) ->Red; p9 = obraz(i+2,j+1) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if (i == 0 && j == obraz.TellHeight()-1) { //lewy dolny p1 = obraz(i,j-2) ->Red; p2 = obraz(i+1,j-2) ->Red; p3 = obraz(i+2,j-2) ->Red; p4 = obraz(i,j-1) ->Red; p5 = obraz(i+1,j-1) ->Red; p6 = obraz(i+2,j-1) ->Red; p7 = obraz(i,j) ->Red; p8 = obraz(i+1,j) ->Red; p9 = obraz(i+2,j) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if (i == obraz.TellWidth() -1 && j == obraz.TellHeight() -1 ) { //prawy dolny p1 = obraz(i-2,j-2) ->Red; p2 = obraz(i-1,j-2) ->Red; p3 = obraz(i,j-2) ->Red; p4 = obraz(i-2,j-1) ->Red; p5 = obraz(i-1,j-1) ->Red; p6 = obraz(i,j-1) ->Red; p7 = obraz(i-2,j) ->Red; p8 = obraz(i-1,j) ->Red; p9 = obraz(i,j) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if (i == obraz.TellWidth() -1 && j==0 ) { //prawy gorny p1 = obraz(i-2,j) ->Red; p2 = obraz(i-1,j) ->Red; p3 = obraz(i,j) ->Red; p4 = obraz(i-2,j+1) ->Red; p5 = obraz(i-1,j+1) ->Red; p6 = obraz(i,j+1) ->Red; p7 = obraz(i-2,j+2) ->Red; p8 = obraz(i-1,j+2) ->Red; p9 = obraz(i,j+2) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } else if ( 0 < i < obraz.TellWidth() -1 && 0 < j < obraz.TellHeight() -1 ) { // srodek p1 = obraz(i-1,j-1) ->Red; p2 = obraz(i,j-1) ->Red; p3 = obraz(i+1,j-1) ->Red; p4 = obraz(i-1,j) ->Red; p5 = obraz(i,j) ->Red; p6 = obraz(i+1,j) ->Red; p7 = obraz(i-1,j+1) ->Red; p8 = obraz(i,j+1) ->Red; p9 = obraz(i+1,j+1) ->Red; sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9; sum2 = p1 + 2*p4 + p7 - p3 - 2*p6 - p9; sum = (int)sqrt(sum1*sum1 + sum2*sum2); if (sum> 255) sum = 255; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } obraz = img_temp; }
Filtr uśredniający
Zasada działania filtru jest bardzo podobna do filtru wykrywającego krawędzie. W tym przypadku wartość RGB otoczenia jest uśredniona. Algorytm jest również zabezpieczony przed pobieraniem danych spoza zakresu. Różnica polega na ważeniu średniej wartości otoczenia każdego piksela.
sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
Następnie zapisanie wartości RGB:
img_temp(i,j)->"Kolor" = (int)sum/9;
void fsrednia(BMP& obraz) { int p1,p2,p3,p4,p5,p6,p7,p8,p9; int sum = 0, sum1, sum2; BMP img_temp(obraz); for( int j=1 ; j < obraz.TellHeight()-1 ; j++) { for( int i=1 ; i < obraz.TellWidth()-1 ; i++) { if (i == 0 && j == 0) { //lewy gorny p1 = obraz(i,j) ->Red; p2 = obraz(i+1,j) ->Red; p3 = obraz(i+2,j) ->Red; p4 = obraz(i,j+1) ->Red; p5 = obraz(i+1,j+1) ->Red; p6 = obraz(i+2,j+1) ->Red; p7 = obraz(i,j+2) ->Red; p8 = obraz(i+1,j+2) ->Red; p9 = obraz(i+2,j+2) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if ( 0 < i && i < obraz.TellWidth()-1 && j == 0) { //gora p1 = obraz(i-1,j) ->Red; p2 = obraz(i,j) ->Red; p3 = obraz(i+1,j) ->Red; p4 = obraz(i-1,j+1) ->Red; p5 = obraz(i,j+1) ->Red; p6 = obraz(i+1,j+1) ->Red; p7 = obraz(i-1,j+2) ->Red; p8 = obraz(i,j+2) ->Red; p9 = obraz(i+1,j+2) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if ( 0 < i && i < obraz.TellWidth() -1 && j == obraz.TellHeight()-1) { //dol p1 = obraz(i-1,j-2) ->Red; p2 = obraz(i,j-2) ->Red; p3 = obraz(i+1,j-2) ->Red; p4 = obraz(i-1,j-1) ->Red; p5 = obraz(i,j-1) ->Red; p6 = obraz(i+1,j-1) ->Red; p7 = obraz(i-1,j) ->Red; p8 = obraz(i,j) ->Red; p9 = obraz(i+1,j) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if (i == obraz.TellWidth() -1 && 0 < j && j < obraz.TellHeight() -1 ) { // prawy p1 = obraz(i-2,j-1) ->Red; p2 = obraz(i-1,j-1) ->Red; p3 = obraz(i,j-1) ->Red; p4 = obraz(i-2,j) ->Red; p5 = obraz(i-1,j) ->Red; p6 = obraz(i,j) ->Red; p7 = obraz(i-2,j+1) ->Red; p8 = obraz(i-1,j+1) ->Red; p9 = obraz(i,j+1) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if (i == 0 && 0 < j && j < obraz.TellHeight() -1 ) { //lewy p1 = obraz(i,j-1) ->Red; p2 = obraz(i+1,j-1) ->Red; p3 = obraz(i+2,j-1) ->Red; p4 = obraz(i,j) ->Red; p5 = obraz(i+1,j) ->Red; p6 = obraz(i+2,j) ->Red; p7 = obraz(i,j+1) ->Red; p8 = obraz(i+1,j+1) ->Red; p9 = obraz(i+2,j+1) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if (i == 0 && j == obraz.TellHeight()-1) { //lewy dolny p1 = obraz(i,j-2) ->Red; p2 = obraz(i+1,j-2) ->Red; p3 = obraz(i+2,j-2) ->Red; p4 = obraz(i,j-1) ->Red; p5 = obraz(i+1,j-1) ->Red; p6 = obraz(i+2,j-1) ->Red; p7 = obraz(i,j) ->Red; p8 = obraz(i+1,j) ->Red; p9 = obraz(i+2,j) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if (i == obraz.TellWidth() -1 && j == obraz.TellHeight() -1 ) { //prawy dolny p1 = obraz(i-2,j-2) ->Red; p2 = obraz(i-1,j-2) ->Red; p3 = obraz(i,j-2) ->Red; p4 = obraz(i-2,j-1) ->Red; p5 = obraz(i-1,j-1) ->Red; p6 = obraz(i,j-1) ->Red; p7 = obraz(i-2,j) ->Red; p8 = obraz(i-1,j) ->Red; p9 = obraz(i,j) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if (i == obraz.TellWidth() -1 && j==0 ) { //prawy gorny p1 = obraz(i-2,j) ->Red; p2 = obraz(i-1,j) ->Red; p3 = obraz(i,j) ->Red; p4 = obraz(i-2,j+1) ->Red; p5 = obraz(i-1,j+1) ->Red; p6 = obraz(i,j+1) ->Red; p7 = obraz(i-2,j+2) ->Red; p8 = obraz(i-1,j+2) ->Red; p9 = obraz(i,j+2) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } else if ( 0 < i < obraz.TellWidth() -1 && 0 < j < obraz.TellHeight() -1 ) { // srodek p1 = obraz(i-1,j-1) ->Red; p2 = obraz(i,j-1) ->Red; p3 = obraz(i+1,j-1) ->Red; p4 = obraz(i-1,j) ->Red; p5 = obraz(i,j) ->Red; p6 = obraz(i+1,j) ->Red; p7 = obraz(i-1,j+1) ->Red; p8 = obraz(i,j+1) ->Red; p9 = obraz(i+1,j+1) ->Red; sum = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; img_temp(i,j)->Red = (int)sum/9; img_temp(i,j)->Green = (int)sum/9; img_temp(i,j)->Blue = (int)sum/9; } } } obraz = img_temp; }
Filtr mediana
Jest to kolejny filtr wymagający korzystania z otoczenia aktualnie przetwarzanego piksela. Zastosowano tu inny rodzaj pobierania danych z otoczenia przetwarzanego piksela - podwójna pętla zamiast stosoania przypisywania p1..p9. Algorytm jest realizowany za pomocą funkcji
sum+= p1*p2; sum /= podzielnik;
a następnie przypisywane są wartości R,G lub B dla danego piksela.
img_temp(i,j)-> "Kolor" = sum;
void mediana(BMP& obraz,int podzielnik) { BMP img_temp(obraz); int sum = 0, p1=0, p2=0; for (int j = 0; j < obraz.TellHeight() ; j++) { for (int i = 0; i < obraz.TellWidth() ; i++) { if ( 0 < i && i < obraz.TellWidth()-1 && 0 < j && j < obraz.TellHeight() -1 ) { //srodek int sum = 0, p1=0, p2=0; for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x+1,y+1) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if (i == obraz.TellWidth() -1 && 0 < j && j < obraz.TellHeight() -1) { //prawy bok for (int x = -2; x <= 0; x++) { for (int y = -1; y <= 1; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x+2,y+1) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( i == 0 && 0 < j && j < obraz.TellHeight()-1 ) { //lewy bok for (int x = 0; x <= 2; x++) { for (int y = -1; y <= 1; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x,y+1) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( 0 < i && i < obraz.TellWidth()-1 && j == 0 ) { // gorny bok for (int x = -1; x <= 1; x++) { for (int y = 0; y <= 2; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x+1,y) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( 0 < i && i < obraz.TellWidth()-1 && j == obraz.TellHeight()-1 ) { //dolny bok for (int x = -1; x <= 1; x++) { for (int y = -2; y <= 0; y++) { p1 = obraz(i+x,j-2+y) ->Red; p2 = obraz(x+1,y+2) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( i == obraz.TellWidth()-1 && j == 0 ) { //prawy gorny for (int x = -2; x <= 0; x++) { for (int y = 0; y <= 2; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x+2,y) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( i == obraz.TellWidth()-1 && j == obraz.TellHeight()-1 ) { // prawy dolny for (int x = -2; x <= 0; x++) { for (int y = -2; y <= 0; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x+2,y+2) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( i == 0 && j == obraz.TellHeight()-1 ) { // lewy dolny for (int x = 0; x <= 2; x++) { for (int y = -2; y <= 0; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x,y+2) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } else if ( i == 0 && j == 0 ) { // lewy gorny for (int x = 0; x <= 2; x++) { for (int y = 0; y <= 2; y++) { p1 = obraz(i+x,j+y) ->Red; p2 = obraz(x,y) ->Blue; sum+= p1*p2; sum /= podzielnik; img_temp(i,j)->Red = sum; img_temp(i,j)->Green = sum; img_temp(i,j)->Blue = sum; } } } } } obraz = img_temp; }
Kod źródłowy programu:
Main.cpp
#include <iostream> #include <sys/time.h> #define KTORY_SEKW 2 // nr filtru dla sekwencji #include "lib/strconvert.h" #include "lib/EasyBMP.h" #include "img_lib.h" using namespace std; /* ============================================================ Wybierz filtr bitmapy 1 - negatyw 2 - Sobel 3 - mediana 4 - binaryzacja 5 - srednia =============================================================== //==========================MAIN=============================== int main( int argc, char* argv[] ) { img_orginal.ReadFromFile( argv[1] ); // argv[1] - odczyt z pliku, jako argument z linii wywolania programu #include "sekw_function.h" img_orginal.WriteToFile("./res_sek/out.bmp"); //zapis pliku return 0; }
Sekw_function.h
switch(KTORY_SEKW) // KTORY_SEKW - nr uzywanego filtru { case 1: cout << "Wybrales filtr negatyw\n"; negatyw(img_orginal); break; case 2: cout << "Wybrales filtr Sobela\n"; fsobel(img_orginal); break; case 3: cout << "Wybrales filtr mediane\n"; mediana(img_orginal,200); break; case 4: cout << "Wybrales filtr binaryzacja\n"; binaryzacja(img_orginal,160); break; case 5: cout << "Wybrales filtr srednia\n"; fsrednia(img_orginal); break; default: cout << "Wybrales nieprawidlowy nr filtru: " << KTORY_SEKW; break; }
Celem tej części projektu było zrównoleglenie przetwarzania grafiki rastrowej w oparciu o użycie klastra. Zarówno algorytm sekwencyjny jak i równoległy stosuje bibliotekę EasyBMP. Efektywność zastosowania tego typu rozwiązania zostanie zilustrowana w dalszej części dokumentacji - sekcja Podsumowanie.
Efekt użycia filtrów został zobrazowany w poprzednim rozdziale. Filtry takie jak mediana, wyszukiwanie krawędzi (Sobel'a) czy też średnia opierają się na wagowym przetwarzaniu wartości RGB otoczenia przetwarzanego piksela. Dzięki wykorzystaniu biblioteki EasyBMP modyfikowanie algorytmu dla własnych potrzeb staje się bardzo intuicyjne. Uniwersalny kod źródłowy umożliwia wspomnianą kompilację warunkową również dla algorytmu równoległego:
mpic++ main.cpp lib/EasyBMP.cpp -o prog_mpi -D_USEMPI
gdzie _USEMPI jest stałą, której wartość jest definiowana podczas kompilacji. Jeśli jest równa 1, wtedy kompiluje się z obsługą środowiska MPI.
#if _USEMPI == 1 #include <mpi.h> #endif
Po kompilacji program można uruchomić na 2 sposoby:
Dynamiczne przydzielanie ilości procesów do obsługi programu
mpirun program
Przydziela największą możliwą liczbę procesów przetwarzania.
Statyczne przydzielanie liczby procesów do obsługi programu
mpiun -np x program
x to zadana ilość procesów do obsługi programu
W zależności od ilości wykorzystanych procesów do obsługi programu, bitmapa jest dzielona na kolumny. Każda z kolumn jest wysyłana do osobnego procesu. Poniżej została zaprezentowana bitmapa, która została przetworzona z wykorzystaniem 25 procesów. Na poniższej bitmapie wyraźnie widać, że obraz został podzielony na 24 kolumny. Dzieje się tak dlatego, że jeden z 25 procesów staje się procesem zarządzającym i łączy przetworzone już części. Zaprezentowany filtr nie posiadał możliwości pobierania i przetwarzania danych z otoczenia piksela, dzięki czemu można ujrzeć schemat działania.
Poniżej zaprezentowany jest efekt działania filtru z mozliwością przetwarzania danych z otoczenia piksela oraz obsługą krawędzi bocznych oraz narożników.
Kod źródłowy programu:
Main
#include <iostream> #include <sys/time.h> #if _USEMPI == 1 #include <mpi.h> #endif #define KTORY_MPI 3 // nr filtru dla MPI #include "lib/strconvert.h" #include "lib/EasyBMP.h" #include "img_lib.h" #if _USEMPI == 1 #include "mpi_img_lib.h" #endif using namespace std; /* ============================================================ Wybierz filtr bitmapy 1 - negatyw 2 - Sobel 3 - mediana 4 - binaryzacja 5 - srednia =============================================================== */ //==========================MAIN=============================== int main( int argc, char* argv[] ) { #if _USEMPI == 1 //MPI #include "mpi_function.h" if (rank == 0) { img_orginal.ReadFromFile( "./res_mpi/512.BMP" ); //odczyt obrazu zrodlowego mpi_img_proc_master( img_orginal,ImagePixel ); //przetwarzanie img_orginal.WriteToFile( "./res_mpi/out/512.bmp" ); //zapis obrazu wynikowego do pliku } else { mpi_img_proc_slave(ImagePixel); } MPI_Finalize(); // zatrzymanie srodowiska MPI #else // SEKWENCJA img_orginal.ReadFromFile( argv[1] ); //odczyt pliku #include "sekw_function.h" img_orginal.WriteToFile("./res_sek/out.bmp"); //zapis pliku #endif return 0; }
MPI Library
#define MASTER_DOES_WORK 0 //czy wezel sterujacy bedzie rowniez przetwarzal dane //#define _USEMPI 1 //Sekwencja czy MPI (0 v 1) void img_proc(BMP& image_tmp) { //Wybor filtru przetwarzania switch(KTORY_MPI) { case 1: //cout << "Wybrales filtr negatyw\n"; negatyw(image_tmp); break; case 2: //cout << "Wybrales filtr Sobela\n"; fsobel(image_tmp); break; case 3: //cout << "Wybrales filtr mediana\n"; mediana(image_tmp,200); break; case 4: //cout << "Wybrales filtr binaryzacja\n"; binaryzacja(image_tmp,160); break; case 5: //cout << "Wybrales filtr srednia\n"; fsrednia(image_tmp); break; default: cout << "Wybrales nieprawidlowy nr filtru: " << KTORY_MPI; break; } } void mpi_send_img(BMP& image, int dest, MPI_Datatype& ImagePixel) //Funkcja wysylajaca informacje oraz macierz bitmapy { // dest - adres początku bufora zawierającego wysyłane dane // datatype - typ każdego z elementów bufora int tag = 50; // tag - etykieta (opis) komunikatu int width = image.TellWidth(); int height = image.TellHeight(); int depth = image.TellBitDepth(); MPI_Send(&width, 1, MPI_INT, dest, tag, MPI_COMM_WORLD); //szerokosc MPI_Send(&height, 1, MPI_INT, dest, tag, MPI_COMM_WORLD); //wysokosc MPI_Send(&depth, 1, MPI_INT, dest, tag, MPI_COMM_WORLD); //glebokosc MPI_Send(&image.Pixels[0][0], width*height, ImagePixel, dest, tag, MPI_COMM_WORLD); //macierz bitmapy } void mpi_recv_img(BMP& image, int source, MPI_Datatype& ImagePixel) //funckja odbierajaca bitmape { MPI_Status status; int tag=50; int height,width,depth; //Funkcje odbierajaca szerokosc, wysokosc oraz glebokosc bitmapy MPI_Recv(&width, 1, MPI_INT, source, tag, MPI_COMM_WORLD, &status); MPI_Recv(&height, 1, MPI_INT, source, tag, MPI_COMM_WORLD, &status); MPI_Recv(&depth, 1, MPI_INT, source, tag, MPI_COMM_WORLD, &status); image.SetSize(width,height); image.SetBitDepth(depth); //Funkcja odbierajaca macierz bitmapy MPI_Recv(&image.Pixels[0][0], width*height, ImagePixel, source, tag, MPI_COMM_WORLD, &status); } void mpi_img_struct(MPI_Datatype& ImagePixel) { int struc_counts[1]; MPI_Aint struc_offsets[1]; MPI_Datatype struc_types[1]; struc_counts[0] = 4; struc_offsets[0] = 0; struc_types[0] = MPI_BYTE; MPI_Type_struct(1, struc_counts, struc_offsets, struc_types, &ImagePixel); MPI_Type_commit(&ImagePixel); } /* ---------------------------------------------------------------- MPI Coordination Algorithm Functions ---------------------------------------------------------------- */ void mpi_img_proc_master(BMP& image, MPI_Datatype& ImagePixel) // Funckja oblugujaca proces glowny przetwarzania { BMP image_part; MPI_Status status; int i, rows_per, extra_count, base_row, sub_rows; int master_does_work, worker_count, slave_count; int rank, np, tag=50; // rank - numer procesu, np- ilosc procesów //parametry obrazka: int width = image.TellWidth(); //szerokosc int height = image.TellHeight(); //wysokosc int depth = image.TellBitDepth(); //głebia kolorow MPI_Comm_rank(MPI_COMM_WORLD, &rank); //numer bieżącego węzła MPI_Comm_size(MPI_COMM_WORLD, &np); //liczba wszystkich węzłów slave_count = np - 1; //liczba węzłów przetwarzajscych master_does_work = (MASTER_DOES_WORK) ? 1: 0; //okreslenie czy węzeł sterujący również bedzie przetwarzał dane worker_count = slave_count + master_does_work; //calkowita liczba węzłów przetwarzających rows_per = width / worker_count; //ilosd wierszy na jeden wezel extra_count = width % worker_count; //ilosc dodatkowych wierszy base_row = 0; //wiersz bazowy //wyslanie danych do procesow przetwarzajacych for (i = 0; i < worker_count; i++) { sub_rows = rows_per + (i < extra_count ? 1: 0); image_part.SetBitDepth(depth); image_part.SetSize(sub_rows,height); RangedPixelToPixelCopy(image, base_row, base_row+sub_rows, 0, height, image_part, 0, 0 ); if (i < slave_count){ mpi_send_img(image_part,i+1,ImagePixel); } else { /* else: jezeli wezel centralny ma przetwarzac nie musi robić wysylania ... */ img_proc(image_part); RangedPixelToPixelCopy(image_part, 0, image_part.TellWidth(), 0, image_part.TellHeight(), image,base_row, 0 ); } base_row += sub_rows; } base_row = 0; for (i = 0; i < slave_count; i++) { //odebranie danych od procesow przetwarzajacych mpi_recv_img(image_part,i+1,ImagePixel); RangedPixelToPixelCopy(image_part, 0, image_part.TellWidth(), 0, image_part.TellHeight(), image,base_row, 0 ); base_row += image_part.TellWidth(); } } void mpi_img_proc_slave(MPI_Datatype& ImagePixel) { // funckja obslugujaca pozostale procesy int root = 0; BMP image_tmp; mpi_recv_img(image_tmp,root,ImagePixel); //odebranie danych od procesu zarzadzajacego img_proc(image_tmp); //przetwarzanie obrazu mpi_send_img(image_tmp,root,ImagePixel); //wyslanie przetworzonego obrazu do procesu zarzadzajacego }
MPI Function - plik nagłówkowy
int rank,np; //definicje zmiennych u funkcje obslugujace int root; root = 0; MPI_Init(&argc, &argv); MPI_Datatype ImagePixel; BMP img_orginal, image_tmp; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &np); mpi_img_struct(ImagePixel); cout << "my rank=" << rank << endl; //wypisuje nr procesu
W celu przeprowadzenia bardziej szczegółowych badań dotyczących szybkości przetwarzania obrazu pomiary przeprowadzono na 3 bitmapach o różnej wielkości. Po zastosowaniu różnych filtrów pliki wyjściowe zachowują pierwotny rozmiar. Badanie przeprowadzone w różnych odstępach czasowych daje odmienne wyniki uzależniając je od obecnego obciążenia klastra. Pomiary przeprowadzono z zastosowaniem filtru Sobel'a, którego algorytm znajduje bardzo dobre wykorzystanie dla wieloprocesowego przetwarzania bitmap ze względu na stosowanie nadmiarowości wyyłanych danych dla sasiednich procesów.
Poniżej zostanie przedstawiona charakterystyka czasowa szybkości wykonywania filtru Sobel'a na danym rozmiarze bitmapy:
Bitmapa 512x512
W zależności od ilości przetwarzanych danych szybkość wykonywania się algorytmu dla małej ilości danych wejściowych jest wysoka podczas wykorzystania 32 procesów przetwarzających i wynosi ponad 70% krócej niż w przypadku wykorzystania kilku procesów. Rozmiar bitmapy wynosi 0,76MB.
Bitmapa 1024x1024
W przypadku przetwarzania bitmapy o rozmiarze 1024x1024 (3,0MB) charakterystyka czasowa jest również nieliniowa i ma podobny kształt. Czas realizacji wykonania algorytmu jest najlepszy podczas zastosowania dużej ilości procesów przetwarzających. Wzrost wydajności zastosowania dużej liczby procesów przetwarzania szacuje się również na poziomie wzrostu wydajności o około 70%.
Bitmapa 5400x5400
Charakterystyka przetwarzania bitmapy o rozmiarze 5400x5400 (84,3MB) również i w tym przypadku zachowuje swój pierwotny kształt jak dla poprzednich ilości danych wejściowych. Nie inaczej jest ze wzrostem wydajności i wynosi 70%. Czas zrealizowania zrównoleglonego algorytmu z wykorzystaniem 32 procesów przetwarzania wynosi około 1360 sekund.