it-swarm.com.de

Schnellste Implementierung von Gaußscher Unschärfe

Wie implementiere ich den schnellsten Gaußschen Unschärfe Algorithmus?

Ich werde es in Java implementieren, so dass GPU -Lösungen ausgeschlossen sind. Meine Anwendung, planetGenesis , ist plattformübergreifend, daher möchte ich nicht JNI .

32
Sid Datta

Sie sollten die Tatsache nutzen, dass ein Gaußscher Kern trennbar ist, d. H. e. Sie können eine 2D-Faltung als Kombination zweier 1D-Faltungen ausdrücken.

Wenn das Filter groß ist, kann es auch sinnvoll sein, die Tatsache zu verwenden, dass die Faltung im räumlichen Bereich der Multiplikation im Frequenzbereich (Fourier-Bereich) entspricht. Dies bedeutet, dass Sie die Fourier-Transformation des Bildes und des Filters nehmen, die (komplexen) Ergebnisse multiplizieren und dann die inverse Fourier-Transformation durchführen können. Die Komplexität der FFT (schnelle Fourier-Transformation) ist O (n log n), während die Komplexität einer Faltung O (n ^ 2) ist. Wenn Sie viele Bilder mit demselben Filter verwischen möchten, müssen Sie die FFT des Filters nur einmal durchführen.

Wenn Sie sich für eine FFT entscheiden, ist die FFTW-Bibliothek eine gute Wahl.

26
Dima

Math Jocks wissen das wahrscheinlich, aber für alle anderen ..

Aufgrund einer mathematischen Nizza-Eigenschaft des Gaussian können Sie ein 2D-Bild schnell verwischen, indem Sie zunächst eine 1D-Gaußsche Unschärfe in jeder Zeile des Bildes und dann eine 1D-Unschärfe in jeder Spalte ausführen. 

20
DarenW
  1. Ich habe Quasimondo: Inkubator: Verarbeitung: schnelle Gaußsche Unschärfe gefunden. Diese Methode enthält viele Annäherungen, z. B. die Verwendung von Ganzzahlen und Nachschlagen von Tabellen anstelle von Gleitkommazahlen und Gleitkommadivisionen. Ich weiß nicht, wie viel Beschleunigung moderner Java-Code ist.

  2. Fast Shadows on Rectangles hat einen Näherungsalgorithmus, der B-Splines verwendet. 

  3. Schneller Gaußscher Unschärfealgorithmus in C # behauptet, einige coole Optimierungen zu haben.

  4. Fast Gaussian Blur (PDF) von David Everly hat eine schnelle Methode für die Gaußsche Unschärfeverarbeitung.

Ich würde die verschiedenen Methoden ausprobieren, sie bewerten und die Ergebnisse hier veröffentlichen.

Für meine Zwecke habe ich die grundlegende Methode (X-Y-Achse unabhängig verarbeiten) und die Methode Fast Gaussian Blurvon David Everly aus dem Internet kopiert und implementiert. Sie unterscheiden sich in den Parametern, daher konnte ich sie nicht direkt vergleichen. Letzteres durchläuft jedoch eine viel geringere Anzahl von Iterationen für einen großen Verwischungsradius. Letzteres ist auch ein Näherungsalgorithmus.

17
Sid Datta

ULTIMATIVE LÖSUNG

Ich war sehr verwirrt von so vielen Informationen und Implementierungen, ich wusste nicht, welchem ​​ich trauen sollte. Nachdem ich es herausgefunden hatte, entschied ich mich, meinen eigenen Artikel zu schreiben. Ich hoffe, es spart Ihnen Stunden.

Schnellste Gaußsche Unschärfe (in linearer Zeit)

Es enthält den Quellcode, der (ich hoffe) kurz, sauber und in jede andere Sprache leicht wiederbeschreibbar ist. Bitte stimme zu, damit andere Leute es sehen können.

12
Ivan Kuckir

Sie möchten wahrscheinlich die Box verwischen, was viel schneller ist. Siehe diesen Link für ein großartiges Tutorial und einige Copy & Paste C-Codes .

8
Steve Hanov

Versuchen Sie für größere Unschärferadien dreimal eine Box-Unschärfe anzuwenden. Dies wird einer Gaußschen Unschärfe sehr nahe kommen und viel schneller als eine echte Gaußsche Unschärfe sein.

