Falsificação em miniatura

26

Como qualquer fotógrafo amador pode dizer, pós-processamento extremo é sempre bom. Uma dessas técnicas é chamada de " falsificação em miniatura ".

O objetivo é fazer com que uma imagem pareça uma fotografia de uma versão em miniatura de brinquedo em si. Isso funciona melhor para fotografias tiradas de um ângulo moderado / alto em relação ao solo, com uma baixa variação na altura do assunto, mas pode ser aplicado com eficácia variável a outras imagens.

O desafio: tire uma fotografia e aplique um algoritmo de falsificação em miniatura. Há muitas maneiras de fazer isso, mas, para os propósitos deste desafio, tudo se resume a:

  • Desfoque seletivo

    Alguma parte da imagem deve ficar embaçada para simular uma profundidade de campo rasa. Isso geralmente é feito ao longo de algum gradiente, linear ou moldado. Escolha o algoritmo de desfoque / gradiente desejado, mas entre 15% a 85% da imagem deve ter desfoque "perceptível".

  • Impulso de saturação

    Aumente a cor para fazer as coisas parecerem pintadas à mão. A saída deve ter um nível de saturação médio de> + 5% quando comparada à entrada. (usando saturação HSV )

  • Reforço de contraste

    Aumente o contraste para simular condições de iluminação mais severas (como a luz interna / de estúdio, em vez de o sol). A saída deve ter um contraste de> + 5% quando comparada à entrada. (usando o algoritmo RMS )

Essas três alterações devem ser implementadas e nenhuma outra melhoria / alteração é permitida. Sem cortes, nitidez, ajustes de balanço de branco, nada.

  • A entrada é uma imagem e pode ser lida a partir de um arquivo ou memória. Você pode usar bibliotecas externas para ler e gravar a imagem, mas não pode usá-las para processar a imagem. As funções fornecidas também não são permitidas para esse fim (você não pode simplesmente chamar, Image.blur()por exemplo)

  • Não há outra entrada. As forças, níveis, etc. do processamento devem ser determinados pelo programa, não por um ser humano.

  • A saída pode ser exibida ou salva como um arquivo em um formato de imagem padronizado (PNG, BMP, etc).

  • Tente generalizar. Não deve funcionar apenas em uma imagem, mas é compreensível que não funcione em todas as imagens. Algumas cenas simplesmente não respondem bem a essa técnica, por melhor que seja o algoritmo. Aplique o bom senso aqui, ao responder e votar nas respostas.

  • O comportamento é indefinido para entradas inválidas e para as imagens impossíveis de satisfazer as especificações. Por exemplo, uma imagem em escala de cinza não pode ser saturada (não há matiz de base), uma imagem em branco puro não pode ter maior contraste etc.

  • Inclua pelo menos duas imagens de saída na sua resposta:

    É necessário gerar uma das imagens nesta pasta da caixa de depósito . Há seis para escolher, mas tentei torná-los todos viáveis ​​em diferentes graus. Você pode ver exemplos de saídas para cada um na example-outputssubpasta. Observe que estas são imagens JPG de 10MP completas diretamente da câmera, para que você tenha muitos pixels para trabalhar.

    O outro pode ser qualquer imagem de sua escolha. Obviamente, tente escolher imagens que possam ser usadas livremente. Além disso, inclua a imagem original ou um link para comparação.


Por exemplo, nesta imagem:

original

Você pode produzir algo como:

processado

Para referência, o exemplo acima foi processado no GIMP com um desfoque gaussiano de gradiente angular em forma de caixa, saturação +80, contraste +20. (Não sei quais unidades o GIMP usa para elas)

Para obter mais inspiração ou para ter uma idéia melhor do que você está tentando alcançar, consulte este site ou este . Você também pode procurar miniature fakinge tilt shift photographyobter exemplos.


Este é um concurso de popularidade. Eleitores, vote nas entradas que você achar melhor, mantendo-se fiel ao objetivo.


Esclarecimento:

