Falsificación en miniatura


26

Como cualquier fotógrafo aficionado puede decirle, el postprocesamiento extremo siempre es bueno. Una de esas técnicas se llama " falsificación en miniatura ".

El objetivo es hacer que una imagen parezca una fotografía de una versión miniaturizada de juguete de sí misma. Esto funciona mejor para fotografías tomadas desde un ángulo moderado / alto al suelo, con una baja variación en la altura del sujeto, pero se puede aplicar con eficacia variable a otras imágenes.

El desafío: tomar una fotografía y aplicarle un algoritmo de simulación en miniatura. Hay muchas maneras de hacer esto, pero a los efectos de este desafío, se reduce a:

  • Desenfoque selectivo

    Alguna parte de la imagen debe ser borrosa para simular una profundidad de campo poco profunda. Esto generalmente se realiza a lo largo de algún gradiente, ya sea lineal o con forma. Elija el algoritmo de desenfoque / gradiente que desee, pero entre el 15 y el 85% de la imagen debe tener un desenfoque "notable".

  • Impulso de saturación

    Sube el color para que las cosas parezcan pintadas a mano. La salida debe tener un nivel de saturación promedio de> + 5% en comparación con la entrada. (usando saturación de HSV )

  • Aumento de contraste

    Aumente el contraste para simular condiciones de iluminación más severas (como se ve con una luz interior / de estudio en lugar de con el sol). La salida debe tener un contraste de> + 5% en comparación con la entrada. (usando el algoritmo RMS )

Esas tres alteraciones deben implementarse y no se permiten otras mejoras / alteraciones. Sin recorte, afilado, ajustes de balance de blancos, nada.

  • La entrada es una imagen y se puede leer desde un archivo o memoria. Puede usar bibliotecas externas para leer y escribir la imagen, pero no puede usarlas para procesar la imagen. Las funciones suministradas también están prohibidas para este propósito (no puede llamar, Image.blur()por ejemplo)

  • No hay otra entrada. Las fuerzas de procesamiento, los niveles, etc., deben ser determinados por el programa, no por un humano.

  • La salida se puede mostrar o guardar como un archivo en un formato de imagen estandarizado (PNG, BMP, etc.).

  • Intenta generalizar. No debería funcionar en una sola imagen, pero es comprensible que no funcione en todas las imágenes. Algunas escenas simplemente no responden bien a esta técnica, no importa cuán bueno sea el algoritmo. Aplica el sentido común aquí, tanto al responder como al votar por las respuestas.

  • El comportamiento no está definido para entradas no válidas y aquellas imágenes que son imposibles de satisfacer la especificación. Por ejemplo, una imagen en escala de grises no se puede saturar (no hay un tono base), una imagen en blanco puro no puede tener un mayor contraste, etc.

  • Incluya al menos dos imágenes de salida en su respuesta:

    Uno debe generarse a partir de una de las imágenes en esta carpeta de Dropbox . Hay seis para elegir, pero intenté hacerlos viables en distintos grados. Puede ver resultados de muestra para cada uno en la example-outputssubcarpeta. Tenga en cuenta que estas son imágenes JPG completas de 10MP directamente de la cámara, por lo que tiene muchos píxeles para trabajar.

    El otro puede ser cualquier imagen de su elección. Obviamente, trate de elegir imágenes que se puedan usar libremente. Además, incluya la imagen original o un enlace para compararla.


Por ejemplo, de esta imagen:

original

Puede generar algo como:

procesada

Como referencia, el ejemplo anterior se procesó en GIMP con un desenfoque gaussiano de gradiente angular en forma de caja, saturación +80, contraste +20. (No sé qué unidades usa GIMP para esos)

Para obtener más inspiración o para tener una mejor idea de lo que está tratando de lograr, consulte este sitio o este . También puede buscar miniature fakingy tilt shift photographyejemplos.


Este es un concurso de popularidad. Votantes, voten por las entradas que sientan mejor mientras se mantienen fieles al objetivo.


Aclaración:

Aclarando qué funciones están prohibidas, no era mi intención prohibir las funciones matemáticas . Era mi intención prohibir las funciones de manipulación de imágenes . Sí, hay cierta superposición allí, pero cosas como FFT, convoluciones, matemática matricial, etc., son útiles en muchas otras áreas. Usted debe no estar utilizando una función que simplemente toma una imagen y borrones. Si encuentras una forma matemática adecuada para crear un desenfoque, ese juego justo.


