Implementación de desenfoque gaussiano más rápida

8 minutos de lectura

¿Cómo se implementa lo más rápido posible? desenfoque gaussiano ¿algoritmo?

Voy a implementarlo en Java, así que GPU Se descartan soluciones. Mi aplicación, planetaGénesises multiplataforma, así que no quiero JNI.

avatar de usuario
Dima

Debe utilizar el hecho de que un núcleo gaussiano es separable, es decir, puede expresar una convolución 2D como una combinación de dos convoluciones 1D.

Si el filtro es grande, también puede tener sentido usar el hecho de que la convolución en el dominio espacial es equivalente a la multiplicación en el dominio de frecuencia (Fourier). Esto significa que puede tomar la transformada de Fourier de la imagen y el filtro, multiplicar los resultados (complejos) y luego tomar la transformada inversa de Fourier. La complejidad de la FFT (Fast Fourier Transform) es O(n log n), mientras que la complejidad de una convolución es O(n^2). Además, si necesita desenfocar muchas imágenes con el mismo filtro, solo necesitará tomar la FFT del filtro una vez.

Si decide utilizar una FFT, la biblioteca FFTW es una buena elección.

  • También tenga en cuenta que el conjunto de funciones gaussianas está cerrado bajo las transformadas de Fourier: tomar la transformada de Fourier de una gaussiana solo le dará una gaussiana diferente.

    -Dietrich Epp

    15 de septiembre de 2012 a las 12:58

Es probable que los deportistas matemáticos sepan esto, pero para cualquier otra persona…

Debido a una buena propiedad matemática de Gaussian, puede desenfocar una imagen 2D rápidamente ejecutando primero un desenfoque gaussiano 1D en cada fila de la imagen, luego ejecute un desenfoque 1D en cada columna.

  • Gracias por traducir “Debes usar el hecho de que un núcleo gaussiano es separable, es decir, puedes expresar una convolución 2D como una combinación de dos convoluciones 1D”. (Dima)

    – Josías Yoder

    26 de agosto de 2015 a las 14:31

  • @JosiahYoder En caso de que no lo haya notado, esta respuesta se publicó más de 12 horas antes de la respuesta que supuestamente “traduce”.

    – EvilTak

    18 de febrero de 2018 a las 16:32

SOLUCIÓN DEFINITIVA

Estaba muy confundido por tanta información e implementaciones, no sabía en cuál debería confiar. Después de que lo descubrí, decidí escribir mi propio artículo. Espero que te ahorre horas de tiempo.

Desenfoque gaussiano más rápido (en tiempo lineal)

Contiene el código fuente, que (espero) sea corto, limpio y fácilmente reescribible a cualquier otro idioma. Por favor vota para que otras personas puedan verlo.

  • Hice una versión RGBA de su código para comparar la velocidad y la calidad con StackBlur. Aquí está el código: pastebin.com/mS0fNYFF – Pero debo decir que StackBlur es aún más rápido y maneja las condiciones de los bordes de una mejor manera (no estoy seguro de si falta algo, pero estoy viendo un derrame en su código)

    – Cuasimondo

    21 de noviembre de 2013 a las 17:45


  • ¿Qué quieres decir con StackBlur? Si te refieres al algoritmo “acumulador”, lo estoy usando en el algoritmo 4.

    – Iván Kuckir

    21 de noviembre de 2013 a las 20:06

  • StackBlur es un algoritmo de desenfoque cuasi-gaussiano que, al menos que yo sepa, es uno de los algoritmos de desenfoque sin caja más rápidos. El resultado de una pasada está entre un desenfoque de cuadro y un gaussiano y el resultado debería ser lo suficientemente bueno si lo necesita más para efectos visuales que para el análisis científico de imágenes.

    – Cuasimondo

    22 de noviembre de 2013 a las 10:03

  • @IvanKuckir No puedo hacerlo funcionar. ¿Podría proporcionar un ejemplo sobre cómo llamar a su método en una página HTML? (Muy necesario)

    – Kamrán

    28 mayo 2016 a las 20:38


  • @IvanKuckir ¿Podría echar un vistazo a esto: el desenfoque gaussiano más rápido no funciona?

    – Kamrán

    28 mayo 2016 a las 23:01

avatar de usuario
sid datta

  1. encontré Quasimondo: Incubadora: Procesamiento: Desenfoque gaussiano rápido. Este método contiene muchas aproximaciones, como el uso de números enteros y tablas de búsqueda en lugar de flotadores y divisiones de punto flotante. No sé cuánta aceleración hay en el código Java moderno.

  2. Sombras rápidas en rectángulos tiene un algoritmo de aproximación usando B-splines.

  3. Algoritmo de desenfoque gaussiano rápido en C# afirma tener algunas optimizaciones geniales.

  4. También, Desenfoque gaussiano rápido (PDF) de David Everly tiene un método rápido para el procesamiento de desenfoque gaussiano.

Probaría los diversos métodos, los compararía y publicaría los resultados aquí.

Para mis propósitos, he copiado e implementado el método básico (procesar el eje XY de forma independiente) y el método de David Everly Desenfoque gaussiano rápido método de Internet. Difieren en los parámetros, por lo que no pude compararlos directamente. Sin embargo, este último pasa por un número mucho menor de iteraciones para un radio de desenfoque grande. Además, este último es un algoritmo aproximado.

avatar de usuario
steve hanov

Probablemente desee el desenfoque de cuadro, que es mucho más rápido. Ver este enlace por un gran tutorial y algunos copiar y pegar código C.

  • ¿Cómo se vincula entre el STD del Kernel gaussiano y la longitud del Box Blur?

    – Royi

    30 de abril de 2014 a las 0:00

Para radios de desenfoque más grandes, intente aplicar un caja de desenfoque tres veces. Esto se aproximará muy bien a un desenfoque gaussiano y será mucho más rápido que un verdadero desenfoque gaussiano.

  • ¿Cómo se vincula entre el STD del Kernel gaussiano y la longitud del Box Blur?

    – Royi

    30 de abril de 2014 a las 0:00

He convertido la implementación de Ivan Kuckir de un desenfoque gaussiano rápido que usa tres pases con desenfoques de cuadro lineal a java. El proceso resultante es O (n) como ha dicho en su propio blog. Si desea obtener más información sobre por qué el desenfoque de 3 cuadros de tiempo se aproxima al desenfoque gaussiano (3%), mi amigo, puede consultar caja de desenfoque y desenfoque gaussiano.

Aquí está la implementación de 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;
        }
    }
}

  • funciona muy bien, pero la imagen resultante es en blanco y negro, ¿cómo puedo hacer que sea de color, por favor?

    – Tamer Saleh

    24 de noviembre de 2021 a las 17:33

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad