Upload
vantruc
View
226
Download
0
Embed Size (px)
Citation preview
Politechnika Warszawska
Wydział Elektroniki i Technik Informacyjnych
Instytut Informatyki
Rok akademicki 2010/2011
Praca dyplomowa inżynierska
Stanisław Ogórkis
Kompresja falkowa w środowiskuNVIDIA CUDA
Opiekun pracy:
mgr inż. Julian Myrcha
Ocena . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Podpis Przewodniczącego
Komisji Egzaminu Dyplomowego
Specjalność: Informatyka –
Inżynieria systemów informatycznych
Data urodzenia: 15 lutego 1988 r.
Data rozpoczęcia studiów: 1 października 2007 r.
Życiorys
Nazywam się Stanisław Ogórkis i urodziłem się 15 lutego 1988 r. w Suwałkach. Po
ukończeniu gimnazjum, kontynuowałem naukę w I L.O. im. Marii Konopnickiej w Su-
wałkach. W szkole średniej uczęszczałem do klasy o profilu matematyczono-fizycznym. W
październiku 2007 r. rozpocząłem studia na Wydziale Elektroniki i Technik Informacyj-
nych Politechniki Warszawskiej.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
podpis studenta
Egzamin dyplomowy
Złożył egzamin dyplomowy w dn. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Z wynikiem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ogólny wynik studiów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Dodatkowe wnioski i uwagi Komisji . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Streszczenie
Głównym celem pracy jest zaimplementowanie kompresora obrazów i sekwencji wideo
wykorzystującego transformatę falkową. Ponadto wykorzystana jest technologia NVIDIA
CUDA pozwalająca na wykonywanie obliczeń na procesorze karty graficznej. Zaprezento-
wane są algorytmy związane z transformatą falkową, kwantyzacją i kodowaniem arytme-
tycznym.
W pracy przedstawione są podstawy platformy NVIDIA CUDA. Szczególny nacisk poło-
żony jest na możliwości realizacji części algorytmów na procesorze graficznym. Wykonany
koder jest zweryfikowany pod kątem efektywności implementacji GPU.
Słowa kluczowe: transformata falkowa, falki, kompresja stratna, kwantyzacja, cuda.
Abstract
Title: Wavelet compression in NVIDIA CUDA.
The main goal of this thesis is to implement image and video sequence wavelet com-
pressor. The compressor is implemented using NVIDA CUDA technology which allows
computing on GPU. The algorithms for wavelet transform, quantization and arithmetic
coding are presented.
The thesis describes basics of the NVIDA CUDA architecture. The particular emphasis
is placed on the feasibility of the algorithms on the GPU. Created encoder is verified for
GPU implementation effectiveness.
Key words: wavelet transform, wavelets, lossy compression, quantization, cuda.
Spis treści
1 Wstęp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1 Zdefiniowanie problemu . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Cele pracy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Zawartość pracy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Transformata falkowa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1 Informacje ogólne . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Funkcja falkowa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3 Realizacja dyskretnej transformaty falkowej przy użyciu filtrów . . . 6
2.4 Przykład obliczenia transformaty i transformaty odwrotnej . . . . . 7
2.5 Współczynniki filtrów falkowych . . . . . . . . . . . . . . . . . . . . 9
2.6 Transformata sygnałów wielowymiarowych . . . . . . . . . . . . . . 11
2.7 Algorytm konwolucji . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Metody kwantyzacji . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.1 Skalarna kwantyzacja równomierna . . . . . . . . . . . . . . . . . . 14
3.2 Skalarna kwantyzacja nierównomierna . . . . . . . . . . . . . . . . 14
3.3 Skalarna kwantyzacja równomierna z rozszerzonym przedziałem ze-
rowym . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4 Kodowanie arytmetyczne . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.1 Kodowanie arytmetyczne oparte o arytmetykę rzeczywistoliczbową . 17
4.2 Przykład . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.3 Kodowanie arytmetyczne oparte o arytmetykę całkowitoliczbową . . 19
4.4 Koder adaptacyjny . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5 Technologia NVIDIA CUDA . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.1 Efektywność obliczeniowa procesorów graficznych . . . . . . . . . . 22
5.2 Model programowania . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.3 Funkcje kerneli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.4 Hierarchia wątków . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.5 Hierarchia pamięci . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6 Opis proponowanego rozwiązania . . . . . . . . . . . . . . . . . . . . . . . 29
6.1 Wymagania dotyczące aplikacji . . . . . . . . . . . . . . . . . . . . 29
6.2 Funkcje aplikacji . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1
6.3 Architektura rozwiązania . . . . . . . . . . . . . . . . . . . . . . . . 31
7 Implementacja projektu . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
7.1 Platforma sprzętowa . . . . . . . . . . . . . . . . . . . . . . . . . . 39
7.2 Narzędzia i biblioteki . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.3 Algorytm obliczania transformaty falkowej na procesorze GPU . . . 41
7.4 Mechanizmy pomiaru czasu . . . . . . . . . . . . . . . . . . . . . . 44
8 Weryfikacja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
8.1 Zbiór testowy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
8.2 Precyzja obliczeń . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.3 Efektywność kompresji . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.4 Szybkość działania . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
9 Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2
1 Wstęp
1.1 Zdefiniowanie problemu
Uzyskanie możliwie najkrótszej reprezentacji danych jest głównym kryterium stoso-
wanym przy ocenie algorytmów kompresji. Wraz z rozwojem technik multimedialnych
oraz wykorzystania komputerów w medycynie oraz inżynierii, wzrosło zapotrzebowanie
na skuteczne kompresowanie obrazów i sekwencji wideo. Metody bezstratne umożliwia-
ją dla typowych obrazów uzyskiwanie współczynników kompresji do około 4 : 1. Aby
uzyskać krótszą reprezentację wykorzystujemy algorytmy, które nie gwarantują jednako-
wego odtworzenia skompresowanych danych. Wówczas poza kryterium stopnia kompresji
i szybkości działania ważna staje się jakość odtworzonych danych.
Przez lata powstał szereg metod stratnej kompresji danych. Ich główna idea opiera
się na odrzuceniu tych informacji, które są najmniej istotne dla odbiorcy. Dzięki temu
w kolejnych krokach możemy skuteczniej kompresować dane przy użyciu standardowych
algorytmów kompresji bezstratnej. Powszechnie stosowane algorytmy wykorzystują trans-
formaty, które zmieniają dziedzinę danych na taką gdzie energia sygnału koncentruje się w
mniejszej liczbie współczynników niż ma to miejsce w pierwotnej reprezentacji [2] [10]. Na
przykład, po zastosowaniu transformaty kosinusowej dla bloków 8x8 obrazu w standarcie
JPEG, energia sygnału kumuluje się lewej górnej części macierzy współczynników. Kwan-
tyzacja uzyskanych współczynników jest krokiem, który decyduje o jakości odtworzonych
danych. W tym kroku selektywnie rezygnujemy z części danych, aby uzyskać uproszczoną
reprezentację, która lepiej poddaje się kompresji bezstratnej.
Najnowsze algorytmy kompresji (JPEG2000, REDCODE) jako transformatę wyko-
rzystują funkcje falkowe. Pierwszą funkcję falkową opisał na początku XX wieku Haar,
ale wstępne próby ich zastosowania do kompresji pojawiły się dopiero po pracach Ingrid
Daubechies [4] oraz Stephane Mallat [9]. Transformata falkowa umożliwia wielokrotnie
powtarzaną dekompozycję danych na składowe wysoko i niskoczęstotliwościowe. Dzię-
ki temu na niewielkiej liczbie współczynników możemy skoncentrować większość energii
sygnału związanej z pasmem niskoczęstotliwościowym. Równie ważną cechą jest separo-
walność funkcji falkowych, która umożliwia liczenie transformat wielowymiarowych przez
odpowiednie złożenie transformat jednowymiarowych.
Wyznaczenie współczynników transformaty dla przypadku danych więcej niż dwu-
wymiarowych wymaga znacznych nakładów obliczeniowych. Przy ciągłym rozwoju gra-
fiki komputerowej, współczesne akceleratory graficzne posiadające setki rdzeni uzyskały
większą moc obliczeniową niż jednostki centralne. Udostępnione na początku 2007 roku
API NVIDIA CUDA umożliwia programowanie karty graficznej do wykonywania obli-
czeń (GPU Computing). Kompresja falkowa bardzo dobrze nadaje się do implementacji
na GPU dzięki możliwości zrównoleglenia obliczeń.
3
1.2 Cele pracy
Celem niniejszej pracy jest realizacja kompresji obrazów oraz sekwencji wideo przy
użyciu metod falkowych. Szczególny nacisk został położony na porównanie zwykłej im-
plementacji z implementacją bazującą na równoległych obliczeniach zrealizowanych na
procesorze karty graficznej. Sprawdzona została również efektywność kompresji różnych
algorytmów kwantyzacji i funkcji falkowych.
Praca zawiera kompletny opis algorytmów wykorzystanych w poszczególnych krokach
kompresji. Przedstawiłem też kilka przykładów pozwalających na zrozumienie działania
poszczególnych metod. Umieściłem również krótki opis technologii NVIDIA CUDA. Dzięki
temu praca może służyć jako materiał dydaktyczny dla osób chcących zapoznać się z
zastosowaniem metod falkowych w kompresji, jak również z wykonywaniem obliczeń na
GPU.
1.3 Zawartość pracy
Rozdział drugi zawiera wstęp teoretyczny dotyczący transformaty falkowej. Przedsta-
wiłem w nim algorytmy obliczenia transformaty wraz z przykładem. W rozdziale trzecim
przedstawione są różne metody kwantyzacji skalarnej. Rozdział czwarty opisuje metodę
kompresji bezstratnej jaką jest kodowanie arytmetyczne i zawiera przykład kodowania
i dekodowania wybranego ciągu. Rozdział piąty jest w całości poświęcony technologii
NVIDIA CUDA, skupiając się na elementach wykorzystanych w implementacji. W roz-
dziale szóstym znajduje się opis proponowanego rozwiązania, a w rozdziale siódmym opis
implementacji. Rozdział ósmy przedstawia wyniki testów wykonanej aplikacji. Ostatni,
dziewiąty rozdział zawiera podsumowanie całej pracy.
4
2 Transformata falkowa
2.1 Informacje ogólne
Transformaty wykorzystywane w przetwarzaniu sygnałów wykorzystują iloczyn ska-
larny badanego sygnału przez część nazywaną jądrem przekształcenia. Wykonanie takiego
działania na sygnale pozwala uzyskać jego reprezentację w nowej dziedzinie, która może
być korzystniejsza dla jego reprezentacji bądź analizy.
Transformata falkowa, podobnie jak transformata Fouriera należy do przekształceń
całkowych. Podstawowy wzór opisujący transformatę falkową funkcji ciągłej x(t) ma po-
stać:
X(a, b) =1√a
∫ ∞−∞
x(t)ψ∗(t− ba
)dt (1)
gdzie współczynnik a to czynnik skalujący, b to przesunięcie, ψ∗ to sprzężenie zespolone
falki bazowej nazywanej falką matką (mother wavelet). Wykorzystując współczynniki a i
b jesteśmy w stanie wygenerować całą rodzinę falek umożliwiając reprezentację badanej
funkcji na różnym poziomie szczegółowości. Do odtworzenia pierwotnego ciągłego sygnału
wykorzystujemy transformatę odwrotną o postaci:
x(t) =∫ ∞
0
∫ ∞−∞
1a2X(a, b)
1√|a|ψ∼
(t− ba
)dbda (2)
gdzie ψ∼ jest funkcją odwrotną do ψ∗.
2.2 Funkcja falkowa
W najbardziej znanej transformacie Fouriera jako jądro wykorzystywane są funkcje
sinusoidalne. Transformata falkowa nie ma ściśle określonej funkcji jądra [1]. Może być
nią dowolna funkcja spełniająca następujące warunki:
1. Jej wartość średnia ma mieć wartość 0
∫ ∞−∞
ψ(x)dx = 0
2. Musi mieć skończone pasmo przenoszenia, tj. poza określonym zakresem jej wartość
jest równa 0
3. Musi mieć odpowiadającą funkcję skalującą ϕ(t)
5
2.3 Realizacja dyskretnej transformaty falkowej przy użyciu fil-
trów
Na potrzeby cyfrowego przetwarzania sygnałów wykorzystywana jest dyskretna trans-
formata falkowa (DWT - Discrete Wavelet Transform) [2] [10] [19]. Mnożenie we wzorach
1 i 2 przekłada się na obliczenie splotu ciągów, które w dziedzinie cyfrowego przetwa-
rzania sygnałów wyrażane są przez operację filtracji. Aby efektywnie zaimplementować
transformatę falkową należy najpierw wyznaczyć zestawy filtrów.
Dzięki właściwościom funkcji falkowych możemy wygenerować całą rodzinę funkcji
falkowych do reprezentacji sygnału na różnym poziomie szczegółowości. Dla danej funk-
cji falkowej należy wyliczyć współczynniki filtru górno i dolnoprzepustowego (oznaczane
odpowiednio H0 i H1) oraz filtrów syntezy (F0 i F1). Sygnał poddawany jest filtracji i
uzyskujemy na wyjściu filtra dolno przepustowego składowe niskoczęstotliwościowe (yL),
a na wyjściu filtru górno przepustowego składowe wysokoczęstotliwościowe (yH).
yL[n] = (x ∗H1)[n]
yH [n] = (x ∗H0)[n](3)
W wyniku tego działania dostajemy jednak dwa wektory współczynników o długości
równej wektorowi wejściowemu. Tą nadmiarowość redukujemy usuwając co drugą próbkę
z tych wektorów (symbolicznie oznaczane jest to przez ↓ 2) i mnożąc przez√
2. Sygnał
poddany takiej filtracji podzielony jest na dwa pasma i aby wrócić do jego poprzedniej
postaci należy najpierw przywrócić pierwotną ilość próbek. Wykonuje się to poprzez wsta-
wienie zer w miejsca poprzednio usuniętych wartości (oznaczane przez ↑ 2). Jako że prób-
ki nieusunięte zostały pomnożone przez√
2 moc sygnału jest zachowana. Tak otrzymane
wartości poddawane są filtracji odwrotnej przez bank filtrów F0 i F1 i sumowane.
Rysunek 1: Bank filtrów analizy i syntezy
Na rysunku 1 przedstawiony został bank filtrów realizujący dekompozycję, a następnie
syntezę danego sygnału x. W pracach [1], [19] zostały szczegółowo podane wyprowadzenia
6
wzorów jakie mają spełniać zestawy filtrów H0H1F0F1, aby sygnał po przejściu przez nie
został identycznie zrekonstruowany. Mają one postać:
H0(z)F0(z) +H1(z)F1(z) = 2
H0(−z)F0(z) +H1(−z)F1(z) = 0
Wyznaczając odpowiednie wartości współczynników filtrów jesteśmy w stanie efektyw-
nie implementować algorytmy transformaty falkowej. Dekompozycja sygnału może być
kontynuowana w podpaśmie najniżej częstotliwości. Na rysunku 2 przedstawiono trzy-
poziomowy schemat dekompozycji wraz z podziałem pasma częstotliwości. Obliczanie
transformaty dla poziomu drugiego odbywa się w dolnym paśmie częstotliwości, który
stanowi połowę długości wejściowego sygnału.
Rysunek 2: Podział pasma częstotliwości w banku filtrów analizy
Współczynniki transformaty falkowej z poszczególnych poziomów dekompozycji po-
krywają całe spektrum częstotliwości. Nie tracimy więc żadnej informacji w wyniku wy-
konania tego przekształcenia. Współczynniki zwrócone przez filtr górnoprzepustowy na-
zywamy współczynnikami detali, a współczynniki zwrócone przez filtr dolnoprzepustowy
współczynnikami uśrednionymi.
2.4 Przykład obliczenia transformaty i transformaty odwrotnej
Obliczanie transformaty oraz transformaty odwrotnej zostanie przedstawione przy wy-
korzystaniu falki Haara. Transformacie zostanie poddany ośmioelementowy ciąg liczbowy.
Wykonana zostanie trzypoziomowa dekompozycja sygnału wejściowego. Najpierw przed-
stawię postać falki Haara.
7
Falka Haara
Falka Haara jest najstarszą i najprostszą funkcją falkową. Wartość średnia funkcji ψ(x)
wynosi 0. Ma ona również ograniczony nośnik (poza określonym zakresem jej wartość jest
równa 0) i posiada odpowiednią funkcję skalującą. Spełnione są więc wszystkie warunki
stawiane funkcji falkowej. Podstawowy wzór opisujący falkę Haara ma postać:
ψ(x) =
1 dla 0 ¬ t < 1/2
−1 dla 12 ¬ t < 1
0 dla pozostałych t
a funkcję skalującą ϕ(t):
ϕ(t) =
0 dla 0 ¬ t < 1
1 dla pozostałych t
Rysunek 3: Falka Haara
Obliczenie współczynników
Załóżmy, że dany mamy ośmio elementowy ciąg:
4 2 5 5 7 5 1 3
W pierwszym kroku obliczymy współczynniki uśrednione zmniejszając jednocześnie ich
liczbę o dwa (operator ↓ 2). Dla ośmio elementowego ciągu potrzebować będziemy więc
czterech współczynników uśrednionych. Obliczenie średniej wykonujemy parami. Pierwszy
współczynnik ma wartość 3 ((4+2)/2), drugi 5 ((5+5)/2), itd. Współczynniki uśrednione
wynoszą:
8
3 5 6 2
Współczynniki detali w dziedzinie Haara zawierają utraconą w wyniku uśrednienia
informację. Pierwszy współczynnik ma wartość 1, ponieważ współczynnik uśredniony (3)
jest o 1 mniejszy od współczynnika 4 i o 1 większy od współczynnika 2. Widzimy więc,
że z jednego współczynnika uśrednionego i jednego detali jesteśmy w stanie zrekonstru-
ować dwie wartości wejściowe. Nie tracimy zatem w wyniku tego przekształcenia żadnej
informacji. Współczynniki detali wynoszą:
1 0 1 −1
Obliczanie transformaty będziemy kontynuować w dolnym podpaśmie częstotliwości,
czyli na obliczonych współczynnikach detali. W poniższej tabeli znajdują się wyniki pełnej
dekompozycji sygnału:
Poziom Współczynniki uśrednione Współczynniki detali
1 [3 5 6 2] [1 0 1 −1]
2 [4 4 ] [−1 2]
3 [4] [0]
W przedstawiony sposób obliczyliśmy współczynniki transformaty falkowej przy wyko-
rzystaniu banku filtrów. Rekonstrukcja wejściowego ciągu polega na rekursywnym doda-
waniu i odejmowaniu współczynników detali do współczynników uśrednionych na poszcze-
gólnych poziomach dekompozycji. Ostatecznie obliczone współczynniki mają wartości:
4 0 −1 2 1 0 1 −1
Warto od razu zauważyć korzyść wynikającą z przejścia do bazy współczynników
Haara. Energia sygnału kumuluje się w współczynnikach pasma dolnoprzepustowego, a
współczynniki detali mają bardzo małą amplitudę i ich wartości są bliskie zeru. Wiele z
tych współczynników można przybliżyć do zera wprowadzając jedynie niewielki błąd przy
rekonstrukcji. Tak przedstawiony ciąg lepiej poddaje sie kompresji przy użyciu koderów
entropijnych.
2.5 Współczynniki filtrów falkowych
W przedstawionym wcześniej przykładzie użyłem najprostszej falki, czyli falki Haara.
Do kompresji sygnałów wykorzystuje się zazwyczaj bardziej skomplikowane funkcje falko-
we umożliwiające uzyskanie mniejszych zniekształceń po wykonaniu kwantyzacji współ-
czynników. Filtry mają wówczas większą długość przez co zwiększa się koszt obliczenia
pojedynczego współczynnika.
9
W mojej aplikacji wykorzystałem filtry ortogonalne Haar (haar) oraz Daubechies o
długościach 4 i 6 (daub4 i daub6 ). Filtry ortogonalne Daubechies o różnych długościach
konstruuje się według specjalnego wzoru przedstawionego m.in w pracy [4]. Filtr o długości
2 sprowadza się do filtru Haara. Filtry ortogonalne cechują się m.in. tym, że współczynniki
banków analizy i syntezy są identyczne.
Zaimplementowałem również filtry biortogonalne CDF97 (Cohen-Daubechies-
Feauveau) oraz antonini (z pracy [10]). Filtry biortogonalne mają znaczenie lepsze cechy je-
śli chodzi o kompresję. Filtr CDF97 jest wykorzystywany m.in. w standardzie JPEG2000.
Filtry biortogonalne zapewniają rekonstrukcję sygnału. Banki filtrów analizy i syntezy nie
mają jednak identycznych współczynników.
W tabeli 1 znajdują się współczynniki banków filtrów dla falek ortogonalnych Haar,
Daub4 i Daub6, natomiast w tabelach 2 i 3 współczynniki dla falki CDF97 i antonini.
Ortogonalność zapewnia to, że do opisania falki wystarczą dwa zestawy współczynników.
W przypadku falek biortogonalnych potrzeba czterech zestawów współczynników. Filtry
falki CDF97 mają centrum w środkowym współczynniku (ma to znaczenie przy obliczaniu
splotu z ciągiem wejściowym). Szczegółowy opis takich własności funkcji falkowych jak
ortogonalność/biortogonalność, liczba momentów zanikających (vanishing moments), czy
ostrość (sharpness) opisana jest w pracy [9].
Indekswspółczynnika
haarlow haarhigh daub4low daub4high daub6low daub6high
0 1 1 0.6830127 -0.830127 0.47046721 0.049817501 1 -1 1.1830127 -0.3169873 1.14111692 0.120832212 0.3169873 1.1830127 0.65036500 -0.190934423 -0.1830127 -0.6830127 -0.19093442 -0.65036504 -0.12083221 1.141116925 0.0498175 -0.47046721
Tabela 1: Współczynniki filtrów falek ortogonalnych Haar, Daub4 i Daub6
Indekswspółczynnika
analysislow analysishigh synthesislow synthesishigh
-4 0.026748757411 0 0 0.026748757411-3 -0.016864118443 0.091271763114 -0.091271763114 0.016864118443-2 -0.078223266529 -0.057543526229 -0.057543526229 -0.078223266529-1 0.266864118443 -0.591271763114 0.591271763114 -0.2668641184430 0.602949018236 1.11508705 1.11508705 0.6029490182361 0.266864118443 -0.591271763114 0.591271763114 -0.2668641184432 -0.078223266529 -0.057543526229 -0.057543526229 -0.0782232665293 -0.016864118443 0.091271763114 -0.091271763114 0.0168641184434 0.026748757411 0 0 0.026748757411
Tabela 2: Współczynniki filtrów falki biortogonalnej CDF97
10
Indekswspółczynnika
analysislow analysishigh synthesislow synthesishigh
-4 8.28284032 0 0 -12.28284032-3 4.482956738 -19.543487188 -19.543487188 -8.482956738-2 -4.007083083 9.06053045 -13.06053045 2.007083083-1 9.258873244 10.364926289 10.364926289 9.2588732440 22.178753243 -22.433261231 20.433261231 -24.1787532431 9.258873244 10.364926289 10.364926289 9.2588732442 -4.007083083 9.06053045 -13.06053045 2.0070830833 -8.482956738 -19.543487188 -19.543487188 -8.4829567384 8.28284032 0 0 -12.28284032
Tabela 3: Współczynniki filtrów falki biortogonalnej antonini
2.6 Transformata sygnałów wielowymiarowych
Transformatę sygnału wielowymiarowego możemy uzyskać przez odpowiednie złożenie
transformat jednowymiarowych. Dla kompresji szczególne znaczenie mają takie metody
które koncentrują podobne współczynniki wokół siebie. W artykule [7] opisane zostały
dwie podstawowe metody dekompozycji sygnału dwuwymiarowego (obraz), aby uzyskać
jak najkorzystniejsze właściwości dla kompresji. Standard JPEG2000 wykorzystuje algo-
rytm dekompozycji Mallata, który dzieli obraz na podpasma, w których znajdują się odpo-
wiednio składowe niskoczęstotliwościowe (tło) i wysokoczęstotliwościowe (krawędzie). Na
rysunku 4 przedstawiony jest sposób dekompozycji obrazu przedstawionego jako macierz
X o wymiarach n x n.
Rysunek 4: Dekompozycja obrazu przy użyciu algorytmu Mallata
Na wejście banku filtrów na początku podawane są wektory wierszy i dokonywana
jest na nich transformata jednowymiarowa. Następnie usuwamy co drugą kolumnę. W ko-
lejnym kroku wykonujemy analogiczne działanie na wektorach kolumn. Wynik działania
takiej procedury pokazany jest na rysunku 5. Dekompozycję można kontynuować w pod-
paśmie A, aby jeszcze zmniejszyć ilość współczynników pasma niskoczęstotliwościowego.
11
Rysunek 5: Schematyczny sposób działania dekompozycji wielorozdzielczej obrazu: a)obraz orginalny; b) obraz poddany dekompozycji jednopoziomowej c) przykład działaniana obrazie Lena;
W wyniku działania tego schematu w podpaśmie A uzyskujemy obraz w niższej skali,
w podpaśmie B krawędzie pionowe, w C krawędzie poziome, a w D krawędzie ukośne.
Kontynuując dekompozycję jesteśmy w stanie uzyskać silnie upakowaną reprezentację
sygnału. Większość współczynników ma wartości zerowe bądź bardzo bliskie zeru. Po
kroku kwantyzacji uzyskamy w pasmach wysokoczęstotliwościowych bardzo długie ciągi
zer które skutecznie będą poddawały się kompresji bezstratnej. Transformata falkowa
bardzo dobrze nadaje się do reprezentacji typowych obrazów, które składają się głównie
z obszarów tła, czyli gradientów (składowe niskoczęstotliwościowe) oraz niewielkiej ilości
obszarów reprezentujących krawędzie (składowe wysokoczęstotliwościowe).
2.7 Algorytm konwolucji
Obliczenie transformaty oraz transformaty odwrotnej wymaga obliczenia wyjścia fil-
trów analizy i syntezy. Często stosowanym rozwiązaniem jest algorytm konwolucji. Dla
ciągu x i filtru h wyjście y ma postać:
y[n] = (x ∗ h)[n] =+∞∑k=−∞
= x[k]h[n− k] (4)
Przedstawione wcześniej wzory (3) na dyskretną transformatę przyjmują postać:
yL[n] = (x ∗H1)[n] =+∞∑k=−∞
= x[k]H1[2n− k]
yH [n] = (x ∗H0)[n] =+∞∑k=−∞
= x[k]H0[2n− k]
(5)
Zastosowano tutaj od razu operator ↓ 2 wyrzucania co drugiej próbki.1 Takie zastoso-
wanie algorytmu konwolucji doprowadza jednak do kilku problemów. Przede wszystkim
1czynnik H[n− k] został zamieniony na H[2n− k]
12
w praktycznej realizacji dysponujemy sygnałami oraz filtrami o skończonej liczbie współ-
czynników. Moglibyśmy oczywiście uznać, że poza zadanym zakresem wartości te są równe
zero (zero padding). W wyniku tego działania uzyskamy jednak wyjście filtru o długości
N +K−1 (N - długość sygnału wejściowego, K - liczba współczynników filtru). Usunięcie
K−1 nadmiarowych współczynników uniemożliwiłoby prawidłową rekonstrukcję sygnału.
Lepszym rozwiązaniem jest periodyczne rozszerzenie sygnału na granicach (periodic
extension). Na rysunku 6 pokazany został schemat rozszerzenia periodycznego. Po wyko-
naniu filtracji na tak roszerzonym sygnale o okresie N, otrzymamy wyjście filtru o okresie
N. Wiedząc, że rozszerzaliśmy sygnał periodycznie całą informację o wyjściu filtru mo-
żemy zapisać wykorzystując N współczynników. Periodyczne rozszerzanie sygnału x o
długości N wyraża zależność:
x[n] =
x[n] dla 0 n < N
x[−n] dla n < 0
x[N − n] dla n N
Rysunek 6: Rozszerzanie periodyczne oraz roszerzanie symetryczne
Przedstawiony schemat rozszerzania sygnału ma jednak kilka wad, które rozwiązuje
rozszerzanie symetryczne (symmetric extension). Rozszerzanie periodyczne jest ograni-
czone do sygnałów o długości podzielnej przez dwa. Tak więc dla transformaty falkowej
o D poziomach dekompozycji, sygnał musi mieć długość będącą wielokrotnością liczby
2D. Dodatkowo na granicy sygnału pojawia się nieciągłość, która powoduje większą war-
tość współczynników wysokich częstotliwości, co skutkuje większym kosztem kodowania
entropijnego. Rozwiązanie tych problemów zapewnia rozszerzanie symetryczne (rysunek
6). Działa na sygnałach dowolnej wielkości i usuwa nieciągłość na granicach. Ogólnym
celem jest uzyskanie symetrycznego sygnału wyjściowego. Schemat rozszerzania jest jed-
nak bardziej skomplikowany i zależny od zastosowanych banków filtrów. Szczegółowy opis
działania rozszerzania symetrycznego znajduje się w pracy [3].
13
3 Metody kwantyzacji
W wyniku obliczenia transformaty falkowej otrzymujemy współczynniki o wartościach
ciągłych. Aby możliwe było ich skuteczne skompresowanie koderem bezstratnym należy
najpierw je skwantyzować. Krok ten jest krokiem stratnym powodującym zniekształce-
nia zrekonstruowanego sygnału. Dobór odpowiedniego algorytmu kwantyzacji umożliwia
znaczącą redukcję alfabetu występujących symboli.
Najczęściej stosowaną miarą zniekształceń spowodowanych kwantyzacją jest miara
błędu średniokwadratowego MSE (ang. Mean Square Error). Dla sygnału X o długości n
wyraża się ona wyrażana wzorem:
MSE =1n
∑i
(x(i)− x∗(i))2 (6)
Warto zauważyć że ta miara przy jej minimalizacji dąży do globalnego zmniejszenia
błędu. Korzystne więc może być uzyskanie mniejszych błędów dla dużej liczby współczyn-
ników kosztem uzyskania znacznych błędów dla niewielkiej liczby współczynników.
3.1 Skalarna kwantyzacja równomierna
Podstawowym schematem kwantyzacji jest kwantyzacja równomierna (ang. Uniform
Quantization). Stosuje się ją dla sygnału o wartościach pomiędzy (fmin, fmax). Cały zakres
jest dzielony na L równych przedziałów o długości Q nazywanych przedziałem kwantyzacji
(rysunek 7):
Q = (fmax − fmin)/L (7)
Wartość leżąca w danym przedziale kwantyzacji jest odwzorowywana na środkową
wartość tego przedziału. W wyniku kwantyzacji uzyskujemy indeks przedziału kwantyza-
cji:
Qi(f) = bf − fminQ
c (8)
Aby przeprowadzić dekwantyzację należy znać wartości fmin i fmax oraz indeksy prze-
działów kwantyzacji dla poszczególnych symboli:
Q(f) = Qi(f)Q+Q/2 + fmin (9)
3.2 Skalarna kwantyzacja nierównomierna
Przedstawiona wcześniej metoda kwantyzacji jest optymalna jedynie dla sygnałów
o rozkładzie równomiernym. Aby zminimalizować błąd średniokwadratowy (6), należy
14
Rysunek 7: Podział na przedziały kwantyzacji w kwantyzatorze równomiernym
wykorzystać schemat, w którym przedziały kwantyzacji nie są równej wielkości. Tam
gdzie występuje skupienie wartości należy zmniejszyć przedziały kwantyzacji (rysunek 8).
Rysunek 8: Podział na przedziały kwantyzacji w kwantyzatorze nierównomiernym
Nieliniowy charakter przekształcenia wymaga, aby przesłać informację o optymalnych
przedziałach kwantyzacji do dekodera. Takie rozwiązanie zwiększa ilość potrzebnych bi-
tów, więc nie jest stosowane w kompresji. Wykorzystywane są specjalne modele, które
pozwalają na nieliniowe odwzorowanie przedziałów kwantyzacji bez wysłania dodatko-
wych informacji.
3.3 Skalarna kwantyzacja równomierna z rozszerzonym prze-
działem zerowym
Kwantyzacja równomierna z rozszerzonym przedziałem zerowym jest szczególnie efek-
tywna przy kwantyzacji współczynników transformaty falkowej. W pasmach wysokich
częstotliwości wiele współczynników ma wartości bardzo bliskie zeru. Dzięki niewielkiemu
15
rozszerzeniu przedziału zerowego przy niewielkim wzroście błędu średniokwadratowego
znacząco zwiększamy ilość współczynników o wartości 0 (rysunek 9).
Taki kwantyzator został opisany w części drugiej standartu JPEG2000 ([12]). Oznacz-
my przez ∆ przedział kwantyzacji dla kwantyzatora równomiernego. Wówczas wartość
skwantyzowana q dla elementu f wynosi:
q =
0 dla |f | ¬ −nz∆
sign(f)b |f |+nz∆∆ c dla |f | −nz∆(10)
W przedstawionym wzorze parametr nz odpowiada za szerokość przedziału zerowego i
musi być przesłany do dekodera. Szerokość przedziału zerowego wynosi więc 2(1− nz)∆.
W standardzie JPEG2000 jako potencjalnie korzystną wartość wskazuje się nz ≈ 0.25
czyli 1.5∆ ([12]). Dekwantyzację wykonuje się wykorzystując wzór:
f =
0 dla q = 0
sign(q)(|q| − nz + 0.5)∆ dla q 6= 0(11)
Rysunek 9: Podział na przedziały kwantyzacji w kwantyzatorze równomiernym z rozsze-rzonym przedziałem zerowym
16
4 Kodowanie arytmetyczne
Uzyskany w wyniku kwantyzacji strumień symboli kodowany jest koderem entropij-
nym. Metoda Huffmana wykorzystuje do zakodowania symbolu całkowitą liczbę bitów.
Takie działanie powoduje jednak, że metoda ta jest nieoptymalna. Przytaczając zamiesz-
czony w [11] przykład dla symbolu o prawdopodobieństwie 1/3 optymalna długość słowa
kodowego wynosi około 1.6 bitów. Koder Huffmana musi jednak przypisać dla niego dwa
bity przez co nie jest on optymalny. Różnica ta staje się jeszcze bardziej widoczna w
przypadku kompresji obrazów, gdy jeden z symboli stanowi np. 90 procent wszystkich
wystąpień. Wówczas optymalna długość słowa kodowego wynosi 0.15 bitów. Przypisując
symbolowi jeden bit uzyskujemy ponad sześciokrotnie gorszy wynik niż wynosi optimum.
4.1 Kodowanie arytmetyczne oparte o arytmetykę rzeczywisto-
liczbową
Kodowanie arytmetyczne to zupełnie inne podejście do kompresji ciągu symboli. Nie
wykonujemy tutaj odwzorowania każdego symbolu na zakodowany ciąg bitów. Dla ca-
łego strumienia przyporządkowujemy jedną liczbę ułamkową nazywaną liczbą kodową.
Wyznaczanie liczby kodowej odbywa się poprzez sukcesywne zacieśnianie przedziału ko-
dowego wraz z kodowaniem kolejnych symboli. Ostatecznie liczbą kodową przekazywaną
do dekodera jest dowolna liczba ułamkowa wybrana z przedziału kodowego po zakodowa-
niu wszystkich elementów. W dalszej części przedstawiony zostanie podstawowy algorytm
kompresji i dekompresji kodera arytmetycznego opartego na liczbach rzeczywistych oraz
prosty przykład.
Algorytm kompresji:
1. Określenie prawdopodobieństwa występowania symboli.
2. Podzielenie przedziału [0, 1) na podprzedziały w zależności od częstości występo-
wania symbolu. Kolejność w jakiej symbole są przypisywane do przedziałów nie ma
znaczenia, ważne, aby była taka sama w koderze i dekoderze. Podprzedziały muszą
pokrywać cały przedział początkowy i nie mogą na siebie zachodzić.
3. Czytanie symboli ze strumienia i zmiana przedziału kodowego. Po wczytaniu każ-
dego z symboli wykonujemy aktualizację górnej (high) i dolnej (low) wartości prze-
działu kodowego korzystając z zależności:
range = high − low ;
high = low + range ∗ high range ( symbol ) ;
low = low + range ∗ low range ( symbol ) ;
17
gdzie funkcja high range zwraca górną granicę, a low range zwraca dolną granicę
podprzedziału danego symbolu.
4. Wybranie liczby kodowej z pomiędzy otrzymanego przedziału. Dodatkowo aby moż-
liwe było zdekodowanie ciągu do dekodera, musi być przekazana informacja o praw-
dopodobieństwach występowania symboli oraz o liczbie zakodowanych symboli.
Algorytm dekompresji:
1. Pobranie informacji o prawdopodobieństwie występowania poszczególnych symboli
oraz o liczbie symboli. Dodatkowo należy pobrać zakodowaną liczbę kodową.
2. Podzielenie przedziału [0, 1) analogicznie jak w punkcie 2 algorytmu kompresji.
3. Dekodowanie symbolu odbywa się poprzez określenie przedziału, w który wpada
liczba kodowa. Po zdekodowaniu należy usunąć wpływ odczytanego symbolu na
liczbę kodową korzystając z zależności:
code number = ( code number ∗ low range ( symbol ) ) /
( h igh range ( symbol ) − low range ( symbol ) ) ;
4. Jeśli nie odczytano wszystkich symboli powrót do punktu 3.
4.2 Przykład
Załóżmy, że mamy do czynienia z czteroelementowym alfabetem a, b, c, d o prawdopo-
dobieństwach występowania kolejno (0.5, 0.2, 0.2, 0.1 ). Po określeniu prawdopodobieństw
dokonujemy podziału przedziału początkowego. Przyjmujemy leksykalną kolejność wystę-
powania symboli:
symbol prawd. zakres
a 0.5 0.0 ¬ p < 0.5
b 0.2 0.5 ¬ p < 0.7
c 0.2 0.7 ¬ p < 0.9
d 0.1 0.9 ¬ p < 1.0
Znając podział zakresu możemy przystąpić do kodowania ciągu symboli. W poniższej
tabeli przedstawiono dolną i górna granicę przedziału kodowego po zakodowaniu poszcze-
gólnych symboli przykładowego ciągu wejściowego a, c, a, a, b, a, d, b.
18
symbol dolny przedział górny przedział
0.0 1.0
a 0.0 0.5
c 0.35 0.45
a 0.35 0.4
a 0.35 0.375
b 0.3625 0.3675
a 0.3625 0.2650
d 0.36475 0.3650
b 0.364875 0.364925
Po zakodowaniu wybieramy liczbę kodową z końcowego zakresu [0.364875, 0.364925).
Przyjmijmy więc, że będzie to liczba 0.36488. Liczba ta wraz z informacją o prawdopodo-
bieństwach symboli i ich liczbie jednoznacznie reprezentuje wejściowy ciąg. W poniższej
tabeli przedstawiony został proces dekodowania. Po sprawdzeniu, w którym przedziale
znajduje się liczba i wypisaniu symbolu na wyjście usuwany jest wpływ tego symbolu na
liczbę kodową zgodnie z punktem trzecim algorytmu dekompresji.
Liczba kodowa Symbol wyjściowy dolny przedział górny przedział
a 0.36488 0.0 0.5
c 0.72976 0.7 0.9
a 0.1488 0.0 0.5
a 0.2976 0.0 0.5
b 0.5952 0.5 0.7
a 0.476 0.0 0.5
d 0.952 0.9 1.0
b 0.52 0.5 0.7
STOP 0.1 - -
Podsumowując przedstawiony przykład należy zauważyć, że proces kodowania polega
na sukcesywnym zawężaniu przedziału kodowego. W zależności od tego jakie prawdopodo-
bieństwo ma kodowany symbol, przedział kodowy zawęża się w mniejszym bądź większym
stopniu. Im szerszy uzyskamy przedział, tym mniej miejsc po przecinku zajmie na końcu
liczba kodowa.
4.3 Kodowanie arytmetyczne oparte o arytmetykę całkowito-
liczbową
Przedstawiony wcześniej schemat kodowania arytmetycznego opierał się na arytmetyce
rzeczywistoliczbowej. Nie da go się jednak zrealizować w praktycznych implementacjach
19
ze względu na ograniczoną długość rejestrów procesorów. Po zakodowaniu zaledwie kil-
kunastu symboli dochodziłoby do ich przepełnienia.
Możliwe jest jednak zaimplementowanie kodera arytmetycznego przy wykorzystaniu
arytmetyki całkowitoliczbowej wykorzystującej proste operacje bitowe na rejestrach. Bi-
ty reprezentujące liczbę kodową są wysuwane na wyjście w miarę kodowania kolejnych
elementów. Dzięki temu możliwe jest w praktyce kodowanie dowolnie długich ciągów.
Aby możliwe było reprezentowanie prawdopodobieństw w rejestrach całkowitoliczbo-
wych, należy przyjąć że dolną granicę 0.0000 reprezentują wszystkie bity ustawione na
zero, a górna granicę 0.9999 reprezentują wszystkie bity ustawione na jeden. Przez wszyst-
kie bity rozumiane są tu wszystkie bity reprezentujące ułamek (implementacja używa 31
bitów z 32 bitowego rejestru). Warto również zauważyć, iż w obu liczbach część całkowita
wynosi 0, więc można ją pominąć.
Koder posiada zatem dwa rejestry dół i góra zainicjalizowane przez wartości 0000 i
FFFF. Teraz w miarę kodowania kolejnych symboli wysuwane będą bity rejestru dół w
momencie, gdy najbardziej znaczące bity obu rejestrów będą sobie równe. Zmiany będą
zachodziły już tylko na mniej znaczących bitach, więc można bezpiecznie je wysunąć.
Rejestr dół jest uzupełniany zerami natomiast rejestr góra jest uzupełniany jedynkami.
Koder musi również zabezpieczyć się przed sytuacją, w której wysuwanie zostanie
zatrzymane przez niedomiar. Kodowanie kolejnych symboli zacieśnia przedział kodowy,
lecz różnica na najbardziej znaczących bitach może nie wystąpić od razu. Załóżmy, że
posiadamy czterobitowe rejestry. Wartość rejestru dół wynosi 0110, a wartość rejestru
góra wynosi 1001. Teraz w miarę procesu kodowania może dojść do sytuacji gdy po za-
kodowaniu kolejnego symbolu wartości te będą wynosić odpowiednio 0111 i 1000. W tym
momencie najbardziej znaczące bity będą różne, uniemożliwiając wysunięcie. Rozwiąza-
niem tego problemu jest wcześniejsza detekcja możliwości wystąpienia tego problemu.
Rozpatrując poprzedni przykład niedomiar występuje wtedy, gdy rejestr dół ma postać
01XX, a rejestr góra 10XX. Po wykryciu niedomiaru należy przesunąć w lewo wszystkie
bity obu rejestrów oprócz najbardziej znaczących.
Dekoder wczytuje sekwencyjne kolejne bity zakodowanej liczby kodowej do rejestru
kod. Musimy również posiadać dwa rejestry dół i góra analogiczne do tych w koderze. W
dekoderze trzeba wykonywać te same operacje na tych rejestrach jak w koderze, włącza-
jąc wykrywanie niedomiaru. Po określeniu aktualnego prawdopodobieństwa dokonujemy
odpowiednich przesunięć na wszystkich trzech rejestrach.
Kompletny algorytm kodowania arytmetycznego w arytmetyce całkowitoliczbowej
wraz w wyjaśnieniami można znaleźć w książkach [16] [11].
20
4.4 Koder adaptacyjny
Przedstawiony wcześniej algorytm kompresji posiada pewne wady. Pierwszą z nich jest
konieczność wstępnego określenia prawdopodobieństwa występowania symboli. W takim
przypadku algorytm musi być dwuprzebiegowy. Najpierw zliczana jest liczność poszcze-
gólnych symboli, a następnie wykonywane jest kodowanie. Tablica występowania symboli
musi być dodatkowo przesłana do dekodera, zmniejszając efektywność kompresji. Warto
zmniejszyć dynamikę elementów tablicy poprzez skalowanie, np. jeśli do zliczania liczno-
ści poszczególnych symboli wykorzystywaliśmy 32 bitowe liczby można je przeskalować
i zapisać na 8 bitach. W ten sposób można w oszczędny sposób przesłać informację do
dekodera o prawdopodobieństwach, nie wpływając znacząco na efektywność kompresji
symboli.
Lepszym rozwiązaniem jest zastąpienie statycznego modelu statystycznego modelem
dynamicznym. Model ten jest aktualizowany w miarę kodowania kolejnych symboli, dopa-
sowując się do charakterystyki kodowanych danych. Dekoder działa w analogiczny sposób.
Po odkodowaniu danego symbolu, aktualizowane jest jego prawdopodobieństwo. Nie jest
więc konieczne dwukrotne iterowanie przez dane. Dzięki temu nie trzeba również przesyłać
informacji o prawdopodobieństwie występowania symboli. Zanim model dynamiczny do-
stosuje się do charakterystyki danych, kodowanie będzie mniej efektywne. Po zakodowaniu
pewnej liczby symboli prawdopodobieństwa już się ustalą i kodowanie będzie efektywne.
Dodatkowo model adaptacyjny dostosowuje się do charakterystyki lokalnej danych. Ta-
kie rozwiązanie pozwala uzyskać w większości przypadków lepszy stopień kompresji niż
rozwiązanie z modelem statycznym.
21
5 Technologia NVIDIA CUDA
W tym rozdziale zostały przedstawione podstawy technologii NVIDIA CUDA (Compu-
te Unified Device Architecture). Szczególny nacisk został położony na elementy wykorzy-
stane przy implementacji. Rozdział został przygotowany w oparciu o oficjalną dokumen-
tację [14] oraz książkę [5]. Specyficzne konstrukcje kompilatora CUDA zostały wyjaśnione
na prostych przykładach.
5.1 Efektywność obliczeniowa procesorów graficznych
Jednostki obliczeniowe procesorów graficznych były rozwijane przez szereg lat, aby
sprostać zapotrzebowaniu na renderowanie wysokiej jakości grafiki 3D. Przez ten czas wy-
ewoluowały one w wysoce równoległe procesory posiadające znaczącą moc obliczeniową.
Udostępniony przez firmę NVIDIA interfejs CUDA umożliwia wykorzystanie procesora
graficznego do obliczeń nie związanych z renderowaniem grafiki. Procesory graficzne po-
siadają dużą liczbę prostszych rdzeni pozwalających na uzyskanie wysokiej efektywności
obliczeń. Zwykłe jednostki obliczeniowe CPU zmniejszyły tempo wzrostu taktowania na
rzecz wzrostu liczby rdzeni.
Rysunek 10: Teoretyczna przepustowość obliczeń zmiennoprzecinkowych na liczbach po-jedynczej precyzji [14]
22
Jako porównanie weźmy pod uwagę rodzinę procesorów Intel Core i7. Procesor ten
posiada cztery rdzenie wspierające technologię hyperthreading, przez co może wykonywać
równolegle osiem wątków. Procesor graficzny karty NVIDIA GeForceGTX 580 posiada 512
rdzeni pozwalając na równoczesne uruchomienie kilku tysięcy wątków. Osiąga on blisko
10 razy większą teoretyczną przepustowość obliczeń zmiennoprzecinkowych na liczbach
pojedynczej precyzji (rysunek 10).
Tak duża rozbieżność w wydajności bierze się z fundamentalnych różnic w architek-
turze obu rozwiązań (rysunek 11). Procesor CPU jest przystosowany do sekwencyjnego
wykonywania instrukcji. Istnieją w nim mechanizmy, które sprawiają, że instrukcje we-
wnątrz procesora wykonywane są poza kolejnością mimo wrażenia sekwencyjnego wyko-
nania (na rysunku blok CONTROL). Kolejną ważną cechą jest rozbudowany mechanizm
dużej pamięci podręcznej zmniejszającej opóźnienia przy dostępnie do pamięci głównej.
Procesor graficzny ze względu na swój uproszczony model pamięci ma kilkukrotnie
większą przepustowość od głównej pamięci DRAM procesora. Renderowanie wysokiej ja-
kości grafiki 3D wymaga wykonania ogromnych ilości obliczeń zmiennoprzecinkowych na
każdą ramkę obrazu. Zajmuje się tym duża liczba wątków o uproszczonej logice wykona-
nia. Mała pamięć podręczna pozwala zmniejszyć zużycie przepustowości pamięci DRAM,
bez wprowadzania kosztownej logiki związanej z zarządzaniem nią, przeciwnie niż ma to
miejsce w pamięci podręcznej procesora CPU.
Rysunek 11: Architektury procesorów CPU i GPU [14]
Procesor graficzny składa się z szeregu multiprocesorów strumieniowych (SM - stre-
aming multiprocessor). Multiprocesory składają się grupy procesorów strumieniowych (SP
- streaming processor), które mają wspólną pamięć podręczną. Ilości multiprocesorów i
procesorów są zróżnicowane w zależności od danego procesora. Procesor graficzny działa
na pamięci wewnętrznej karty graficznej. Pamięć karty graficznej będziemy nazywać pa-
mięcią urządzenia (device memory), a zwykłą pamięć RAM pamięcią hosta (host memo-
ry). Szczegóły dotyczące architektury sprzętowej kart wspierających technologię CUDA
23
można znaleźć w książce [5]. W dalszej części przedstawię abstrakcje modelu programowa-
nia pozwalające na programowanie jednostki GPU bez znajomości sprzętowych szczegółów
implementacyjnych.
5.2 Model programowania
Z perspektywy programisty system składa się z hosta, który jest jest zwykłym proceso-
rem, oraz z urządzeń (devices), które są wysoce wielowątkowymi procesorami graficznymi.
Dzięki dużej liczbie jednostek obliczeniowych w procesorze graficznym, możliwe są równo-
czesne obliczenia na dużej ilości danych. W technologii CUDA kluczowe jest znalezienie
tych fragmentów kodu, które wymagają intensywnych obliczeń arytmetycznych i można
je zrównoleglić. Program wykorzystujący CUDA składa się z kodu wykonywanego na ho-
ście oraz na urządzeniu. Do kompilacji kodu wykorzystywanego zarówno przez hosta jak
i urządzenie wykorzystywany jest specjalny kompilator NVIDIA C (nvcc). Kod hosta to
zwykły kod ANSI C i jest uruchamiany jak normalny proces. Kod urządzenia to ANSI C
z pewnymi rozszerzeniami opisanymi w dalszej części rozdziału.
5.3 Funkcje kerneli
Funkcje kerneli nazywane w skrócie kernelami (kernels), pozwalają na definiowanie ko-
du wykonywanego przez wątki w urządzeniu. Każdy z kerneli jest wykonywany równolegle
przez osobne wątki, przeciwnie niż ma to miejsce w zwykłych funkcjach C. Aby zdefinio-
wać kernel, należy przed deklaracją funkcji umieścić specyfikator global . Wywołanie
kernela z odpowiednią liczbą wątków odbywa się poprzez wykorzystanie specjalnej składni
<<<>>> (execution syntax ). Całość musi być skompilowana kompilatorem nvcc.
Poniżej znajduje się przykład kernela podnoszącego zadany ciąg liczb do kwadratu.
Normalnie rozwiązanie takiego zadania opierałby się na sekwencyjnym obliczeniu kwadra-
tu każdej liczby w pętli. Aby wykorzystać wielowątkowe możliwości procesora graficznego,
każdy z elementów będzie obliczany oddzielnie. Indeks elementu w tablicy wyznaczany
jest na podstawie unikalnego identyfikatora wątku (thread ID). Pobierany jest on z wbu-
dowanej zmiennej threadIdx. W wywołaniu kernela używamy specjalnej składni podając
ilość wątków którą chcemy uruchomić.
// d e f i n i c j a k e r n e l a
g l o b a l void square ( f loat ∗ data ) {int index = threadIdx . x ;
data [ index ] = data [ index ]∗ data [ index ] ;
}
int main ( ) {. . .
24
// wywolanie k e r n e l a z N watkami
// data − d e v i c e memory
square<<<1, N>>>(data ) ;
}
5.4 Hierarchia wątków
Wątki mogą być indeksowane w trzech wymiarach x, y, z przy użyciu specjalnej zmien-
nej threadIdx. Tworzą one wówczas odpowiednio jedno, dwu, lub trójwymiarowy blok
wątków (thread block). Takie grupowanie pozwala w łatwy sposób wywoływać obliczenia
na wektorach, macierzach i danych wolumetrycznych. Każdy z uruchomionych w ramach
bloku wątków ma unikalne wartości indeksów x, y oraz z.
Istnieje limit określający liczbę wątków w ramach bloku i obecnie wynosi on 512. Wątki
danego bloku są przetwarzane na jednym multiprocesorze. Dzielą więc one zasoby ogra-
niczonej pamięci podręcznej. Mają możliwość współpracy ze sobą poprzez wykorzystanie
wspólnej pamięci podręcznej jak również synchronizację wykonania.
Bloki są grupowane w jedno, bądź dwuwymiarowe kraty (grid). Maksymalny wymiar
kraty zależy od urządzenia. Tak przygotowana krata wątków może liczyć od tysięcy do
milionów prostych wątków. Schemat hierarchii wątków przedstawiony jest na rysunku 12.
Rysunek 12: Schemat hierarchii wątków [5]
25
Poprawmy więc teraz wcześniejszy przykład tak, aby możliwe było wykorzystanie go
dla tablicy dłuższych niż 512 elementów. Znając już pojęcie bloków, można wyjaśnić
dokładnie, składnie wywołania kernela. Pierwszym argumentem jest liczba bloków, nato-
miast drugim jest ilość wątków na blok. Dla uproszczenia załóżmy, że długość tablicy jest
wielokrotnością ilości wątków na blok:
// i l o s c watkow na b l o k
const int THREADS PER BLOCK = 512 ;
g l o b a l void square ( f loat ∗ data ) {int index = blockIdx . x ∗ THREADS PER BLOCK
+ threadIdx . x ;
data [ i ] = data [ i ]∗ data [ i ] ;
}
int main ( ) {. . .
// wywolanie k e r n e l a z N watkami
int blockNum = N / THREADS PER BLOCK;
square<<<blockNum , THREADS PER BLOCK>>>(data ) ;
}
Do uzyskania indeksu bloku w kracie użyliśmy specjalnej zmiennej blockIdx. Jeśli chce-
my wykorzystać kernel do przetwarzania macierzy bądź danych wolumetrycznych jako
argumenty należy przekazać struktury dim3. Posiadają one trzy wartości x, y, z, którymi
można określić wymiar bloku i kraty wątków.
5.5 Hierarchia pamięci
Host i urządzenie mają oddzielne przestrzenie pamięci. Tak więc przed wykonaniem
obliczeń należy zaalokować pamięć i przenieść dane z pamięci hosta do pamięci global-
nej urządzenia (global memory). Po wywołaniu kernela pobieramy z powrotem wyniki do
pamięci hosta i zwalniamy zaalokowaną pamięć urządzenia. Poniżej znajduje się uzupeł-
niona o alokację i przesyłanie pamięci procedura main. Do alokacji pamięci na urządzeniu
wykorzystywana jest funkcja cudaMalloc, działająca analogicznie do funkcji z standar-
dowej biblioteki C. Analogicznie działa również funkcja zwalniająca pamięć, cudaFree.
Funkcja cudaMemcpy zajmuje się kopiowaniem pamięci do i z urządzenia. To, w którą
stronę zachodzi przesyłanie danych, zależy od czwartego argumentu wywołania będącego
enumeracją cudaMemcpyKind.
int main ( ) {f loat ∗ hostMemory ;
26
. . .
f loat ∗ deviceMemory ;
cudaMalloc ( ( void ∗∗) deviceMemory , N ∗ s i z e ( f loat ) ) ;
cudaMemcpy( deviceMemory , hostMemory , N ∗ s i z e ( f loat ) ,
cudaMemcpyHostToDevice ) ;
. . .
square<<<blockNum , THREADS PER BLOCK>>>(deviceMemory ) ;
cudaMemcpy( hostMemory , deviceMemory , N ∗ s i z e ( f loat ) ,
cudaMemcpyDeviceToHost ) ;
cudaFree ( deviceMemory ) ;
}
Po przesłaniu danych do pamięci urządzenia, wątki pobierają swoją porcję danych
z pamięci globalnej przy użyciu zmiennych threadIdx oraz blockIdx. Takie rozwiązanie
nie wykorzystuje jednak pełnej wydajności procesora graficznego. Wątek ma dostęp do
kilku innych rodzajów pamięci, które można wykorzystać dla uzyskania większej szybkości
wykonania. Rysunek 13 pokazuje wszystkie rodzaje pamięci i ich związek z hierarchią
wątków.
Host przy użyciu funkcji cudaMemcpy ma możliwość zapisu do pamięci globalnej (glo-
bal memory) oraz do pamięci stałych (constant memory). Pamięć globalna została już
przedstawiona na przykładach i służy do przekazywania danych i wyników obliczeń. Wątki
mogą ją odczytywać i zapisywać. Pamięć stałych zapewnia krótszy czas dostępu i wysoką
przepustowość przy założeniu tylko odczytu przez wątki. Ma ona również ograniczoną
wielkość (64KB).
Każdy z wątków posiada swoją pamięć lokalną oraz rejestry. Pamięć tego typu znajduje
się bezpośrednio na procesorze. Jest ona dostępna jedynie dla tego wątku i zapewnia naj-
lepszą wydajność. Wykorzystuje się ją do przechowywania często używanych zmiennych
prywatnych dla danego wątku.
Wątki będące w jednym bloku mają dostęp do wspólnej pamięci współdzielonej (shared
memory). Ma ona znacznie krótszy czas dostępu niż pamięć główna i ograniczoną wielkość
(16KB lub 48KB na multiprocesor). Można ją sobie wyobrazić jako ręcznie zarządzaną
pamięć podręczną w ramach bloku. Wykorzystuje się ją zwłaszcza w przypadku dostę-
pu do tych samych danych wejściowych przez wiele wątków, bądź dostępu do obliczeń
pośrednich.
Przedstawione rodzaje pamięci mają swój czas życia. Pamięć globalna i stałych jest
dostępna przez czas działania aplikacji. Pamięci lokalne wątków oraz pamięć współdzielona
ma czas życia równy czasowi życia bloku. Po wykonaniu bloku, pamięć jest zwalniana
umożliwiając uruchomienie kolejnych bloków wątków.
27
Rysunek 13: Schemat hierarchii pamięci w procesorze graficznym [5]
Do określenia pewnych typów pamięci, API określa odpowiednie specyfikatory:
• pamięć współdzielona - shared
• pamięć globalna - device
• pamięć stałych - constant
Wykorzystanie pamięci współdzielonej, stałych i lokalnej pozwala ograniczyć użycie
przepustowości pamięci globalnej, która jest wąskim gardłem w aplikacji wykorzystujących
CUDA. Należy mieć jednak na uwadze, że wymienione rodzaje pamięci mają swoje limity.
Przy zwiększaniu ich wykorzystania, możemy doprowadzić do zmniejszenia ilości wątków
wykonywanych na danym multiprocesorze.
28
6 Opis proponowanego rozwiązania
W tym rozdziale przedstawiony jest opis stworzonego przeze mnie narzędzia do kom-
presji sekwencji wideo i obrazów. Zaimplementowałem kompresor działający na procesorze
CPU oraz na karcie graficznej wykorzystującej CUDA. Na początku przedstawię wyma-
gania postawione aplikacji. Następnie opiszę funkcje udostępnione przez kompresor. Na
końcu opiszę architekturę rozwiązania z wyszczególnieniem tych fragmentów aplikacji,
które udało się zaimplementować przy użyciu procesora karty graficznej.
6.1 Wymagania dotyczące aplikacji
Głównym celem zaprojektowanej aplikacji jest stworzenie narzędzia do kompresji se-
kwencji wideo przy wykorzystaniu metod falkowych i technologii NVIDIA CUDA. Aby
możliwe było łatwe porównanie skuteczności kompresji z innymi rozwiązaniami, stworzony
przeze mnie kompresor rozszerzyłem o możliwość kompresji obrazów.2
Aplikacja musi umożliwiać kompresję w dwóch trybach, tj. przy wykorzystaniu CPU
oraz przy wykorzystaniu CPU i procesora graficznego. W program powinny być wbudo-
wane mechanizmy liczące czas wykonania poszczególnych jego części. Posiadając te dane
można określić efektywność implementacji CUDA w stosunku do implementacji CPU, co
jest głównym celem pracy.
Kompresor powinien umożliwiać odczyt i zapis szeroko wykorzystywanych formatów
plików. Zakodowane pliki w własnym formacie muszą posiadać wszelkie dane niezbędne
do ich poprawnego odkodowania bez względu na wybrane parametry kodera. Ważne jest
również poprawne dekodowanie plików zakodowanych na różnych platformach sprzęto-
wych.
6.2 Funkcje aplikacji
Interfejs
Aplikacja udostępnia swoje funkcje poprzez argumenty wywołania. Komunikacja
zwrotna odbywa się poprzez komunikaty. W argumentach wywołania możliwe jest
określenie stopnia szczegółowości wyświetlanych informacji. W przypadku wystą-
pienia błędu jest wyświetlony odpowiedni komunikat oraz zwrócony kod błędu.
Kompresja obrazów
Aplikacja umożliwia kompresję obrazów bitmapowych. Możliwa jest kompresja ob-
razów w odcieniach szarości lub kolorowych. Obsługiwane formaty odczytu i zapisu
plików bitmapowych to: pgm, bmp, jpg, tiff. Ze względu na zastosowany algorytm
2Wwiększości prac z dziedziny kompresji stratnej można, np. znaleźć wyniki dla standardowego obrazutestowego ”Lena”.
29
obliczania transformaty falkowej, obraz musi mieć wymiary będące wielokrotnością
liczby 2d, gdzie d to liczba poziomów dekompozycji.
Kompresja sekwencji wideo
Aplikacja umożliwia kompresję sekwencji wideo. Wymagania dotyczące liczby kolo-
rów oraz wymiarów są takie same jak dla kompresji obrazów. Obsługiwany wejściowy
i wyjściowy format pliku to avi w formacie RAW i420 bez dźwięku.
Wybór sposobu wykonania obliczeń
Domyślnie aplikacja działa przy użyciu implementacji CPU. Możliwe jest jednak
określenie by program zastosował implementację wykorzystującą procesor karty gra-
ficznej.
Wyświetlanie czasu wykonania poszczególnych części programu
W aplikację wbudowane są mechanizmy liczące czas wykonania poszczególnych kro-
ków algorytmu kompresji i dekompresji.
Określenie stopnia kompresji
Aplikacja przy kompresji pobiera parametr określający żądany stopień kompresji.
Dla przykładu chcąc uzyskać stopień kompresji 4:1 musimy podać wartość 4. Aplika-
cja nie obsługuje parametryzacji poprzez żądaną jakość obrazu zrekonstruowanego.
Określenie innych parametrów
Przekazując odpowiednie argumenty wywołania aplikacja przy kodowaniu pozwa-
la na wybór algorytmów wykorzystywanych w poszczególnych krokach kompresji.
Informacje te są zapisywane w zakodowanym pliku na potrzeby dekodera. Parame-
tryzowane są następujące opcje:
• wykorzystana funkcja falkowa (dostępne filtry: Haar, Daub4, Daub6, CDF97
oraz Antonini),
• liczba poziomów dekompozycji transformaty falkowej,
• typ kwantyzatora (dostępny: kwantyzator równomierny, kwantyzator równo-
mierny z rozszerzonym przedziałem zerowym)
30
6.3 Architektura rozwiązania
Zaimplementowany przeze mnie kompresor opiera się na standardowym schemacie
transformacyjnej kompresji stratnej. Na rysunku 14 znajduje się schemat kompresji ob-
razów. Kompresja sekwencji wideo przechodzi przez dokładnie te same kroki z pewnymi
drobnymi różnicami.
Rysunek 14: Schemat kompresji obrazów
Formatowanie danych
Pierwszym krokiem algorytmu kompresji jest formatowanie danych. Zadany obraz ory-
ginalny musi być odpowiednio przechowywany, aby wykonanie na karcie graficznej było
efektywne. Ważne jest, aby kanały kolorowego obrazu były przetwarzane oddzielnie. Wy-
nika to z faktu, że transformata i transformata odwrotna musi być wykonana oddzielnie
dla każdego kanału. Procesor karty graficznej przy pobieraniu danych z pamięci globalnej
pobiera je od razu w większych blokach 32, 64 lub 128 bitowych. Technika ta nazywa się
memory coalescing i ma na celu zmniejszenie liczby żądań do pamięci globalnej.
Rysunek 15: Zmiana struktury przechowywania pikseli w pamięci
Zakładając, że każdy z wątków oblicza jeden współczynnik transformaty, musiałby
by on sięgać po co trzeci element. Miałoby to oczywiście zły wpływ na wydajność ze
względu na zbędne pobranie niewykorzystywanych w obliczeniach wartości. Rysunek 15
przedstawia niezbędne w tym kroku ustawienie danych. Oddzielne przetwarzanie kanałów
pozwala również zmniejszyć zużycie pamięci globalnej karty graficznej. W danej chwili
wykonywane są obliczenia dla tylko jednego kanału.
31
Rysunek 16: Sekwencja obrazów przy przetwarzaniu jest grupowana (w - szerokość; h -wysokość; f - ilość ramek tworzących grupę)
Wejściowym formatem dla sekwencji wideo jest sekwencja obrazów. Podział kanałów
barw jest analogiczny do zastosowanego przy formatowaniu obrazów. Kompresor w da-
nej chwili pracuje na określonej liczbie ramek (na rysunku 16 parametr f). Transformata
falkowa dla sekwencji wideo odbywa się po trzech wymiarach, tak więc konieczne jest
zastosowanie odpowiednio dużej liczby ramek, aby możliwe było wykonanie kilkupozio-
mowej dekompozycji sygnału. Po wykonaniu pozostałych kroków algorytmu na grupie
ramek i zapisie zakodowanych danych do strumienia wyjściowego przetwarzanie odbywa
się na kolejnej grupie ramek.
Transformata kolorów
Dla obrazów barwnych wykonywana jest transformata kolorów. Jej głównym celem jest
wykorzystanie psychowizualnych cech ludzkiego zmysłu wzroku. Jak powszechnie wiado-
mo ludzki wzrok jest bardziej czuły na zmiany luminancji niż chrominancji. Powszechnie
wykorzystywany model RGB przechowuje jasności w każdej z swoich składowych. Dla
celów kompresji korzystniejsze są modele gdzie jasność jest przechowywana na jednej
składowej, a barwy na dwóch pozostałych.
W zaimplementowanym kompresorze wykorzystałem przejście do modelu Y CbCr, gdzie
Y to jasność, a Cb i Cr to składowe opisujące barwę. Poniżej znajdują się wykorzystane
wzory transformacyjne.
Y
Cb
Cr
=
0
128
128
+
0.299 0.587 0.114
−0.169 −0.331 0.500
0.500 −0.419 −0.081
∗R
G
B
32
R
G
B
=
1.000 0.000 1.400
1.000 −0.343 −0.711
1.000 1.765 0.000
∗
Y
(Cb − 128)
(Cr − 128)
Algorytm obliczania transformaty kolorów łatwo daje się zrównoleglić i zaimplemen-
tować na GPU. Każdy z wątków ma dostęp do składowych RGB piksela. Na podstawie
blockIdx i threadIdx może określić swój indeks. Następnie wykorzystując przedstawione
wzory, dokonuje obliczenia wartości Y CbCr i zapisuje je na miejsce wartości RGB. Przy
tak prostym dostępie do pamięci nie należy korzystać z pamięci współdzielonej. Każdy
wątek korzysta jedynie ze swoich danych.
Transformata falkowa
Po opcjonalnym wykonaniu transformacji kolorów wykonywana jest transformata falko-
wa. We wstępie teoretycznym przedstawiona została metoda obliczania współczynników
transformaty falkowej, przy użyciu algorytmu konwolucji z periodycznym rozszerzaniem
sygnału na granicach. Taki właśnie algorytm został zaimplementowany zarówno na pro-
cesorze CPU i GPU.
Kompresor pozwala na użycie kilku zdefiniowanych funkcji falkowych. Każda falka
składa się z zestawu czterech filtrów. Są to pary nisko i wysokoczęstotliwościowych filtrów
analizy i syntezy. Każdy z filtrów posiada ciąg współczynników o danej długości i parametr
określający pierwszy współczynnik filtru. Mówi on o ile pozycji w lewo należy przesunąć
filtr przy obliczaniu splotu (rysunek 17).
Rysunek 17: Konwolucja obrazu dla filtru o czterech współczynnikach bez przesunięcia iz przesunięciem równym jeden
Obliczenie splotu wejściowego ciągu x z filtrem h, można zaimplementować przy uży-
ciu dwóch prostych pętli zagnieżdżonych. Obliczenie wykonujemy tylko dla co drugiego
elementu ze względu na wyrzucenie w kolejnym kroku co drugiej próbki. Ważne jest pe-
riodyczne rozszerzenie sygnału na granicach.
W przypadku implementacji na GPU każdy z wątków wykonuje obliczenie jednego
współczynnika dla niskich częstotliwości i jednego dla wysokich. Jeśli oba filtry analizy
33
mają podobną długość i przesunięcie, wówczas wykorzystują one te same elementy wej-
ściowe do obliczenia obu współczynników (rysunek 18). Wykonując więc obliczenie obu
współczynników, zmniejszamy zużycie przepustowości pamięci. Dodatkowo warto wyko-
rzystać pamięć współdzieloną ze względu na dostęp do tych samych współczynników przez
kilka wątków.
Rysunek 18: Równoczesne obliczenie współczynników nisko i wysokoczęstotliwościowych
Obliczanie transformaty dla sygnałów wielowymiarowych wykonujemy dla każdego
wymiaru oddzielnie korzystając z separowalności funkcji falkowej. W zaprojektowanym
przeze mnie kompresorze ramki sekwencji wideo grupuję w bloki tworząc trójwymiarową
macierz pikseli (rysunek 19). Tak uzyskaną macierz pikseli poddaję transformacie falkowej
po każdym z wymiarów. Sposób dekompozycji dla obrazów został już przedstawiony w
wstępnie teoretycznym.
Rysunek 19: Trójwymiarowa macierz pikseli
34
W wyniku obliczenia transformaty uzyskamy trójwymiarowe podmacierze, w których
znajdą się współczynniki poszczególnych podpasm. Tak uzyskane dane są sprowadzane
do postaci liniowej. Na rysunku 20 przedstawiony został sposób linearyzacji dla obrazu.
Dane w każdym podpaśmie są kopiowane wiersz po wierszu. Poszczególne podpasma są
ustawione od pasma najniższych częstotliwości do najwyższych. Dla obliczonej trójwy-
miarowej macierzy współczynników dane w podmacierzy są kopiowane ramka po ramce,
a w ramach ramki, wiersz po wierszu.
Wykonanie linearyzacji ułatwia w kolejnych krokach dostęp do współczynników. Dane
są tam przetwarzane w ramach podpasm, nie jest więc konieczne złożone indeksowanie.
Wystarczy znać wskaźnik do pierwszego elementu podpasma i długość podpasma.
Rysunek 20: Sposób linearyzacji obrazu poddanego transformacie
Kwantyzacja
Po wykonaniu transformaty uzyskujemy zbiór podpasm współczynników o wartościach
ciągłych. Aby w kolejnym kroku możliwe było zakodowanie ich koderem arytmetycznym,
należy je zdyskretyzować. W tym kroku odpowiednio dobierając liczbę poziomów dekom-
pozycji zmniejszamy jego dynamikę w taki sposób, aby skutecznie poddawał się kompresji
koderem arytmetycznym. Wybór liczby poziomów dekompozycji realizuje algorytm opty-
malizacji R-D (rate-distortion).
Każde z podpasm jest kwantyzowane przy użyciu innej liczby poziomów dekompozy-
cji. Wynika to oczywiście z znaczących różnic w dynamice poszczególnych podpasm. W
pasmach wysokiej częstotliwości amplituda współczynników jest niewielka w porównaniu
z pasmami niskich częstotliwości, gdzie kumuluje się energia sygnału. Liczba poziomów
kwantyzacji jest równa 2b, gdzie b to ilość bitów przeznaczonych na dany współczynnik.
W wykonanym koderze zaimplementowałem dwa kwantyzatory: kwantyzator równo-
mierny i kwantyzator równomierny z rozszerzonym przedziałem zerowym. W przypadku
kwantyzatora z rozszerzonym przedziałem zerowym sygnał jest uznawany za symetryczny.
35
Oznacza to, że jego amplituda nie wyraża się poprzez różnicę między największym i naj-
mniejszym współczynnikiem, a poprzez dwukrotność wartości bezwzględnej z maksimum
wartości bezwzględnej tych wartości (2 ∗ max(|max|, |min|)). Algorytm kwantyzacji nie
został zaimplementowany na GPU ze względów wyjaśnionych w dalszej części pracy.
Kodowanie arytmetyczne
Po wykonaniu kwantyzacji uzyskujemy w każdym z podpasm całkowite współczynniki
mieszczące się na b bitach. W zaprojektowanym kompresorze krok kodowania bezstrat-
nego realizuje adaptacyjny koder arytmetyczny. Model statystyczny jest inicjalizowany
równomiernie, tj. każdy symbol uzyskuje to samo prawdopodobieństwo. Wraz z kodowa-
niem kolejnych symboli model dostosowuje się do charakterystyki danych.
Zdecydowałem się na wykorzystanie nowego modelu dla każdego z podpasm. Do wyja-
śnienia tej decyzji przedstawię przykład. W wyniku optymalizacji R-D pasmo najniższych
częstotliwości zostało skwantyzowane przy użyciu 8 bitów, a pasmo najwyższych często-
tliwości przy użyciu 3 bitów. Tak jak zostało wspomniane wcześniej w pasmach wysokich
częstotliwości większość współczynników przyjmuje wartości 0. Wykorzystując jeden i ten
sam model, kodowanie pasma wysokich częstotliwości byłoby nieefektywne ze względu na
to, że model przystosowałby się do pasma niższych częstotliwości. Oczywiście po pew-
nym czasie przystosowałby się on do charakterystyki danych, ale trwałoby to dłużej niż w
przypadku rozpoczęcia kodowania od prawdopodobieństw wynoszących 1/8. Dodatkowo
zaadoptowany przeze mnie algorytm optymalizacji R-D uniemożliwiał wykorzystanie tego
samego modelu.
Sekwencyjny charakter algorytmu kodowania arytmetycznego polegający na przesu-
nięciach bitowych na trzech rejestrach dyskwalifikuje go przy implementacji na procesorze
karty graficznej. Uzyskany w wyniku działania algorytmu strumień bitów jest zapisywany
do pliku. Jest on poprzedzony odpowiednim nagłówkiem oraz danymi każdego z kwanty-
zatorów.
Optymalizacja R-D
W przedstawionym do tej pory schemacie kompresji brakuje informacji o sposobie wybo-
ru liczby poziomów dekompozycji. Kompresor posiada parametr określający żądany po-
ziom kompresji, np. 4 : 1. Żeby uzyskać taki poziom kompresji, należy tak dobrać liczbę
poziomów kwantyzacji poszczególnych podpasm, aby po zakodowaniu skwantyzowanych
współczynników koderem arytmetycznym osiągnąć żądaną liczbę bitów.
Dobraniem liczby poziomów dekompozycji zajmują się algorytmy optymalizacji R-D
(rate distorion). Na rysunku 21 przedstawiona jest przykładowa krzywa R-D dla jednego z
kwantyzatorów. Wraz z zmniejszaniem kosztu bitowego (rate), zwiększają się zniekształ-
cenia (distortion). Gdyby był wykorzystywany tylko jeden kwantyzator, wystarczyłoby
zaczynając od pewnej ustalonej liczby bitów na symbol sprawdzać, czy mieścimy się za-
łożonym budżecie bitów. Jeśli nie, to należałoby zmniejszyć liczbę bitów na symbol, co
36
spowodowałoby większe zniekształcenia kwantyzacyjne i równocześnie zmniejszenie liczby
potrzebnych bitów.
Rysunek 21: Krzywa R-D dla jednego kwantyzatora
Ze względu na to, iż koder wykorzystuje wiele kwantyzatorów, problem staje się trud-
niejszy. Ważne staje się takie dobranie liczby poziomów kwantyzacji w poszczególnych
pasmach, aby globalne zniekształcenia były minimalne przy spełnieniu ograniczenia na
liczbę wykorzystanych bitów. Silna kwantyzacja w pasmach wysokich częstotliwości po-
zwala uzyskać niski koszt bitowy przy niewielkich zniekształceniach.
Na potrzeby mojego kodera zaadaptowałem algorytm R-D z biblioteki Wavelet Ima-
ge Compression Construction Kit,3 który opiera się na artykule [20]. Korzysta on z po-
wszechnie wykorzystywanej metody mnożników Lagrange’a. Problem minimalizacji można
przedstawić jako minimalizację funkcji D + λR, gdzie λ jest mnożnikiem Lagrange’a. Na
rysunku 21 zmienna λ określa nachylenie stycznej do krzywej R-D. Optymalizacja przy
użyciu mnożników Lagrange’a polega na zmianie parametru λ aż do uzyskania żądanego
kosztu bitowego. Szczegóły dotyczące algorytmu opisane są w wspomnianej pracy.
Wykorzystana metoda optymalizacji wymaga, aby dla każdego z podpasm przygoto-
wać krzywe R-D. Miarą zniekształceń D jest przedstawiony wcześniej błąd średniokwa-
dratowy. Aby uzyskać możliwie precyzyjnie żądaną liczbę bitów, zdecydowałem się na
obliczanie kosztu bitowego poprzez kodowanie uzyskanych w wyniku kwantyzacji współ-
czynników. Koder arytmetyczny działa wtedy w trybie zliczania bajtów i nie jest wówczas
zapisywany strumień wyjściowy. Konieczność wielokrotnego kodowania koderem aryt-
metycznym skwantyzowanych współczynników uniemożliwiła zaimplementowanie kroku
kwantyzacji na GPU. Wynika to z wysokiego kosztu przesyłania danych z pamięci urzą-
dzenia do pamięci głównej.
3http://www.geoffdavis.net/dartmouth/wavelet/wavelet.html
37
Dekompresja
Skompresowany plik zawiera w sobie nagłówek określający wszystkie niezbędne do wyko-
nania dekompresji dane. Są to między innymi wymiary, liczba kanałów, liczba poziomów
dekompozycji, zastosowana funkcja falkowa, kwantyzator oraz koder arytmetyczny. Do-
datkowo przed zakodowanymi współczynnikami znajdują się dane wykorzystywane przez
kwantyzatory w procesie dekwantyzacji. Są to wartość minimalna, maksymalna współ-
czynników oraz liczba poziomów kwantyzacji.
Po odczytaniu nagłówka następuje zdekodowanie danych zakodowanych koderem aryt-
metycznym. Ważne jest śledzenie liczby odczytanych elementów, aby przełączyć model
statystyczny kodera przy odczycie kolejnego podpasma. Po odczytaniu każdego elementu
jest on od razu poddawany dekwantyzacji. Dzięki temu nie jest konieczne wykorzystanie
pośredniego bufora dla skwantyzowanych wartości.
Przed wykonaniem transformaty odwrotnej należy zdelinearyzować współczynniki fal-
kowe do pierwotnej postaci. Następnie współczynniki falkowe są poddawane odwrotnej
transformacie falkowej, która jest wykonywana analogicznie do zwykłej transformaty. Je-
dyną różnicą jest wykorzystanie par filtrów syntezy zamiast filtrów analizy i wstawienie
dodatkowych zer pomiędzy współczynnikami.
Jeśli obraz bądź sekwencja wideo była poddana transformacie kolorów, należy wykonać
odwrotną transformatę kolorów. Po jej wykonaniu uzyskujemy już pełną rekonstrukcję w
pamięci. Ostatnim krokiem jest reorganizacja danych, mająca na celu ustalenie takiego
formatu danych, aby możliwy był zapis w innej postaci (np. jeśli wartości poszczególnych
barw piksela muszą być obok siebie). Kroki, które musi wykonać dekoder, są przedstawione
na rysunku 22.
Rysunek 22: Schemat dekompresji obrazów
38
7 Implementacja projektu
W tym rozdziale przedstawiony jest opis implementacji projektu. Najpierw opiszę wy-
korzystywaną platformę sprzętową, niezbędne narzędzia oraz biblioteki. Następnie przed-
stawię w szczegółach zaimplementowany algorytm obliczania transformaty falkowej na
procesorze GPU. Na końcu opiszę wbudowane w aplikację mechanizmy mierzenia czasu
wykonania.
7.1 Platforma sprzętowa
Ze względu na konieczność porównania implementacji CPU i GPU ważne jest na jakim
sprzęcie została ona przetestowana. Na potrzeby implementacji i testów wykorzystano
następującą konfigurację sprzętową:
CPU AMD Atholon II X3 440 (3x3GHz)
Chipset AMD 785
RAM DDR3 2x2GB (1333Mhz)
GPU NVIDIA GeForce GTS250 1GB
W poniższej tabeli zamieszczam właściwości wykorzystanego akceleratora graficznego
pod kątem technologii NVIDIA CUDA:
Compute capability* 1.1Liczba multiprocesorów (MP) 16Liczba rdzeni w multiprocesorze (SP) 8Łączna liczba rdzeni 128Taktowanie zegara 1.46GHzŁączna ilość pamięci globalnej 1073020928 bajtówIlość pamięci współdzielonej na rdzeń 65535 bajtówLiczba rejestrów na rdzeń 8192Maksymalna liczba wątków na blok 512Maksymalna wielkość każdego z wymiarów w bloku 512 x 512 x 64Maksymalna wielkość każdego z wymiarów w kracie 65535 x 65535 x 1* Compute capability określa możliwości obliczeniowe procesora graficznego. Głów-
ny numer rewizji mówi o architekturze procesora, natomiast numer podrewizjiokreśla stopniowo rozwijane rozszerzenia w ramach tej samej architektury. Szcze-góły można znaleźć w dodatku G dokumentacji [14].
Komputer pracował pod kontrolą systemu Ubuntu Linux 10.10 64bit z jądrem w wersji
2.6.35.28. Wersja sterowników NVIDIA to 270.35.
39
7.2 Narzędzia i biblioteki
Do implementacji projektu wykorzystałem następujące narzędzia:
Cuda Toolkit v4.0
Zawiera zbiór niezbędnych narzędzi (np. kompilator nvcc) i bibliotek. Dołączona
jest tu również dokumentacja i przykładowe aplikacje wraz z kodem źródłowym.
Scons
Narzędzie służące do budowania aplikacji, zastępujące makefile. Dzięki temu, że
jest napisane w języku Python bardzo łatwo zwiększa się jego funkcjonalność. Wy-
korzystałem rozszerzenia pozwalające na kompilację kodu CUDA i automatyczne
uruchamianie testów jednostkowych po każdym zbudowaniu aplikacji.
valgrind
Narzędzie umożliwiające detekcję wycieków pamięci i nieprawidłowych odwołań do
pamięci.
QtCreator
Środowisko programistyczne dla języka C++. Szczególnie przydatną funkcją jest
dobrze działający graficzny debugger bazujący na gdb.
svn
Użyłem systemu kontroli wersji svn. Kod projektu znajduje się w pu-
blicznie dostępnym repozytorium pod adresem: http://code.google.com/p/
wavelet-compressor/.
Poniżej znajdują się użyte biblioteki oraz zaadaptowany kod wraz z opisem ich wyko-
rzystania w zaimplementowanym projekcie:
OpenCV
Biblioteka OpenCV (Open Source Computer Vision) została wykorzystana do od-
czytu i zapisu różnych formatów plików obrazów i sekwencji wideo.
googletest
Biblioteka do tworzenia testów jednostkowych w C++.
Wavelet Image Compression Construction Kit
Szkielet aplikacji kompresującej obrazy przy wykorzystaniu transformaty falkowej.
Zaadaptowałem stąd kod algorytmu optymalizacji R-D.
Koder arytmetyczny z publikacji [6]
Wykorzystałem kod kodera arytmetycznego opisanego w wyżej wymienionej pracy.
Wprowadzone zostały zmiany umożliwiające zmianę modelu i kodowanie/dekodo-
wanie pojedynczych symboli.
40
7.3 Algorytm obliczania transformaty falkowej na procesorze
GPU
W rozdziale poświęconym opisowi proponowanego rozwiązania przedstawione zosta-
ły główne założenia dotyczące implementacji transformaty falkowej na procesorze karty
graficznej. Każdy z wątków będzie zajmował się równocześnie obliczaniem jednego współ-
czynnika wysokich częstotliwości i jednego niskich. Tak więc dla jednowymiarowego sy-
gnału o długości N będziemy potrzebowali N/2 wątków. Dodatkowo dla filtrów o większej
długości kilka wątków będzie korzystało z tych samych współczynników (rysunek 23).
Rysunek 23: Dostęp do tych samych elementów przy obliczaniu konwolucji z filtrem oośmiu współczynnikach
Dzięki takiemu dostępowi do pamięci warto jest wykorzystać pamięć współdzieloną.
Wątki ładują dane do pamięci współdzielonej, a następnie obliczają transformatę korzy-
stając z zgromadzonych tam danych. Jako że każdy z wątków oblicza dwa współczynniki,
musi on również załadować dwa współczynniki. Nie można również zapomnieć o tym, że
do obliczenia konwolucji potrzebujemy kilku sąsiednich elementów. Na granicach należy
pobrać o tyle więcej elementów ile wynosi suma długości najdłuższego filtra i jego prze-
sunięcia. Część wątków uczestniczy tylko w ładowaniu danych do pamięci współdzielonej
bez wykonywania obliczeń. Ze względu na małą długość filtrów w porównaniu do liczby
wątków nie wpływa to znacząco na efektywność.
Rysunek 24: Obliczanie współczynników falkowych z wykorzystaniem pamięci współdzie-lonej
41
Na rysunku 24 przedstawiony został schemat obliczania współczynników falkowych z
wykorzystaniem pamięci współdzielonej. Wykorzystana musi być funkcja syncthreads()
synchronizująca wątki w ramach bloku. Wątki nie biorące udziału w obliczaniu współ-
czynników kończą działanie po wywołaniu funkcji syncthreads().
Wszystkie wątki przy liczeniu transformaty wykorzystują współczynniki filtrów dolno
i górnoprzepustowego. Naturalnym miejscem do przechowywania tych wartości jest pa-
mięć stałych. Współczynniki filtrów są umieszczane w pamięci stałych przez hosta przed
uruchomieniem kerneli.
Obliczenie transformaty dla sygnałów wielowymiarowych odbywa się poprzez osobne
obliczenie transformaty jednowymiarowej po każdym z wymiarów. Dla obrazu wykonuje-
my więc najpierw obliczenie transformaty po wierszach, a następnie po kolumnach. Dzięki
wykorzystaniu pamięci współdzielonej znacznie uprościła się implementacja poszczegól-
nych kerneli. Każdy z nich ładuje najpierw dane w ramach bloku do pamięci współdzie-
lonej. Na rysunku 25 pokazane jest ładowanie danych w przypadku transformaty obrazu.
Należy oczywiście uwzględnić dodatkowe elementy na granicach.
Rysunek 25: Załadowanie danych do pamięci współdzielonej w przypadku transformatyfalkowej obrazu
Po załadowaniu danych do pamięci współdzielonej, możemy zastosować algorytm kon-
wolucji wykorzystujący współczynniki z pamięci współdzielonej i współczynniki filtrów z
pamięci stałych. Dzięki wstępnemu załadowaniu danych do tej pamięci nie jest wyma-
gane złożone indeksowanie. Kernele dla transformaty jedno, dwu i trójwymiarowej mają
identyczną strukturę. W pierwszym kroku ładują one dane do pamięci współdzielonej,
np. w sekwencji wideo ładowana jest kolumna danej ramki. Po zsynchronizowaniu wąt-
ków wywoływana jest funkcja obliczająca i-ty współczynnik falkowy na podstawie danych
zgromadzonych w pamięci współdzielonej. Następnie otrzymany współczynnik jest zapi-
sywany w pamięci globalnej przeznaczonej na wynik.
42
Transformata odwrotna działa w analogiczny sposób z tą różnicą, że każdy z wątków
oblicza tylko jeden współczynnik. Musi on najpierw załadować do pamięci współdzielonej
jeden współczynnik wysokich częstotliwości i jeden współczynnik niskich częstotliwości.
Między pobrane wartości wstawiane są od razu zera (rysunek 26). Następnie wywoływana
jest generyczna funkcja obliczająca transformatę odwrotną na podstawie przekazanych
w pamięci współdzielonej danych. Zrekonstruowany element zapisywany jest w pamięci
globalnej.
Rysunek 26: Obliczenie transformaty odwrotnej z załadowaniem danych pasma niskich iwysokich częstotliwości do pamięci współdzielonej
Posiadając zaimplementowane funkcje kerneli należy dla wybranych danych wybrać
odpowiednią ilość bloków i wywołać funkcje kerneli. Trzeba również wziąć pod uwagę licz-
bę poziomów dekompozycji. Poniżej umieściłem kod wywoływany przy obliczaniu trans-
formaty falkowej dla kilku poziomów dekompozycji w obrazie.
while ( l e v e l s−− > 0) {// C a l c u a l t e dimensions o f b l o c k f o r row transform
. . .
// C a l c u a l t e dwt on d e v i c e f o r each row
forwardTransform2DRow<<<numBlocks , threadsPerBlock >>>(. . .) ;
// C a l c u a l t e dimensions o f b l o c k f o r row transform
. . .
// C a l c u a l t e dwt on d e v i c e f o r each column
forwardTransform2DColumn<<<numBlocks , threadsPerBlock >>>(. . .) ;
currWidth = ( currWidth+1) / 2 ;
currHeight = ( currHe ight +1) / 2 ;
}
43
7.4 Mechanizmy pomiaru czasu
Aplikacja posiada wbudowane w siebie mechanizmy pomiaru czasu wykonania poszcze-
gólnych kroków algorytmu kompresji/dekompresji. Dzięki temu możliwe jest określenie
zysku uzyskanego przy implementacji CUDA. Standardowym podejściem przy pomiarze
czasu jest wykorzystanie funkcji clock() z biblioteki time.h. Taki zegar posiada jednak
niską rozdzielczość przez co nie mógł być wykorzystany w mojej aplikacji. Do pomiaru
czasu wykonania przy pomocy zegara CPU można wykorzystać funkcję clock gettime(),
która posiada większą rozdzielczość.
Należy jednak mieć na uwadze, że część wywołań CUDA jest asynchroniczna (m.in.
wywołania kerneli) [13]. Aby możliwy był poprawny pomiar czasu wykonania, należy
wówczas wywołać przed zatrzymaniem zegara funkcję cudaThreadSynchronize(). Można
tego uniknąć wykorzystując CUDA Event API, które pozwala na tworzenie zdarzeń (cu-
daEventCreate) i zarejestrowanie czasu (cudaEventRecord) poprzez znacznik czasu. Syn-
chronizacja wywołań asynchronicznych nie jest wówczas wymagana. Taki właśnie sposób
wykorzystałem w swojej aplikacji.
Pomiarowi czasu zostały poddane przedstawione na rysunku 27 kroki algorytmu kom-
presji. Dla implementacji CUDA uwzględniłem czas konieczny na zaalokowanie pamięci
urządzenia oraz na wykonanie transferów. Kwantyzacja i kodowanie arytmetyczne zosta-
ły uwzględnione razem, ze względu na sposób implementacji. Współczynniki falkowe są
kwantyzowane jeden po drugim i od razu kodowane koderem arytmetycznym. Nie jest
więc możliwy oddzielny pomiar czasu wykonania tych kroków.
Rysunek 27: Kroki algorytmu kompresji poddane pomiarowi czasu dla implementacji CPUi GPU
44
Rysunek 28: Kroki algorytmu dekompresji poddane pomiarowi czasu dla implementacjiCPU i GPU
Analogiczny pomiar czasu działania jest zaimplementowany przy algorytmie dekom-
presji (rysunek 28). Dekodowanie arytmetyczne oraz dekwantyzacja została uwzględniona
razem. Zdekodowane koderem arytmetycznym symbole są od razu poddawane dekwanty-
zacji. Nie jest wykorzystywany żaden pośredni bufor przez co nie jest możliwy oddzielny
pomiar czasu.
Na obu rysunkach 27 i 28 przedstawiłem również bloki związane z odczytem i zapisem
przy wykorzystaniu biblioteki OpenCV. W kroku tym ujęty jest czas wykorzystywany do
odpowiedniego sformatowania danych. Przedstawiony pomiar czasu nie obejmuje całości
czasu wykonania programu. Nie są w nim uwzględnione, np. czasy związane z wykona-
niem parsowania argumentów wywołania. Czas ten jednak jest zaniedbywalnie mały w
porównaniu z czasem wykonania poszczególnych kroków kompresji/dekompresji.
45
8 Weryfikacja
W tym rozdziale przedstawione są metody weryfikacji rozwiązania wraz z uzyskanymi
wynikami. Zaimplementowany kompresor został przetestowany pod względem skuteczno-
ści kompresji oraz szybkości działania. Zbadano efektywność kompresji dla poszczególnych
funkcji falkowych i kwantyzatorów. Na potrzeby porównania implementacji CUDA zmie-
rzono czas wykonania dla implementacji na procesorze CPU i GPU. Wykorzystano wbudo-
wane w aplikację mechanizmy pomiaru czasu działania poszczególnych kroków algorytmu.
Dodatkowo wykonane zostały syntetyczne testy dla transformaty falkowej sprawdzające
precyzję obliczeń na procesorze GPU. Każdy z testów został opisany w oddzielnym pod-
rozdziale wraz z krótkim opisem metodyki wykonania testu. Na początku przedstawiam
wybrany zbiór testowy obrazów i sekwencji wideo.
8.1 Zbiór testowy
lena 512x512 (768kB)
lena gray 512x512 (256kB)
barbara 512x512 (256kB)
46
pw 1024x1024 (3MB)
globe 2048x2048 (12MB)
moon 4096x4096 (48MB)
klip wideo crysis 2
• 640x360, 256 klatek (168,75 MB)
• 512x256, 256 klatek (96 MB)
• 256x128, 256 klatek (24 MB)
47
8.2 Precyzja obliczeń
Wyniki obliczeń wykonywane na procesorze GPU mogą nieco różnić się od wyników na
procesorze GPU. Wykorzystywana przeze mnie karta graficzna wspiera liczby zmienno-
przecinkowe pojedynczej precyzji zbliżone do standardu IEEE 754. W tabeli 5 znajdują
się wyniki dla wykonanego testu precyzji obliczeń. Przetestowana została transformata
i transformata odwrotna dla sygnałów jedno, dwu i trójwymiarowych z zadaną liczbą
poziomów dekompozycji. Danymi wejściowymi były losowe liczby całkowite z zakresu [0 -
255] (tak jak wartości jasności pikseli). Obliczony został całkowity błąd bezwzględny oraz
błąd na symbol.
Uzyskane wyniki świadczą o niewielkiej rozbieżności precyzji obliczeń na GPU. Dys-
kretna transformata falkowa jest stabilna numerycznie i niewielkie niedokładności nie
wpływają na uzyskane wyniki.
Liczba poziomówdekompozycji
Wymiary Całkowity błąd Błąd na symbol
DWT 1D 16 16777216 250.073 1.49 ∗ 10−5
DWT 2D 8 1024 x 1024 18.157 1.73 ∗ 10−5
DWT 3D 5 512 x 512 x 64 306.206 1.83 ∗ 10−5
IDWT 1D 16 16777216 213.801 1.73 ∗ 10−5
IDWT 2D 8 1024 x 1024 102.353 9.76 ∗ 10−5
IDWT 3D 5 512 x 512 x 64 278.142 1.81 ∗ 10−5
Tabela 5: Wyniki testów porównujących precyzję obliczeń transformaty CPU i GPU
8.3 Efektywność kompresji
Efektywność kompresji stratnej jest oceniana pod dwoma kryteriami: uzyskanej liczby
bitów (rate) oraz zniekształceń (distortion). Miarę uzyskanej liczby bitów określa się przy
pomocy stopnia kompresji wyrażającego stosunek liczby bitów danych oryginalnych do
liczby bitów uzyskanych w wyniku kompresji. Często korzysta się również z pojęcia bitrate
określającego liczbę bitów przypadających na jeden symbol zakodowanego sygnału. W
przypadku kompresji obrazów i sekwencji wideo jest to liczba bitów przypadających na
jeden piksel (bpp - bit per pixel).
Do opisania zniekształceń wybrałem powszechnie wykorzystywaną miarę PSNR (Pe-
ak signal-to-noise ratio). Wyraża ona stosunek maksymalnej możliwej energii sygnału i
energii szumu (w tym przypadku zniekształceń). Wykorzystuje się w niej skalę logaryt-
miczną, ze względu na różną dynamikę sygnałów, dla których używa się tej miary. Do
opisania szumu wykorzystywany jest opisany wcześniej błąd średniokwadratowy MSE 6.
Maksymalna energia sygnału MAX to maksymalna możliwa wartość jasności piksela (dla
8 bitów na piksel to 255).
48
Wzór opisujący PSNR ma następującą postać:
PSNR = 10 log10
(MAX2
MSE
)= 20 log10
(MAX√MSE
)(12)
Dla obrazów barwnych miara PSNR jest również obliczona dla każdego z kanałów osob-
no. W testach efektywności kompresji najlepiej jednak korzystać z obrazów w odcieniach
szarości, ponieważ uzyskujemy możliwość łatwego porównania skuteczności kompresji z
innymi rozwiązaniami. Dodatkowo dla obrazów barwnych jest obliczana miara PSNR po
sprowadzeniu obrazu do odcieni szarości. Warto zauważyć, że przy braku zniekształceń
błąd średniokwadratowy wynosi 0 i miara PSNR dąży do nieskończoności. W pracy [15],
można znaleźć wiele innych miar syntetycznych, jak również miar subiektywnych. W wy-
konanych przeze mnie testach aplikacji ograniczyłem się do najczęściej wykorzystywanej
miary PSNR.
W koder wbudowane są mechanizmy pomiaru powstałych w wyniku kompresji znie-
kształceń, według miary PSNR. Liczba bitów jest obliczana przez pomiar długości po-
wstałego pliku wynikowego. Na podstawie uzyskanej liczby bitów określane jest również
odchylenie uzyskanego stopnia kompresji od zadanego. Wszystkie te informacje można
uzyskać po podaniu odpowiedniego argumentu wywołania programu.
Jako pierwsze przedstawię wyniki porównania dwóch zaimplementowanych kwantyza-
torów: kwantyzatora równomiernego (UTQ) i kwantyzatora równomiernego z rozszerzo-
nym przedziałem zerowym (DUTQ). Obrazem testowym jest lena w odcieniach szarości.
Dla lepszego zobrazowania uzyskanych wyników wykorzystam przedstawione wcześniej
krzywe R-D (rysunek 29).
Rysunek 29: Wyniki kompresji dla kwantyzatorów UTQ i DUTQ
49
Zgodnie z przewidywaniami, wykorzystanie kwantyzatora z rozszerzonym przedziałem
zerowym umożliwiło uzyskanie mniejszych zniekształceń dla wyższych stopni kompresji
(0.5 PSNR dla stopnia 64 i ponad 0.8 PSNR dla stopnia 128). Wraz z wzrostem stopnia
kompresji silniej kwantyzujemy współczynniki i rozszerzony przedział zerowy kwantyzuje
więcej współczynników do zera. Przy niskiej kompresji wykorzystanie tego kwantyzatora
nie daje lepszych wyników. Dalsze testy były przeprowadzane przy użyciu kwantyzatora z
rozszerzonym przedziałem zerowym, który charakteryzuje się lepszym stopniem kompresji.
Kolejnym testem, który przeprowadziłem było określenie optymalnej liczby poziomów
dekompozycji przy kompresji obrazów i plików wideo. Do testów wybrałem obraz lena
w odcieniach szarości oraz klip testowy o rozmiarze ramki 640x360 pikseli. Testy zostały
wykonane przy użyciu banku filtrów antonini dla stopnia kompresji 32 dla obrazu i 32
dla sekwencji wideo. Pomiarowi został również poddany uzyskany stopień kompresji. W
tabeli 6 znajdują się uzyskane wyniki. Dla sekwencji wideo zostały uwzględnione uzyskane
wyniki dla poszczególnych kanałów.
Poziomydekomp.
Lena CrysisUzyskanystopień
PSNRUzyskanystopień
PSNR PSNR-CY PSNR-Cb PSNR-Cr
1 37.75 16.636 32.286 30.161 29.899 37.571 36.0292 32.05 29.128 32.173 33.318 35.110 42.000 43.6763 32.45 31.176 32.068 43.761 43.816 51.412 53.2874 32.24 31.529 32.072 31.437 30.848 40.880 47.3255 32.00 31.544 32.112 30.086 30.317 40.213 46.7536 32.00 31.5447 32.02 31.5418 32.04 31.537
Tabela 6: Wyniki testów porównujących wpływ liczby poziomów dekompozycji na stopieńkompresji
Przy kompresji obrazu zadowalające wyniki uzyskujemy dopiero po wykonaniu przy-
najmniej trzy poziomowej dekompozycji. Wartością optymalną jest 5 i taką też przyjąłem
jako domyślną w mojej aplikacji.4 Wraz z zwiększeniem liczby poziomów dekompozycji
zwiększa się ilość podpasm, a co za tym idzie ilość kwantyzatorów, których dane trzeba
przesłać (szerokość przedziału kwantyzacji itp.). Należy mieć również na uwadze to, że
większa liczba poziomów dekompozycji zwiększa koszt obliczenia transformaty falkowej.
W przypadku kompresji wideo optymalną wartość uzyskujemy przy trzypoziomowej
dekompozycji. Najważniejsze dla odbiorcy dane zawarte są w kanale z jasnością (Y). Dla
każdego z 3 kanałów przeznaczona jest inna ilość bitów. Kanał Y uzyskuje 0.8 budżetu
bitowego natomiast kanały Cb i Cr dostają po 0.1 budżetu bitów. Wyższa jakość kompresji
dla kanału z chrominancją wynika z cech zastosowanej transformaty kolorów.
4Inne obrazy mogą mieć inną wartość optymalną jednak będzie ona blisko tej wartości.
50
Kolejną zbadaną przeze mnie kwestią były wybrane filtry falkowe. W tabeli 7 przed-
stawione są wyniki testów dla zadanych stopni kompresji przy obrazie barbara. Obraz był
kompresowany przy użyciu 5 poziomów dekompozycji. Obraz barbara posiada znacznie
więcej krawędzi niż obraz lena, w którym przeważały dość łagodne gradienty. Skutkuje to
oczywiście niższą skutecznością kompresji.
Stopień kompr. haar daub4 cdf97 antonini2 47.628 48.401 48.985 49.9834 37.568 39.259 40.666 40.6668 30.647 32.642 34.584 34.58416 25.854 28.278 29.801 29.80132 23.907 24.766 25.291 25.291
Tabela 7: Wyniki testów porównujących wybrane filtry falkowe według miary PSNR
Najlepszymi zestawami filtrów okazały się filtry cdf97 i antonini. Uzyskane przez nie
wyniki są prawie takie same. Różnice w ilości bitów i uzyskanej miary PSNR są bardzo
małe. Jako domyślny filtr falkowy ustaliłem filtr antonini. Rysunek 30 pokazuje fragment
uzyskanego w wyniku kompresji obrazu.
Rysunek 30: Wyniki ośmiokrotnej kompresji dla różnych filtrów: A - oryginał, B - haar, C -daub04, D - cdf97
51
Stopień kompresji lena lena gray barbara pw globe moon
4
PSNR 45.651 42.357 40.666 49.148 51.270 50.524PSNR-Y 45.991 49.032 51.348 50.011PSNR-Cb 40.981 51.721 53.243 51.885PSNR-Cr 41.657 51.733 55.499 52.700
8
PSNR 42.582 36.557 34.583 44.483 49.085 49.000PSNR-Y 42.469 44.463 49.256 48.756PSNR-Cb 39.724 49.157 50.525 49.335PSNR-Cr 39.878 49.416 55.100 51.537
16
PSNR 38.593 33.929 29.801 37.695 41.553 43.416PSNR-Y 38.005 37.691 41.621 43.380PSNR-Cb 38.506 45.975 47.903 46.437PSNR-Cr 38.503 46.223 53.545 48.910
32
PSNR 35.693 31.544 25.291 32.477 36.036 39.483PSNR-Y 35.096 32.467 36.070 39.480PSNR-Cb 36.585 42.288 45.507 44.390PSNR-Cr 36.350 42.516 51.835 46.851
64
PSNR 32.331 29.317 23.201 28.851 32.550 36.723PSNR-Y 31.852 28.828 32.569 36.723PSNR-Cb 32.785 38.543 43.448 42.499PSNR-Cr 33.808 38.921 50.156 44.726
128
PSNR 29.104 26.815 21.103 25.827 30.076 34.055PSNR-Y 29.032 25.749 30.089 33.990PSNR-Cb 28.193 32.673 41.294 41.374PSNR-Cr 32.105 33.430 48.129 42.932
Tabela 8: Wyniki testów kompresji obrazów dla stopni 4, 8, 16, 32, 64 i 128 według miaryPSNR. PSNR dla obrazów barwnych został obliczony po przekształceniu na przestrzeńbarw Y CbCr, a także po przekształceniu na obraz monochromatyczny.
Stopień kompresji crysis 512x256
32
PSNR 42.677PSNR-Y 42.746PSNR-Cb 50.285PSNR-Cr 52.701
64
PSNR 38.630PSNR-Y 38.640PSNR-Cb 47.520PSNR-Cr 49.905
128
PSNR 34.721PSNR-Y 34.701PSNR-Cb 44.127PSNR-Cr 47.426
Tabela 9: Wyniki testów efektywności kompresji sekwencji wideo ”crysis 2” dla stopni 32,64 i 128 według miary PSNR. PSNR został obliczony po przekształceniu na przestrzeńbarw Y CbCr, a także po przekształceniu na sekwencję obrazów monochromatyczną.
52
8.4 Szybkość działania
Dzięki wbudowanym w aplikację mechanizmom pomiaru czasu udało się precyzyjnie
określić ile czasu zajmuje wykonanie poszczególnych kroków algorytmu kompresji/dekom-
presji. Głównym celem było oczywiście porównanie efektywności implementacji CUDA z
implementacją CPU. Każdy z testów czasu działania był pięciokrotnie powtarzany, a z
zmierzonych czasów była wyciągnięta średnia.
Jako pierwszą porównałem efektywność działania dla obrazów, różnych rozmiarów.
Wybrałem obrazy barwne, aby określić korzyść z wykorzystania obliczeń GPU. Szcze-
gółowe wyniki przedstawione są w tabelach 10, 11, 12, 13 na końcu podrozdziału. Na
wykresach 31 i 32 przedstawiłem czas działania dla kompresji i dekompresji obrazów dla
poszczególnych rozmiarów. Dla obrazu o rozmiarze 512x512 pikseli nie ma w praktyce
zysku z wykorzystania implementacji GPU. Przy obrazach większego rozmiaru zysk staje
się zauważalny.
Rysunek 31: Czas kompresji obrazów dla implementacji CPU i CUDA
Rysunek 32: Czas dekompresji obrazów dla implementacji CPU i CUDA
53
Jak da się zauważyć zysk przy dekompresji jest znacznie większy niż przy kompresji.
Wynika to z tego, iż większą część czasu działania przy kompresji zajmuje wykonanie opty-
malizacji RD (rysunki 33 i 34). Dlatego pomimo blisko dziesięciokrotnego przyśpieszenia
obliczania transformaty falkowej dla obrazu 4096x4096 uzyskałem wzrost wydajności rzę-
du 40 procent. Dekompresja nie wymaga wykonania optymalizacji RD, więc przyśpiesze-
nie transformaty odwrotnej skutkuje ponad trzykrotnym przyśpieszeniem czasu działania
(rysunki 35 i 36).
Implementacja transformaty kolorów na procesorze GPU jest średnio kilkunastokrot-
nie szybsza niż na CPU. Jednak ze względu na znikomy koszt w porównaniu z pozostałymi
krokami nie wpływa ona znacząco na całkowitą efektywność kompresora. Należy również
zwrócić uwagę na koszty alokacji i transferów danych na urządzeniu. Dla transformaty
dwuwymiarowej koszty alokacji i transferów stanowią do 20 procent całego czasu prze-
twarzania na GPU. Przy dekompresji znaczący czas zaczyna zajmować zapis do innego
formatu pliku wykonywany przez bibliotekę OpenCV.
Rysunek 33: Czas wykonania kompresji dla implementacji CPU
Rysunek 34: Czas wykonania kompresji dla implementacji CUDA
54
Rysunek 35: Czas wykonania dekompresji dla implementacji CPU
Rysunek 36: Czas wykonania dekompresji dla implementacji CUDA
Dla kompresji i dekompresji sekwencji wideo uzyskałem analogiczne wyniki (rysunki
37 i 38) jak dla obrazów. Szczegółowe wyniki znajdują się w tabelach 14, 15, 16 i 17.
Zdecydowaną większość czasu kompresji zajmuje wykonanie optymalizacji RD (dla se-
kwencji wideo 640x360 ponad 70 procent). Da się również zauważyć mniejszy stosunek
czasu obliczenia transformaty na CPU do czasu obliczenia na GPU. Dla transformaty
trójwymiarowej wynosi on około trzech.
Mniejsza efektywność ma swoje źródło w dostępie do pamięci. Przy transformacie
trójwymiarowej dostęp do pamięci zazwyczaj nie odbywa się po kolejnych fragmentach
pamięci5 przez co nie jest wykorzystywana technika memory coalescing. Współczynniki
ładowane do pamięci współdzielonej bloku są zazwyczaj fizycznie rozroszone w pamięci
głównej.
5dostęp po kolejnych elementach w pamięci ma miejsce przy transformacie po wierszach
55
Rysunek 37: Czas kompresji wideo dla implementacji CPU i CUDA
Rysunek 38: Czas dekompresji wideo dla implementacji CPU i CUDA
Rysunek 39: Czas kompresji wideo dla implementacji CUDA
Rysunek 40: Czas dekompresji wideo dla implementacji CUDA
56
512x512 1024x1024 2048x2048 4096x4096OpenCV open 0,005083 0,031146 0,097367 0,399815Transformata kolorów 0,002358 0,009395 0,036423 0,147462Transformata falkowa 0,130907 0,702710 2,813774 13,048168Linearyzacja 0,002362 0,006939 0,026502 0,095985Optymalizacja RD 0,326178 1,494638 5,031271 21,891127Kwantyzacja i kodowanie arytm. 0,025705 0,102173 0,405921 1,642238RAZEM 0,492593 2,347001 8,411258 37,224795
Tabela 10: Czas działania dla kompresji obrazów CPU w sekundach
512x512 1024x1024 2048x2048 4096x4096OpenCV open 0,005095 0,031177 0,097305 0,399116CUDA malloc 0,000512 0,000488 0,000293 0,000385CUDA host → device 0,018101 0,004106 0,017445 0,074997CUDA transformata kolorów 0,000148 0,000600 0,002196 0,008890CUDA transformata falkowa 0,013011 0,057505 0,337600 1,556905CUDA device → host 0,002468 0,008503 0,028768 0,123334Linearyzacja 0,001890 0,007802 0,027649 0,105700Optymalizacja RD 0,327318 1,498222 5,062164 21,894482Kwantyzacja i kodowanie arytm. 0,025596 0,101571 0,403725 1,631859RAZEM 0,394139 1,709974 5,977145 25,795668
Tabela 11: Czas działania dla kompresji obrazów CUDA w sekundach
512x512 1024x1024 2048x2048 4096x4096Dekodowanie i dekwantyzacja 0,0391400 0,146900 0,588237 2,315925Transformata kolorów 0,001748 0,006974 0,029906 0,119898Transformata falkowa 0,197179 1,000924 4,080403 17,531343Delinearyzacja 0,001935 0,006971 0,026498 0,099524OpenCV save 0,028433 0,164328 0,685740 2,983716RAZEM 0,268435 1,326097 5,410784 23,050406
Tabela 12: Czas działania dla dekompresji obrazów CPU w sekundach
512x512 1024x1024 2048x2048 4096x4096OpenCV save 0,036930 0,167261 0,665624 2,99187Delinearyzacja 0,001438 0,006763 0,026201 0,099930Dekodowanie i dekwantyzacja 0,039090 0,147483 0,587026 2,304214CUDA malloc 0,000504 0,000457 0,000102 0,000699CUDA host → device 0,001731 0,004969 0,015638 0,061258CUDA transformata kolorów 0,000280 0,000620 0,000207 0,000788CUDA transformata falkowa 0,012298 0,042797 0,231374 1,552105CUDA device → host 0,004244 0,011857 0,045230 0,182405RAZEM 0,096515 0,382207 1,571402 7,193269
Tabela 13: Czas działania dla dekompresji obrazów CUDA w sekundach
57
256x128 512x256 640x360OpenCV open 0,193679 1,270311 1,479300Transformata kolorów 0,074103 0,293666 0,516504Transformata falkowa 7,813984 31,581902 36,333046Linearyzacja 0,055390 0,186920 0,327388Optymalizacja RD 9,268871 34,757854 60,533020Kwantyzacja i kodowanie arytm. 0,845549 3,356462 5,920140RAZEM 18,251576 71,447115 105,109398
Tabela 14: Czas działania dla kompresji wideo CPU w sekundach
256x128 512x256 640x360OpenCV open 0,192346 1,283418 1,483076CUDA malloc 0,000080 0,001169 0,000623CUDA host → device 0,034953 0,148364 0,290110CUDA transformata kolorów 0,004485 0,017685 0,036854CUDA transformata falkowa 2,651940 10,222892 14,795855CUDA device → host 0,046034 0,176791 0,296816Linearyzacja 0,056424 0,186734 0,326103Optymalizacja RD 9,320011 34,808395 60,633026Kwantyzacja i kodowanie arytm. 0,827814 3,323836 5,872478RAZEM 13,134087 50,169284 83,734941
Tabela 15: Czas działania dla kompresji wideo CUDA w sekundach
256x128 512x256 640x360OpenCV save 0,392186 1,781573 2,693752Transformata kolorów 0,059673 0,236618 0,413091Transformata falkowa 10,922973 44,181793 55,494530Delinearyzacja 0,052526 0,187883 0,337915Dekodowanie i dekwantyzacja 1,205501 4,757320 8,300419RAZEM 12,632859 51,145187 67,239707
Tabela 16: Czas działania dla dekompresji wideo CPU w sekundach
256x128 512x256 640x360OpenCV save 0,492696 2,184490 3,434199Delinearyzacja 0,052753 0,188992 0,334865Dekodowanie i dekwantyzacja 1,221008 4,733652 8,288025CUDA malloc 0,000552 0,000738 0,000509CUDA host → device 0,032174 0,164386 0,215942CUDA transformata kolorów 0,004426 0,017633 0,036558CUDA transformata falkowa 1,606770 6,311075 8,970387CUDA device → host 0,035390 0,143260 0,248643RAZEM 3,445769 13,744226 21,529128
Tabela 17: Czas działania dla dekompresji wideo CUDA w sekundach
58
9 Podsumowanie
W niniejszej pracy zaprezentowane zostały poszczególne kroki wykonywane przez
stratny kompresor falkowy. Opis poszczególnych algorytmów uzupełniony jest o przykłady
dla łatwiejszego zrozumienia ich działania. Przedstawiłem również krótki opis technolo-
gii NVIDIA CUDA skupiając się na elementach wykorzystanych przy implementacji. W
ramach pracy wykonany został stratny koder falkowy umożliwiający wykonywanie części
obliczeń na procesorze GPU. Aplikację zweryfikowałem pod kątem efektywności kompresji
oraz szybkości działania.
W pracy składającej się z dziewięciu rozdziałów przedstawiłem najpierw teoretyczne
podstawy dotyczące transformaty falkowej. Schemat obliczania współczynników falkowych
przedstawiony jest na prostym przykładzie dla lepszego zrozumienia charakteru transfor-
maty falkowej. Opisane są kwestie związane z transformatą sygnałów wielowymiarowych
i zachowaniem na granicach. W kolejnym rozdziale zaprezentowałem klasyczny schemat
kwantyzacji skalarnej z dwoma wariantami (kwantyzacja równomierna i kwantyzacja rów-
nomierna z rozszerzonym przedziałem zerowym). Bezstratny krok kompresji realizowany
przez koder arytmetyczny opisany jest w rozdziale czwartym. Wszystkie przedstawione
algorytmy opisane są w kontekście ich wykorzystania przy kompresji. Dzięki temu oraz
dzięki licznym przykładom, praca może stanowić materiał dydaktyczny dla osób chcących
zapoznać się z falkową kompresją stratną.
Opis proponowanego rozwiązania i jego implementacji skupia się na implementacji
GPU. Na procesorze graficznym udało się zrealizować kroki związane z transformatą ko-
lorów i transformatą falkową. Kodowanie arytmetyczne ze względu na swój sekwencyj-
ny charakter nie nadaje się do zrównoleglenia. Naturalnym kandydatem do wykonania
na GPU wydaje się krok kwantyzacji. Wykorzystany przeze mnie algorytm optymaliza-
cji RD, w którym koszt bitowy jest precyzyjne obliczany przez zakodowanie każdego z
współczynników, sprawia, że kwantyzacja na GPU zwiększa czas działania. Dzięki wyko-
rzystaniu technologii NVIDIA CUDA udało mi się uzyskać kilkukrotne szybsze wykonanie
kroku obliczania transformaty falkowej (blisko dziesięciokrotne dla transformaty dwuwy-
miarowej i 3-4 krotne dla transformaty trójwymiarowej). Wąskim gardłem przy kompresji
wykorzystującej GPU jest optymalizacja RD i zajmuje ona do 85 procent czasu wykona-
nia.
Uzyskane wyniki stopnia kompresji świadczą o umiarkowanej efektywności wykonane-
go rozwiązania. Wynika to przede wszystkim z dość trywialnego kodera arytmetycznego.
Standard JPEG2000 wykorzystuje binarny koder arytmetyczny z modelowaniem staty-
stycznym wysokiego rzędu (MQ-Coder) [8]. Bardzo skutecznym rozwiązaniem jest również
połączenie transformaty falkowej z wykorzystaniem zero-drzew [18]. Wariacja tej meto-
dy wykorzystywana jest przez jeden najskuteczniejszych obecnie kompresorów stratnych
59
SPIHT [17]. Zastosowanie jednej z tych metod umożliwiłoby uzyskanie wyższej efektyw-
ności kompresji.
Podsumowując w ramach pracy przedstawiłem algorytmy niezbędne do zrealizowania
kompresji stratnej przy użyciu metod falkowych. Zaprojektowałem i zaimplementowa-
łem kompletny kompresor falkowy umożliwiający kompresję obrazów i sekwencji wideo w
oparciu o wybrane algorytmy. Zrealizowana przeze mnie aplikacja wykorzystuje technolo-
gię NVIDIA CUDA w celu przyśpieszenia czasu wykonania. Dzięki modularnej budowie
można łatwo rozszerzyć istniejący kompresor o inne rodzaje kwantyzatorów, koderów aryt-
metycznych czy filtrów falkowych. Aplikacja może być z powodzeniem wykorzystywana w
celach dydaktycznych.
60
Bibliografia
[1] Jan T. Białasiewicz. Falki i aproksymacje. Wydawinctwo Naukowo-Techniczne, War-
szawa, 2000.
[2] Chorng-Yann Su Bing-Fei Wu. A fast convolution algorithm for biorthogonal wavelet
image compression. 1999.
[3] Chris Brislawn. Classification of nonexpansive symmetric extension transforms for
multirate filter banks. Los Alamos Tech Report LA-UR-94-1747, 1994.
[4] Ingrid Daubechies. Ten Lectures on Wavelets. SIAM: Society for Industrial and
Applied Mathematics, 1992.
[5] Wen-mei W. Hwu David B. Kirk. Programming Massively Parallel Processors: A
Hands-on Approach. Morgan Kaufman, Burlington, 2010.
[6] Joachim Kneis Eric Bodden, Malte Clasen. Arithmetic coding revealed - a guided
tour from theory to praxis. 2007.
[7] David H. Salesin Eric J Stollnitz, Tony D. DeRose. Wavelets for computer graphics:
A primer, part 1. IEEE Computer Graphics and Applications, 15(3), 1995.
[8] JPEG. ”JPEG 2000 Core coding system (Part 1)”, 2000.
[9] Stephane Mallat. A Wavelet Tour of Signal Processing. Academic Press, 1998.
[10] Pierre Mathieu Ingrid Daubechies Marc Antonini, Michel Barlaud. Image coding
using wavelet transform. 1992.
[11] Jean-loup Gailly Mark Nelson. The Data Compression Book. M&T Books, New
York, 1995.
[12] Margaret A. Lepleyb Michael W. Marcellina. An overview of quantization in jpeg
2000. Signal Processing: Image Communication 17, 2002.
[13] NVIDIA. ”NVIDIA CUDA C Programming Best Practices Guide”, 2010.
[14] NVIDIA. ”NVIDIA CUDA Programming Guide”, 2010.
61
[15] Artur Przelaskowski. Falkowe metody kompresji danych obrazowych. 2002.
[16] Artur Przelaskowski. Kompresja danych: podstawy, metody bezstratne, kodery obra-
zów. Wydawnictwo BTC, Warszawa, 2005.
[17] Pearlman William A. Said, Amir. A new fast and efficient image codec based on set
partitioning in hierarchical trees. 1996.
[18] Jerome M. Shapiro. Embedded image coding using zerotrees of wavelet coefficients.
1993.
[19] W. Skarbek. Multimedia. Algorytmy i standardy kompresji. Akademicka Oficyna
Wydawnicza PLJ, Warszawa, 1998.
[20] A. Gersho Y. Shoham. Efficient bit allocation for an arbitrary set of quantizers.
IEEE Transactions on Acoustics, Speech, and Signal Processing, Vol. 36, 1988.
62