7
Tom Sirgedas
  • Schritt 1: SIMD 1-dimensionale Gaußsche Unschärfe
  • Schritt 2: transponieren
  • Schritt 3: Wiederholen Sie Schritt 1

Dies geschieht am besten mit kleinen Blöcken, da eine Vollbildtransponierung langsam ist, während eine Kleinblocktransponierung extrem schnell mit einer Kette von PUNPCKs ( PUNPCKHBW, PUNPCKHDQ, PUNPCKHWD, PUNPCKLBW, PUNPCKLDQ PUNPCKLWD ).

2
Dark Shikari

Ich habe mit diesem Problem für meine Forschungen zu kämpfen gehabt und versucht, eine schnelle Gaußsche Unschärfe zu finden. Erstens, wie erwähnt, ist es am besten, die Unschärfe in zwei 1D-Unschärfen zu trennen. Abhängig von Ihrer Hardware für die tatsächliche Berechnung der Pixelwerte können Sie tatsächlich alle möglichen Werte vorberechnen und in einer Nachschlagetabelle speichern. 

Mit anderen Worten: Berechnen Sie jede Kombination von Gaussian coefficient * input pixel value vor. Natürlich müssen Sie Ihre Koeffizienten diskretisieren, aber ich wollte diese Lösung nur hinzufügen. Wenn Sie ein Abonnement IEEE haben, können Sie unter Schnelles Verschwimmen von Bildern mit Hilfe der Lookup-Tabelle zur Echtzeit-Extraktion von Features lesen. 

Letztendlich habe ich schließlich CUDA benutzt :)

2
Ben McIntosh

Ich habe Ivan Kuckirs Implementierung einer schnellen Gaußschen Unschärfe, die drei Durchgänge mit linearen Box-Unschärfen verwendet, in Java konvertiert. Der resultierende Prozess ist O(n), wie er in seinem eigenen Blog angegeben hat. Wenn Sie mehr darüber erfahren möchten, warum die 3-Zeit-Box-Unschärfe sich an die Gaußsche Unschärfe (3%) annähert, können Sie Box-Unschärfe und Gauß-Unschärfe überprüfen.

Hier ist die Java-Implementierung. 

@Override
public BufferedImage ProcessImage(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight();

    int[] pixels = image.getRGB(0, 0, width, height, null, 0, width);
    int[] changedPixels = new int[pixels.length];

    FastGaussianBlur(pixels, changedPixels, width, height, 12);

    BufferedImage newImage = new BufferedImage(width, height, image.getType());
    newImage.setRGB(0, 0, width, height, changedPixels, 0, width);

    return newImage;
}

private void FastGaussianBlur(int[] source, int[] output, int width, int height, int radius) {
    ArrayList<Integer> gaussianBoxes = CreateGausianBoxes(radius, 3);
    BoxBlur(source, output, width, height, (gaussianBoxes.get(0) - 1) / 2);
    BoxBlur(output, source, width, height, (gaussianBoxes.get(1) - 1) / 2);
    BoxBlur(source, output, width, height, (gaussianBoxes.get(2) - 1) / 2);
}

private ArrayList<Integer> CreateGausianBoxes(double sigma, int n) {
    double idealFilterWidth = Math.sqrt((12 * sigma * sigma / n) + 1);

    int filterWidth = (int) Math.floor(idealFilterWidth);

    if (filterWidth % 2 == 0) {
        filterWidth--;
    }

    int filterWidthU = filterWidth + 2;

    double mIdeal = (12 * sigma * sigma - n * filterWidth * filterWidth - 4 * n * filterWidth - 3 * n) / (-4 * filterWidth - 4);
    double m = Math.round(mIdeal);

    ArrayList<Integer> result = new ArrayList<>();

    for (int i = 0; i < n; i++) {
        result.add(i < m ? filterWidth : filterWidthU);
    }

    return result;
}

private void BoxBlur(int[] source, int[] output, int width, int height, int radius) {
    System.arraycopy(source, 0, output, 0, source.length);
    BoxBlurHorizantal(output, source, width, height, radius);
    BoxBlurVertical(source, output, width, height, radius);
}

private void BoxBlurHorizontal(int[] sourcePixels, int[] outputPixels, int width, int height, int radius) {
    int resultingColorPixel;
    float iarr = 1f / (radius + radius);
    for (int i = 0; i < height; i++) {
        int outputIndex = i * width;
        int li = outputIndex;
        int sourceIndex = outputIndex + radius;

        int fv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex]);
        int lv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex + width - 1]);
        float val = (radius) * fv;

        for (int j = 0; j < radius; j++) {
            val += Byte.toUnsignedInt((byte) (sourcePixels[outputIndex + j]));
        }

        for (int j = 0; j < radius; j++) {
            val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex++]) - fv;
            resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
            outputPixels[outputIndex++] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
        }

        for (int j = (radius + 1); j < (width - radius); j++) {
            val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex++]) - Byte.toUnsignedInt((byte) sourcePixels[li++]);
            resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
            outputPixels[outputIndex++] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
        }

        for (int j = (width - radius); j < width; j++) {
            val += lv - Byte.toUnsignedInt((byte) sourcePixels[li++]);
            resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
            outputPixels[outputIndex++] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
        }
    }
}

private void BoxBlurVertical(int[] sourcePixels, int[] outputPixels, int width, int height, int radius) {
    int resultingColorPixel;
    float iarr = 1f / (radius + radius + 1);
    for (int i = 0; i < width; i++) {
        int outputIndex = i;
        int li = outputIndex;
        int sourceIndex = outputIndex + radius * width;

        int fv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex]);
        int lv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex + width * (height - 1)]);
        float val = (radius + 1) * fv;

        for (int j = 0; j < radius; j++) {
            val += Byte.toUnsignedInt((byte) sourcePixels[outputIndex + j * width]);
        }
        for (int j = 0; j <= radius; j++) {
            val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex]) - fv;
            resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
            outputPixels[outputIndex] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
            sourceIndex += width;
            outputIndex += width;
        }
        for (int j = radius + 1; j < (height - radius); j++) {
            val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex]) - Byte.toUnsignedInt((byte) sourcePixels[li]);
            resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
            outputPixels[outputIndex] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
            li += width;
            sourceIndex += width;
            outputIndex += width;
        }
        for (int j = (height - radius); j < height; j++) {
            val += lv - Byte.toUnsignedInt((byte) sourcePixels[li]);
            resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
            outputPixels[outputIndex] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
            li += width;
            outputIndex += width;
        }
    }
}
2
Ali Akdurak

Ich würde in Betracht ziehen, CUDA oder ein anderes GPU-Programmierwerkzeug zu verwenden, insbesondere wenn Sie einen größeren Kernel verwenden möchten. Wenn Sie dies nicht tun, können Sie die Loops in Assembly immer von Hand anpassen.

2
Dana the Sane

In 1D:

Das wiederholte Verwischen mit fast jedem Kernel neigt zu einem Gaußschen Kernel. Das ist es, was an der Gaußschen Distribution so cool ist, weshalb Statistiker es mögen. Wählen Sie also etwas, das leicht zu verwischen ist, und wenden Sie es mehrmals an.

Zum Beispiel ist es einfach, mit einem kastenförmigen Kern zu verwischen. Berechnen Sie zuerst eine kumulierte Summe:

y(i) = y(i-1) + x(i)

dann:

blurred(i) = y(i+radius) - y(i-radius)

Mehrmals wiederholen.

Oder Sie gehen ein paar Mal mit einem IIR - Filter hin und her, diese sind ähnlich schnell.

In 2D oder höher:

Verwischen Sie jede Dimension nacheinander, wie DarenW sagte.

2
Paul Harrison

Versuchen Sie, Box Blur so zu verwenden, wie ich es hier getan habe: Annäherung der Gaußschen Unschärfe mit Extended Box Blur

Dies ist die beste Annäherung.

Mit Integral Images können Sie es noch schneller machen.
Wenn Sie dies tun, teilen Sie bitte Ihre Lösung mit.

0
Royi

Dave Hale von CWP hat ein Minejtk-Paket, das einen rekursiven Gauß-Filter (Deriche-Methode und Van-Vliet-Methode) enthält. Die Java-Unterroutine kann unter https://github.com/dhale/jtk/blob/0350c23f91256181d415ea7369dbd62855ac4460/core/src/main/Java/edu/mines/jtk/dsp/RecursiveGaussianFilter.Java gefunden werden.

Die Deriches Methode scheint eine sehr gute Methode für Gaußsche Unschärfe (und auch für Gaußsche Derivate) zu sein. 

0
Kai

Ich habe mehrere Antworten an verschiedenen Orten gesehen und sammle sie hier, damit ich versuchen kann, sie mit meinen Gedanken zu umwickeln und an sie zu erinnern:

Unabhängig davon, welche Methode Sie verwenden, horizontale und vertikale Bemaßungen separat filtern mit 1D-Filtern statt mit einem einzigen quadratischen Filter.

  • Der "langsame" Standardansatz: Faltungsfilter
  • Hierarchische Pyramide von Bildern mit reduzierter Auflösung wie in SIFT
  • Wiederholte Box-Unschärfen, die vom zentralen Grenzwertsatz motiviert werden. Die Box-Unschärfe ist für die Gesichtserkennung von Viola und Jones von zentraler Bedeutung. Sie nennen es ein ganzheitliches Bild, wenn ich mich richtig erinnere. Ich denke, Haar-ähnliche Funktionen verwenden auch etwas Ähnliches.
  • Stack Blur : Eine warteschlangenbasierte Alternative zwischen den Konvolution- und Box-Blur-Ansätzen
  • IIR-Filter

Nachdem ich all das durchgesehen habe, werde ich daran erinnert, dass einfache, schlechte Annäherungen in der Praxis oft gut funktionieren. In einem anderen Bereich stellte Alex Krizhevsky fest, dass ReLUs schneller sind als die klassische Sigmoidfunktion in seinem bahnbrechenden AlexNet, auch wenn sie auf den ersten Blick als eine schreckliche Annäherung an das Sigmoid erscheinen.

0
Josiah Yoder

Beantworten Sie diese alte Frage mit neue Bibliotheken, die jetzt implementiert wurden (Stand 2016), da es mit Java viele Neuerungen in der GPU-Technologie gibt. 

Wie in wenigen anderen Antworten vorgeschlagen, ist CUDA eine Alternative. Aber Java unterstützt jetzt CUDA

IBM CUDA4J-Bibliothek: Stellt eine Java-API für die Verwaltung und den Zugriff auf GPU-Geräte, Bibliotheken, Kernel und Speicher bereit. Mit diesen neuen APIs ist es möglich, Java-Programme zu schreiben, die die GPU-Geräteeigenschaften verwalten und Arbeit auf die GPU verlagern, wobei das Java-Speichermodell, Ausnahmen und die automatische Ressourcenverwaltung verwendet werden.

Jcuda: Java-Bindungen für NVIDIA CUDA und verwandte Bibliotheken. Mit JCuda ist es möglich, über Java-Programme mit der CUDA-Laufzeit- und Treiber-API zu interagieren.

Aparapi: Java-Entwickler können die Rechenleistung von GPU- und APU - Geräten nutzen, indem sie parallele Codefragmente auf der GPU ausführen und nicht auf die lokale CPU beschränkt sind.

Einige Java OpenCL-Bindung -Bibliotheken

https://github.com/ochafik/JavaCL : Java-Bindungen für OpenCL: Eine objektorientierte OpenCL-Bibliothek, die auf automatisch generierten Low-Level-Bindungen basiert

http://jogamp.org/jocl/www/ : Java-Bindungen für OpenCL: Eine objektorientierte OpenCL-Bibliothek, die auf automatisch generierten Low-Level-Bindungen basiert

http://www.lwjgl.org/ : Java-Bindungen für OpenCL: Automatisch generierte Low-Level-Bindungen und objektorientierte Convenience-Klassen

http://jocl.org/ : Java-Bindungen für OpenCL: Einfache Bindungen, die eine 1: 1-Zuordnung der ursprünglichen OpenCL-API darstellen

Alle diese Bibliotheken helfen, Gaußsche Unschärfe schneller als jede Implementierung in Java auf CPU zu implementieren.

0
Tejus Prasad

Es gibt mehrere schnelle Methoden für die Gauß-Unschärfe von 2D-Daten. Was Sie darüber wissen sollten.

  1. Dies ist ein separierbarer Filter, sodass nur zwei 1d-Faltungen erforderlich sind. 
  2. Bei großen Kerneln können Sie eine verkleinerte Bildkopie verarbeiten und dann wieder hochskalieren.
  3. Eine gute Annäherung kann durch mehrere Box-Filter (auch trennbar) erfolgen (Anzahl der Iterationen und Kerngrößen kann eingestellt werden)
  4. Es gibt einen O(n) - Komplexitätsalgorithmus (für eine beliebige Kerngröße) für eine genaue Gauß-Approximation durch IIR-Filter.

Ihre Wahl hängt von der erforderlichen Geschwindigkeit, Präzision und Komplexität der Implementierung ab. 

0
minorlogic