Esta notable demostración demostrations.wolfram.com/DigitalTiltShiftPhotography en Digital Tilt-Shift Image Processing, por Yu-Sung Chang, transmite una gran cantidad de ideas sobre cómo ajustar el contraste, el brillo y el enfoque local (dentro de una región ovalada o rectangular de la foto ) usando funciones incorporadas de Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, y ImageAdjust.) Incluso con la ayuda de las funciones de procesamiento de imágenes de alto nivel, el código lleva hasta 22 k. Sin embargo, el código para la interfaz de usuario es muy pequeño.
DavidC

55
Debería haber dicho "ocupa solo 22 k". Hay tanto código detrás de escena encapsulado en las funciones mencionadas anteriormente que una respuesta exitosa a este desafío debería resultar muy, muy difícil de lograr en la mayoría de los idiomas sin usar bibliotecas dedicadas de procesamiento de imágenes.
DavidC

Actualización: se realizó en 2.5 k caracteres, por lo que fue aún más eficiente.
DavidC

1
@DavidCarraher Por eso limité explícitamente la especificación. No es difícil escribir algo que solo cubra las especificaciones, como mi implementación de referencia a continuación se muestra en 4,3 k caracteres de Java sin golf . Absolutamente no espero resultados a nivel de estudio profesional. Por supuesto, cualquier cosa que exceda la especificación (que conduzca a mejores resultados) debe ser votado sinceramente, IMO. Estoy de acuerdo en que este no es un desafío simple para sobresalir , pero no estaba destinado a serlo. El esfuerzo mínimo es básico, pero las entradas "buenas" necesariamente estarán más involucradas.
Geobits

Otro algoritmo que se puede combinar con estos para producir "miniaturas" aún más convincentes es utilizar la descomposición de wavelet para filtrar pequeñas características de la imagen, mientras se mantienen nítidas las características más grandes.
AJMansfield

Respuestas:


15

Java: Implementación de referencia

Aquí hay una implementación de referencia básica en Java. Funciona mejor en tomas de alto ángulo, y es terriblemente ineficiente.

El desenfoque es un desenfoque de cuadro muy básico, por lo que recorre los mismos píxeles mucho más de lo necesario. El contraste y la saturación también podrían combinarse en un solo bucle, pero la gran mayoría del tiempo invertido es en desenfoque, por lo que no vería mucha ganancia de eso. Dicho esto, funciona bastante rápido en imágenes de menos de 2MP. La imagen de 10MP tardó un tiempo en completarse.

La calidad del desenfoque podría mejorarse fácilmente utilizando básicamente cualquier cosa menos un desenfoque de caja plana. Los algoritmos de contraste / saturación hacen su trabajo, por lo que no hay quejas reales allí.

No hay inteligencia real en el programa. Utiliza factores constantes para el desenfoque, la saturación y el contraste. Jugué con él para encontrar configuraciones medianas felices. Como resultado, no hay algunas escenas que no lo hacen muy bien. Por ejemplo, bombea tanto el contraste / saturación que las imágenes con grandes áreas de colores similares (piense en el cielo) se dividen en bandas de color.

Es simple de usar; simplemente pase el nombre del archivo como único argumento. Sale en PNG independientemente de cuál sea el archivo de entrada.

Ejemplos:

De la selección de dropbox:

Estas primeras imágenes se reducen para facilitar su publicación. Haga clic en la imagen para verla en tamaño completo.

Después:

ingrese la descripción de la imagen aquí

Antes de:

ingrese la descripción de la imagen aquí

Selección miscelánea:

Después:

ingrese la descripción de la imagen aquí

Antes de:

