web-dev-qa-db-fra.com

Mise en oeuvre du flou gaussien le plus rapide

Comment implémentez-vous l’algorithme le plus rapide flou gaussien ?

Je vais l'implémenter en Java, donc les solutions GPU sont exclues. Mon application, planetGenesis , est multi-plateforme, donc je ne veux pas JNI .

32
Sid Datta

Vous devriez utiliser le fait qu'un noyau gaussien est séparable, i. e. vous pouvez exprimer une convolution 2D en combinant deux convolutions 1D.

Si le filtre est grand, il peut également être judicieux d'utiliser le fait que la convolution dans le domaine spatial équivaut à la multiplication dans le domaine de fréquence (Fourier). Cela signifie que vous pouvez prendre la transformation de Fourier de l'image et du filtre, multiplier les résultats (complexes), puis prendre la transformation de Fourier inverse. La complexité de la transformée de Fourier rapide (FFT) est de O (n log n), tandis que la complexité d'une convolution est de O (n ^ 2). En outre, si vous devez brouiller plusieurs images avec le même filtre, il vous suffira de prendre la FFT du filtre une fois.

Si vous décidez d'utiliser une FFT, la bibliothèque FFTW est un bon choix.

26
Dima

Les maths vont probablement le savoir, mais pour n'importe qui d'autre ..

En raison d'une propriété mathématique gaussienne intéressante, vous pouvez rapidement rendre une image 2D floue en appliquant d'abord un flou gaussien 1D sur chaque ligne de l'image, puis en appliquant un flou 1D sur chaque colonne. 

20
DarenW
  1. J'ai trouvé Quasimondo: Incubateur: Traitement: Flou gaussien rapide. Cette méthode contient de nombreuses approximations, telles que l’utilisation d’entiers et de tables de recherche, au lieu des divisions et des divisions. Je ne sais pas à quel point le code Java moderne est accéléré.

  2. Fast Shadows on Rectangles utilise un algorithme d'approximation utilisant B-splines

  3. L'algorithme de flou gaussien rapide en C # prétend avoir des optimisations intéressantes.

  4. En outre, Fast Gaussian Blur (PDF) de David Everly propose une méthode rapide de traitement du flou gaussien.

Je voudrais essayer les différentes méthodes, les comparer et afficher les résultats ici.

Pour mes besoins, j'ai copié et mis en œuvre la méthode de base (processus axe X-Y indépendamment) et la méthode Fast Gaussian Blur de David Everly depuis Internet. Ils diffèrent par les paramètres, donc je ne pouvais pas les comparer directement. Toutefois, le nombre d'itérations dans ce dernier rayon est beaucoup moins important. En outre, ce dernier est un algorithme approximatif.

17
Sid Datta

SOLUTION ULTIME

J'étais très dérouté par tant d'informations et d'implémentations, je ne savais pas à qui faire confiance. Après avoir compris, j'ai décidé d'écrire mon propre article. J'espère que cela vous fera gagner des heures.

Le flou gaussien le plus rapide (en temps linéaire)