Esclarecendo quais funções são proibidas, não era minha intenção banir funções matemáticas . Minha intenção era proibir as funções de manipulação de imagens . Sim, existem algumas sobreposições, mas coisas como FFT, convoluções, matemática matricial etc. são úteis em muitas outras áreas. Você não deve usar uma função que simplesmente captura uma imagem e desfoca. Se você encontrar uma maneira matemática adequada de criar um desfoque, esse jogo justo.

Geobits
fonte
Esta demonstração notável demonstrations.wolfram.com/DigitalTiltShiftPhotography on Digital Tilt-Shift Image Processing, por Yu-Sung Chang, transmite uma riqueza de idéias sobre como ajustar o contraste, brilho e foco local (dentro de uma região oval ou retangular da foto ) utilizando funções internas de Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, e ImageAdjust.) Mesmo com a ajuda de tais funções de processamento de imagem de alto nível, o código ocupa 22 k. O código para a interface do usuário ainda é muito pequeno.
DavidC
5
Eu deveria ter dito "ocupa apenas 22 k". Há tanto código de bastidores encapsulado nas funções mencionadas acima que uma resposta bem-sucedida a esse desafio deve ser muito, muito difícil de ser alcançada na maioria dos idiomas, sem o uso de bibliotecas dedicadas de processamento de imagens.
DavidC
Atualização: foi realizada em 2,5 k caracteres, tornando-a ainda mais eficiente.
26414
1
@DavidCarraher É por isso que limitamos explicitamente as especificações. Não é difícil escrever algo para cobrir apenas as especificações, como minha implementação de referência abaixo mostra em 4,3 k caracteres de Java não destruído . Eu absolutamente não estou esperando resultados em nível de estúdio profissional. Obviamente, qualquer coisa que exceda as especificações (levando a melhores resultados) deve ser votada com entusiasmo, IMO. Concordo que este não é um desafio simples para se destacar , mas não era para ser. O esforço mínimo é básico, mas entradas "boas" serão necessariamente mais envolvidas.
Geobits
Outro algoritmo que pode ser combinado com estes para produzir "miniaturas" ainda mais convincentes é usar a decomposição de wavelets para filtrar pequenos recursos da imagem, mantendo nítidos os recursos maiores.
precisa saber é o seguinte

Respostas:

15

Java: Implementação de Referência

Aqui está uma implementação de referência básica em Java. Funciona melhor em fotos de alto ângulo e é terrivelmente ineficiente.

O desfoque é um desfoque de caixa muito básico, portanto passa pelos mesmos pixels muito mais do que o necessário. O contraste e saturação podem ser combinados em um único loop, mas a grande maioria do tempo gasto é borrada, para que não haja muito ganho com isso. Dito isto, ele funciona rapidamente em imagens com menos de 2MP. A imagem de 10MP levou algum tempo para ser concluída.

A qualidade do desfoque pode ser facilmente aprimorada usando basicamente qualquer coisa, menos um desfoque de caixa plana. Os algoritmos de contraste / saturação fazem seu trabalho, portanto, não há queixas reais por lá.

Não há inteligência real no programa. Ele usa fatores constantes para o desfoque, saturação e contraste. Eu brinquei com ele para encontrar configurações médias felizes. Como resultado, há são algumas cenas que não fazem muito bem. Por exemplo, ele bombeia tanto o contraste / saturação que as imagens com grandes áreas de cores semelhantes (think sky) se dividem em faixas coloridas.

É simples de usar; apenas passe o nome do arquivo como o único argumento. Ele gera em PNG, independentemente do arquivo de entrada.

Exemplos:

Na seleção da caixa de depósito:

Essas primeiras imagens são reduzidas para facilitar a postagem. Clique na imagem para ver em tamanho real.

Depois de:

insira a descrição da imagem aqui

Antes:

insira a descrição da imagem aqui

Seleção variada:

Depois de:

insira a descrição da imagem aqui

Antes:

insira a descrição da imagem aqui

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;} 
}
Geobits
fonte
12

C #