ingrese la descripción de la imagen aquí

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class MiniFake {

    int maxBlur;
    int maxDist;
    int width;
    int height;

    public static void main(String[] args) {
        if(args.length < 1) return;
        new MiniFake().run(args[0]);
    }

    void run(String filename){
        try{
            BufferedImage in = readImage(filename);
            BufferedImage out = blur(in);
            out = saturate(out, 0.8);
            out = contrast(out, 0.6);

            String[] tokens = filename.split("\\.");
            String outname = tokens[0];
            for(int i=1;i<tokens.length-1;i++)
                outname += "." + tokens[i];
            ImageIO.write(out, "png", new File(outname + "_post.png"));
            System.out.println("done");
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    BufferedImage contrast(BufferedImage in, double level){
        BufferedImage out = copyImage(in);
        long lumens=0;
        for(int x=0;x<width;x++)
            for(int y=0;y<height;y++){
                int color = out.getRGB(x,y);
                lumens += lumen(getR(color), getG(color), getB(color));
            }
        lumens /= (width * height);

        for(int x=0;x<width;x++)
            for(int y=0;y<height;y++){
                int color = out.getRGB(x,y);
                int r = getR(color);
                int g = getG(color);
                int b = getB(color);
                double ratio = ((double)lumen(r, g, b) / (double)lumens) - 1d;
                ratio *= (1+level) * 0.1;
                r += (int)(getR(color) * ratio+1);
                g += (int)(getG(color) * ratio+1);
                b += (int)(getB(color) * ratio+1);
                out.setRGB(x,y,getColor(clamp(r),clamp(g),clamp(b)));
            }   
        return out;
    }

    BufferedImage saturate(BufferedImage in, double level){
        BufferedImage out = copyImage(in);
        for(int x=0;x<width;x++)
            for(int y=0;y<height;y++){
                int color = out.getRGB(x,y);
                int r = getR(color);
                int g = getG(color);
                int b = getB(color);
                int brightness = Math.max(r, Math.max(g, b));
                int grey = (int)(Math.min(r, Math.min(g,b)) * level);
                if(brightness == grey)
                    continue;
                r -= grey;
                g -= grey;
                b -= grey;
                double ratio = brightness / (double)(brightness - grey);
                r = (int)(r * ratio);
                g = (int)(g * ratio);
                b = (int)(b * ratio);
                out.setRGB(x, y, getColor(clamp(r),clamp(g),clamp(b)));
            }
        return out;
    }


    BufferedImage blur(BufferedImage in){
        BufferedImage out = copyImage(in);
        int[] rgb = in.getRGB(0, 0, width, height, null, 0, width);
        for(int i=0;i<rgb.length;i++){
            double dist = Math.abs(getY(i)-(height/2));
            dist = dist * dist / maxDist;
            int r=0,g=0,b=0,p=0;
            for(int x=-maxBlur;x<=maxBlur;x++)
                for(int y=-maxBlur;y<=maxBlur;y++){
                    int xx = getX(i) + x;
                    int yy = getY(i) + y;
                    if(xx<0||xx>=width||yy<0||yy>=height)
                        continue;
                    int color = rgb[getPos(xx,yy)];
                    r += getR(color);
                    g += getG(color);
                    b += getB(color);
                    p++;
                }

            if(p>0){
                r /= p;
                g /= p;
                b /= p;
                int color = rgb[i];
                r = (int)((r*dist) + (getR(color) * (1 - dist)));
                g = (int)((g*dist) + (getG(color) * (1 - dist)));
                b = (int)((b*dist) + (getB(color) * (1 - dist)));
            } else {
                r = in.getRGB(getX(i), getY(i));
            }
            out.setRGB(getX(i), getY(i), getColor(r,g,b));
        }
        return out;
    }

    BufferedImage readImage(String filename) throws IOException{
         BufferedImage image = ImageIO.read(new File(filename));
         width = image.getWidth();
         height = image.getHeight();
         maxBlur = Math.max(width, height) / 100;
         maxDist =  (height/2)*(height/2);
         return image;
    }

    public BufferedImage copyImage(BufferedImage in){
        BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics g = out.getGraphics();
        g.drawImage(in, 0, 0, null);
        g.dispose();
        return out;
    }

    static int clamp(int c){return c<0?0:c>255?255:c;}
    static int getColor(int a, int r, int g, int b){return (a << 24) | (r << 16) | (g << 8) | (b);}
    static int getColor(int r, int g, int b){return getColor(0xFF, r, g, b);}
    static int getR(int color){return color >> 16 & 0xFF;}
    static int getG(int color){return color >> 8 & 0xFF;}
    static int getB(int color){return color & 0xFF;}
    static int lumen(int r, int g, int b){return (r*299)+(g*587)+(b*114);}
    int getX(int pos){return pos % width;}
    int getY(int pos){return pos / width;}
    int getPos(int x, int y){return y*width+x;} 
}

12

DO#

En lugar de hacer cuadros borrosos iterativos, decidí seguir todo el camino y escribir un desenfoque gaussiano. Las GetPixelllamadas realmente se ralentizan cuando se usan núcleos grandes, pero realmente no vale la pena convertir los métodos a usar a LockBitsmenos que procesemos algunas imágenes más grandes.

A continuación se muestran algunos ejemplos, que utilizan los parámetros de ajuste predeterminados que configuré (no jugué mucho con los parámetros de ajuste, porque parecían funcionar bien para la imagen de prueba).

Para el caso de prueba proporcionado ...

1-original 1-modificado

Otro...

2-original 2-modificado

Otro...

3-original 3-modificado

Los aumentos de saturación y contraste deben ser bastante sencillos del código. Hago esto en el espacio HSL y lo convierto de nuevo a RGB.

El núcleo Gaussiano 2D se genera en función del tamaño nespecificado, con:

EXP(-((x-x0)^2/2+(y-y0)^2/2)/2)

... y normalizado después de que se asignan todos los valores del núcleo. Tenga en cuenta que A=sigma_x=sigma_y=1.

Para averiguar dónde aplicar el núcleo, utilizo un peso borroso, calculado por:

SQRT([COS(PI*x_norm)^2 + COS(PI*y_norm)^2]/2)

... lo que da una respuesta decente, esencialmente creando una elipse de valores que están protegidos del desenfoque que gradualmente se desvanece más. Un filtro de paso de banda combinado con otras ecuaciones (quizás alguna variante de y=-x^2) podría funcionar mejor aquí para ciertas imágenes. Fui con el coseno porque dio una buena respuesta para el caso base que probé.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace FakeMini
{
    static class Program
    {
        static void Main()
        {
            // Some tuning variables
            double saturationValue = 1.7;
            double contrastValue = 1.2;
            int gaussianSize = 13; // Must be odd and >1 (3, 5, 7...)

            // NxN Gaussian kernel
            int padding = gaussianSize / 2;
            double[,] kernel = GenerateGaussianKernel(gaussianSize);

            Bitmap src = null;
            using (var img = new Bitmap(File.OpenRead("in.jpg")))
            {
                src = new Bitmap(img);
            }

            // Bordering could be avoided by reflecting or wrapping instead
            // Also takes advantage of the fact that a new bitmap returns zeros from GetPixel
            Bitmap border = new Bitmap(src.Width + padding*2, src.Height + padding*2);

            // Get average intensity of entire image
            double intensity = 0;
            for (int x = 0; x < src.Width; x++)
            {
                for (int y = 0; y < src.Height; y++)
                {
                    intensity += src.GetPixel(x, y).GetBrightness();
                }
            }
            double averageIntensity = intensity / (src.Width * src.Height);

            // Modify saturation and contrast
            double brightness;
            double saturation;
            for (int x = 0; x < src.Width; x++)
            {
                for (int y = 0; y < src.Height; y++)
                {
                    Color oldPx = src.GetPixel(x, y);
                    brightness = oldPx.GetBrightness();
                    saturation = oldPx.GetSaturation() * saturationValue;

                    Color newPx = FromHSL(
                                oldPx.GetHue(),
                                Clamp(saturation, 0.0, 1.0),
                                Clamp(averageIntensity - (averageIntensity - brightness) * contrastValue, 0.0, 1.0));
                    src.SetPixel(x, y, newPx);
                    border.SetPixel(x+padding, y+padding, newPx);
                }
            }

            // Apply gaussian blur, weighted by corresponding sine value based on height
            double blurWeight;
            Color oldColor;
            Color newColor;
            for (int x = padding; x < src.Width+padding; x++)
            {
                for (int y = padding; y < src.Height+padding; y++)
                {
                    oldColor = border.GetPixel(x, y);
                    newColor = Convolve2D(
                        kernel,
                        GetNeighbours(border, gaussianSize, x, y)
                       );

                    // sqrt([cos(pi*x_norm)^2 + cos(pi*y_norm)^2]/2) gives a decent response
                    blurWeight = Clamp(Math.Sqrt(
                        Math.Pow(Math.Cos(Math.PI * (y - padding) / src.Height), 2) +
                        Math.Pow(Math.Cos(Math.PI * (x - padding) / src.Width), 2)/2.0), 0.0, 1.0);
                    src.SetPixel(
                        x - padding,
                        y - padding,
                        Color.FromArgb(
                            Convert.ToInt32(Math.Round(oldColor.R * (1 - blurWeight) + newColor.R * blurWeight)),
                            Convert.ToInt32(Math.Round(oldColor.G * (1 - blurWeight) + newColor.G * blurWeight)),
                            Convert.ToInt32(Math.Round(oldColor.B * (1 - blurWeight) + newColor.B * blurWeight))
                            )
                        );
                }
            }
            border.Dispose();

            // Configure some save parameters
            EncoderParameters ep = new EncoderParameters(3);
            ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
            ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int)EncoderValue.ScanMethodInterlaced);
            ep.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int)EncoderValue.RenderProgressive);
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
            ImageCodecInfo ici = null;
            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.MimeType == "image/jpeg")
                    ici = codec;
            }
            src.Save("out.jpg", ici, ep);
            src.Dispose();
        }

        // Create RGB from HSL
        // (C# BCL allows me to go one way but not the other...)
        private static Color FromHSL(double h, double s, double l)
        {
            int h0 = Convert.ToInt32(Math.Floor(h / 60.0));
            double c = (1.0 - Math.Abs(2.0 * l - 1.0)) * s;
            double x = (1.0 - Math.Abs((h / 60.0) % 2.0 - 1.0)) * c;
            double m = l - c / 2.0;
            int m0 = Convert.ToInt32(255 * m);
            int c0 = Convert.ToInt32(255*(c + m));
            int x0 = Convert.ToInt32(255*(x + m));
            switch (h0)
            {
                case 0:
                    return Color.FromArgb(255, c0, x0, m0);
                case 1:
                    return Color.FromArgb(255, x0, c0, m0);
                case 2:
                    return Color.FromArgb(255, m0, c0, x0);
                case 3:
                    return Color.FromArgb(255, m0, x0, c0);
                case 4:
                    return Color.FromArgb(255, x0, m0, c0);
                case 5:
                    return Color.FromArgb(255, c0, m0, x0);
            }
            return Color.FromArgb(255, m0, m0, m0);
        }

        // Just so I don't have to write "bool ? val : val" everywhere
        private static double Clamp(double val, double min, double max)
        {
            if (val >= max)
                return max;
            else if (val <= min)
                return min;
            else
                return val;
        }

        // Simple convolution as C# BCL doesn't appear to have any
        private static Color Convolve2D(double[,] k, Color[,] n)
        {
            double r = 0;
            double g = 0;
            double b = 0;
            for (int i=0; i<k.GetLength(0); i++)
            {
                for (int j=0; j<k.GetLength(1); j++)
                {
                    r += n[i,j].R * k[i,j];
                    g += n[i,j].G * k[i,j];
                    b += n[i,j].B * k[i,j];
                }
            }
            return Color.FromArgb(
                Convert.ToInt32(Math.Round(r)),
                Convert.ToInt32(Math.Round(g)),
                Convert.ToInt32(Math.Round(b)));
        }

        // Generates a simple 2D square (normalized) Gaussian kernel based on a size
        // No tuning parameters - just using sigma = 1 for each
        private static double [,] GenerateGaussianKernel(int n)
        {
            double[,] kernel = new double[n, n];
            double currentValue;
            double normTotal = 0;
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    currentValue = Math.Exp(-(Math.Pow(i - n / 2, 2) + Math.Pow(j - n / 2, 2)) / 2.0);
                    kernel[i, j] = currentValue;
                    normTotal += currentValue;
                }
            }
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    kernel[i, j] /= normTotal;
                }
            }
            return kernel;
        }

        // Gets the neighbours around the current pixel
        private static Color[,] GetNeighbours(Bitmap bmp, int n, int x, int y)
        {
            Color[,] neighbours = new Color[n, n];
            for (int i = -n/2; i < n-n/2; i++)
            {
                for (int j = -n/2; j < n-n/2; j++)
                {
                    neighbours[i+n/2, j+n/2] = bmp.GetPixel(x + i, y + j);
                }
            }
            return neighbours;
        }
    }
}

