Aufgabe 6: Ein JPEG

Werbung
Aufgabe 6: Ein JPEG-Decoder
Einleitung
JPEG ist ein populärer Standard zur Kompression von Bildern. Neben verlustfreien Kompressionsalgorithmen werden auch verlustbehaftete benutzt. Bei diesen wird die besondere Struktur von Bildern
so ausgenutzt, daß der Informationsverlust der Kompression dem menschlichen Betrachter nur wenig
auffällt. JPEG is nach seinen Schöpfern benannt, der Joint Photographic Expert Group.
In JPEG werden verschiedene Techniken kombiniert: Huffman-Kodierung, Quantisierung, FourierTransformation, Differentielle Kodierung und andere.
Ziel dieser Aufgabe ist es, ein C-Programm zu schreiben, welches eine JPEG-Datei der einfachsten
und häufigsten Art (nicht-differentielle Huffman-Kodierung, drei Farbebenen, keine Zusatzfunktionen)
dekodiert und dekomprimiert. Als Ergebnis soll eine Datei im PPM (portable pixmap)-Format erzeugt
werden.
Digitalisierte Bilder: das PPM-Format
Ein digitalisiertes Bild besteht aus einzelnen Bildpunkten, Pixel genannt. PPM ist eine ”naive” Form der
Speicherung dieser Pixel ohne jegliche Komprimierung oder Umordnung der Daten. Eine PPM-Datei
beginnt mit einer Kennung. Das ist eine Zeile, die nur aus den 2 Zeichen ”P6” besteht. Es folgt eine
Zeile, die aus 2 Zahlen besteht, der Breite w und Höhe h des Bildes in Pixel ( z.B. ”608 320”) und eine
weitere Zeile, die für unsere Zwecke nur aus der Zahl ”255” besteht. An diese 3 Zeilen schließt sich ein
Strom von 3 ∗ w ∗ h Bytes an. Jeweils 3 Bytes kodieren Farbe und Helligkeit eines Pixels. Dazu wird
jedes Byte als vorzeichenlose ganze Zahl zwischen 0 und 255 interpretiert. Die 3 Zahlen beschreiben
die Intensität des Rot-, Grün- und Blauanteils (RGB-Kodierung): (0, 0, 0) ist Schwarz, (255, 255, 255)
Weiß, (230, 230, 0) ein helles Gelb etc. Die Pixeldaten stehen einfach ”in Leserichtung” (Zeile für Zeile
von links nach rechts und oben nach unten) hintereinander.
Huffman-Kodierung
Die Huffman-Kodierung ist ein verlustfreier Kompressionsalgorithmus. Ein Alphabet soll so kodiert
werden, daß die häufigere Zeichen durch kürzere Bit-Sequenzen dargestellt werden (ein Prinzip, von
dem sich schon Samuel Morse bei der Erfindung seines Morsealphabets leiten ließ).
Wie bei wohl allen Kompressionsalgorithmen ist das Entpacken wesentlich einfacher als das Packen.
Letzteres erfordert eine umfangreiche Analyse der Input-Daten, ersteres bloß das Anwenden eines mitgelieferten Rezeptes.
Das Rezept zur Huffman-Dekodierung läßt sich am einfachsten in Form einer Baumstruktur kodieren.
Nehmen wir als Beispiel die Sprache ”Vokalisch”, deren Alphabet die 5 Buchstaben A,E,I,O,U umfaßt.
Der häufigste Buchstabe sei E, gefolgt von O,A,I und U.
0
1
E
1
0
0
O
1
A
0
U
1
Der Datenstrom 101111101000 wird mit Hilfe des Huffman-Baums
so dekodiert: Wir starten an der Wurzel des Baumes (wie in der
Informatik üblich, steht der Baum auf dem Kopf) und nehmen die
Bitfolge als Wegbeschreibung: 0 heißt den linken Weg nehmen, 1 den
rechten Weg nehmen. Wenn wir eine Zweigspitze erreicht haben, steht
ein Buchstabe fest und wir beginnen wieder an der Wurzel. Damit
führt uns 1011 zu I, 11 zu O und 1010 zu U, 0 zu E und 0 zu E:
IOUEE.
I
Obwohl wir es mit einem Code variabler Länge zu tun haben, ist kein spezielles Trennzeichen nötig.
Natürlich ist die Kompression nicht optimal. Sie könnte verbessert werden, indem man Silben aus
2 Buchstaben in den Huffman-Baum aufnimmt, dann Silben aus 3 Buchstaben etc.
1
Der JPEG-Algorithmus verwendet mehrere Huffman-Bäume, allerdings auf recht indirekte Art und
Weise.
Beschreibung des JPEG-Formats
Im Folgenden wird das JPEG-Format beschrieben, wobei die Kapitel dieser Beschreibung (Farbmodell,
Downsampling, Quantisierung, Fourier-Transformation, Huffman-Dekodierung, Analyse der Datei) vom
Dekompressor in umgekehrter Reihenfolge angewandt werden müssen.
Farbmodell
JPEG verwendet nicht das RGB-Farbmodell, sondern eines, das YUV genannt wird: 3 Zahlen beschreiben Helligkeit (Y), Blauanteil (U) und Rotanteil (V) eines Pixels. Die Umrechnung in RGB ist durch
die lineare Transformation
 



