std.wavelet — diskrete Wavelet-Transformation

DWT mit Haar und Daubechies db2/db3/db4, mehrstufig, zweidimensional nach Mallat, Schwellenwertverfahren und Entrauschen.

→ Mathematik-Units · std.fft · 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? std.fft
Wo tritt eine Frequenz auf? std.wavelet
Filtern mit einem Kern 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.

Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).