9

Java

Utiliza un desenfoque de caja bidireccional de promedio rápido para ser lo suficientemente rápido como para ejecutar múltiples pases, emulando un desenfoque gaussiano. El desenfoque es un gradiente elíptico en lugar de bi-lineal también.

Visualmente, funciona mejor en imágenes grandes. Tiene un tema más oscuro y sucio. Estoy contento con cómo resultó el desenfoque en imágenes de tamaño apropiado, es bastante gradual y difícil de discernir dónde "comienza".

Todos los cálculos realizados en matrices de enteros o dobles (para HSV).

Espera la ruta del archivo como argumento, envía el archivo a la misma ubicación con el sufijo "miniaturized.png" También muestra la entrada y la salida en un JFrame para su visualización inmediata.

(haga clic para ver versiones grandes, son mucho mejores)

Antes de:

http://i.imgur.com/cOPl6EOl.jpg

Después:

ingrese la descripción de la imagen aquí

Puede que tenga que agregar un mapeo de tonos más inteligente o preservación de luma, ya que puede oscurecer bastante:

Antes de:

ingrese la descripción de la imagen aquí

Después:

ingrese la descripción de la imagen aquí

Sin embargo, sigue siendo interesante, lo pone en una atmósfera completamente nueva.

El código:

import java.awt.*;
import java.awt.image.*;
import java.io.*;

