Falsificazione in miniatura


26

Come ogni fotografo amatoriale può dirti, la post-elaborazione estrema è sempre buona. Una di queste tecniche si chiama " fingersi in miniatura ".

L'obiettivo è quello di far apparire un'immagine come una fotografia di una versione miniaturizzata e giocattolo di se stessa. Funziona meglio per le fotografie scattate da un angolo moderato / alto rispetto al suolo, con una bassa variazione dell'altezza del soggetto, ma può essere applicato con efficacia variabile ad altre immagini.

La sfida: scatta una foto e applica un algoritmo di simulazione in miniatura. Esistono molti modi per farlo, ma ai fini di questa sfida, si riduce a:

  • Sfocatura selettiva

    Alcune parti dell'immagine devono essere sfocate per simulare una profondità di campo. Questo di solito viene fatto lungo un certo gradiente, lineare o sagomato. Scegli l'algoritmo di sfocatura / sfumatura che preferisci, ma tra il 15-85% dell'immagine deve avere una sfocatura "evidente".

  • Aumento della saturazione

    Aumenta il colore per far apparire le cose che sono state dipinte a mano. L'output deve avere un livello di saturazione medio> + 5% rispetto all'input. (utilizzando la saturazione HSV )

  • Incremento del contrasto

    Aumenta il contrasto per simulare condizioni di illuminazione più rigide (come vedi con una luce interna / da studio anziché con il sole). L'output deve avere un contrasto di> + 5% rispetto all'input. (usando l' algoritmo RMS )

Queste tre modifiche devono essere implementate e non sono consentiti altri miglioramenti / modifiche. Nessun ritaglio, nitidezza, regolazioni del bilanciamento del bianco, niente.

  • L'input è un'immagine e può essere letto da un file o da una memoria. È possibile utilizzare librerie esterne per leggere e scrivere l'immagine, ma non è possibile utilizzarle per elaborare l'immagine. Le funzioni fornite non sono consentite per questo scopo (non si può semplicemente chiamare Image.blur()per esempio)

  • Non ci sono altri input. I punti di forza, i livelli, ecc. Di elaborazione devono essere determinati dal programma, non da un essere umano.

  • L'output può essere visualizzato o salvato come file in un formato immagine standardizzato (PNG, BMP, ecc.).

  • Prova a generalizzare. Non dovrebbe funzionare su una sola immagine, ma è comprensibile che non funzionerà su tutte le immagini. Alcune scene semplicemente non rispondono bene a questa tecnica, non importa quanto sia buono l'algoritmo. Applicare il buon senso qui, sia quando si risponde e si vota sulle risposte.

  • Il comportamento non è definito per input non validi e per quelle immagini che sono impossibili da soddisfare le specifiche. Ad esempio, un'immagine in scala di grigi non può essere saturata (non esiste una tonalità di base), un'immagine bianca pura non può avere un contrasto maggiore, ecc.

  • Includi almeno due immagini di output nella tua risposta:

    Uno deve essere generato da una delle immagini in questa cartella dropbox . Ce ne sono sei tra cui scegliere, ma ho cercato di renderli tutti fattibili a vari livelli. Puoi vedere gli output di esempio per ciascuno nella example-outputssottocartella. Si prega di notare che queste sono immagini JPG da 10 MP complete direttamente dalla fotocamera, quindi hai molti pixel su cui lavorare.

    L'altra può essere qualsiasi immagine di tua scelta. Ovviamente, prova a scegliere immagini che sono liberamente utilizzabili. Inoltre, includere l'immagine originale o un collegamento ad essa per il confronto.


Ad esempio, da questa immagine:

originale

Potresti ottenere qualcosa del tipo:

trasformati

Per riferimento, l'esempio sopra è stato elaborato in GIMP con una sfocatura gaussiana a gradiente angolare a forma di scatola, saturazione +80, contrasto +20. (Non so quali unità GIMP utilizza per quelle)

Per ulteriori ispirazioni o per avere un'idea migliore di ciò che stai cercando di ottenere, consulta questo sito o questo . Puoi anche cercare miniature fakinge tilt shift photographyper esempi.


Questo è un concorso di popolarità. Votanti, votate le voci che ritenete siano le migliori, pur rimanendo fedeli all'obiettivo.


Una precisazione:

Chiarire quali funzioni sono vietate, non era mia intenzione vietare le funzioni matematiche . Avevo intenzione di vietare le funzioni di manipolazione delle immagini . Sì, c'è qualche sovrapposizione lì, ma cose come FFT, convoluzioni, matematica matriciale, ecc., Sono utili in molte altre aree. Si dovrebbe non utilizzare una funzione che prende semplicemente un'immagine e sfocature. Se trovi un modo opportunamente matematico per creare una sfocatura, quel gioco equo.


Questa straordinaria dimostrazione dimostrations.wolfram.com/DigitalTiltShiftPhotography su Digital Tilt-Shift Image Processing, di Yu-Sung Chang, trasmette molte idee su come regolare il contrasto, la luminosità e la messa a fuoco locale (all'interno di una regione ovale o rettangolare della foto ) utilizzando funzioni integrate di Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, e ImageAdjust.) Anche con l'aiuto di tali funzioni di elaborazione delle immagini di alto livello, il codice riprende 22 k. Il codice per l'interfaccia utente è comunque molto piccolo.
DavidC,

5
Avrei dovuto dire "occupa solo 22 k". C'è così tanto codice dietro le quinte incapsulato nelle funzioni sopra menzionate che una risposta di successo a questa sfida dovrebbe rivelarsi molto, molto difficile da ottenere nella maggior parte delle lingue senza usare librerie di elaborazione delle immagini dedicate.
DavidC,

Aggiornamento: è stato fatto in 2,5 k caratteri quindi era ancora più efficiente.
DavidC,

1
@DavidCarraher Ecco perché ho esplicitamente limitato le specifiche. Non è difficile scrivere qualcosa per coprire solo le specifiche, come la mia implementazione di riferimento di seguito mostra in 4,3 k caratteri di Java non golfato . Non mi aspetto assolutamente risultati a livello di studio professionale. Ovviamente, qualsiasi cosa che superi le specifiche (portando a risultati migliori) dovrebbe essere valorizzata di cuore, IMO. Concordo sul fatto che questa non è una sfida semplice da eccellere , ma non doveva essere. Lo sforzo minimo è di base, ma le voci "buone" saranno necessariamente più coinvolte.
Geobits il

Un altro algoritmo che può essere combinato con questi per produrre "miniature" ancora più convincenti è quello di utilizzare la decomposizione wavelet per filtrare piccole funzionalità dall'immagine, mantenendo nitide le funzionalità più grandi.
AJMansfield,

Risposte:


15

Java: implementazione di riferimento

Ecco un'implementazione di riferimento di base in Java. Funziona meglio con i colpi ad alto angolo ed è terribilmente inefficiente.

La sfocatura è una sfocatura di base molto semplice, quindi circola sugli stessi pixel molto più del necessario. Il contrasto e la saturazione potrebbero anche essere combinati in un singolo loop, ma la stragrande maggioranza del tempo trascorso è sfocata, quindi non vedrebbe molto guadagno da quello. Detto questo, funziona abbastanza rapidamente su immagini con meno di 2 MP circa. L'immagine da 10 MP ha impiegato del tempo per essere completata.

La qualità della sfocatura potrebbe essere facilmente migliorata usando praticamente tutto tranne una sfocatura a scatola piatta. Gli algoritmi di contrasto / saturazione fanno il loro lavoro, quindi nessuna vera lamentela lì.

Non c'è vera intelligenza nel programma. Utilizza fattori costanti per la sfocatura, la saturazione e il contrasto. Ci ho giocato attorno per trovare contesti medi felici. Di conseguenza, ci sono alcune scene che non vanno molto bene. Ad esempio, pompa il contrasto / saturazione così tanto che le immagini con ampie aree di colore simile (pensa al cielo) si dividono in bande di colore.

È semplice da usare; passa il nome del file come unico argomento. Emette in PNG indipendentemente da quale fosse il file di input.

Esempi:

Dalla selezione della casella personale:

Queste prime immagini sono ridimensionate per facilitare la pubblicazione. Clicca sull'immagine per vederla a dimensione originale.

Dopo:

inserisci qui la descrizione dell'immagine

Prima:

inserisci qui la descrizione dell'immagine

Selezione varia:

Dopo:

inserisci qui la descrizione dell'immagine

Prima:

inserisci qui la descrizione dell'immagine

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

C #

Invece di fare delle sfocature casuali iterative, ho deciso di percorrere tutta la strada e scrivere una sfocatura gaussiana. Le GetPixelchiamate rallentano davvero quando si usano kernel di grandi dimensioni, ma non vale davvero la pena convertire i metodi da utilizzare a LockBitsmeno che non si stiano elaborando immagini più grandi.

Di seguito sono riportati alcuni esempi, che utilizzano i parametri di ottimizzazione predefiniti impostati (non ho giocato molto con i parametri di ottimizzazione, perché sembravano funzionare bene per l'immagine di prova).

Per il caso di prova fornito ...

1-originale 1-Modified

Un altro...

2-Original 2-Modified

Un altro...

3-Original 3-Modified

Gli aumenti di saturazione e contrasto dovrebbero essere abbastanza semplici dal codice. Lo faccio nello spazio HSL e lo converto in RGB.

Il kernel gaussiano 2D viene generato in base alla dimensione nspecificata, con:

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

... e normalizzato dopo l'assegnazione di tutti i valori del kernel. Si noti che A=sigma_x=sigma_y=1.

Per capire dove applicare il kernel, utilizzo un peso sfocato, calcolato da:

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

... che dà una risposta decente, essenzialmente creando un'ellisse di valori che sono protetti dalla sfocatura che gradualmente si attenua ulteriormente. Un filtro passa-banda combinato con altre equazioni (forse una variante di y=-x^2) potrebbe potenzialmente funzionare meglio qui per alcune immagini. Sono andato con il coseno perché ha dato una buona risposta per il caso base che ho testato.

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

Giava

Utilizza una sfocatura box bidirezionale a media corsa veloce per essere abbastanza veloce da eseguire passaggi multipli, emulando una sfocatura gaussiana. La sfocatura è un gradiente ellittico e non bi-lineare.

Visivamente, funziona meglio su immagini di grandi dimensioni. Ha un tema più oscuro e volgare. Sono contento di come la sfocatura si è rivelata su immagini di dimensioni adeguate, è abbastanza graduale e difficile discernere dove "inizia".

Tutti i calcoli eseguiti su array di numeri interi o doppi (per HSV).

Si aspetta il percorso del file come argomento, invia il file nella stessa posizione con il suffisso "miniaturized.png" Visualizza anche l'input e l'output in un JFrame per la visualizzazione immediata.

(clicca per vedere le versioni più grandi, sono molto meglio)

Prima:

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

Dopo:

inserisci qui la descrizione dell'immagine

Potrei dover aggiungere una mappatura dei toni più intelligente o la conservazione della luminanza, poiché può diventare piuttosto scura:

Prima:

inserisci qui la descrizione dell'immagine

Dopo:

inserisci qui la descrizione dell'immagine

Comunque interessante, lo mette in un'atmosfera completamente nuova.

Il codice:

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

Questa è stata una bella sfida. Le impostazioni di sfocatura, saturazione e contrasto sono codificate ma possono essere facilmente modificate se lo si desidera. Tuttavia, l'area a fuoco è codificata come una linea orizzontale al centro. Non può essere semplicemente modificato come le altre impostazioni. È stato scelto poiché la maggior parte delle immagini di prova mostrano viste in una città.

Dopo aver eseguito una sfocatura gaussiana, ho diviso l'immagine in orizzontale in 5 regioni. Le regioni superiore e inferiore riceveranno il 100% della sfocatura. La regione centrale riceverà lo 0% della sfocatura. Le due restanti regioni scaleranno entrambe proporzionalmente al cubo inverso dallo 0% al 100%.

Il codice deve essere utilizzato come script in J e tale script sarà nella stessa cartella in input.bmpcui sarà l'immagine di input. Creerà output.bmpquale sarà una miniatura falsa dell'input.

Le prestazioni sono buone e sul mio PC usando un i7-4770k, ci vogliono circa 20 secondi per elaborare un'immagine dal set dell'OP. In precedenza, sono stati necessari circa 70 secondi per elaborare un'immagine utilizzando la convoluzione standard con l' ;._3operatore del subarray. Le prestazioni sono state migliorate utilizzando FFT per eseguire la convoluzione.

Ciclo continuo Loop-Mini Città City-Mini

Questo codice richiede l' installazione dei componenti aggiuntivi bmpe math/fftw. Puoi installarli usando install 'bmp'e install 'math/fftw'. Il tuo sistema potrebbe anche aver bisogno fftwdell'installazione della libreria.

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 ''
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.