====== 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.