import javax.imageio.*;
import javax.swing.*;

public class SceneMinifier {

    static final double CONTRAST_INCREASE = 8;
    static final double SATURATION_INCREASE = 7;

    public static void main(String[] args) throws IOException {

        if (args.length < 1) {
            System.out.println("Please specify an input image file.");
            return;
        }

        BufferedImage temp = ImageIO.read(new File(args[0]));

        BufferedImage input = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_INT_ARGB);
        input.getGraphics().drawImage(temp, 0, 0, null); // just want to guarantee TYPE_ARGB

        int[] pixels = ((DataBufferInt) input.getData().getDataBuffer()).getData();

        // saturation

        double[][] hsv = toHSV(pixels);
        for (int i = 0; i < hsv[1].length; i++)
            hsv[1][i] = Math.min(1, hsv[1][i] * (1 + SATURATION_INCREASE / 10));

        // contrast

        int[][] rgb = toRGB(hsv[0], hsv[1], hsv[2]);

        double c = (100 + CONTRAST_INCREASE) / 100;
        c *= c;

        for (int i = 0; i < pixels.length; i++)
            for (int q = 0; q < 3; q++)
                rgb[q][i] = (int) Math.max(0, Math.min(255, ((rgb[q][i] / 255. - .5) * c + .5) * 255));

        // blur

        int w = input.getWidth();
        int h = input.getHeight();

        int k = 5;
        int kd = 2 * k + 1;
        double dd = 1 / Math.hypot(w / 2, h / 2);

        for (int reps = 0; reps < 5; reps++) {

            int tmp[][] = new int[3][pixels.length];
            int vmin[] = new int[Math.max(w, h)];
            int vmax[] = new int[Math.max(w, h)];

            for (int y = 0, yw = 0, yi = 0; y < h; y++) {
                int[] sum = new int[3];
                for (int i = -k; i <= k; i++) {
                    int ii = yi + Math.min(w - 1, Math.max(i, 0));
                    for (int q = 0; q < 3; q++)
                        sum[q] += rgb[q][ii];
                }
                for (int x = 0; x < w; x++) {

                    int dx = x - w / 2;
                    int dy = y - h / 2;
                    double dist = Math.sqrt(dx * dx + dy * dy) * dd;
                    dist *= dist;

                    for (int q = 0; q < 3; q++)
                        tmp[q][yi] = (int) Math.min(255, sum[q] / kd * dist + rgb[q][yi] * (1 - dist));

                    if (y == 0) {
                        vmin[x] = Math.min(x + k + 1, w - 1);
                        vmax[x] = Math.max(x - k, 0);
                    }

                    int p1 = yw + vmin[x];
                    int p2 = yw + vmax[x];

                    for (int q = 0; q < 3; q++)
                        sum[q] += rgb[q][p1] - rgb[q][p2];
                    yi++;
                }
                yw += w;
            }

            for (int x = 0, yi = 0; x < w; x++) {
                int[] sum = new int[3];
                int yp = -k * w;
                for (int i = -k; i <= k; i++) {
                    yi = Math.max(0, yp) + x;
                    for (int q = 0; q < 3; q++)
                        sum[q] += tmp[q][yi];
                    yp += w;
                }
                yi = x;
                for (int y = 0; y < h; y++) {

                    int dx = x - w / 2;
                    int dy = y - h / 2;
                    double dist = Math.sqrt(dx * dx + dy * dy) * dd;
                    dist *= dist;

                    for (int q = 0; q < 3; q++)
                        rgb[q][yi] = (int) Math.min(255, sum[q] / kd * dist + tmp[q][yi] * (1 - dist));

                    if (x == 0) {
                        vmin[y] = Math.min(y + k + 1, h - 1) * w;
                        vmax[y] = Math.max(y - k, 0) * w;
                    }
                    int p1 = x + vmin[y];
                    int p2 = x + vmax[y];

                    for (int q = 0; q < 3; q++)
                        sum[q] += tmp[q][p1] - tmp[q][p2];

                    yi += w;
                }
            }
        }

