Komprese obrázků

Tato kapitola si klade za úkol, ukázat, že přepočítávání souřadnic z jedné báze do druhé může být celkem užitečná věc. V podstatě si ukážeme, jak funguje kompresní algoritmus, který používá velmi známý formát na ukládání obrázků jpg. Začneme s poněkud umělým ale názorným příkladem, z kterého bude patrné, o co nám půjde. Představme si, že máme za úkol zkomprimovat signály s dvěma vzorky, tj. naše signály mají tento tvar $(x_1,x_2)\in\mathbb{R}^2$. Může jít například o jakási dvě měření, která proběhla za sebou a jejich výsledkem byly hodnoty $x_1$ a $x_2$. Dále předpokládejme, že naše měření $x_1,x_2$ jsou celá čísla z intervalu $\langle 0,255\rangle$, tj. náš signál obsahuje dva byty. Naším úkolem je aproximovat vektory $\mathbf{x}=(x_1,x_2)$ pomocí jen jednoho celého čísla $y$ z intervalu $\langle 0,255\rangle$, tak abychom byli schopni z čísla $y$ zpětně zrekonstruovat původní vektor $\mathbf{x}$, pokud možno s co největší přesností. Tím vlastně dojde k oné kompresi. Spojením ``s co největší přesností'' máme na mysli to, aby vzdálenost mezi koncovými body vektoru $\mathbf{x}$ a zrekonstruovaného vektoru $\mathbf{x}'$ byla pokud možno co nejmenší, tj. chceme aby $\vert\mathbf{x}-\mathbf{x}'\vert$ bylo malé. Nakonec budeme předpokládat, že naše měření $x_1,x_2$ jsou vždy přibližně stejné hodnoty.

Jedna z možností jak tuto úlohu řešit, je reprezentovat každý vektor $\mathbf{x}$ pomocí souřadnic v nějaké vhodně zvolené uspořádané bázi $(B)=(\mathbf{b}_1,\mathbf{b}_2)$ a následně vektor $\mathbf{x}$ popsat jen pomocí první souřadnice v této bázi. Přesněji řečeno, pokud $\mathbf{x}=(\alpha _1,\alpha _2)_B$, pak ono hledané číslo je $\alpha _1$ a zrekonstruovaný vektor $\mathbf{x}'=\alpha _1\cdot\mathbf{b}_1$. Samozřejmě číslo $\alpha _1$ je obecně reálné číslo, takže je potřeba ho ještě zaokrouhlit a báze $(B)$ musí být zvolena tak, aby padlo do intervalu $\langle 0,255\rangle$. To je, ale technická záležitost, která není pro vysvětlení užití lineární algebry v kompresi důležitá.

Otázkou tedy je, jakou uspořádanou bázi pro tento účel použít. Pokud bychom vzali za $(B)$ přímo standardní bázi $((1,0),(0,1))$, pak bychom popisovali vektor $\mathbf{x}=(x_1,x_2)$ číslem $x_1$. Tato volba by ale byla dobrá, jen v případě, že bychom předpokládali, že druhé měření $x_2$ je blízké nule. Vzdálenost $\vert\mathbf{x}-\mathbf{x}'\vert$ je totiž v tomto případě rovna $\vert x_2\vert$. My ale předpokládáme, že hodnoty $x_1,x_2$ jsou přibližně stejné. Proto bude lepší vzít za $(B)$ tuto bázi $(B)=((1,1),(1,-1))$. Spočtěte si, že v tomto případě souřadnice vektoru $\mathbf{x}$ vzhledem k bázi $(B)$ jsou $((x_1+x_2)/2,(x_1-x_2)/2)$. Vektor $\mathbf{x}$ budeme tedy v tomto případě aproximovat pomocí aritmetického průměru $(x_1+x_2)/2$, což je celkem přirozená volba vzhledem k našemu předpokladu blízkosti čísel $x_1$ a $x_2$. Zrekonstruovaný vektor tedy je

\begin{displaymath}\mathbf{x}'=\frac{x_1+x_2}{2}\cdot(1,1)=\left(\frac{x_1+x_2}{2},\frac{x_1+x_2}{2}\right) .\end{displaymath}

