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