        // pseudo-lighting pass

        for (int i = 0; i < pixels.length; i++) {
            int dx = i % w - w / 2;
            int dy = i / w - h / 2;
            double dist = Math.sqrt(dx * dx + dy * dy) * dd;
            dist *= dist;

            for (int q = 0; q < 3; q++) {
                if (dist > 1 - .375)
                    rgb[q][i] *= 1 + (Math.sqrt((1 - dist + .125) / 2) - (1 - dist) - .125) * .7;
                if (dist < .375 || dist > .375)
                    rgb[q][i] *= 1 + (Math.sqrt((dist + .125) / 2) - dist - .125) * dist > .375 ? 1 : .8;
                rgb[q][i] = Math.min(255, Math.max(0, rgb[q][i]));
            }
        }

        // reassemble image

        BufferedImage output = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB);

        pixels = ((DataBufferInt) output.getData().getDataBuffer()).getData();

        for (int i = 0; i < pixels.length; i++)
            pixels[i] = 255 << 24 | rgb[0][i] << 16 | rgb[1][i] << 8 | rgb[2][i];

        output.setRGB(0, 0, output.getWidth(), output.getHeight(), pixels, 0, output.getWidth());

        // display results

        display(input, output);

        // output image

        ImageIO.write(output, "PNG", new File(args[0].substring(0, args[0].lastIndexOf('.')) + " miniaturized.png"));

    }

    private static int[][] toRGB(double[] h, double[] s, double[] v) {
        int[] r = new int[h.length];
        int[] g = new int[h.length];
        int[] b = new int[h.length];

        for (int i = 0; i < h.length; i++) {
            double C = v[i] * s[i];
            double H = h[i];
            double X = C * (1 - Math.abs(H % 2 - 1));

            double ri = 0, gi = 0, bi = 0;

            if (0 <= H && H < 1) {
                ri = C;
                gi = X;
            } else if (1 <= H && H < 2) {
                ri = X;
                gi = C;
            } else if (2 <= H && H < 3) {
                gi = C;
                bi = X;
            } else if (3 <= H && H < 4) {
                gi = X;
                bi = C;
            } else if (4 <= H && H < 5) {
                ri = X;
                bi = C;
            } else if (5 <= H && H < 6) {
                ri = C;
                bi = X;
            }

            double m = v[i] - C;

            r[i] = (int) ((ri + m) * 255);
            g[i] = (int) ((gi + m) * 255);
            b[i] = (int) ((bi + m) * 255);
        }

        return new int[][] { r, g, b };
    }

    private static double[][] toHSV(int[] c) {
        double[] h = new double[c.length];
        double[] s = new double[c.length];
        double[] v = new double[c.length];

        for (int i = 0; i < c.length; i++) {
            double r = (c[i] & 0xFF0000) >> 16;
            double g = (c[i] & 0xFF00) >> 8;
            double b = c[i] & 0xFF;

            r /= 255;
            g /= 255;
            b /= 255;

            double M = Math.max(Math.max(r, g), b);
            double m = Math.min(Math.min(r, g), b);
            double C = M - m;

            double H = 0;

            if (C == 0)
                H = 0;
            else if (M == r)
                H = (g - b) / C % 6;
            else if (M == g)
                H = (b - r) / C + 2;
            else if (M == b)
                H = (r - g) / C + 4;

            h[i] = H;
            s[i] = C / M;
            v[i] = M;
        }
        return new double[][] { h, s, v };
    }

    private static void display(final BufferedImage original, final BufferedImage output) {

        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int wt = original.getWidth();
        int ht = original.getHeight();
        double ratio = (double) wt / ht;
        if (ratio > 1 && wt > d.width / 2) {
            wt = d.width / 2;
            ht = (int) (wt / ratio);
        }
        if (ratio < 1 && ht > d.getHeight() / 2) {
            ht = d.height / 2;
            wt = (int) (ht * ratio);
        }

        final int w = wt, h = ht;

        JFrame frame = new JFrame();
        JPanel pan = new JPanel() {
            BufferedImage buffer = new BufferedImage(w * 2, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D gg = buffer.createGraphics();

            {
                gg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            }

            @Override
            public void paint(Graphics g) {
                gg.drawImage(original, 0, 0, w, h, null);
                gg.drawImage(output, w, 0, w, h, null);
                g.drawImage(buffer, 0, 0, null);
            }
        };
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pan.setPreferredSize(new Dimension(w * 2, h));
        frame.setLayout(new BorderLayout());
        frame.add(pan, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }
}

8

J

Este fue un buen desafío. La configuración de desenfoque, saturación y contraste está codificada pero puede cambiarse fácilmente si lo desea. Sin embargo, el área en foco está codificada como una línea horizontal en el centro. No se puede modificar simplemente como las otras configuraciones. Fue elegido ya que la mayoría de las imágenes de prueba presentan vistas de una ciudad.

Después de realizar un desenfoque gaussiano, dividí la imagen horizontalmente en 5 regiones. Las regiones superior e inferior recibirán el 100% del desenfoque. La región central recibirá el 0% del desenfoque. Las dos regiones restantes se escalarán proporcionalmente al cubo inverso del 0% al 100%.

El código se utilizará como un script en J y ese script estará en la misma carpeta input.bmpque será la imagen de entrada. Creará output.bmpcuál será una miniatura falsa de la entrada.

El rendimiento es bueno y en mi PC con un i7-4770k, tarda unos 20 segundos en procesar una imagen desde el conjunto del OP. Anteriormente, se tardaban unos 70 segundos en procesar una imagen utilizando una convolución estándar con el ;._3operador de submatriz. El rendimiento se mejoró mediante el uso de FFT para realizar convolución.

Lazo Loop-Mini Ciudad City-Mini

Este código requiere que se instalen los complementos bmpy math/fftw. Puede instalarlos usando install 'bmp'y install 'math/fftw'. Su sistema también puede necesitar la fftwbiblioteca para ser instalada.

load 'bmp math/fftw'

NB. Define 2d FFT
fft2d =: 4 : 0
  s =. $ y
  i =. zzero_jfftw_ + , y
  o =. 0 * i
  p =. createplan_jfftw_ s;i;o;x;FFTW_ESTIMATE_jfftw_
  fftwexecute_jfftw_ p
  destroyplan_jfftw_ p
  r =. s $ o
  if. x = FFTW_BACKWARD_jfftw_ do.
    r =. r % */ s
  end.
  r
)