Vzdálenost $\vert\mathbf{x}-\mathbf{x}'\vert$ je v tomto případě rovna $\vert x_1-x_2\vert/\sqrt{2}$, tj. je přímo úměrná velikosti rozdílu $\vert x_1-x_2\vert$. Tzn. že pokud jsou si čísla $x_1,x_2$ blízká, je i vzdálenost $\vert\mathbf{x}-\mathbf{x}'\vert$ malá. Jinak řečeno, aproximujeme naše vektory $\mathbf{x}$ pomocí ortogonální projekce, tj. zrekonstruovaný vektor $\mathbf{x}'$ je ortogonální projekce vektoru $\mathbf{x}$ do podprostoru $\langle\mathbf{b}_1\rangle=\langle(1,1)\rangle$. Graficky to vypadá takto:

\includegraphics{aprox1}

Originální modrý vektor $\mathbf{x}$ leží poblíž přímky procházející počátkem se směrovým vektorem $(1,1)$, protože předpokládáme, že hodnoty $x_1,x_2$ jsou přibližně stejné. My tento vektor aproximujeme ortogonální projekcí, což je ten červený vektor $\mathbf{x}'$.

Dříve než přistoupíme k samotné kompresi obrázků, zobecněme náš příklad se signály tvaru $(x_1,x_2)$ do vyšší dimenze. Tentokrát budeme mít signály s osmi vzorky, tj. signály tvaru $\mathbf{x}=(x_1,x_2,x_3,x_4,x_5,x_6,x_7,x_8)\in\mathbb{R}^8$. Opět budeme předpokládat, že hodnoty $x_i$ se nemění příliš rychle, tj. rozdíly mezi sousedními vzorky $\vert x_i-x_{i+1}\vert$, $i=1,\ldots,7$, jsou malé. Postup bude stejný. Zvolíme nějakou vhodnou uspořádanou bázi $(B)=(\mathbf{b}_1,\ldots,\mathbf{b}_8)$ a vyjádříme vektor $\mathbf{x}$ v této bázi, tj. najdeme jeho souřadnice v bázi $(B)$. Následně zahodíme ty souřadnice, jejichž hodnoty budou velmi malé (v porovnání s ostatními). Tím budeme aproximovat vektor $\mathbf{x}$ z prostoru dimenze osm vektorem $\mathbf{x}'$ z prostoru s menší dimenzí.

Způsob jak vybrat vhodnou bázi $(B)$ není úplně jednoduchý. Existuje mnoho možností a každá má své výhody a nevýhody. My zde použijeme bázi, která se používá v diskrétní kosinové transformaci (viz tyto stránky). Porovnejte zde uvedený postup také s teorií, kterou se budete učit v předmětu Matematika 2 v části o Fourierových řadách.

Definujme tedy naši bázi $(B)=(\mathbf{b}_1,\ldots,\mathbf{b}_8)$. Pro $k=1,\ldots,8$, označme $i$-tou složku vektoru $\mathbf{b}_k$ symbolem $b_{ki}$, tj. $\mathbf{b}_k=(b_{k1},b_{k2},\ldots,b_{k8})$. Pak

\begin{displaymath}b_{ki}=\cos\left(\frac{(k-1)\pi}{8}\cdot(i-0.5)\right) .\end{displaymath}

Takže $\mathbf{b}_1=(1,1,\ldots,1)$ a pro $k>1$ je vektor $\mathbf{b}_k$ jakoby navzorkovaný kosinus s $(k-1)/2$ periodami v osmi bodech. Graficky vypadá naše báze $(B)$ takto:

\includegraphics[width=20cm,height=10cm]{baze}

Tyto stránky si nekladou za cíl přesně vysvětlit, proč je tato volba dobrá. Spokojme se s neformálním vysvětlením. Protože předpokládáme, že změny v našem signálu $\mathbf{x}$ nejsou velké, je dobré mít v bázi $(B)$ vektory, jejichž sousední hodnoty se mění jen o málo a naopak také vektory jejichž sousední hodnoty se mění hodně. Konkrétně v naší bázi $(B)$ vektory na začátku (tj. $\mathbf{b}_1,\mathbf{b}_2,\mathbf{b}_3,\mathbf{b}_4$) jsou vektory, jejichž sousední hodnoty se mění pozvolna. Naopak sousední hodnoty vektorů na konci (tj. $\mathbf{b}_5,\mathbf{b}_6,\mathbf{b}_7,\mathbf{b}_8$) se mění výrazněji. Pokud najdeme souřadnice našeho málo měnícího se signálu $\mathbf{x}$ v této bázi, budou souřadnice odpovídající bázovým vektorům $\mathbf{b}_1,\mathbf{b}_2,\mathbf{b}_3,\mathbf{b}_4$ velké v porovnání se souřadnicemi, které odpovídají bázovým vektorům $\mathbf{b}_5,\mathbf{b}_6,\mathbf{b}_7,\mathbf{b}_8$. Ukažme si to na příkladě. Vezměme následující skript pro program Octave:

t=(1:8)';       % indexy vzorku
B=zeros(8,8);   % B bude matice prechodu od standardni baze k nami zvolene bazi (B)
% Spocti bazove vektory b_k a napln jimi sploupce matice B
for k=1:8
   B(:,k)=cos(pi*(k-1)*(t-0.5)/8);
endfor

x=3+0.1*(t-4).^2+0.5*rand(8,1);   % vyrob nejaky malo se menici signal
y=inv(B)*x;                       % y jsou souradnice x v bazi (B)
yap=y; yap(5:8)=0;                % do yap dej y a posledni 4 souradnice vynuluj
xap=B*yap;                        % spocti aproximaci puvodniho vektoru

figure(1);
bar([x,xap]);                     % vykresli puvodni signal a jeho aproximaci
figure(2);
bar(y);                           % vykresli souradnice x v (B)
Tento skript vyrobí málo se měnící signál $\mathbf{x}$ tak, že sečte hodnoty polynomu $3+0.1(t-4)^2$ pro $t=1,\ldots,8$ s náhodným vektorem osmi čísel z intervalu $\langle0,0.5\rangle$. Pak najde souřadnice $(\alpha _1,\ldots,\alpha _8)$ vektoru $\mathbf{x}$ v bázi $(B)$, tj.

\begin{displaymath}\mathbf{x}=\alpha _1\mathbf{b}_1+\alpha _2\mathbf{b}_2+\alpha...
...a _6\mathbf{b}_6+\alpha _7\mathbf{b}_7+\alpha _8\mathbf{b}_8 .\end{displaymath}

Přesněji řečeno skript vezme matici přechodu $\mathbf{B}$ od standardní báze k bázi $(B)$ a spočte souřadnice takto:

\begin{displaymath}\left(\begin{array}{c}\alpha _1 \vdots \alpha _8\end{arra...
...ot\left(\begin{array}{c}x_1 \vdots x_8\end{array}\right) .\end{displaymath}

Pak vynuluje poslední čtyři souřadnice $\alpha _5,\ldots,\alpha _8$ a spočte aproximaci $\mathbf{x}'$ původního vektoru $\mathbf{x}$:

\begin{displaymath}\mathbf{x}'=\alpha _1\mathbf{b}_1+\alpha _2\mathbf{b}_2+\alpha _3\mathbf{b}_3+\alpha _4\mathbf{b}_4 .\end{displaymath}

Výsledná aproximace $\mathbf{x}'$ není opět nic jiného než ortogonální projekce vektoru $\mathbf{x}$ do lineárního podprostoru $\langle\mathbf{b}_1,\mathbf{b}_2,\mathbf{b}_3,\mathbf{b}_4\rangle$.

Konkrétní ukázky, které vypadly ze skriptu jsou tyto:

\includegraphics[width=20cm,height=10cm]{aprox2}

Vlevo vidíme původní signál $\mathbf{x}$ (modrá barva) a jeho aproximaci $\mathbf{x}'$ (červená barva). Vpravo vidíme souřadnice vektoru $\mathbf{x}$ v bázi $(B)$. Všimněte si, že souřadnice odpovídající vektorům $\mathbf{b}_1,\mathbf{b}_2,\mathbf{b}_3$ jsou mnohem větší než zbývající souřadnice. To je dáno tím, že se sousední hodnoty našeho signálu $\mathbf{x}$ nemění příliš rychle.

A toto je v podstatě princip, na kterém je založen algoritmus pro ukládání fotek ve formátu jpg. Obecně totiž předpokládáme, že fotky v sobě neobsahují příliš mnoho rychle měnících se oblastí. Naopak velmi často obsahují oblasti, kde se jas mění jen nepatrně. Postup si ukážeme na černobílé fotce. S barevnými fotkami se dělá to samé třikrát pro každou barevnou složku zvlášť. Fotku si můžeme představit jako matici, jejíž jednotlivé hodnoty představují hodnoty jasu jednotlivých pixelů. Např. matice typu $(3,3)$

\begin{displaymath}\mathbf{A}=\left(\begin{array}{rrr}
128 & 255 & 13\\
0 & 54 & 178\\
12 & 255 & 100\\
\end{array}\right)\„\end{displaymath}

představuje obrázek 3x3 pixely, jejichž jasy odpovídají číslům $128,255,13,0,54,\ldots$. Víme, že matice daného typu $(m,n)$ se sčítáním matic a násobením skalárem tvoří lineární prostor dimenze $mn$. To znamená, že báze v tomto prostoru mají $mn$ prvků. Např. výše uvedená matice $\mathbf{A}$ typu $(3,3)$ lze vyjádřit pomocí devíti bázových vektorů a odpovídajících souřadnic takto:

\begin{displaymath}
\mathbf{A}=128\cdot\left(\begin{array}{rrr}
1 & 0 & 0\\
0 &...
...}
0 & 0 & 0\\
0 & 0 & 0\\
0 & 0 & 1\\
\end{array}\right)
 .\end{displaymath}

Naším úkolem opět bude vyjádřit matici $\mathbf{A}$ představující obrázek v jiné bázi $(B)=(\mathbf{B}_{11},\mathbf{B}_{12},\ldots,\mathbf{B}_{mn})$, kde bude hodně souřadnic skoro nula. Prvky báze $(B)$ budeme indexovat dvojitým indexem stejně jako indexujeme jednotlivé prvky matice. Bázi $(B)$ opět zkonstruujeme pomocí funkce kosinus. Nicméně obrázek má dva rozměry, takže budeme používat funkci kosinus dvakrát ve vodorovném a svislém směru. Nechť $k\in\{1,\ldots,m\}$ a $l\in\{1,\ldots,n\}$. Pak definujeme matici $\mathbf{B}'_{kl}$ tak, že na $i$-tém řádku v $j$-tém sloupci má číslo:

\begin{displaymath}\cos\left(\frac{(k-1)\pi}{m}\cdot(i-0.5)\right)\cdot\cos\left(\frac{(l-1)\pi}{n}\cdot(j-0.5)\right) .\end{displaymath}

Matice $\mathbf{B}'_{kl}$ tedy obsahuje součin dvou kosinů, přičemž čím větší je $k$, tím kratší periodu má kosinus ve svislém směru a podobně čím větší je $l$, tím kratší periodu má kosinus ve vodorovném směru. Uspořádaná $mn$-tice $(\mathbf{B}'_{11},\mathbf{B}'_{12},\ldots,\mathbf{B}'_{mn})$ tvoří bázi lineárního prostoru matic typu $(m,n)$. Nicméně tato báze není ortonormální (vůči standardnímu skalárnímu součinu), ale jen ortogonální, tj. jednotlivé bázové vektory jsou na sebe kolmé, ale nemusí obecně mít délku $1$. Proto ještě jednotlivé vektory znormalizujeme (tj. vyrobíme z nich vektory délky $1$). Naše báze $(B)$ tedy obsahuje matice $\mathbf{B}_{kl}=\frac{1}{\vert\mathbf{B}'_{kl}\vert}\cdot\mathbf{B}'_{kl}$, kde $\vert\mathbf{B}'_{kl}\vert=\sqrt{\mathbf{B}'_{kl}\cdot\mathbf{B}'_{kl}}$ je velikost vektoru $\mathbf{B}'_{kl}$ vzhledem ke standardnímu skalárnímu součinu. Připomeňme, že standardní skalární součin v lin. prostoru matic typu $(m,n)$ je definován pro matice $\mathbf{C}=(c_{ij}),\mathbf{D}=(d_{ij})$ vztahem:

\begin{displaymath}\mathbf{C}\cdot\mathbf{D}=\sum_{i=1}^m\sum_{j=1}^nc_{ij}d_{ij} .\end{displaymath}

Pozor symbol $\cdot$ tady značí skalární součin a ne součin matic.

Ukažme si několik bázových vektorů $\mathbf{B}_{kl}$. Protože dole bude následovat příklad s maticí typu $(100,88)$, ukážeme si bázové vektory tohoto typu. Matice $\mathbf{B}_{11}$ je podobně jako v předchozím příkladě konstantní, tj. všechny její prvky jsou stejné a rovné $1/\sqrt{8800}$. Pokud vezmeme první index větší než jedna, začne se projevovat kosinus ve svislém směru. Např. $\mathbf{B}_{41}$ vypadá takto:

Image baze2D41
Vlevo vidíte její prvky zobrazené pomocí 3D grafu a vpravo pomocí různých jasů jako obrázek.

Podobně pokud vezmeme druhý index větší než jedna, začne se projevovat kosinus ve vodorovném směru. Např. $\mathbf{B}_{14}$ vypadá takto:

Image baze2D14
Pokud zvětšíme oba indexy, budou se projevovat oba kosiny. Navíc čím větší index, tím kratší je perioda daného kosinu. Např. $\mathbf{B}_{47}$ vypadá takto:
Image baze2D47
Vidíte, že ve svislém směru máme jen $1.5$ periody, kdežto ve vodorovném periody $3$, protože první index je $4$ a druhý $7$.

Zvětšováním obou indexů se bude frekvence obou kosinů dále zvětšovat, tj. matice $\mathbf{B}_{100,88}$ má u obou kosinů nejkratší periody a vypadá takto:

Image baze2D10088

Nyní si ukážeme konkrétní příklad fotky s rozměry 100x88, kterou vyjádříme ve výše uvedené bázi $(B)=(\mathbf{B}_{11},\ldots,\mathbf{B}_{100,88})$. Skutečný algoritmus, který je použit ve formátu jpg rozdělí každou fotku na malé čtverečky o rozměrech 8x8 a na nich nezávisle aplikuje přepočet souřadnic. My to tady uděláme s celou fotkou najednou. Je to sice výpočetně náročné, ale alespoň budeme moci lépe porovnat originální fotku s její aproximací. Na čtvercích velikosti 8x8 toho totiž není mnoho patrného. Přesný popis formátu jpg a v něm použitého kompresního algoritmu najdete zde.

Vezměme následující fotku zpěvačky Britney Spears.

Image original

Proč zrovna tato zpěvačka. Nevím přesně, asi mi přišlo komické říkat, že vezmeme Britney Spears a vyjádříme jí v jiné bázi. Každopádně tím, že nám její fotka poslouží k vysvětlení problematiky formátu jpg, se její život jistě stane záslužnější. Původně tato fotka má rozměry 100x88, ale tady jsem ji zobrazil trochu zvětšenou.

Označme matici odpovídající této fotce $\mathbf{I}$. Tj. $\mathbf{I}$ je matice typu $(100,88)$ a její složky jsou celá čísla z intervalu $\langle 0,255\rangle$ představující hodnoty jasu jednotlivých pixelů. Podobně jako výše najdeme souřadnice $\mathbf{I}$ v bázi $(B)$, potom některé souřadnice vynulujeme, čímž získáme aproximaci původního obrázku vektorem z prostoru menší dimenze. To je náplní následujícího skriptu pro program Octave (k tomu aby Vám fungoval je potřeba mít nainstalován image toolbox):

I=imread("britney2.jpg");          % nacte obrazek
I=I(:,:,1);                        % protoze je obrazek cernobily obsahuje 3 stejne barevne slozky RGB 
                                   % vezmi jednu z nich a dej ji do matice I

figure(1);     
imshow(I);                         % zobraz originalni obrazek
axis equal;

I=cast(I,"double")-128;            % pretypuj hodnoty jasu na realna cisla a posun do rozsahu <-128,127>

[m,n]=size(I);                     % zjisti velikost obrazku

[j,i]=meshgrid(1:n,1:m);           % priprav definicni obor pro bazove matice Bkl

for l=1:n
  for k=1:m
    Bkl=cos(pi*(k-1)*(i-0.5)/m).*cos(pi*(l-1)*(j-0.5)/n);   % spocti bazovou matici Bkl
    v=sqrt(Bkl(:)'*Bkl(:));                                 % spocti jeji velikost
    Bkl(:)=Bkl(:)/v;                                        % udelej z Bkl vektor velikosti 1
    S=Bkl.*I;                                          
    J(k,l)=sum(S(:));                                       % spocti souradnici odpovidajici Bkl pomoci skalarniho soucinu
  endfor
endfor                                                      % nyni matice J obsahuje souradnice I v bazi (B)

J=round(J/16);                                              % vydel vsechny souradnice 16 aby byly v rozsahu <-128,127> a udelej z nich cela cisla

figure(2);
surf(J); axis equal;                                        % nakresli 3D graf se souradnicemi J

J25=J; J75=J;                                               % promenne pro souradnice aproximaci
J25(round(m/2):end,:)=0;                                    % J25 je matice J, kde se vynuluje 75% souradnic
J25(:,round(n/2):end)=0;
J75(round(m/2):end,round(n/2):end)=0;                       % J75 je matice J, kde se vynulule 25% souradnic

RI25=zeros(m,n); RI75=zeros(m,n);                           % promenne pro vlastni aproximace
for l=1:n
  for k=1:m
    Bkl=cos(pi*(k-1)*(i-0.5)/m).*cos(pi*(l-1)*(j-0.5)/n);   % spocti bazovou matici Bkl
    v=sqrt(Bkl(:)'*Bkl(:));
    Bkl(:)=Bkl(:)/v;                                        % udelej z ni vektor velikosti 1
    A=16*J25(k,l)*Bkl;                                      % spocitej prispek matice Bkl podle souradnic z J25. Nasobime 16, protoze jsme predtim veschny souradnice 16 vydelili.
    RI25=RI25+A;                                            % pricti tento prispevek k R25
    A=16*J75(k,l)*Bkl;                                      % spocitej prispek matice Bkl podle souradnic z J75. Nasobime 16, protoze jsme predtim veschny souradnice 16 vydelili.
    RI75=RI75+A;                                            % pricti tento prispevek k R25
  endfor
endfor                                                      % matice R25 tedy rovna linearni kombinaci bazovych matic Bkl s koeficienty J25
                                                            % matice R75 tedy rovna linearni kombinaci bazovych matic Bkl s koeficienty J75
figure(3);
imshow(uint8(RI75+128));                                    % zobraz aproximaci po vymazani 25% dat
axis equal;

figure(4);
imshow(uint8(RI25+128));                                    % zobraz aproximaci po vymazani 75% dat
axis equal;

Vysvětleme si, co tento skript dělá. Poté co zobrazí originální obrázek, odečte od všech hodnot v matici $\mathbf{I}$ číslo $128$. Tím se její hodnoty dostanou do rozsahu $\langle -128,127\rangle$. To se dělá pravděpodobně kvůli tomu, aby souřadnice odpovídající bázové matici $\mathbf{B}_{11}$ nebyla příliš velká v porovnání s ostatními souřadnicemi. Dále tento skript přepočítá souřadnice matice $\mathbf{I}$ do báze $(B)$. Protože je báze $(B)$ ortonormální, je to jednoduché. Jak víme z přednášky, souřadnice vůči ortonormální bázi, lze vyjádřit jako skalární součiny matice $\mathbf{I}$ s jednotlivými bázovými maticemi $\mathbf{B}_{kl}$. Tím dostaneme $8800$ souřadnic matice $\mathbf{I}$ v bázi $(B)$, které skript uloží do matice $\mathbf{J}$. Jednotlivé hodnoty v matici $\mathbf{J}$, ale nemusí být v rozsahu $\langle -128,127\rangle$ (tj. na jejich uložení bychom potřebovali více než 1 byte). Proto všechny hodnoty v matici $\mathbf{J}$ vydělíme $16$, abychom je dostali opět do rozsahu $\langle -128,127\rangle$, a zaokrouhlíme. 3D graf hodnot v matici $\mathbf{J}$ po tomto přeškálování vypadá takto:

Image souradnice

Vidíme, že velké hodnoty mají jenom souřadnice, které odpovídají bázovým maticím $\mathbf{B}_{kl}$ s malými indexy $k$ a $l$. Takže opět jako předtím, můžeme nějaké malé souřadnice zanedbat (což znamená vynulovat je) a aproximovat původní obrázek jeho ortogonální projekcí do lin. podprostoru generovaného nějakou podmnožinou báze $(B)$. V našem případě spočteme dvě aproximace. První kde zanedbáme 25% původních souřadnic a druhou kde zanedbáme 75%. Skript vyrobí dvě matice $\mathbf{J}_{75}$ a $\mathbf{J}_{25}$. Matice $\mathbf{J}_{75}$ vznikne z matice $\mathbf{J}$ vynulováním souřadnic, které odpovídají bázovým maticím $\mathbf{B}_{kl}$ s indexy většími než $m/2$ a $n/2$, tj. $k\geq m/2$ a $l\geq n/2$. 3D graf hodnot matice $\mathbf{J}_{75}$ vypadá takto:

Image souradnice75

Matice $\mathbf{J}_{25}$ vznikne z matice $\mathbf{J}$ vynulováním souřadnic, které odpovídají bázovým maticím $\mathbf{B}_{kl}$ s indexy kde $k\geq m/2$ nebo $l\geq n/2$. 3D graf hodnot matice $\mathbf{J}_{25}$ vypadá takto:

Image souradnice25

Matice $\mathbf{J}_{75}$ a $\mathbf{J}_{25}$ představují onu kompresi. Na jejich uložení totiž nepotřebujeme 8800 bytů, ale jen 6600 v případě matice $\mathbf{J}_{75}$ a 2200 v případě matice $\mathbf{J}_{25}$. Poznamenejme, že skutečný algoritmus používaný ve formátu jpg je trochu složitější a nedělá jen ortogonální projekci, jako jsme udělali my. Skutečný algoritmus používá tzv. kvantizační matici, která vyjadřuje jak je lidské oko citlivé na vnímání jednotlivých frekvencí v obrazu. Touto maticí ováží jednotlivé souřadnice v matici $\mathbf{J}$. Po zaokrouhlení mu poté zbude jen málo nenulových souřadnic (detaily viz tyto stránky).

Na konec výše uvedený skript zpětně zrekonstruuje z matic $\mathbf{J}_{75}$ a $\mathbf{J}_{25}$ původní obraz $\mathbf{I}$. Tím, že jsme ale některé souřadnice vynulovali, nedostaneme původní obraz, ale jen jeho aproximace. Vezmeme tedy souřadnice z matic $\mathbf{J}_{75}$ a $\mathbf{J}_{25}$, vynásobíme je $16$ (protože jsme je předtím $16$ dělili) a spočítáme lineární kombinace bázových matic $\mathbf{B}_{kl}$ s koeficienty z $\mathbf{J}_{75}$ a $\mathbf{J}_{25}$. Tím dostaneme aproximace původního obrazu $\mathbf{R}_{75}$ a $\mathbf{R}_{25}$. Samozřejmě nesmíme zapomenout přičíst ke všem hodnotám z obou matic $\mathbf{R}_{75}$ a $\mathbf{R}_{25}$ hodnotu $128$, abychom dostali zpět hodnoty z rozsahu $\langle 0,255\rangle$. Aproximace $\mathbf{R}_{75}$ a $\mathbf{R}_{25}$ vypadají takto:

Image aprox75 Image aprox25
$\mathbf{R}_{75}$ $\mathbf{R}_{25}$

Vidíme, že na obou aproximacích jsou ošklivé artefakty způsobené zejména oním dělením $16$ a následným zaokrouhlováním. Dále je vidět, že aproximace $\mathbf{R}_{25}$ jakoby ztrácí ostrost. To je dáno tím, že jsme jí odtranili souřadnice odpovídající bázovým maticím s vysokými frekvencemi. Nicméně, když si zobrazíme tyto výsledky v původní velikosti 100x88 pixelů, není to už tak strašné, jak ukazují následující obrázky:

Image originalm Image aprox75m Image aprox25m
originál $\mathbf{R}_{75}$ $\mathbf{R}_{25}$

Rostislav Horcik 2009-01-04