Il contient le code source qui (j'espère) est court, propre et facilement réinscriptible dans n’importe quelle autre langue. S'il vous plaît, votez pour que les autres personnes puissent le voir.

12
Ivan Kuckir

Vous voulez probablement le flou de la boîte, ce qui est beaucoup plus rapide. Voir ce lien pour un excellent tutoriel et quelques copier/coller du code C .

8
Steve Hanov

Pour les plus grands rayons de flou, essayez d’appliquer un bloc flou trois fois. Cela correspond très bien au flou gaussien et est beaucoup plus rapide qu’un vrai flou gaussien.

7
Tom Sirgedas
  • Étape 1: flou gaussien SIMD à 1 dimension
  • Étape 2: transposer
  • Étape 3: Répétez l'étape 1

Le mieux est de le faire sur de petits blocs, car la transposition d’une image complète est lente, tandis que la transposition d’un petit bloc peut être extrêmement rapide en utilisant une chaîne de PUNPCKs ( PUNPCKHBW, PUNPCKHDQ, PUNPCKHWD, PUNPCKLBW, PUNPCKLDQ, PUNPCKLWD ).

2
Dark Shikari

Je me suis battu avec ce problème pour mes recherches et j'ai essayé une méthode intéressante pour un flou gaussien rapide. Premièrement, comme mentionné, il est préférable de séparer le flou en deux flous 1D, mais en fonction de votre matériel pour le calcul des valeurs de pixel, vous pouvez réellement pré-calculer toutes les valeurs possibles et les stocker dans une table de correspondance. 

En d'autres termes, pré-calculez chaque combinaison de Gaussian coefficient * input pixel value. Bien sûr, vous devrez discréditer vos coefficients, mais je voulais simplement ajouter cette solution. Si vous avez un abonnement IEEE , vous pouvez en savoir plus dans Flou d'image rapide à l'aide de la table de consultation pour l'extraction de caractéristiques en temps réel

Finalement, j'ai fini par utiliser CUDA quoique :)

2
Ben McIntosh

J'ai converti l'implémentation par Ivan Kuckir d'un flou gaussien rapide qui utilise trois passes avec des flous de boîte linéaires en Java. Le processus résultant est O(n) comme il l'a déclaré sur son propre blog . Si vous souhaitez en savoir plus sur les raisons pour lesquelles le flou temporel à trois blocs se rapproche du flou gaussien (3%), vous pouvez consulter le champ flou et le flou gaussien .

Voici l'implémentation Java. 

@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

Pour ce faire, j’envisagerais d’utiliser CUDA ou un autre outil de programmation GPU, en particulier si vous souhaitez utiliser un noyau plus volumineux. En cas d'échec, il y a toujours une mise au point manuelle de vos boucles dans Assembly.

2
Dana the Sane

En 1D:

Flouter en utilisant presque n'importe quel noyau à plusieurs reprises aura tendance à un noyau gaussien. C’est ce qui est génial dans la distribution gaussienne et pourquoi les statisticiens l’apprécient. Alors choisissez quelque chose qui est facile à brouiller et appliquez-le plusieurs fois.

Par exemple, il est facile de flouter avec un noyau en forme de boîte. Commencez par calculer une somme cumulée:

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

puis:

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

Répétez plusieurs fois.

Ou vous pouvez aller et venir plusieurs fois avec une variété de filtres IIR , ceux-ci sont tout aussi rapides.

En 2D ou supérieur:

Flou dans chaque dimension les uns après les autres, comme dit DarenW.

2
Paul Harrison

Essayez d’utiliser Box Blur comme je l’ai fait ici: Approximation du flou gaussien à l’aide du flou élargi

C'est la meilleure approximation.

En utilisant Integral Images, vous pouvez le rendre encore plus rapidement.
Si vous le faites, partagez votre solution.

0
Royi

Dave Hale de CWP a un paquetage minejtk, qui inclut un filtre gaussien récursif (méthode Deriche et méthode Van Vliet). Le sous-programme Java peut être trouvé à l'adresse https://github.com/dhale/jtk/blob/0350c23f91256181d415ea7369dbd62855ac4460/core/src/main/Java/edu/mines/jtk/dsp/RecursiveGaussianFilter.Java .

La méthode de Deriche semble être une très bonne méthode pour le flou gaussien (ainsi que pour les dérivés de gaussien). 

0
Kai

J'ai vu plusieurs réponses à différents endroits et je les recueille ici pour pouvoir essayer de les envelopper dans mon esprit et de m'en souvenir pour plus tard:

Quelle que soit l'approche utilisée, filtre les dimensions horizontale et verticale séparément avec des filtres 1D plutôt que d'utiliser un seul filtre carré.

  • L'approche standard "lente": filtre de convolution
  • Pyramide hiérarchique d'images à résolution réduite comme dans SIFT
  • Flous de boîtes répétées motivés par le théorème central limite. Le flou de la boîte est au centre de la détection des visages de Viola et Jones, qu’ils appellent une image intégrale, si je me souviens bien. Je pense que les fonctionnalités de type Haar utilisent quelque chose de similaire, aussi.
  • Stack Blur : une alternative basée sur la file d'attente quelque part entre les approches de convolution et de flou en boîte
  • Filtres IIR

Après avoir passé en revue tout cela, je me rappelle que des approximations simples et médiocres fonctionnent souvent bien dans la pratique. Dans un autre domaine, Alex Krizhevsky a découvert que ReLU était plus rapide que la fonction sigmoïde classique de son AlexNet novateur, même s’ils semblaient à première vue être une terrible approximation du Sigmoïde.

0
Josiah Yoder

Répondez à cette ancienne question avec nouvelles bibliothèques qui ont été implémentées maintenant (à partir de 2016), car de nombreuses avancées ont été apportées à la technologie GPU avec Java. 

Comme suggéré dans quelques autres réponses, CUDA est une alternative. Mais Java supporte CUDA maintenant. 

Bibliothèque IBM CUDA4J: fournit une API Java pour gérer et accéder aux unités, bibliothèques, noyaux et mémoire GPU. À l'aide de ces nouvelles API, il est possible d'écrire des programmes Java qui gèrent les caractéristiques des périphériques GPU et de décharger le travail sur le GPU à l'aide du modèle de mémoire Java, des exceptions et de la gestion automatique des ressources.

Jcuda: liaisons Java pour NVIDIA CUDA et les bibliothèques associées. Avec JCuda, il est possible d'interagir avec le runtime CUDA et l'API de pilote à partir de programmes Java.

Aparapi: permet aux développeurs Java de tirer parti de la puissance de calcul des périphériques GPU et APU en exécutant des fragments de code de données parallèles sur le GPU plutôt que d'être confinés au processeur local.

Quelques bibliothèques liaison Java OpenCL

https://github.com/ochafik/JavaCL : Liaisons Java pour OpenCL: une bibliothèque OpenCL orientée objet, basée sur des liaisons de bas niveau générées automatiquement

http://jogamp.org/jocl/www/ : Liaisons Java pour OpenCL: une bibliothèque OpenCL orientée objet, basée sur des liaisons de bas niveau générées automatiquement.

http://www.lwjgl.org/ : Liaisons Java pour OpenCL: Liaisons de bas niveau générées automatiquement et classes de confort orientées objet

http://jocl.org/ : Liaisons Java pour OpenCL: Liaisons de bas niveau correspondant à un mappage 1: 1 de l'API OpenCL d'origine

Toutes ces bibliothèques aideront à implémenter le flou gaussien plus rapidement que toute implémentation en Java sur processeur.

0
Tejus Prasad

Il existe plusieurs méthodes rapides de gauss flou de données 2D. Ce que vous devez savoir.

  1. Ce filtre est séparable, il ne nécessite donc que deux convolutions 1d. 
  2. Pour les gros noyaux, vous pouvez traiter une copie réduite de l’image et revenir en arrière.
  3. Une bonne approximation peut être faite par plusieurs filtres de boîte (également séparables), (peut être réglé le nombre d'itérations et la taille du noyau)
  4. Algorithme de complexité O(n) existant (quelle que soit la taille du noyau) pour une approximation de Gauss précise par filtre IIR.

Votre choix dépend de la vitesse, de la précision et de la complexité d’implémentation requises. 

0
minorlogic