Em vez de fazer qualquer borrão iterativo da caixa, decidi percorrer todo o caminho e escrever um borrão gaussiano. As GetPixelchamadas realmente diminuem a velocidade ao usar kernels grandes, mas não vale a pena converter os métodos para uso, a LockBitsmenos que processemos algumas imagens maiores.

Alguns exemplos estão abaixo, que usam os parâmetros de ajuste padrão que eu defini (não brinquei muito com os parâmetros de ajuste, porque eles pareciam funcionar bem para a imagem de teste).

Para o caso de teste fornecido ...

1-Original 1-Modificado

Outro...

2-Original 2-Modificado

Outro...

3-Original 3-Modified

Os aumentos de saturação e contraste devem ser bastante diretos a partir do código. Eu faço isso no espaço HSL e o converto novamente em RGB.

O kernel Gaussiano 2D é gerado com base no tamanho nespecificado, com:

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

... e normalizado após a atribuição de todos os valores do kernel. Observe queA=sigma_x=sigma_y=1 .

Para descobrir onde aplicar o kernel, eu uso um peso de desfoque, calculado por:

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

... que fornece uma resposta decente, criando essencialmente uma elipse de valores que são protegidos do desfoque que desaparece gradualmente. Um filtro passa-banda combinado com outras equações (talvez uma variante de y=-x^2) poderia funcionar melhor aqui para determinadas imagens. Fui com o cosseno porque deu uma boa resposta para o caso base que testei.

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;
        }
    }
}
grovesNL
fonte
9

Java

Usa um rápido desfoque de caixa bidirecional com média de execução rápida para ser rápido o suficiente para executar vários passes, emulando um desfoque gaussiano. O desfoque é um gradiente elíptico em vez de bi-linear também.

Visualmente, funciona melhor em imagens grandes. Tem um tema mais escuro e sujo. Estou feliz com a forma como o desfoque resultou em imagens de tamanho apropriado, é bastante gradual e difícil de discernir onde "começa".

Todos os cálculos feitos em matrizes de números inteiros ou duplos (para HSV).

Espera o caminho do arquivo como argumento, gera o arquivo no mesmo local com o sufixo "miniaturized.png" Também exibe a entrada e a saída em um JFrame para visualização imediata.

(clique para ver versões grandes, elas são muito melhores)

Antes:

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

Depois de:

insira a descrição da imagem aqui

Talvez eu precise adicionar um mapeamento de tom mais inteligente ou preservação da luma, pois pode ficar bem escuro:

Antes:

insira a descrição da imagem aqui

Depois de:

insira a descrição da imagem aqui

Ainda interessante, porém, o coloca em uma atmosfera totalmente nova.

O 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);
    }
}
BurntPizza
fonte
8

J

Este foi um bom desafio. As configurações de desfoque, saturação e contraste são codificadas, mas podem ser facilmente alteradas, se desejado. No entanto, a área em foco é codificada como uma linha horizontal no centro. Não pode ser simplesmente modificado como as outras configurações. Foi escolhido porque a maioria das imagens de teste apresenta vistas de uma cidade.

Depois de realizar um desfoque gaussiano, dividi a imagem horizontalmente em 5 regiões. As regiões superior e inferior receberão 100% do desfoque. A região do meio receberá 0% do desfoque. As duas regiões restantes serão dimensionadas proporcionalmente ao cubo inverso de 0% a 100%.

O código deve ser usado como um script em J e esse script estará na mesma pasta em input.bmpque será a imagem de entrada. Ele criará output.bmpuma miniatura falsa da entrada.

O desempenho é bom e, no meu PC, usando um i7-4770k, leva cerca de 20 segundos para processar uma imagem do conjunto do OP. Anteriormente, eram necessários cerca de 70 segundos para processar uma imagem usando convolução padrão com o ;._3operador de subarray. O desempenho foi aprimorado usando a FFT para realizar a convolução.

Loop Loop-Mini Cidade City-Mini

Este código exige que o bmpe math/fftwcomplementos para ser instalado. Você pode instalá-los usando install 'bmp'e install 'math/fftw'. Seu sistema também pode precisar da instalação da fftwbiblioteca.

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 ''
milhas
fonte