8
1 0
R
Y
5
 G  =  1 −1 −4  U 
3
5
B
V
1 2
0
möglich.
Implementierungshinweis: Die YUV-Werte, die man nach Fourier-Transformation und Dequantisierung aus den JPEG-Daten erhält, sind vorzeichenbehaftet, liegen zwischen -128 und 127.
Dasselbe gilt auch für die RGB-Werte, die obige Transformation liefert. Manchmal können sie auch
über diesen Bereich hinausschießen. Um sie z.B. mit der Funktion putc (die ihr Argument zu einem
unsigned char konvertiert) korrekt in eine PPM-Datei ausgeben zu können, empfiehlt es sich:
a) 128 zu addieren,
b) zu testen, ob der Wert nun zwischen 0 und 255 liegt und kleinere Werte durch 0, größere durch
255 zu ersetzen.
Downsampling
• Zentrales Objekt der Kompressionsalgorithmen ist eine ”data unit”. Dies ist eine 8 × 8-Matrix von
Y-, U- oder V-Werten. Im einfachsten Fall beschreibt sie auch einen Bildausschnitt von 8 × 8
Pixeln. Durch eine (verlustbehaftete) Kompressionsmethode namens ”Downsampling” kann sie
jedoch auch die Informationen für bis zu 32×32 Pixel enthalten. Dabei wird jeweils ein kleiner (z.B.
2×2) Block von Pixeln durch ein ”gemitteltes”Pixel ersetzt. Üblicherweise wird das Downsampling
nicht auf die Helligkeit (Y) angewendet, sondern nur auf die Farbkomponenten. Dadurch bleibt
der wahrnehmbare Qualitätsverlust gering.
• Für jede der 3 Farbkomponenten YUV wird das Downsampling beschrieben durch 2 Parameter
(h, w) (height, width): Blöcke aus h × w Pixel wurden duch einen Pixel ersetzt. h und w können
Werte zwischen 1 und 4 annehmen. Beim Entpacken muß also ein ”Upsampling” durchgeführt
werden, bei dem die 8 × 8-Matrix zu einer8h × 8w-Matrix
aufgebläht wird, indem man jeden
x x
ersetzt. Für die Downsampling-Parameter
Eintrag x durch eine h × w-Blockmatrix
x x
hY , hV , hU muß gelten kgV(hi ) = max(hi ) und kgV(wi ) = max(wi ).∗
• Das Bild wird in kleine Rechtecke aus (8∗max(hi )×8∗max(wi )) Pixel zerlegt, die MCUs (Minimal
Coding Units) genannt werden. Nehmen wir an, die Y-Information wird nicht ”downsampled” und
die U- und V-Komponente werden um die Faktoren 2×2 bzw 4×4 ”downsampled”. Dann beschreibt
eine MCU 32 × 32 Pixel des Bildes und sie ist kodiert durch 16 Y-data-units, 4 U-data-units und
eine V-data-unit. In genau dieser Reihenfolge sind sie auch im Datenstrom gespeichert, wobei die
16 Y-data-units (und 4 U-data-units) in ”Lesereihenfolge” zeilenweise von links nach rechts und
oben nach unten abgespeichert sind.
∗ kgV:
kleinstes gemeinsames Vielfaches. Dies bedeutet einfach, daß die elementaren Quadrate oder Rechtecke, in die das
Bild für die verschiedenen Farbkomponenten zerschnitten wird, ”ineinander aufgehen” müssen.
2
• Der Datenstrom, der das Gesamtbild beschreibt, besteht aus einer Folge von MCUs
([16Y, 4U, 1V ], [16Y, 4U, 1V ], . . .), ebenfalls in ”Lesereihenfolge”.
• Nach der Dekomprimierung hat man ein Bild, dessen Größe ein Vielfaches der MCU-Größe ist.
Um es auf die Größe (H, W ) des Originals zu bringen, muß man eventuell rechts und unten ein
paar Pixel wegschneiden.
Fourier-Transformation und Quantisierung
Der Schlüssel zur hohen Kompressionsrate von JPEG ist, daß die Bilddaten nicht direkt komprimiert,
sondern vorher Fourier-transformiert werden. Die verwendete spezielle Form der Fourier-Transformation
wird als ”Diskrete Cosinus-Transformation” (DCT) bezeichnet. Die DCT führt eine 8 × 8-Matrix von
Farbwerten in eine 8 × 8-Matrix über, deren [0, 0]-Komponente den Mittelwert der Originaldaten beschreibt, die anderen beschreiben die Amplituden der verschiedenen Schwankungen um diesen Wert.
Zur Rücktransformation sind folgende Schritte nötig:
• Die JPEG-Datei enthält für jede Komponente (Y, U und V) eine Quantisierungstabelle aus 64
ganzen Zahlen. Jede der 64 Zahlen du[i], i=0...63 einer data-unit ist mit der entsprechenden Zahl aus der Quantisierungstabelle zu multiplizieren. Das Ergebnis ist in Fließkommazahlen
umzuwandeln.
• Aus diesen 64 Zahlen wird jetzt eine 8 × 8-Matrix aufgebaut. Um eine optimale Kompression
zu erreichen, sind diese Zahlen allerdings nicht zeilen- oder spaltenweise angeordnet, sondern im
Zigzag. Das folgende Codestück beschreibt die notwendige Umordnung:
const int zigzag[8][8] ={ { 0, 1, 5, 6,14,15,27,28},
{ 2, 4, 7,13,16,26,29,42},
{ 3, 8,12,17,25,30,41,43},
{ 9,11,18,24,31,40,44,53},
{10,19,23,32,39,45,52,54},
{20,22,33,38,46,51,55,60},
{21,34,37,47,50,56,59,61},
{35,36,48,49,57,58,62,63}};
for(i=0; i<8; i++)
for(j=0; j<8; j++) mat[i][j] = du[ zigzag[i][j] ];
• Auf jede Zeile dieser Matrix (gelesen als Vektor Y0 , . . . , Y7 ) und anschließend auf jede Spalte des
Ergebnisses ist die inverse DCT
7
X
1
Yj
π
Xi = √ Y 0 +
cos (2i + 1)j
2
16
2 2
j=1
für i = 0 . . . 7
anzuwenden.
• Die Fließkommazahlen werden in ganze Zahlen umgewandelt. Dabei ist ”zur Null hin” zu runden:
3, 7 → 3; −12, 8 → −12.
Huffman-Dekodierung
• Zur Kodierung werden für jede der 3 Farbkomponenten 2 Huffman-Bäume verwendet, DC-Baum
und AC-Baum genannt.
• Das Einlesen eines der 64 Werte du[i], i=0...63 einer data-unit aus dem Datenteil des SOSSegments ist ein zweistufiger Prozeß, da nicht der Wert selbst, sondern seine Länge Huffmankodiert ist.
3
• Der DC-Baum dient nur zum Einlesen des ersten Wertes du[0]:
• Lese Bits vom Datenstrom und laufe durch den DC-Baum, bis eine Spitze erreicht ist. Der
an dieser Spitze gespeicherte Wert s gibt die Anzahl der nun einzulesenden Bits an.
• Die nächsten s Bits aus dem Datenstrom sind als positive ganze Zahl n zu interpretieren.
• Diese Zahl wird in eine vorzeichenbehaftete Zahl konvertiert nach der Vorschrift
m = extend(n, s)
n
extend(n, s) =
n + 1 − 2s
if n ≥ 2s−1
otherwise
• Dieser Wert m ist nur dann gleich du[0], wenn es sich um die erste data-unit des Bildes zu
einer Farbkomponente handelt. Sonst ist m die Differenz zum Wert du[0] der vorhergehenden
data-unit derselben Farbkomponente.
• Zur Dekodierung der übrigen 63 Werte wird der AC-Baum verwendet. Die an dessen Spitzen
gespeicherten Bytes (Zahlen x zwischen 0 und 255) sind als zwei 4-Bit-Zahlen (”nibbles”)
(r, s) = (x/16, mod (x, 16)) zu interpretieren. Die Dekodierung erfordert folgende Schritte:
1. Lese Bits aus dem Datenstrom ein, bis eine Spitze (r, s) des AC-Baumes ereicht ist.
2. Wenn r = s = 0, fülle die restlichen Felder von du[] mit Nullen, die data-unit ist fertig
gelesen.
3. Wenn r > 0 ist, werden die nächsten r Felder in du[] mit Nullen gefüllt.
4. Dann werden die die nächsten s Bits aus dem Datenstrom gelesen und das Ergebnis von
extend(n, s) wird in das nächste freie Feld von du[] gespeichert.
5. Wiederhole 1.-4., bis alle Felder von du[] gefüllt sind.
Aufbau einer JPEG-Datei
Eine JPEG-Datei besteht aus einer Folge von Segmenten verschiedenen Typs. Segmente beginnen mit
2 Startbytes, die den Segmenttyp kodieren, gefolgt von 2 Bytes, die die Länge L des Segments (ohne
die beiden Startbytes) angeben und von L − 2 Datenbytes. Eine Ausnahme sind die Segmente ”Start
of Image” (SOI) am Dateianfang und ”End of Image” (EOI) am Dateiende, sie bestehen nur aus den
2 Bytes FFD8 (SOI) und FFD9 (EOI). In der folgenden Übersicht steht ein ∗ für ein Halbbyte (4 Bits),
auch nibble genannt.
SOI Start of Image
FFD8
EOI End of Image
FFD9
COM Comment
FFFE ∗ ∗ ∗∗
L
L-2 Bytes
Kommentartext
DQT Define Quantization Table
FFDB ∗ ∗ ∗∗ ∗∗
L i1
64 Bytes . . . ∗∗
QT1
in
SOF Start of Frame
FFC0 ∗ ∗ ∗∗ ∗∗ ∗ ∗ ∗∗ ∗ ∗ ∗∗ ∗∗
L cd H
W ne
64 Bytes
QTn
3 Bytes
Ebene 1
...
Jede Ebene hat den Aufbau ∗∗ ∗ ∗ ∗∗
ie w h nqt
4
L = 2 + 65 ∗ n
3 Bytes
Ebene ne
L = 2 + 6 + 3 ∗ ne
DHT Define Huffman Table
FFC4 ∗ ∗ ∗∗ ∗ ∗ 16 Bytes
data
... ∗ ∗
L tc th
ns[i]
tc th
P15
P15
L = 2 + 1 + 16 + i=0 ns[i] + . . . + 1 + 16 + i=0 ns[i]
SOS Start of Scan
FFDA ∗ ∗ ∗∗ ∗∗
L
nc
2 Bytes
Komponente 1
...
2 Bytes
Komponente nc
16 Bytes
ns[i]
∗ ∗ ∗ ∗ ∗∗
3 Bytes
data
data
Jede Komponente hat den Aufbau ∗∗ ∗ ∗
ic td ta
Es gibt noch eine Reihe weiterer Segmente. Wenn Segmente mit den Kennungen FFC1, FFC2, FFC3,
FFC5, FFC6, FFC7, FFC9, FFCA, FFCB, FFCC, FFCD, FFCE oder FFCF auftauchen, soll unser
Entpacker abbrechen. Sie bedeuten, daß noch andere Kompressionsalgorithmen (differentielle arithmetische Kodierung, differentielle Huffman-Kodierung u.a.) angewendet wurden.
Andere Segmente der Form
FF∗∗ ∗ ∗ ∗∗
L-2 Bytes
können ignoriert werden. Sie könenn z.B. ”thumbnails” (”daumennagelgroße” Varianten des Bildes, die
ein Programm zur Vorschau o.ä. verwenden kann) enthalten.
Beschreibung der Segmente
DQT JPEG-Dateien können bis zu 4 Quantisierungstabellen enthalten (auch wenn die hier besprochene Variante nur 3 verwendet). Sie können durch mehrere DQT-Segmente oder gemeinsam in einem
DQT-Segment gespeichert sein. Die Anzahl der Tabellen in einem DQT-Segment läßt sich aus der Längenangabe L ableiten. Jede Tabelle hat eine Nummer in . Die Tabelle selbst besteht aus 64 Bytes, von
denen jedes eine Zahl zwischen 0 und 255 repräsentiert.
Implementierungshinweis:
int i;
i=getc(filepointer);
liest ein Byte korrekt in diesem Sinne (als Zahl zwischen 0 und 255) ein, weil getc() das eingelesene
Byte als unsigned char interpretiert. Ebenso kann man die durch 2 Bytes kodierten Zahlen (wie
die Längenangabe L) einlesen:
int i,L;
i=getc(filepointer);
L=256*i+getc(filepointer);
SOF Ein Frame enthält globale Informationen über das Bild.
• dc (1 Byte) enthält die Farbtiefe (color depth). Für unsere Zwecke sollte sie 8 sein: Jeder Farbwert
wird mit 8 Bit (einem Byte) kodiert.
• H und W (je 2 Byte) geben die tatsächliche Größe des Bildes (Höhe und Breite in Pixeln) an.
• ne (1 Byte) gibt die Anzahl der Farbkomponenten an. Unser Dekoder soll nur mit genau 3 Farbkomponenten umgehen können. Es gibt auch schwarzweiße JPEG-Dateien mit nur einer Farbkomponente.
• Es folgen jeweils 3 Bytes für jede Farbkomponente. Sie enthalten eine Nummer ie und die Downsampling-Parameter w und h dieser Farbkomponente sowie die Nummer der Quantisierungstabelle
nqt , die für diese Komponente zu verwenden ist. Es ist die Tabelle zu verwenden, für die in = nqt
gilt.
5
Implementierungshinweis: Aus diesen Daten läßt sich die Größe einer MCU sowie die
Anzahl der MCUs berechnen. Hierbei ist daran zu denken, daß man aufrunden muß, wenn H
oder W nicht durch die Höhe oder Breite einer MCU teilbar ist. Die ”nibbles” h und w lassen
sich so einlesen:
int x,h,w;
x=getc(filepointer);
w=x/16;
h=x%16;
DHT
• Für jede Farbkomponente Y,U und V werden 2 Huffman-Bäume benötigt, ein DC- und ein ACBaum. Die Bäume können in einem odere mehreren DHT-Feldern beschrieben sein. Sie sind numeriert durch die nibbles tc und th. Wenn tc ungleich 0 ist, dient der Baum als AC-Baum, wenn
tc gleich 0 ist, als DC-Baum. th ist seine Nummer.
• Es folgt eine 16 Bytes lange Liste ns[]. Die Bytes geben an, wieviele Huffman-Kodes der Länge
1 Bit, 2 Bit, . . . 16 Bit abgespeichert sind. (Für unseren Vokal-Beispielbaum hieße das ns =
[1, 1, 1, 2, 0, . . .].) Danach folgt eine Liste der an den Zweigspitzen zu speichernden Daten, nach
P15
aufsteigender Länge der Kodes sortiert. Diese Liste ist somit i=0 ns[i] Bytes lang.
• Die Daten sind Bytes, Zahlen zwischen 0 und 255. Bei den AC-Bäumen werden sie später als 2
nibbles interpretiert.
• Diese Daten bestimmen einen Baum eindeutig, wenn er so konstruiert wird, daß der Abstand
Spitze-Wurzel von links nach rechts zunimmt.
"! $$## &&%% (('' &&%% (('' ((''
"!""!! $#$#$# &%&%&% ('('(' &%&%&% ('('(' ('('('
B "! $# &% (' &% (' **))*) (' **))*) ,,++,+ ,,++,+
)*) *)*) ,+,++ ,+,++ A D E F G *
,..-- 00// ,..-- 00// 00//
-.- 0/0/ .-.- 0/0/ 0/0/
C .
Ein Beispiel: Sei ns = [0, 1, 5, 1, 1] und die 8 Bytes
in diesem Baum sind in der Reihenfolge BADEFGCH gegeben. Damit sind die Huffman-Kodes B=00,
A=010, D=011, E=100, F=101, G=110, C=1110,
H=11110. Die letzte Spitze bleibt leer.
H
Implementierungshinweis: Material zu Bäumen und anderen Datenstrukturen findet man
z.B. in R. Sedgewick, Algorithmen in C. Eine Möglichkeit ist, einen Knoten des Baumes als
Struktur mit Zeigern auf die Unterknoten zu definieren und den Baum gemäß den DHT-Daten
durch Aufrufe von createNode zu erzeugen. Dies kann z.B. auch rekursiv geschehen.
struct node { unsigned char Item; struct node *l; struct node *r; };
struct node * createNode(unsigned char a){
struct node * x = malloc(sizeof(struct node));
x->l=0;
x->r=0;
x->Item=a;
return x;
}
SOS Das eigentliche Datenfeld. Bevor es losgeht mit dem Strom Huffman-kodierter und komprimierter
MCUs, folgen nochmal Daten über die Farbkomponenten. Im nc-Byte steht nochmal deren Anzahl, dann
folgt für jede Komponente ein 2-Byte-Feld, das aus 3 Zahlen besteht: einem Index ic und den Nummern
td und ta der für diese Komponente zu verwendenden DC- und AC-Huffmanbäume. Die Daten mit dem
Index ic ergänzen die Daten aus dem SOF-Segment mit dem gleichen Index ie .
Es folgen noch 3 Bytes, deren Inhalt für die hier beschriebene JPEG-Variante irrelevant ist.
Beim Einlesen des folgenden Datenstroms ist eine Besonderheit zu beachten: Er endet, wenn ein neuer
Segment-Markierer kommt, d.h., ein FF-Byte gefolgt von einem Byte ungleich 0. In der Regel wird das
der EOI-Marker FFD9 und auch das Ende der Datei sein.
Nun können solche Sequenzen natürlich auch als Teil der Bilddaten entstehen. Deshalb gilt eine
Sonderregel: Wenn die Bilddaten ein FF-Byte enthalten, wird ihm ein zusätzliches 00-Byte angehängt.
Dieses 00-Byte ist nicht Teil der Bilddaten und muß beim Einlesen herausgefiltert werden.
6
Herunterladen