Prowadzący zajęcia projektowe: Rafał Petryniak (strona domowa zajęć projektowych).
Spis treści
Projekt dotyczy demonstracji działania niektórych filtrów graficznych na specyficznym typie obrazu, jakim jest bitmapa 3d otrzymana z tomografu komputerowego. Operacje takie mogą znaleźć zastosowanie przy analizie obrazu w medycynie, gdzie wymagana jest wyjątkowa szczegółowość i wierność odwzorowywanego obiektu. Odpowiednio przetworzone obrazy mogą okazać się pomocne dla zdiagnozowania problemu, lub też dać fundament do dalszej pracy.
Wczytanie pliku bitmapy
Zastosowanie wybranego filtru
Zapis przetworzonego pliku
W programie główną rolę (z wyjątkiem funkcji odpowiadających za modyfikacje obrazu) odgrywają funkcje odpowiadające za wczytywanie i zapisywanie danych. Funkcja "ZwrocStrumien" zwraca wskaźnik do dynamicznej tablicy 3 wymiarowej z załadowanym plikiem *.raw gdzie [x][y][z] to kolejno: szerokość obrazu, długość obrazu, ilość obrazów. Kolejna funkcja "ZapiszStrumien" zgodnie z nazwą, zapisuje dane bitmapy z tabeli do pliku o takiej nazwie jak parametr za to odpowiadający (nazwaPliku). W funkcji main zawarto podstawową obsługę programu, z wywołaniami poszczególnych funkcji. Kolejne funkcje to filtry odpowiadające za żądane rodzaje przekształceń.
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef unsigned char JEDNOSTKA; JEDNOSTKA ***ZwrocStrumien(const char *nazwaPliku); void ZapiszStrumien(const char *nazwaPliku, JEDNOSTKA ***model); void Binaryzacja(JEDNOSTKA ***model, int prog); void Negatyw(JEDNOSTKA ***model); int szerokoscX=512,wysokoscY=512,dlugoscZ=83; int main(int argc, char* argv[]) { JEDNOSTKA ***wsk; // char *nazwa = NULL; int i; printf("%d",sizeof(JEDNOSTKA)); do { printf("Witamy w programie szachownica !\n\n"); printf("1 - Wczytanie bitmapy\n"); printf("2 - Zapisanie bitmapy do pliku\n"); printf("3 - Binaryzacja\n"); printf("4 - Negatyw\n"); printf("0 - Zakonczenie programu\n"); scanf("%d",&i); switch (i) { case 1: // printf("Podaj nazwe pliku : \n"); // scanf("%s",&nazwa); wsk = ZwrocStrumien("men.raw"); break; case 2: ZapiszStrumien("Nowy.raw",wsk); break; case 3: Binaryzacja(wsk,100); break; case 4: Negatyw(wsk); break; case 0: printf("Koniec Programu"); break; } system("cls"); } while (i!=0); return 0; } //--------------------------------------------------------------------------- JEDNOSTKA ***ZwrocStrumien(const char *nazwaPliku) { JEDNOSTKA ***model;JEDNOSTKA wsk; unsigned char wsk1; int k,i,j; FILE *plik; unsigned char *ww; plik = fopen(nazwaPliku,"rb+"); if (!plik) { printf("Nie udalo sie otworzyc pliku %s\n",nazwaPliku); return NULL; }
model=(JEDNOSTKA ***)calloc(szerokoscX,sizeof(JEDNOSTKA **)); for(k=0;k<szerokoscX;k++) model[k]=(JEDNOSTKA **)calloc(wysokoscY,sizeof(JEDNOSTKA *)); for(k=0;k<szerokoscX;k++) for(i=0;i<wysokoscY;i++) model[k][i]=(JEDNOSTKA *)calloc(dlugoscZ,sizeof(JEDNOSTKA));
for (j=0; j<dlugoscZ; j++) for (k=0; k<wysokoscY; k++) for (i=0; i < szerokoscX; i++) { fread(&wsk,1,1,plik); model[i][k][j] = wsk; } return model; } void ZapiszStrumien(const char *nazwaPliku, JEDNOSTKA ***model) { FILE *plik; int k,i,j; JEDNOSTKA wsk; if (model ==NULL)
{ exit(1); } plik = fopen(nazwaPliku,"wb+"); if (!plik) { printf("Nie udalo sie otworzyc pliku %s\n",nazwaPliku); exit(1); } //zapisywanie do nowego pliku danym for (j=0; j<dlugoscZ; j++) for (k=0; k<wysokoscY; k++) for (i=0; i < szerokoscX; i++) { wsk = model[i][k][j]; fwrite(&wsk,1,1,plik); }
} void Binaryzacja(JEDNOSTKA ***model, int prog) { int k,i,j; unsigned char *wsk; for (j=0; j<dlugoscZ; j++) for (k=0; k<wysokoscY; k++) for (i=0; i < szerokoscX; i++) { wsk = (unsigned char *)&model[i][k][j]; if (*(wsk)>prog) {
*(wsk) = 0; } else *(wsk) = 255; } } void Negatyw(JEDNOSTKA ***model) { int k,i,j; unsigned char *wsk; for (j=0; j<dlugoscZ; j++) for (k=0; k<wysokoscY; k++) for (i=0; i < szerokoscX; i++) { wsk = (unsigned char *)&model[i][k][j]; *(wsk) = 255 - *(wsk); } }
Założeniem algorytmu równoległego jest rozłożenie wykonywania obliczeń (działania filtrów grafocznych) na poszczególne rdzenie, w celu skrócenia czasu przetwarzania map bitowych. W dziale "Implementacja" opisano dwa główne pliki programu.
Obrazy 3d rozpatrywane w projekcie to pliki .raw, zawierające dane zapisane piksel po pikselu w kolejności od lewego górnego rogu piewszego obrazka, aż do prawego dolnego rogu ostatniego obrazka. Z tego faktu wynika konieczność rezerwowania pamięci jako ciągłego obszaru, jest to również niezbędne, aby uniknąć wielu błędów powstających przy komunikacji poszczególnych watków z pamięcią główną.
W projekcie przyjęto, iż :
szerokoscX to X współrzędna obrazka w układzie kartezjańskim
wysokoscY to Y współrzędna obrazka w układzie kartezjańskim
dlugoscZ to ilosc obrazków, składających się na obraz 3d
W projekcie istnieje wiele odniesień do w/w zmiennych, gdyż są one niezbędne do wszelkich obliczeń dotyczących przemieszczania się pomiędzy pikselami poszczególnych obrazków.
Podczas swojego działania program tworzony jest dodatkowy plik nazwany nowy.raw, o rozmiarze i strukturze identycznej, jak oryginalny plik z obrazem 3d. Plik nowy.raw zawiera wynik dziania wybranego filtru.
Program Filtry3D daje możliwość używania filtrów działających w otoczeniu trójwymiarowym, jak również działających w otoczeniu dwuwymiarowym. Po wybraniu z menu głównego programu odpowiedniego filtru program oblicza ilość pojedynczych obrazków 2D w pliku wejściowym i na podstawie zmiennej reprezentującej ilość wątków, które mają zostać stworzone obliczana jest ilość obrazków 2D, która przypadnie do przefiltrowania przez każdy z wątków spe. Jeśli nie jest możliwe równe podzielenie pracy pomiędzy wszystkie wątki spe to nadmiar pracy, czyli pozostałą resztę obrazków przejmuje do przetworzenia pierwszy wątek.
Jest to główny plik programu, zawiera podstawowe menu oraz niżej opisane funkcje.
Funkcja Tworz_Watki odpowiada za utworzenie odpowiedniej ilości watków spe, oraz uruchomieniu dla każdego z nich programu do wykonania. Funkcję wywołujemy z 3 parametrami, kolejno : number - nr filtru, który będziemy używać dlZ - liczba 0 lub 2, dla filtrów działających w 2 wymiarach wpisujemy 0, dla filtrów 3d liczba 2 prog - określenie progu dla binaryzacji, dla innych filtrów wartość 0 Funkcja nie zwraca żadnych wartości, chyba że wystąpi wyjątek. Funkcja przekazuje do każdego z wątków spe ilość obrazów 2D, które mają zostać stworzone w pliku wynikowym. Dodatkowo przekazywane są odpowiednio zmodyfikowane adresy pliku wejściowego, oraz wynikowego, a dokładniej przekazywany jest adres początku pierwszego z obrazów, które mają zostać przetworzone przez wątek spe, oraz adres pierwszego obrazu w pliku wynikowym, gdzie przetworzone dane mają zostać zapisane. Tablica control_blocks[MAX_NUM_SPE_THREADS], o szerokości równej ilości watków spe umożliwia przekazywanie parametrów do każdego utworzonego wątku spe. Jest to jedna z najważniejszych zmiennych w programie. Aby było możliwe przesłanie pojedynczego elementu tablicy do wątku spe jego rozmiar musi wynieść 64kB, w przeciwnym razie wystąpi błąd szyny.
Funkcja Ramka służy do filtrowania pierwszego i ostatniego obrazu z pośród wszystkich obrazów składających się na obraz 3D. Jest wywoływana tylko w przypadku działania filtrów 3D. Wykonanie filtru 3D na pierwszym i ostatnim obrazku jest niemożliwe, gdyż nie posiadamy obrazu -1 i odpowiednio następnego po ostatnim, aby wykonać odpowiednie obliczenia. Dlatego pierwszy obraz jest filtrowany na podstawie piwreszego i drugiego, natomiast ostatni jest filtrowany na podstawie ostatniego i przedostatniego. Wszystkie te obliczenia są wykonywane w głównym programie, po zakończeniu przetwarzania przez wszystkie wątki. Funkcja przyjmuje 18 parametrów, które są wagami filtru odpowiednio dla: x1 -x9 pierwszy i ostatni obraz y1 - y9 drugi i przedostatni obraz x5 to punkt, który będzie uśredniony.
Funkcja ZwrocStrumien zwraca wskaźnik typu unsigned char do ciągłego obszaru pamięci, w którym jest przechowywanyplik wejściowy. Alokacji ciągłego obszaru pamięci dokonano za pomocą funkcji posix_memalign. Jako parametr przyjmuje ona nazwę pliku, który ma zostać wczytany do pamięci.
Funkcja ZapiszStrumien zapisuje dane znajdujące się pod adresem *model do pliku, którego nazwę podajemy jako parametr.
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <libspe2.h> #include <pthread.h> #include "single_buffer.h" #include <dma_example.h>typedef struct _ppu_thread_data { spe_context_ptr_t spe_context;
void* control_block_ptr;
} ppu_thread_data_t;
extern spe_program_handle_t dma_example_spu;
void *ppu_pthread_function(void *argp) { ppu_thread_data_t *thread_data = (ppu_thread_data_t *)argp; spe_context_ptr_t ctx; unsigned int entry = SPE_DEFAULT_ENTRY; ctx = thread_data->spe_context;
if (spe_context_run(ctx, &entry, 0, thread_data->control_block_ptr, NULL, NULL) < 0) { perror ("Failed running context"); exit (1); } pthread_exit(NULL); } void ZapiszStrumien(const char *nazwaPliku, JEDNOSTKA *model); JEDNOSTKA *ZwrocStrumien(const char *nazwaPliku); void Tworz_Watki(unsigned int number, unsigned int dlZ, unsigned int prog); void Ramka(unsigned int x1, unsigned int x2, unsigned int x3, unsigned int x4, unsigned int x5, unsigned int x6, unsigned int x7, unsigned int x8, unsigned int x9, unsigned int y1, unsigned int y2, unsigned int y3, unsigned int y4, unsigned int y5, unsigned int y6, unsigned int y7, unsigned int y8, unsigned int y9); int prog; //----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- MAIN FUNCTION ---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- int main() { int wybor; int rc = posix_memalign ((void**)(&out_data), 128, szerokoscX*wysokoscY*dlugoscZ * sizeof(JEDNOSTKA)); if (rc != 0) { fprintf (stderr, "Failed allocating space for control block\n"); exit (1); } do{ printf("\nWitamy w programie Filtrowanie 3D !\n\n"); printf("1 - Wczytanie bitmapy\n"); printf("2 - Zapisanie bitmapy do pliku\n"); printf("3 - Binaryzacja\n"); printf("4 - Usrednianie 3d\n"); printf("5 - Usun Srednia 3d\n"); printf("6 - Negatyw_Vektor\n"); printf("7 - Sobel Poziomy 2d\n"); printf("8 - Sobel Pionowy 2d\n"); printf("9 - Sobel Poziomy 3d\n"); printf("10 - Usun Srednia 2d\n"); printf("11 - Zmiana ilosci watkow SPE\n"); printf("0 - Zakonczenie programu\n"); scanf("%d",&wybor); switch (wybor) { case 1: in_data = ZwrocStrumien("../img/men.raw"); break; case 2: ZapiszStrumien ("../img/nowy.raw",out_data); break; case 3: printf("\n\nPodaj prog : ");scanf("%d",&prog); Tworz_Watki(3,0,prog); break; case 4: Tworz_Watki(4,2,0); Ramka(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1); break; case 5: Tworz_Watki(8,2,0); Ramka(-1,-1,-1,-1,9,-1,-1,-1,-1,-1,-1,-1,-1,9,-1,-1,-1,-1); break; case 6: Tworz_Watki(5,0,0); break; case 7: Tworz_Watki(6,0,0); break; case 8: Tworz_Watki(7,0,0); break; case 9: Tworz_Watki(9,2,0); Ramka(1,2,1,0,0,0,-1,-2,-1,1,2,1,0,0,0,-1,-2,-1); break; case 10: Tworz_Watki(10,0,0); break; case 11: printf("\n\nAktualna liczba watkow SPE to : %d",MAX_NUM_SPE_THREADS); printf("\nPodaj nowa wartosc : ");scanf("%d",&MAX_NUM_SPE_THREADS); break; case 0: printf("Koniec Programu"); break; } //system("cls"); } while (wybor!=0); printf("\nThe program has successfully executed.\n"); return (0); }
void Tworz_Watki(unsigned int number, unsigned int dlZ, unsigned int prog) { int i, num_spe_threads, num_elements_per_spe, rc; spe_context_ptr_t spe_contexts[MAX_NUM_SPE_THREADS]; pthread_t spe_threads[MAX_NUM_SPE_THREADS]; control_block_t* control_blocks[MAX_NUM_SPE_THREADS]; ppu_thread_data_t ppu_thread_data[MAX_NUM_SPE_THREADS]; union { unsigned long long ull; unsigned int ui[2]; } long_addr; num_spe_threads = MAX_NUM_SPE_THREADS; num_elements_per_spe = szerokoscX*wysokoscY; printf("\nLiczba elementow na 1 spe : %.2d\n", num_elements_per_spe);
int il_obr = (dlugoscZ-dlZ)/num_spe_threads;
![]()
int rest = (dlugoscZ-dlZ) % num_spe_threads;
rest += il_obr; printf("\nReszta %d\n", rest-il_obr); unsigned char *in_data_copy = in_data;
unsigned char *out_data_copy = out_data; out_data_copy += szerokoscX*wysokoscY*(rest-il_obr);
in_data_copy += szerokoscX*wysokoscY*(rest-il_obr); for (i = 0; i < num_spe_threads; i++) {
rc = posix_memalign ((void**)(&control_blocks[i]), 128, sizeof (control_block_t)); if (rc != 0) { fprintf (stderr, "Failed allocating space for control block\n"); exit (1); }
long_addr.ui[0] = 0; if (i == 0)long_addr.ui[1] = (unsigned int)in_data; else long_addr.ui[1] = (unsigned int)in_data_copy + i* num_elements_per_spe*(il_obr)* sizeof(unsigned char); control_blocks[i]->in_addr = long_addr.ull; long_addr.ui[0] = 0; if (i == 0)long_addr.ui[1] = (unsigned int)out_data; else long_addr.ui[1] = (unsigned int)out_data_copy + i* num_elements_per_spe *(il_obr)* sizeof(unsigned char); control_blocks[i]->out_addr = long_addr.ull; control_blocks[i]->num_elements_per_spe = num_elements_per_spe; control_blocks[i]->id = i; control_blocks[i]->pad[0] = number; if (i == 0)control_blocks[i]->pad[1] = rest; else control_blocks[i]->pad[1] = il_obr;
control_blocks[i]->pad[2] = prog;
spe_contexts[i]= spe_context_create (0, NULL); if (spe_contexts[i] == NULL) { perror ("Failed creating SPE context"); exit (1); }
if (spe_program_load (spe_contexts[i], &dma_example_spu)) { perror ("Failed loading SPE program"); exit (1); }
ppu_thread_data[i].spe_context = spe_contexts[i]; ppu_thread_data[i].control_block_ptr = control_blocks[i];
if (pthread_create (&spe_threads[i], NULL, &ppu_pthread_function, &ppu_thread_data[i])) { perror ("Failed creating SPE thread"); exit (1); }
![]()
if (pthread_join (spe_threads[i], NULL)) { perror ("Failed pthread_join"); exit (1); }
if (spe_context_destroy (spe_contexts[i]) != 0) { perror ("Failed destroying SPE context"); exit (1); } free (control_blocks[i]); } printf("\nzakonczono\n"); } void Ramka(unsigned int x1, unsigned int x2, unsigned int x3, unsigned int x4, unsigned int x5, unsigned int x6, unsigned int x7, unsigned int x8, unsigned int x9, unsigned int y1, unsigned int y2, unsigned int y3, unsigned int y4, unsigned int y5, unsigned int y6, unsigned int y7, unsigned int y8, unsigned int y9) {
![]()
int i; int Z = 1; for (i = szerokoscX; i<szerokoscX*wysokoscY - szerokoscX ; i++) { if ((i % szerokoscX == 0) || (i % szerokoscX == szerokoscX-1)) continue; int wynik = 0; wynik = x1*(*(in_data+i-szerokoscX-1)) + x2*(*(in_data+i-szerokoscX)) + x3*(*(in_data+i-szerokoscX+1)) + x4*(*(in_data+i-1)) + x5*(*(in_data+i)) + x6*(*(in_data+i+1)) + x7*(*(in_data+i+szerokoscX+1)) + x8*(*(in_data+i+szerokoscX)) + x9*(*(in_data+i+szerokoscX-1)) + y1*(*(in_data+i-szerokoscX-1 + szerokoscX*szerokoscX)) + y2*(*(in_data+i-szerokoscX + szerokoscX*szerokoscX)) + y3*(*(in_data+i-szerokoscX+1 + szerokoscX*szerokoscX)) + y4*(*(in_data+i-1 + szerokoscX*szerokoscX)) + y5*(*(in_data+i + szerokoscX*szerokoscX)) + y6*(*(in_data+i+1 + szerokoscX*szerokoscX)) + y7*(*(in_data+i+szerokoscX+1 + szerokoscX*szerokoscX)) + y8*(*(in_data+i+szerokoscX + szerokoscX*szerokoscX)) + y9*(*(in_data+i+szerokoscX-1 + szerokoscX*szerokoscX)) ; *(out_data+i) = wynik/18; }
![]()
for (Z=1; Z<dlugoscZ-1; Z++) { for (i= szerokoscX*wysokoscY*Z ; i< szerokoscX*wysokoscY*(Z+1) ; i++) {
if ( (i >= szerokoscX*wysokoscY*Z) && (i <= szerokoscX*wysokoscY*Z+szerokoscX)) { *(out_data+i) = *(in_data+i); } else if (i % szerokoscX == 0) { *(out_data+i-1) = *(in_data+i);
*(out_data+i) = *(in_data+i);
} else if (i >=szerokoscX*wysokoscY*Z+szerokoscX*(szerokoscX-1) +1)
{ *(out_data+i) = *(in_data+i); } } }
for (i = szerokoscX*wysokoscY*dlugoscZ - szerokoscX*wysokoscY + szerokoscX; i<szerokoscX*wysokoscY*dlugoscZ - szerokoscX ; i++) { if ((i % szerokoscX == 0) || (i % szerokoscX == szerokoscX-1)) continue; int wynik = 0; wynik =x1*(*(in_data+i-szerokoscX-1)) + x2*(*(in_data+i-szerokoscX)) + x3*(*(in_data+i-szerokoscX+1)) + x4*(*(in_data+i-1)) + x5*(*(in_data+i)) + x6*(*(in_data+i+1)) + x7*(*(in_data+i+szerokoscX+1)) + x8*(*(in_data+i+szerokoscX)) + x9*(*(in_data+i+szerokoscX-1)) + y1*(*(in_data+i-szerokoscX - 1 - szerokoscX*szerokoscX)) + y2*(*(in_data+i-szerokoscX - szerokoscX*szerokoscX)) + y3*(*(in_data+i-szerokoscX+1 - szerokoscX*szerokoscX)) + y4*(*(in_data+i-1 - szerokoscX*szerokoscX)) + y5*(*(in_data+i - szerokoscX*szerokoscX)) + y6*(*(in_data+i+1 - szerokoscX*szerokoscX)) + y7*(*(in_data+i+szerokoscX+1 - szerokoscX*szerokoscX)) + y8*(*(in_data+i+szerokoscX - szerokoscX*szerokoscX)) + y9*(*(in_data+i+szerokoscX-1 - szerokoscX*szerokoscX)) ; *(out_data+i) = wynik/18; } } JEDNOSTKA *ZwrocStrumien(const char *nazwaPliku) { JEDNOSTKA wsk; JEDNOSTKA *model; long i; FILE *plik; plik = fopen(nazwaPliku,"rb+"); if (!plik) { printf("Nie udalo sie otworzyc pliku %s\n",nazwaPliku); return NULL; }
int rc = posix_memalign ((void**)(&model), 128, szerokoscX*wysokoscY*dlugoscZ * sizeof(JEDNOSTKA)); if (rc != 0) { fprintf (stderr, "Failed allocating space for control block\n"); exit (1); } for (i = 0; i < szerokoscX*wysokoscY*dlugoscZ; i++) { fread(&wsk,1,1,plik); model[i] = wsk; } return model; }
void ZapiszStrumien(const char *nazwaPliku, JEDNOSTKA *model) { FILE *plik; int i; JEDNOSTKA wsk; if (model ==NULL)
{ exit(1); } plik = fopen(nazwaPliku,"wb+"); if (!plik) { printf("Nie udalo sie otworzyc pliku %s\n",nazwaPliku); exit(1); } for (i = 0; i < szerokoscX*wysokoscY*dlugoscZ; i++) { wsk = model[i]; fwrite(&wsk,1,1,plik); } }
Definiujemy strukturę danych do przetrzymywania żądanych danych przekazywanych w funkcji pthread która rozpoczyna wątek spe. | |
spe context | |
wskaźnik do bloku kontrolnego | |
to jest wskaźnik do kodu SPE | |
funkcje PPE pthread która rozpoczyna wątek SPE | |
początek wątku SPE, chcemy przekazać wskaźnik bloku kontrolnego do SPE | |
prog dla binaryzacji, number - nr porzadkowy, dlZ - co odjac od Z | |
ile obrazków na każdy spe | |
2 dla uśredniania | |
reszta obrazków, którą dostanie pierwszy wątek | |
2 dla uśredniania | |
kopie wskaźników na obrazki | |
przesunięcie o resztę z dzielenia | |
utwórz blok kontrolny | |
przekazujemy adresy źrodła i pliku wynikowego do wątku | |
przechowywujemy tutaj ilość obrazków do przetworzenia przez jeden wątek | |
tworzy wątki SPE by wykonać 'dma_example_spu' | |
ładuje program SPE do contextu | |
ładuje ppu_thread_data | |
tworzy wątek SPE | |
SPE jest wykonywane | |
czekaj aż wątek SPE zostanie wykonany | |
zniszcz context | |
wspolrzedne: x1 -x9 pierwszy obrazek y1 - y9 drugi obrazek xx1 - xx9 ostatni obrazek yy1 - yy9 przedostatni obrazek | |
uśrednienie pierwszego obrazka, na podstawie pierwszego i drugiego | |
przepisanie po jednym pikselu wokół obrazka, pierwszy piksel to lewy górny rog | |
obrazki od 2 do n-1 | |
górny rządek | |
prawa ścianka | |
lewa ścianka | |
dolny rządek | |
uśrednienie ostatniego obrazka na podstawie przedostatniego i ostatniego | |
zapisywanie rawu jako ciąglego obszaru w pamięci | |
zapisuje dane bitmapy z tabeli do pliku, o nazwie jak parametr nazwaPliku | |
zabezpieczenie pamieci przed pustą tablicą |
W tym pliku znajdują się funkcje odpowiedzialne za działanie samych filtrów.
Funkcja Filtr2d jako parametr przyjmuje 9 elementową tablicę wag. Funkcja ta filtruje cały obraz 3d, jednak każdy z obrazów składowych jest filtrowany jak obraz 2D.
Postępowanie w przypadku filtrowania 2D. Zakładamy, że filtrowany obraz 2D ma wymiary 512 * 512 pikseli. Z filtrowanego obrazu 2D są pobierane pierwsze trzy rzędy danych, czyli 512*3 a następnie są one ładowane do tablicy 2D. Powstała tablica ma wymiary x = 512 y = 3. Kolejnym krokiem jest wykonanie filtru na podstawie danych zawartych w tablicy 2D oraz wag podanych jako parametr. Filtrujemy piksele o współrzędnych x = 1 .. 510 y = 2. Piksele x = 0 oraz x = 511 są uśredniane osobno na podstawie prawego i odpowiednio lewego otoczenia. Ostatecznie przefiltrowany rząd danych o współrzędnych x = 0 .. 511 y = 2 jest zapisywany do pliku wynikowego i wszystkie operacje są wykonywane od początku dla kolejnej porcji danych( rzędy 2,3,4). Całe wyżej zammieszczone postępowanie zostało zawarte w funkcji Filtr2d(int tabl[9]).
Funkcja Filtr3d przyjmuje jako parametr tablicę wag, na podstawie których jest wykonywane filtrowanie. Parametr ma 27 elementów(9 na każdy obraz, który jest brany pod uwagę przy filtrowaniu pojedynczego piksela), z których pierwsze 9 to wagi elementów obrazu przed obrazem filtrowanym kolejne 9 to wagi obrazu środkowego ostatnie 9 to wagi elementów obrazu za obrazem filtrowanym dzięki takiemu podejściu możliwe jest wykonanie dowolnego filtru, przez zmianę wag.
Postępowanie w przypadku filtrowania 3D. W celu uproszcenia rozumowania zakładamy, że filtrowany będzie drugi obraz z obrazu 3D na podstawie pierwszego, drugiego i trzeciego. Dodatkowo zakładamy, że kostka 3D ma wymiary 512*512*83, gdzie: 512*512 to szerokoscX * wysokoscY dla każdego obrazka 83 to liczba obrazów w kostce 3D Pierwszym zadaniem jest stworzenie tablicy o ilości elementów równej szerokoscX*3*3 czyli 512*3*3, a następnie pobraniu do niej danych z pierwszego, drugiego i trzeciego obrazu, z tym że pobieramy 3 pierwsze rzędy z pierwszego obrazu, trzy pierwsze rzędy z drugiego, oraz 3 pierwsze rzędy z trzeciego obrazu. Następnie tworzona jest tablica trójwymiarowa i pobrane dane są do niej wpisywane w taki sposób, że powstaje trójwymiarowy obraz, o długości x = 512, wysokości y = 3, oraz głębokości z = 3, jest to obraz, którego środek będzie filtrowany. Ostatnim zadaniem jest wykonanie obliczeń filtrujących dla każdego piksela wewnątrz powstałego obrazu czyli dla pikseli o współrzędnych x = 1 .. 510, y = 2, z = 2. Piksele x = 0 oraz x = 511 są przepisywane. Ostatecznie otrzymano zbiór 512 pikseli umieszczonych w tablicy, które reprezentują przefiltrowany środek stworzonego obrazu. Ten rezultat jest zapisywany w pliku wynikowym i następuje powtórzenie wszystkich wykonanych czynności,z tym, że pobierane są kolejne 3 rzędy danych 2,3,4 z wszystkich trzech obrazów. Całe wyżej zammieszczone postępowanie zostało zawarte w funkcji Filtr3d(int tabl[27]).
Filtr negatyw jako jedyny nadawał się do zastosowania wektoryzacji obliczeń. Spowodowne to było tym, że każdy piksel jest tylko zanegowany i niezależny od otoczenia. Na każdym pikselu kolejno wykonujemy tą samą operację. Wektoryzacja pozwoliła na wykonywanie tych samych obliczeń na 16 pikselach jednocześnie
#include <stdlib.h> #include <stdio.h> #include <spu_mfcio.h> #include <single_buffer.h> #include <dma_example.h> //#define USE_TIMER 1 #ifdef USE_TIMER #include <spu_timer.h> #endif /* USE_TIMER */ unsigned char local_buffer[CHUNK_SIZE] __attribute__ ((aligned (128))); unsigned char local_buffer1[szerokoscX*9] __attribute__ ((aligned (128))); unsigned char wynik[szerokoscX] __attribute__ ((aligned (128))); unsigned char local_buffer2[szerokoscX] __attribute__ ((aligned (128))); unsigned char local_buffer3[szerokoscX*3] __attribute__ ((aligned (128))); control_block_t control_block __attribute__ ((aligned (128))); unsigned int tag; unsigned long long in_addr, out_addr; void Filtr2d(int tabl[9]); void Filtr3d(int tabl[27]);int main(unsigned long long speid __attribute__ ((unused)), unsigned long long argp, unsigned long long envp __attribute__ ((unused))) { int i; #ifdef USE_TIMER uint64_t start, time_working; spu_slih_register (MFC_DECREMENTER_EVENT, spu_clock_slih); spu_clock_start(); start = spu_clock_read(); #endif
tag = mfc_tag_reserve(); if (tag == MFC_TAG_INVALID) { printf ("SPU ERROR, unable to reserve tag\n"); return 1; }
mfc_get (&control_block, argp, sizeof (control_block_t), tag, 0, 0);
mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all ();
if(control_block.pad[0] == 3) { printf("SPU %d work pad = %d\n",control_block.id,control_block.pad[1]); int j; for (j=0; j<control_block.pad[1]; j++) {
if(j!=0) { control_block.in_addr += szerokoscX*wysokoscY; control_block.out_addr += szerokoscX*wysokoscY; } for (i = 0; i <szerokoscX; i++) { int k; in_addr = control_block.in_addr + (i* CHUNK_SIZE * sizeof(unsigned char)); out_addr = control_block.out_addr + (i* CHUNK_SIZE * sizeof(unsigned char)); mfc_get (local_buffer2, in_addr, szerokoscX * sizeof(unsigned char), tag, 0, 0); mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all (); for (k=0; k< szerokoscX ; k++) { if (*(local_buffer2+k) > control_block.pad[2]) *(local_buffer2+k) = 255; else *(local_buffer2+k)= 0; }
mfc_put (local_buffer2, out_addr, szerokoscX * sizeof(unsigned char), tag, 0, 0); mfc_write_tag_mask (1 << tag);
mfc_read_tag_status_all (); } } }
if (control_block.pad[0] == 4) { printf("\nSPU %d work, liczba obr do przetworzenia = %d",control_block.id,control_block.pad[1]); int tab_wag[27] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; Filtr3d(tab_wag); }
if (control_block.pad[0] == 8) { printf("\nSPU %d work, liczba obr do przetworzenia = %d",control_block.id,control_block.pad[1]); int tab_wag[27] = {-1,-1,-1,-1,9,-1,-1,-1,-1,-1,-1,-1,-1,9,-1,-1,-1,-1,-1,-1,-1,-1,9,-1,-1,-1,-1}; Filtr3d(tab_wag); }
if (control_block.pad[0] == 5) { printf("SPU %d work pad = %d\n",control_block.id,control_block.pad[1]); int j; for (j=0; j<control_block.pad[1]; j++) {
if (j!=0) { control_block.in_addr += szerokoscX*wysokoscY; control_block.out_addr += szerokoscX*wysokoscY; } for (i = 0; i < szerokoscX; i++) { int k; in_addr = control_block.in_addr + (i* CHUNK_SIZE * sizeof(unsigned char)); out_addr = control_block.out_addr + (i* CHUNK_SIZE * sizeof(unsigned char)); mfc_get (local_buffer2, in_addr, szerokoscX * sizeof(unsigned char), tag, 0, 0); mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all (); vector unsigned char *pixels; vector unsigned char maxPixel = (vector unsigned char) {255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255}; pixels = (vector unsigned char*) local_buffer2; for (k =0; k< szerokoscX/16 ; k++) { pixels[k] = maxPixel - pixels[k]; }
mfc_put (local_buffer2, out_addr, szerokoscX * sizeof(unsigned char), tag, 0, 0); mfc_write_tag_mask (1 << tag);
mfc_read_tag_status_all (); } } }
if (control_block.pad[0] == 6) { printf("SPU %d work pad = %d \n",control_block.id,control_block.pad[1]); int tab_wag[9] = {1,2,1,0,0,0,-1,-2,-1}; Filtr2d(tab_wag); }
if (control_block.pad[0] == 9) { printf("\nSPU %d work, liczba obr do przetworzenia = %d",control_block.id,control_block.pad[1]); int tab_wag[27] = {1,2,1,0,0,0,-1,-2,-1,1,2,1,0,0,0,-1,-2,-1,1,2,1,0,0,0,-1,-2,-1}; Filtr3d(tab_wag); }
if (control_block.pad[0] == 7) { printf("SPU %d work pad = %d\n",control_block.id,control_block.pad[1]); int tab_wag[9] = {1,0,-1,2,0,-2,1,0,-1}; Filtr2d(tab_wag); }
if (control_block.pad[0] == 10) { printf("SPU %d work pad = %d \n",control_block.id,control_block.pad[1]); int tab_wag[9] = {-1,-1,-1,-1,9,-1,-1,-1,-1}; Filtr2d(tab_wag); }
#ifdef USE_TIMER time_working = (spu_clock_read() - start); spu_clock_stop(); printf ("SPE time_working = %lld\n", time_working); #endif return 0; } void Filtr2d(int tabl[9]) { int j,i; for (j=0; j<control_block.pad[1]; j++) { if (j!=0) { control_block.in_addr += szerokoscX*wysokoscY; control_block.out_addr += szerokoscX*wysokoscY; } for (i = 0; i < szerokoscX-2; i++) { in_addr = control_block.in_addr+(i* CHUNK_SIZE * sizeof(unsigned char)); out_addr = control_block.out_addr + (i* CHUNK_SIZE * sizeof(unsigned char)); mfc_get (local_buffer3, in_addr, szerokoscX*3 * sizeof(unsigned char),ctag, 0, 0); mfc_write_tag_mask (1 << tag); mfc_read_tag_status_allc();
int tab2d[szerokoscX][3];
int x,y,k,zm=0; for(y=0;y<3;y++) for(x=0;x<szerokoscX;x++) { tab2d[x][y] = local_buffer3[zm++]; } for (k=1; k< szerokoscX-2; k++) { wynik[k] = ( tabl[0]*tab2d[k-1][0] + tabl[1]*tab2d[k][0] + tabl[2]*tab2d[k+1][0] + tabl[3]*tab2d[k-1][1] + tabl[4]*tab2d[k][1] + tabl[5]*tab2d[k+1][1] + tabl[6]*tab2d[k-1][2] + tabl[7]*tab2d[k][2] + tabl[8]*tab2d[k+1][2] )/9; } wynik[0] = ( tabl[0]*tab2d[k][0] + tabl[1]*tab2d[k+1][0] + tabl[3]*tab2d[k][1] + tabl[4]*tab2d[k+1][1] + tabl[6]*tab2d[k][2] + tabl[7]*tab2d[k+1][2] )/6; wynik[szerokoscX-1] = ( tabl[1]*tab2d[k-1][0] + tabl[2]*tab2d[k][0] + tabl[4]*tab2d[k-1][1] + tabl[5]*tab2d[k][1] + tabl[7]*tab2d[k-1][2] + tabl[8]*tab2d[k][2] )/6;
mfc_put (wynik, out_addr+szerokoscX, szerokoscX * sizeof(unsigned char), tag, 0, 0); mfc_write_tag_mask (1 << tag);
mfc_read_tag_status_all (); } } } void Filtr3d(int tabl[27]) { int j,i; for (j=0; j<control_block.pad[1]; j++) {
if (j!=0) { control_block.in_addr += szerokoscX*wysokoscY; control_block.out_addr += szerokoscX*wysokoscY; } for (i = 0; i < szerokoscX-2; i++) { in_addr = control_block.in_addr+ (i* CHUNK_SIZE * sizeof(unsigned char)); out_addr = control_block.out_addr + (i* CHUNK_SIZE * sizeof(unsigned char));
mfc_get (local_buffer1, in_addr, szerokoscX*3 * sizeof(unsigned char), tag, 0, 0); mfc_get (local_buffer1+szerokoscX*3, in_addr+szerokoscX*wysokoscY, szerokoscX*3 * sizeof(unsigned char), tag, 0, 0); mfc_get (local_buffer1+szerokoscX*6, in_addr+szerokoscX*wysokoscY*2, szerokoscX*3 * sizeof(unsigned char), tag, 0, 0);
mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all ();
int tab3d[szerokoscX][3][3];
int x,y,z,k,zm=0; for (z=0; z<3; z++) for(y=0;y<3;y++) for(x=0;x<szerokoscX;x++) { tab3d[x][y][z] = local_buffer1[zm++]; }
for (k=1; k<szerokoscX-1; k++) { wynik[k] = ( tabl[0]*tab3d[k-1][0][0] + tabl[1]*tab3d[k][0][0] + tabl[2]*tab3d[k+1][0][0] + tabl[3]*tab3d[k-1][1][0] + tabl[4]*tab3d[k][1][0] + tabl[5]*tab3d[k+1][1][0] +
tabl[6]*tab3d[k-1][2][0] + tabl[7]*tab3d[k][2][0] + tabl[8]*tab3d[k+1][2][0] + tabl[9]*tab3d[k-1][0][1] + tabl[10]*tab3d[k][0][1] + tabl[11]*tab3d[k+1][0][1] + tabl[12]*tab3d[k-1][1][1] + tabl[13]*tab3d[k][1][1] + tabl[14]*tab3d[k+1][1][1] +
tabl[15]*tab3d[k-1][2][1] + tabl[16]*tab3d[k][2][1] + tabl[17]*tab3d[k+1][2][1] + tabl[18]*tab3d[k-1][0][2] + tabl[19]*tab3d[k][0][2] + tabl[20]*tab3d[k+1][0][2] + tabl[21]*tab3d[k-1][1][2] + tabl[22]*tab3d[k][1][2] + tabl[23]*tab3d[k+1][1][2] +
tabl[24]*tab3d[k-1][2][2] + tabl[25]*tab3d[k][2][2] + tabl[26]*tab3d[k+1][2][2] ) / 27; }
wynik[0] = 1; wynik[szerokoscX-1] = 1; mfc_put (wynik, out_addr+szerokoscX*wysokoscY+szerokoscX, szerokoscX * sizeof(unsigned char), tag, 0, 0);
mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all (); } } }
Główna funkcja pliku - main | |
Rezerwujemy tag MFC do użytku | |
DMA - blok kontrolny informacji z pamięci systemowej | |
Oczekiwanie na zakończenie DMA | |
Filtr - Binaryzacja | |
Ustawienie adresu obrazka do przetworzenia przez watek | |
Zapisanie danych w pliku wyjsciowym | |
Oczekiwanie na zakonczenie zapisu | |
Filtr - Uśrednianie 3D | |
Filtr - Usuń średnią 3D | |
Filtr - Negatyw | |
Ustawienie adresu obrazka do przetworzenia przez watek | |
Zapisanie danych w pliku wyjsciowym | |
Oczekiwanie na zakonczenie zapisu | |
Filtr - Sobel Poziomy 2D | |
Filtr - Sobel Poziomy 3D | |
Filtr - Sobel Pionowy 2D | |
Filtr - Usuń Średnią 2D | |
Koniec Filtrów | |
Przekształcenie na tablice 2d | |
Obrócona | |
Zapisanie danych w pliku wyjsciowym | |
Oczekiwanie na zakonczenie zapisu | |
Ustawienie adresu obrazka do przetworzenia przez watek | |
Potrzeba 3 getów po 512*3 | |
Oczekiwanie na DMA | |
Tworzenie i wypelnianie tablicy 3d pobranymi danymi | |
Obrócona | |
Średnia | |
Rzad przedni | |
Rzad środkowy | |
Rzad tylnii | |
Dopisanie wartosci skrajnych , mozna tez uśrednic jak bedzie czas | |
Oczekiwanie na DMA |
Wynik działania programu (poszczególnych filtrów) możemy zobaczyć na poniższych obrazach.
Binaryzacja
Filtr binaryzacji przechodzi przez cały obraz .raw piksel po pikselu i sprawdza, czy piksel przekroczył próg podany jako parametr. Jeśli tak, to nastęuje zmiana wartości piksela na 255. W przeciwnym razie następuje zmiana wartości na 0.
Negatyw
Filtr negatyw daje na wyjściu obraz 'zanegowany', wartosc każdego piksela jest obliczana jako różnica: od maksymalnej wartości piksela odejmujemy jego obecną wartość.
Filtry Sobela. Przy filtracjach można wzmacniać wplyw bezposrednio najbliższego otoczenia piksela przetwarzanego.
Wyróżniamy dwie maski Sobela, poziomą i pionową. Dodatkowo w programie wykorzystalimy maskę poziomą 3D, która pozwala braz pod uwagę otoczenie 'w gląb'. Warto zauważyć, że maski Sobela mogą być swobodnie obracane nie tylko o 90 stopni, co pozwala na wyznaczanie gradientów w różnych kierunkach.
Usrednianie
Filtr usrednianie (rozmycie) zamazuje krawędzie obrazu, usuwa szumy. Brane jest pod uwagę otoczenie punktu (w trzech wymiarach).
Usun srednią 3D
Filtr usuń srednią (wyostrzanie) uwydatnia krawędzie obrazu oraz dodaje szumy. Brane jest pod uwagę otoczenie punktu (w trzech wymiarach).
Podczas pracy nad projektem zauważono, że zastosowanie przetwarzania równoległego podczas operacji na plikach graficznych, jest o wiele wydajniejsze, niż przetwarzanie obrazu jako jeden wątek. Zysk czasowy jest na tyle duży, że pomimo większych opóźnień wynikających z konieczności synchronizacji wątków, ich wzajemnej komunikacji, oraz podziału zadań pomiędzy poszczególne wątki, wynik końcowy(czyli czas przetwarzania) jest kilkakrotnie krótszy niż w przypadku przetwarzania przez jeden wątek.
Najszybciej działającym filtrem jest negatyw, gdyż zastosowano tam dodatkowo wektoryzację. Użyte rozwiązanie pozwoliło na 16 krotne zwiększenie szybkości wykonywanych obliczeń przez każdy z wątków spe.
Najwolniej działają filtry działające na całym otoczenie 3d punktu (Usrednianie3d, Usun Srednia3d, Sobel Poziomy3d), gdyż wykonują one najwięcej obliczeń, oraz najczęściej sięgają do pamięci w celu pobrania danych.
Aby program działał optymalnie liczba wątków spe, które mają zostać utworzone(istnieje możliwość wyboru ilości wątków) powinna być podzielna przez ilość obrazów w kostce 3d lub reszta z dzielenia powinna być jak najmniejsza. Takie podejście sprawi, że każdy z wątków dostanie równą część obrazu do przetworzenia. Jeśli warunek nie będzie spełniony to pierwszy z wątków dostanie swoją część obrazu 3d do przetworzenia, oraz pozostałą resztę, co może sprawić że w przypadku dużej reszty większość obliczeń wykona się w jednym wątku.
Aby zobrazować przydatnosc stosowania zrównoleglenia obliczeń poniżej przedstawiono wykresy. Przedstawiają one zależnosć czasu obliczeń od liczby wątków.
Można zauważyć, że nie zawsze zwiększenie ilości wątków sprawia, że program wykonuje się szybciej. W niektórych przypadkach po zwiększeniu ilości wątków spe otrzymujemy dłuższy czas wykonania niż, przy mniejszej ilości wątków. Najlepszym rozwiązaniem dla wszystkich filtrów jest ustalinie ilości wątków pomiędzy 4 - 8, gdyż wtedy otrzymujemy najlepsze wyniki czasowe.
Test szybkosci przeprowadzono dla 4 filtrów, dla każdego obraz podstawowy przetwarzano 30 razy (kolejno dla 1, 2, 3 itd wątków).
Rysunek 7.8. czasy sekwencyjnego wykonania poszczególnych filtrów przez nasz program. (1) Filtr 3D, (2) Filtr 2D, (3) Binaryzacja, (4) Negatyw
Kolejny wykres przedstawia wyniki badania filtru 3D. Można zauważyć, iż już przy 2 wątkach czas przetwarzania skraca sie o 1/3. Dla 6 wątków zaoszczędzamy juz 2/3 czasu potrzebnego do przetworzenia obrazu podstawowego.
Wykres trzeci obrazuje badanie filtru 2D. Wykres zależnośći czasu od liczby wątków spe dla filtru 2d pokazuje, że początkowo uzyskujemy duży zysk czasowy, a nastęnie wyniki ustalają się w przedziale pomiędzy 0,2s a 0,4s. Wybór ilości wątków(powyżej 4) nie ma większego wpływu na szybkość wykonywanego programu.
Binaryzacja jest filtrem, który wykonuje się bardzo szybko. Czas wykonania sekwencyjnego wynosi ok 0,19s. Po zastosowaniu zrównoleglenia czas wykonania bardzo szybko spada do wartości 0,05s już przy 5 wątkach spe. Powyżej 20 wątków czas zaczyna minimalnie wzrastać.
Wykres przedstawia zależność czasu wykonania programu od ilości wątków spe. Zauważa się, że wraz ze wzrostem ilości wątków powyżej 7 czas wykonania filtru zaczyna wzrastać. Optymalna liczba wątków które powinny działać równolegle zawiera się w przedziel pomiędzy 2 a 7. Warto również zauważyć, że czas wykonania negatywu jest najkrótszy z wszystkich filtrów, spowodowane jest to użyciem dodatkowo wektoryzacji.