fft2 =: (FFTW_FORWARD_jfftw_ & fft2d) :. (FFTW_BACKWARD_jfftw & fft2d)
ifft2 =: (FFTW_BACKWARD_jfftw_ & fft2d) :. (FFTW_FORWARD_jfftw & fft2d)

NB. Settings: Blur radius - Saturation - Contrast
br =: 15
s =: 3
c =: 1.5

NB. Read image and extract rgb channels
i =: 255 %~ 256 | (readbmp 'input.bmp') <.@%"_ 0 ] 2 ^ 16 8 0
'h w' =: }. $ i

NB. Pad each channel to fit Gaussian blur kernel
'hp wp' =: (+: br) + }. $ i
ip =: (hp {. wp {."1 ])"_1 i

NB. Gaussian matrix verb
gm =: 3 : '(%+/@,)s%~2p1%~^(s=.*:-:y)%~-:-+&*:/~i:y'

NB. Create a Gaussian blur kernel
gk =: fft2 hp {. wp {."1 gm br

NB. Blur each channel using FFT-based convolution and unpad
ib =: (9 o. (-br) }. (-br) }."1 br }. br }."1 [: ifft2 gk * fft2)"_1 ip

NB. Create the blur gradient to emulate tilt-shift
m =: |: (w , h) $ h ({. >. (({.~ -)~ |.)) , 1 ,: |. (%~i.) 0.2 I.~ (%~i.) h

NB. Tilt-shift each channel
it =: i ((m * ]) + (-. m) * [)"_1 ib

NB. Create the saturation matrix
sm =: |: ((+ ] * [: =@i. 3:)~ 3 3 |:@$ 0.299 0.587 0.114 * -.) s

NB. Saturate and clamp
its =: 0 >. 1 <. sm +/ .*"1 _ it

NB. Contrast and clamp
itsc =: 0 >. 1 <. 0.5 + c * its - 0.5

NB. Output the image
'output.bmp' writebmp~ (2 <.@^ 16 8 0) +/ .* 255 <.@* itsc

exit ''
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.