Mein Format Decodieren
Die Umwandlung eines DSO/TRN in ein BMP-Bild
1.0 Einleitung
Die Umwandlung des TRN- bzw. DSO- Formates war eine sehr schwierige Sache,
da hier der Hauptteil meiner Arbeit lag. Die Umwandlung sollte in "Echtzeit"
geschehen, dh. innnerhalb einer Sekunde sollten die Bilder bei einer Größe
von 384x384 auf dem Bildschirm dargestellt werden. Zu dieser Zeit war ein
486'iger noch ein Standardrechner. Ein Bild mit 384x384 sollte auf einem
486iger mit etwa 50Mhz innerhalb einer Sekunde dargestellt werden. Diese
Anforderung ist sehr hoch, da allein eine Darstellung eines BMP-Bildes
in dieser Größe schon eine halbe Sekunde benötigt. Schuld
daran sind die langsamen Grafiktreiber. Enttäuschend von der Firma
war, daß ich einen 386iger zum Programmieren zur Verfügung gestellt
bekam. Das war für mich unzumutbar. Erstens programmierte ich unter
Windows. Und zweitens konnte ich somit nicht das interne Cacheverhalten
der CPU's austesten, denn darauf beruhen meine Programmroutinen. Ich habe
also meine eigene CPU mitbringen müssen, mehr möchte ich nicht
dazu sagen. Zu dieser Zeit wahr schon abzusehen, daß die CPU's wesentlich
schneller laufen, als der externe Cache. Somit war es von wichtiger Bedeutung,
daß meine Routinen sehr klein werden mußten. Ich habe aus diesem
Grund viele Tests durchführen müssen.
2.0 Die schrittweise Umwandlung von BMP nach DSO
Die Umwandlung des DSO- bzw. TRN- Formates geschieht schrittweise. Die
Schritte der Decodierung sind natürlich genau anders herum, als die
der Codierung. Die Unterteilung der Schritte
ist hier kurz dargestellt.
-
Der 1.Schritt Lesen des Headers
-
Der 2.Schritt Lesen und Entschlüsseln eines Huffmanblocks
-
Der 3.Schritt Nulldecodierung
-
Der 4.Schritt ZigZag- Rescanning
-
Der 5.Schritt Dequantisierung
-
Der 6.Schritt Inverse Cosinustransformation
-
Der 7.Schritt YUV -> RGB
2.1 Der 1.Schritt Lesen des Headers
Eine DSO- oder TRN- Datei hat eine 8Byte langen Header
|
Byte
|
Typ
|
Format
|
Beschreibung
|
|
0 ..1
|
word
|
intel
|
Erkennung immer "DS" |
|
2 ..3
|
word
|
intel
|
X ... Pixels per Row |
|
4 ..5
|
word
|
intel
|
Y ... Lines |
|
6
|
byte
|
intel
|
Quantisierung für Y-Matrix |
|
7
|
byte
|
intel
|
Quantisierung der Farben |
Der Header ist ziemlich klein gehalten. Aus den X, Y- Werten wird der
Rest errechnet, z.B. MCU-Anzahl, Bildgröße, ect. Wie man sieht
wird eine getrennte Qualtäten für Farbe und Helligkeit gespeichert.
Der Aufbau von Bildblöcken geschieht MCU- orientiert. Bei der Codierung
werden die MCU's nullgepackt und dann in einen Block geschrieben, der 8Kb
nicht überschreiten darf, Dies ist der maximale Bildblock. Danach
wird dieser Block Huffmanncodiert, so entsteht ein Bildblock. Vor jedem
Block stehen zwei Wordzahlen. Die erste Zahl gibt Auskunft über die
Größe des Bildblocks. Die zweite Zahl sagt wie groß der
Block nach der Huffmandecodierung ist. Das hat programmtechnische Hintergründe.
2.2 und 2.3 Die Huffmandecodierung
und die Nulldecodierung
Die Huffmandecodierung erfolgt äquivalent zur Huffmancodierung durch
die selbst erstellten festen Tabellen. Der ganze Bildblock wird decodiert
und in einen großen 8kb- Puffer überspielt. Nun werden aus dem
8Kb Block die MCU's stückweise ausgelesen. Dies geschieht so,
-
Lesen eines WORD's
-
Auspacken der Nullen in einen MCU-Vektor
-
So lange bis der MCU-Vektor voll ist, ansonsten gehe zu 1.
Nun steht immer eine MCU zur Verfügung.
Schritt 4 und Schritt 5 Dequantisieren
und ZigZag- Rescanning
Die MCU's werden nun dequantisiert. Dazu werden die Integerzahlen mit einem
bestimmten Faktor multipliziert. Danach werden die Werte aus dem Vektor
an eine bestimmte Stelle in den vorgesehenen Matrizen gespeichert. Nun
stehen die 4 Frequenz- Y- Matrizen und die 2 Farbmatrizen zur Verfügung.
Jetzt kann die inverse Cosinustransformation durchgeführt werden.
Hier ein kleines Beispiel für die Umwandlung des Vektors in
die Matrix;
M[0][0] := V[ 0];
M[1][0] := V[ 2];
M[4][2] := V[23];
M[7][7] := V[64];
64'iger Vektor mit Elementen aus 8 * 8 Matrix
|
|
x0
|
x1
|
x2
|
x3
|
x4
|
x5
|
x6
|
x7
|
x8
|
x9
|
|
0x
|
OO
|
01
|
10
|
20
|
11
|
02
|
03
|
12
|
21
|
30
|
|
1x
|
40
|
31
|
22
|
13
|
04
|
05
|
14
|
23
|
32
|
41
|
|
2x
|
50
|
60
|
51
|
42
|
33
|
24
|
15
|
06
|
07
|
16
|
|
3x
|
25
|
34
|
43
|
52
|
61
|
70
|
71
|
62
|
53
|
44
|
|
4x
|
35
|
26
|
17
|
27
|
36
|
45
|
54
|
63
|
72
|
73
|
|
5x
|
64
|
55
|
46
|
37
|
47
|
56
|
65
|
74
|
75
|
66
|
|
6x
|
57
|
67
|
76
|
77
|
|
|
|
|
|
|
|
===> |
Lage Vektorelemente in 8 * 8 Matrix
| |
xO x1 x2 x3 x4 x5 x6 x7 |
0x
1x
2x
3x
4x
5x
6x
7x |
00 01 05 06 14 15 27 28
02 04 07 13 16 26 29 42
03 08 12 17 25 30 41 43
09 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
|
|
In den folgenden Tabellen sind die Dequantisierungstabellen aufgelistet,
die von meinem Programm verwendet werden. Jede Multiplikation wird mit
SAL (Assembler Shift Arithmetik Left) ausgedrückt. Das
SAL-Effektiv ist deshalb aufgeführt, da mit der SAL-Bewegung in meinem
Programm die Werte gleichzeitig normiert werden. Das heißt, die Brüche
(aus der FPU- Emulation) bestehen aus Zahl/2^5 (Zahl/32'igstel). Mit diesem
SAL werden also 2 Fliegen mit einer Klappe geschlagen, Dequantisierung
plus Normierung. SAL wird deswegen genommen, damit die negativen Zahlen
erhalten bleiben.
1. Quantisierungstabelle 1-2% sehr
gute Qualität
|
Multiplikator Matrix
|
SAL-Matrix
|
SAL-Effektiv
|
|
01 02 02 02 04 08 08 08
02 02 02 08 08 08 08 08
02 04 04 08 08 08 08 08
02 04 08 08 08 08 08 08
04 08 08 08 08 08 08 08
08 08 08 08 08 08 08 08
08 08 08 08 08 08 08 08
08 08 08 08 08 08 08 08
|
0 1 1 1 2 3 3 3
1 1 1 3 3 3 3 3
1 2 2 3 3 3 3 3
1 2 3 3 3 3 3 3
2 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3
|
5 6 6 6 7 8 8 8
6 6 6 8 8 8 8 8
6 7 7 8 8 8 8 8
6 7 8 8 8 8 8 8
7 8 8 8 8 8 8 8
8 8 8 8 8 8 8 8
8 8 8 8 8 8 8 8
8 8 8 8 8 8 8 8
|
2. Quantisierungstabelle 15% gute Qualität
|
Multiplikator Matrix
|
SAL-Matrix
|
SAL-Effektiv
|
|
01 02 02 04 08 16 16 16
02 04 04 16 16 16 16 16
04 08 08 16 16 16 16 16
04 08 16 16 16 16 16 16
08 16 16 16 16 16 16 16
16 16 16 16 16 16 16 16
16 16 16 16 16 16 16 16
16 16 16 16 16 16 16 16
|
0 1 1 2 3 4 4 4
1 2 2 4 4 4 4 4
2 3 3 4 4 4 4 4
2 3 4 4 4 4 4 4
3 4 4 4 4 4 4 4
4 4 4 4 4 4 4 4
4 4 4 4 4 4 4 4
4 4 4 4 4 4 4 4
|
5 6 6 7 8 9 9 9
6 7 7 9 9 9 9 9
7 8 8 9 9 9 9 9
7 8 9 9 9 9 9 9
8 9 9 9 9 9 9 9
9 9 9 9 9 9 9 9
9 9 9 9 9 9 9 9
9 9 9 9 9 9 9 9
|
3. Quantisierungstabelle 40% ausreichende
Qualität
|
Multiplikator Matrix
|
SAL-Matrix
|
SAL-Effektiv
|
|
01 08 08 08 16 16 32 32
08 08 08 32 32 32 32 32
08 16 16 32 32 32 32 32
08 16 32 32 32 32 32 32
16 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32
|
0 3 3 3 4 4 5 5
3 3 3 5 5 5 5 5
3 4 4 5 5 5 5 5
3 4 5 5 5 5 5 5
4 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5
|
5 8 8 8 9 9 A A
8 8 A A A A A A
8 9 9 A A A A A
8 9 A A A A A A
9 A A A A A A A
A A A A A A A A
A A A A A A A A
A A A A A A A A
|
4. Quantisierungstabelle 60% schlechte
Qualität
|
Multiplikator Matrix
|
SAL-Matrix
|
SAL-Effektiv
|
|
01 16 16 16 32 32 64 64
16 16 16 64 64 64 64 64
16 32 32 64 64 64 64 64
16 32 64 64 64 64 64 64
32 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64
|
0 4 4 4 5 5 6 6
4 4 4 6 6 6 6 6
4 4 4 6 6 6 6 6
4 5 6 6 6 6 6 6
5 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6
6 6 6 6 6 6 6 6
|
5 9 9 9 A A B B
9 9 9 B B B B B
9 A A B B B B B
9 A B B B B B B
A B B B B B B B
B B B B B B B B
B B B B B B B B
B B B B B B B B
|
2.6 Aus einer 8 * 8 F-Matrix wird eine 8 * 8 Y bzw U
oder V– Matrix
Die Berechnung der Y-Matrix aus der Frequenzmatrix geschieht durch die
inverse Cosinustransformation. Dies ist eine sehr umständliche
Formel und erfordert eine Menge Berechnungszeit. Es kann aus der Formel
durch genaues Hinsehen eine Matrizenmultiplikation hergeleitet werden,
die das Ganze beschleunigt und übersichtlich macht. Die Multiplikation
sieht wie folgt aus:
Y = DCT' * F * DCT
Die Punkte der 8 * 8 F-Matrix, werden durch eine nur zweifache Matrizenmultiplikation
berechnet. Es werden dazu 2 Koeffizientenmatrizen erstellt, die wie folgt
berechnet werden (allgemeine Formel für N * N-Matrix).
DCT i,j = 1/
N;
wenn i (die Zeile) =0, sonst
DCT i,j = 1/
N
* cos[(2j+1)* i * PI / 2N], wenn i>0;
in diesem Fall ist N=8, dh: i, j "laufen" von 0..7
Dies ist ein Ausschnitt der DCT
| DCT = |
|
.
|
— |
|
|
—
|
. |
|
|
|
+0.3535 |
+0.3535 |
..... |
+0.3535 |
| |
|
|
|
+0.4904 |
+0.4157 |
|
-0.4904 |
| |
|
|
|
:
|
:
|
:
|
:
|
| |
|
|
|
+0.0975 |
-0.2777 |
..... |
-0.0975 |
| |
|
'
|
— |
|
|
—
|
´ |
|
2.6.1 Das Pascalprogramm mit FPU- Lösung
Die Multiplikationen der Matrix habe ich in einer Assemblerlösung
vollzogen, da eine FPU-Lösung viel zu langsam wäre, obwohl das
schön kurz ist. Ich habe das Programm zum besseren Verständnis
als Pascalprogramm aufgelistet.
var d:array[0..7,0..7] of real; --- Die DCT – Matrix ---
t:array[0..7,0..7] of real; --- Die DCT'-
Matrix ---
Y:array[0..7,0..7] of byte; --- Die Y
– Matrix ---
e:array[0..7,0..7] of real; --- ein zwischen
Ergebnis
f:array[0..7,0..7] of real; --- Das Endergebnis
1s,lz,tm:integer;
--- Ein paar Laufvariablen
begin
for 1s:=0 to 7 do d[0][ls]:=1/sqrt(8);
-- 0. Zeile der DCT
for 1z:=1 to 7 do for 1s:=0 to 7 do
-- 1-7. Zeile der DCT
d[lz][ls]:=sqrt(2/8)*cos((2*ls+1)*lz*pi/(2*8));
-- Die Formel
for 1z:=0 to 7 do for 1s:=0 to 7 do
-- Die Transponierte
t[lz][ls]:=d[ls][lz];
-- Die DCT'
for 1z: =0 to 7 do
-- Berechnung
for 1s:=0 to 7 do
-- von DCT' * F
for tm:=0 to 7 do
-- ZwischenErgebnis
e[lz][ls]:=e[lz][ls]+t[lz][tm]*(f[tm][ls]);
-- in E-Matrix
for 1z:=0 to 7 do
-- Berechnung
for 1s:=0 to 7 do
-- (DCT' * F) * DCT
for trn:=0 to 7 do
-- FertigErgebnis in
f[lz][ls]:=f[lz][ls]+e[lz)[tm]*d[tm][ls];
-- später Scale 128
end.
In meinem Assemblerprogramm wird mit Integerarithmetik die FPU emuliert
die Realzahlen z.B. 1/
8
= 0.35355 wurde mit Vielfachen von 2 multipliziet. so z.B. mit 16384, dh.
1/
8 —> 5792. Bei
einer zweifachen Matrixmultiplikation habe ich eine maximale Abweichung
von ±0.75 erreicht und mit einer 92%-igen Sicherheit sind die Werte
im richtigen Bereich gerundet.
2.6.2 Die Optimierung der Multiplikation
Aus Geschwindigkeitsgründen wird die Multiplikation noch einmal optimiert:
Der Prozessor benötigt für eine Multiplikation recht viel Zeit.
Bsp. ein MOV, ADD, SUB,... benötigt einen Taktzyklus. Eine Multiplikation
hingegen benötigt 26 Zyklen.
Weiterhin möchte ich noch folgendes in Erinnerung bringen.
-
Bei der Matrizenmultiplikation wird jedes Element der 8 * 8- Matrix 8 mal
multipliziert und addiert.
-
Es gilt bei der Matrizenmultiplikation Zeile mal Spalte.
-
Man kann die entstehenden Elemente zeilen- oder spaltenorientiert ermitteln
Ein Beispiel für zeilen- bzw. spaltenorientierte Berechnung:
1. Beispiel. E:= (DCT' * F) spaltenorientierte Ermittlungsreihenfolge
|
E00
|
E01
|
. .
|
E07
|
|
E10
|
E11
|
. .
|
E17
|
|
:
|
:
|
. : .
|
:
|
|
E70
|
E71
|
. .
|
E77
|
|
: = |
|
D00
|
D01
|
. .
|
D07
|
|
D10
|
D11
|
. .
|
D17
|
|
:
|
:
|
. : .
|
:
|
|
D70
|
D71
|
. .
|
D77
|
|
*
|
|
F00
|
F01
|
. .
|
F07
|
|
F10
|
F11
|
. .
|
F17
|
|
:
|
:
|
. : .
|
:
|
|
F70
|
F71
|
. .
|
F77
|
|
1. E00:=
(DCT'00
* F00)
+ (DCT'01 * F10)
+ ... + (DCT'07
* F70)
2. E01:=
(DCT'00 *
F01) + (DCT'01
* F11) + ... + (DCT'07
* F71)
2. Beispiel. E:= (DCT' * F) zeilenorientierte Ermittlungsreihenfolge
|
E00
|
E01
|
. .
|
E07
|
|
E10
|
E11
|
. .
|
E17
|
|
:
|
:
|
. : .
|
:
|
|
E70
|
E71
|
. .
|
E77
|
|
: = |
|
D00
|
D01
|
. .
|
D07
|
|
D10
|
D11
|
. .
|
D17
|
|
:
|
:
|
. : .
|
:
|
|
D70
|
D71
|
. .
|
D77
|
|
*
|
|
F00
|
F01
|
. .
|
F07
|
|
F10
|
F11
|
. .
|
F17
|
|
:
|
:
|
. : .
|
:
|
|
F70
|
F71
|
. .
|
F77
|
|
1. E00:=
(DCT'00 *
F00) + (DCT'01
* F10) + ...
+ (DCT'07 *
F70)
2. E10:=
(DCT'10 *
F00) + (DCT'11
* F10) + ...
+ (DCT'17 *
F70)
Das Ergebnis der E- Matrix wird von der "Orientierung" natürlich
nicht beeinflußt. Die Berechnung kann dadurch nur vereinfacht werden.
Das geschieht folgendermaßen. DCT und DCT'
verändern sich nie (Konstanten), nur die Elemente der F- Matrix. Multipliziere
ich DCT' * F in einer zeilenorientierten
Reihenfolge, dann kann ich feste Multiplikatoren bei der Berechnung der
Matrix E einsetzen (Jedes Element der DCT'
wird bei der Bestimmung einer Spalte von E benötigt). Gleiches
geschieht, wenn ich Y:= E * DCT in spaltenorientierter
Reihenfolge berechne. Nun kann ich mir die Multiplikatoren von DCT und
DCT' genauer ansehen. Dabei stelle
ich fest, daß es insgesamt 512 Multiplikationen je Matrizenoperation
gibt. Davon können aber nur 64 mit unterschiedlichen Multiplikatoren
sein. DCT und die DCT' sind mit gleichen
Elementen bestückt. Es gibt in DCT bzw. in der DCT'
genau 14 unterschiedliche Multiplikatoren, die aus 7 verschiedenen
Zahlen jeweils 7 positiven und 7 negativen zusammengesetz sind.
Nun bin ich am Ende mit theoretischen Überlegungen. Nun wird ausgezählt.
Welches der 8 Elemente (entweder Spalten aus der F-Matrix oder Zeilenelemente
aus der E-Matrix) wird mit welchem der DCT bzw. DCT'
multipliziert und vor allem wie oft?
Die Multiplikatoren der folgenden Tabelle sind integernormierte Zahlen
der DCT bzw. DCT'.(DCT* 2^15)
|
Multiplikator
|
X0
|
X1
|
X2
|
X3
|
X4
|
X5
|
X6
|
X7
|
Sum
|
|
16069
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
-16069
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
15137
|
-
|
-
|
2
|
-
|
-
|
-
|
2
|
-
|
4
|
|
-15137
|
-
|
-
|
2
|
-
|
-
|
-
|
2
|
-
|
4
|
|
13623
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
-13623
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
11585
|
8
|
-
|
-
|
-
|
4
|
-
|
-
|
-
|
12
|
|
-11585
|
-
|
-
|
-
|
-
|
4
|
-
|
-
|
-
|
4
|
|
9102
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
-9102
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
6270
|
-
|
-
|
2
|
-
|
-
|
-
|
2
|
-
|
4
|
|
-6270
|
-
|
-
|
2
|
-
|
-
|
-
|
2
|
-
|
4
|
|
3196
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
-3196
|
-
|
1
|
-
|
1
|
-
|
1
|
-
|
1
|
4
|
|
Summe
|
8
|
8
|
8
|
8
|
8
|
8
|
8
|
8
|
64
|
Es treten gerade 43 verschiedene Multiplikationen von 64 auf. Diese Multiplikatoren
können auf 22 Verschiedene herabgesetzt werden, wenn ich das Vorzeichen
nicht mitbetrachte. Produkte mit einem negativem Multiplikator werden vom
enstehenden Element subtrahieren statt addiert. Bei der optimierten Matrizenmultiplikation
werden bei jeder neuanfangenden Zeile bzw. Spalte die 22 verschiedenen
Produkte berechnet.Die Ergebnisse werden in Variablen abgelegt. Nun werden
diese fertigen
Produkte in der weitere Berechnung eingesetzt (addiert und subtrahiert).
Ich brauche statt 64 nur noch 22 Multiplikationen zu berechnen. Das bedeutet
einen fast dreifachen Zeitgewinn.
Die Optimierung mit Multiplikationstabellen, in denen feste Ergebnisse
stehen, wäre noch möglich. Doch eine Anwendung wäre sehr
speicherintensiv. Sieben Multiplikationstabellen mit 2048 Integerzahlen.
dh. 7*4kb sind 28Kb je Multiplikationstabelle. Konstanten in dieser
Größenordnung sind unter der 16bit-Programmierung sehr schlecht
anzuwenden. Die Beschleunigung würde etwa 33%. betragen. Momentan
liegt die Geschwindigkeit auf ein 486DX/33 bei 600ms, bei einem 486DX-2/80
bei 220ms (gemessen mit einem 24bit-Bild 384x384 Pixel). Weitere denkbare
Zeiteinsparungen würden vielleicht nicht gehen, da eine DX2-CPU einen
MiSS im internen Cache hart mit Zeitverlusten bestraft. Es hat sich allgemein
die Kleinprogrammstrategie besser bewährt, als übermäßige
Schleifenausprogrammierungen mit viel Quell- und Binärcode.
2.7 Der 7.Schritt YUV —> RGB
Hier möchte ich nicht allzuviel schreiben. Die Umwandlung geschieht
äquivalent zur RGB —> YUV-Umwandlung. Der Assemblertext sollte hier
Aufschluß geben. Die einzigste Veränderung ist eine Optimierung.
Jegliche Multiplikationen wurden durch Multiplikationstabellen mit MOV
ersetzt. Hier hat es sich angeboten, da nicht allzuviele Eingangswerte
vorhanden sind (nur 256 verschiedene).
Praktikum