====== std.wavelet — diskrete Wavelet-Transformation ======
DWT mit Haar und Daubechies db2/db3/db4, mehrstufig, zweidimensional nach Mallat, Schwellenwertverfahren und Entrauschen.
→ [[lyx_-_programmiersprache:units:mathematik|Mathematik-Units]] · [[lyx_-_programmiersprache:units:fft|std.fft]] · [[lyx_-_programmiersprache:units:convolve|std.convolve]]
import std.wavelet;
Schicht 0, hängt an ''std.math'' und ''std.alloc''. 20 Funktionen, vier Wavelet-Konstanten. Alle Beispiele mit ''lyxc 1.0.21A'' übersetzt und ausgeführt.
----
===== Wavelet wählen =====
^ Konstante ^ Filterlänge ^ Verschwindende Momente ^
| ''WAV_HAAR'' | 2 | 1 |
| ''WAV_DB2'' | 4 | 2 |
| ''WAV_DB3'' | 6 | 3 |
| ''WAV_DB4'' | 8 | 4 |
''WavFilterLen(kind)'' und ''WavVanishingMoments(kind)'' geben beides zur Laufzeit aus; ''WavFilterLow'' und ''WavFilterHigh'' liefern die Koeffizienten selbst.
> **Verschwindende Momente sagen, welche Signalanteile die Details //nicht// sehen.** Haar mit einem Moment bildet konstante Abschnitte auf null ab; db2 mit zwei Momenten zusätzlich lineare Anstiege, db3 auch quadratische. Für Kompression und Entrauschen ist das der entscheidende Punkt: je mehr Momente, desto mehr Detailkoeffizienten sind praktisch null und desto weniger bleibt zu speichern.
>
> Der Preis ist die Filterlänge — db4 verrechnet acht Nachbarn und verschmiert damit einen Sprung im Signal über acht Stellen. Für Signale mit echten Kanten ist Haar oft die bessere Wahl, obwohl es „das einfachste" ist.
----
===== Eine Stufe hin und zurück =====
unit main;
import std.io;
import std.string;
import std.alloc;
import std.wavelet;
fn ZF(t: pchar, v: f64): void { Print(t); PrintF64(v); }
fn main(): int64 {
var n: int64 := 8;
var a: int64 := alloc(n * 8);
var i: int64 := 0;
while (i < n) { pokef64(a + i * 8, (i + 1) as f64); i := i + 1; }
var c: int64 := alloc(n * 8);
WavDwt(a, n, WAV_HAAR, c);
i := 0;
while (i < 4) {
Print("Naeherung["); Print(IntToStr(i)); Print("] = ");
PrintF64(peekf64(c + i * 8));
i := i + 1;
}
i := 4;
while (i < 8) {
Print("Detail ["); Print(IntToStr(i - 4)); Print("] = ");
PrintF64(peekf64(c + i * 8));
i := i + 1;
}
ZF("Energie Signal : ", 204.0);
ZF("Energie Koeffizient : ", WavEnergy(c, n));
ZF("EnergyRatio(k=4) : ", WavEnergyRatio(c, n, 4));
var r: int64 := alloc(n * 8);
WavIdwt(c, n, WAV_HAAR, r);
ZF("Rueck x0 : ", peekf64(r));
ZF("Rueck x7 : ", peekf64(r + 56));
return 0;
}
Naeherung[0] = 2.121320
Naeherung[1] = 4.949747
Naeherung[2] = 7.778174
Naeherung[3] = 10.606601
Detail [0] = -0.707106
Detail [1] = -0.707106
Detail [2] = -0.707106
Detail [3] = -0.707106
Energie Signal : 204.000000
Energie Koeffizient : 203.999999
EnergyRatio(k=4) : 0.990196
Rueck x0 : 0.999999
Rueck x7 : 7.999999
Das Signal ist ''1, 2, …, 8''. Drei Beobachtungen:
* **Die Ausgabe ist eine Hälfte Näherung, eine Hälfte Detail** — bei ''n = 8'' also vier und vier, hintereinander im selben Feld.
* **Alle Detailkoeffizienten sind gleich (−0,707).** Die Steigung ist überall dieselbe, und Haar sieht von einem linearen Anstieg genau diesen konstanten Unterschied. Mit ''WAV_DB2'' wären sie null — zwei verschwindende Momente erfassen lineare Anteile vollständig.
* **Die Energie bleibt erhalten** (204 gegen 203,999999). Die Transformation ist **orthonormal**; das ist eine Prüfung ohne fremde Referenz, die einen falschen Normierungsfaktor sofort auffliegen lässt.
''WavEnergyRatio(c, n, 4)'' sagt, welcher Anteil der Energie in den ersten vier Koeffizienten steckt: **99,02 %**. Genau das ist die Aussage, auf der Kompression beruht — die Hälfte der Zahlen trägt fast die ganze Information.
----
===== Mehrere Stufen =====
^ Funktion ^ Zweck ^
| ''WavMaxLevel(n, kind)'' | wie oft lässt sich zerlegen? |
| ''WavDwtMulti(a, n, kind, levels, out)'' | mehrstufig vorwärts |
| ''WavIdwtMulti(coeff, n, kind, levels, out)'' | zurück |
| ''WavApproxLen(n, levels)'' | Länge des Näherungsteils |
| ''WavDetailRange(n, levels, level, …)'' | wo liegt die Detailebene ''level''? |
Bei jeder Stufe wird nur der **Näherungsteil** weiter zerlegt; die Details bleiben liegen. Deshalb braucht man ''WavDetailRange'', um im Ergebnisfeld die einzelnen Ebenen wiederzufinden. ''WavMaxLevel(64, WAV_HAAR)'' ergibt ''5''.
----
===== Entrauschen =====
var sigma: f64 := WavEstimateSigma(coeff, n, levels);
var t: f64 := WavUniversalThreshold(sigma, n);
WavThresholdSoft(coeff, n, t);
Oder in einem Schritt: ''WavDenoise(a, n, kind, levels, …)''.
^ Funktion ^ Verhalten ^
| ''WavThresholdHard(c, n, t)'' | alles unter ''t'' auf null, der Rest bleibt |
| ''WavThresholdSoft(c, n, t)'' | alles unter ''t'' auf null, der Rest wird um ''t'' verkleinert |
| ''WavEstimateSigma'' | Rauschstärke aus den feinsten Details geschätzt |
| ''WavUniversalThreshold(sigma, n)'' | Schwelle nach Donoho–Johnstone |
> **Hart oder weich ist keine Geschmacksfrage.** Hartes Schwellen lässt die überlebenden Koeffizienten unangetastet und erhält Kanten scharf, erzeugt aber Sprünge an der Schwelle — im Bild sichtbar als Artefakte um Kanten herum. Weiches Schwellen ist stetig und liefert glattere Ergebnisse, verkleinert dabei aber **auch die echten** Koeffizienten, dämpft also das Nutzsignal mit. Für Bilder ist meist weich richtig, für Signale mit scharfen Ereignissen hart.
>
> ''WavEstimateSigma'' schätzt das Rauschen aus den **feinsten** Detailkoeffizienten — dort steckt bei natürlichen Signalen fast nur Rauschen. Das setzt voraus, dass das Signal auf der feinsten Ebene tatsächlich glatt ist; bei bewusst hochfrequenten Daten überschätzt die Schätzung das Rauschen und die Entrauschung nimmt zu viel weg.
----
===== Zweidimensional =====
''WavDwt2D(img, w, h, kind, …)'' und ''WavIdwt2D'' zerlegen nach **Mallat**: erst zeilenweise, dann spaltenweise. Das Ergebnis sind vier Viertel — Näherung oben links, waagerechte, senkrechte und diagonale Details in den übrigen.
----
===== Abgrenzung =====
^ Frage ^ Unit ^
| **Welche** Frequenzen stecken drin? | [[lyx_-_programmiersprache:units:fft|std.fft]] |
| **Wo** tritt eine Frequenz auf? | ''std.wavelet'' |
| Filtern mit einem Kern | [[lyx_-_programmiersprache:units:convolve|std.convolve]] |
Die FFT sagt, welche Frequenzen ein Signal enthält, aber nicht wann — sie verteilt ein kurzes Ereignis über das ganze Spektrum. Die Wavelet-Transformation behält beides, Ort und Skala, und ist deshalb bei Sprüngen, Kanten und einmaligen Ereignissen die passende Wahl.
**Noch nicht auf kompensiertes Summieren umgestellt** — zwei Stellen.
----
Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit ''lyxc 1.0.21A'' übersetzt und ausgeführt; die Energieerhaltung als referenzfreie Invariante nachgerechnet, die Rücktransformation Wert für Wert geprüft.