Faking miniature


26

Comme tout photographe amateur peut vous le dire, un post-traitement extrême est toujours bon. Une de ces techniques est appelée " simulation de miniature ".

Le but est de faire ressembler une image à une photographie d'une version miniature et jouet d'elle-même. Cela fonctionne mieux pour les photographies prises d'un angle modéré / élevé au sol, avec une faible variation de la hauteur du sujet, mais peut être appliquée avec une efficacité variable à d'autres images.

Le défi: prendre une photo et lui appliquer un algorithme de simulation de miniatures. Il existe de nombreuses façons de le faire, mais pour les besoins de ce défi, cela se résume à:

  • Flou sélectif

    Une partie de l'image doit être floue pour simuler une faible profondeur de champ. Cela se fait généralement le long d'un certain gradient, qu'il soit linéaire ou façonné. Choisissez l'algorithme de flou / gradient que vous aimez, mais entre 15 et 85% de l'image doit avoir un flou "perceptible".

  • Boost de saturation

    Augmentez la couleur pour faire apparaître les objets peints à la main. La sortie doit avoir un niveau de saturation moyen de> + 5% par rapport à l'entrée. (en utilisant la saturation HSV )

  • Augmentation du contraste

    Augmentez le contraste pour simuler des conditions d'éclairage plus dures (comme vous le voyez avec une lumière intérieure / de studio plutôt qu'avec le soleil). La sortie doit avoir un contraste de> + 5% par rapport à l'entrée. (en utilisant l' algorithme RMS )

Ces trois modifications doivent être mises en œuvre et aucune autre amélioration / modification n'est autorisée. Aucun recadrage, netteté, réglages de la balance des blancs, rien.

  • L'entrée est une image et peut être lue à partir d'un fichier ou d'une mémoire. Vous pouvez utiliser des bibliothèques externes pour lire et écrire l'image, mais vous ne pouvez pas les utiliser pour traiter l'image. Les fonctions fournies sont également interdites à cette fin (vous ne pouvez pas simplement appeler Image.blur()par exemple)

  • Il n'y a pas d'autre entrée. Les forces de traitement, les niveaux, etc., doivent être déterminés par le programme et non par un être humain.

  • La sortie peut être affichée ou enregistrée sous forme de fichier dans un format d'image standardisé (PNG, BMP, etc.).

  • Essayez de généraliser. Il ne devrait pas fonctionner sur une seule image, mais il est compréhensible qu'il ne fonctionne pas sur toutes les images. Certaines scènes ne répondent tout simplement pas bien à cette technique, quelle que soit la qualité de l'algorithme. Faites preuve de bon sens ici, à la fois pour répondre et voter sur les réponses.

  • Le comportement n'est pas défini pour les entrées non valides et les images qui sont impossibles à satisfaire à la spécification. Par exemple, une image en niveaux de gris ne peut pas être saturée (il n'y a pas de teinte de base), une image en blanc pur ne peut pas avoir un contraste accru, etc.

  • Incluez au moins deux images de sortie dans votre réponse:

    L'une doit être générée à partir d'une des images de ce dossier de liste déroulante . Il y en a six parmi lesquels choisir, mais j'ai essayé de les rendre tous viables à des degrés divers. Vous pouvez voir des exemples de sorties pour chacun dans le example-outputssous - dossier. Veuillez noter que ce sont des images JPG 10MP complètes directement de l'appareil photo, vous avez donc beaucoup de pixels sur lesquels travailler.

    L'autre peut être n'importe quelle image de votre choix. Évidemment, essayez de choisir des images librement utilisables. Incluez également l'image d'origine ou un lien vers celle-ci pour comparaison.


Par exemple, à partir de cette image:

original

Vous pourriez produire quelque chose comme:

traité

Pour référence, l'exemple ci-dessus a été traité dans GIMP avec un flou gaussien dégradé en forme de boîte angulaire, saturation +80, contraste +20. (Je ne sais pas quelles unités GIMP utilise pour celles-ci)

Pour plus d'inspiration ou pour avoir une meilleure idée de ce que vous essayez de réaliser, consultez ce site ou celui-ci . Vous pouvez également rechercher miniature fakinget tilt shift photographyrechercher des exemples.


Il s'agit d'un concours de popularité. Électeurs, votez pour les entrées qui vous semblent les plus belles tout en restant fidèles à l'objectif.


Clarification:

Précisant quelles fonctions sont interdites, je n'avais pas l'intention d'interdire les fonctions mathématiques . J'avais l'intention d'interdire les fonctions de manipulation d'images . Oui, il y a un certain chevauchement, mais des choses comme la FFT, les convolutions, les mathématiques matricielles, etc., sont utiles dans de nombreux autres domaines. Vous ne devez pas utiliser une fonction qui prend simplement une image et brouille. Si vous trouvez un moyen mathématique approprié pour créer un flou, ce jeu équitable.


Cette remarquable démonstration demonstrations.wolfram.com/DigitalTiltShiftPhotography sur le traitement d'image numérique Tilt-Shift, par Yu-Sung Chang, transmet une foule d'idées sur la façon de régler le contraste, la luminosité, et la concentration locale (dans une région ovale ou rectangulaire de la photo ) à l' aide des fonctions intégrées de Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, et ImageAdjust). Même avec l'aide de ces fonctions de traitement d'image de haut niveau, le code prend 22 k. Le code de l'interface utilisateur est néanmoins très petit.
DavidC

5
J'aurais dû dire "ne prend que 22 k". Il y a tellement de code en arrière-plan encapsulé dans les fonctions susmentionnées qu'une réponse réussie à ce défi devrait s'avérer très, très difficile à réaliser dans la plupart des langues sans utiliser des bibliothèques de traitement d'image dédiées.
DavidC

Mise à jour: elle a été faite en caractères 2.5k donc c'était encore plus efficace.
DavidC

1
@DavidCarraher C'est pourquoi j'ai explicitement limité la spécification. Il n'est pas difficile d'écrire quelque chose pour couvrir simplement la spécification, comme le montre mon implémentation de référence ci-dessous en 4,3 k caractères de Java non golfé . Je ne m'attends absolument pas à des résultats de niveau studio professionnel. Bien sûr, tout ce qui dépasse la spécification (conduisant à de meilleurs résultats) devrait être voté de bon cœur, OMI. Je suis d'accord que ce n'est pas un simple défi d' exceller , mais ce n'était pas censé l'être. L'effort minimum est basique, mais les «bonnes» entrées seront nécessairement plus impliquées.
Geobits

Un autre algorithme qui peut être combiné avec ceux-ci pour produire des "miniatures" encore plus convaincantes consiste à utiliser la décomposition en ondelettes pour filtrer les petits traits de l'image, tout en gardant les traits plus grands nets.
AJMansfield

Réponses:


15

Java: implémentation de référence

Voici une implémentation de référence de base en Java. Il fonctionne mieux sur les prises de vue en plongée et il est horriblement inefficace.

Le flou est un flou de boîte très basique, donc il boucle sur les mêmes pixels beaucoup plus que nécessaire. Le contraste et la saturation peuvent également être combinés en une seule boucle, mais la grande majorité du temps passé est sur le flou, donc il n'y aurait pas beaucoup de gain à cela. Cela étant dit, cela fonctionne assez rapidement sur les images sous 2MP ou plus. L'image 10MP a pris un certain temps.

La qualité du flou pourrait facilement être améliorée en utilisant pratiquement tout sauf un flou de boîte plate. Les algorithmes de contraste / saturation font leur travail, donc rien à redire là-bas.

Il n'y a pas de véritable intelligence dans le programme. Il utilise des facteurs constants pour le flou, la saturation et le contraste. J'ai joué avec lui pour trouver des réglages de milieu heureux. En conséquence, il y a des scènes qui ne fonctionnent pas très bien. Par exemple, il pompe le contraste / la saturation à tel point que les images avec de grandes zones de couleur similaire (pensez au ciel) se décomposent en bandes de couleur.

C'est simple à utiliser; passez simplement le nom du fichier comme seul argument. Il sort en PNG quel que soit le fichier d'entrée.

Exemples:

Dans la liste déroulante:

Ces premières images sont réduites pour faciliter la publication. Cliquez sur l'image pour voir en taille réelle.

Après:

entrez la description de l'image ici

Avant:

entrez la description de l'image ici

Sélection divers:

Après:

entrez la description de l'image ici

Avant:

entrez la description de l'image ici

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 #

Au lieu de faire des flous de boîte itératifs, j'ai décidé d'aller jusqu'au bout et d'écrire un flou gaussien. Les GetPixelappels le ralentissent vraiment lors de l'utilisation de grands noyaux, mais cela ne vaut pas vraiment la peine de convertir les méthodes à utiliser à LockBitsmoins que nous ne traitions des images plus grandes.

Quelques exemples sont ci-dessous, qui utilisent les paramètres de réglage par défaut que j'ai définis (je n'ai pas beaucoup joué avec les paramètres de réglage, car ils semblaient bien fonctionner pour l'image de test).

Pour le cas de test fourni ...

1-Original 1-modifié

Un autre...

2-Original 2-modifié

Un autre...

3-Original 3-modifié

La saturation et le contraste augmentent devraient être assez simples à partir du code. Je fais cela dans l'espace HSL et le reconvertis en RVB.

Le noyau gaussien 2D est généré en fonction de la taille nspécifiée, avec:

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

... et normalisé une fois que toutes les valeurs du noyau ont été attribuées. Notez cela A=sigma_x=sigma_y=1.

Afin de savoir où appliquer le noyau, j'utilise un poids de flou, calculé par:

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

... ce qui donne une réponse décente, créant essentiellement une ellipse de valeurs protégées du flou qui s'estompe progressivement. Un filtre passe-bande combiné à d'autres équations (peut-être une variante de y=-x^2) pourrait potentiellement mieux fonctionner ici pour certaines images. Je suis allé avec le cosinus car il a donné une bonne réponse pour le cas de base que j'ai testé.

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

Utilise un flou de boîte bidirectionnelle à moyenne rapide pour être suffisamment rapide pour exécuter plusieurs passes, émulant un flou gaussien. Le flou est également un gradient elliptique au lieu d'être bi-linéaire.

Visuellement, cela fonctionne mieux sur de grandes images. A un thème plus sombre et plus grungier. Je suis satisfait de la façon dont le flou s'est avéré sur des images de taille appropriée, il est assez progressif et difficile de discerner où il "commence".

Tous les calculs effectués sur des tableaux d'entiers ou de doubles (pour HSV).

Attend le chemin du fichier comme argument, envoie le fichier au même emplacement avec le suffixe "miniaturized.png" Affiche également l'entrée et la sortie dans un JFrame pour une visualisation immédiate.

(cliquez pour voir les grandes versions, elles sont bien meilleures)

Avant:

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

Après:

entrez la description de l'image ici

Je devrais peut-être ajouter un mappage de ton plus intelligent ou une préservation de la luma, car cela peut devenir assez sombre:

Avant:

entrez la description de l'image ici

Après:

entrez la description de l'image ici

Toujours intéressant cependant, le met dans une toute nouvelle atmosphère.

Le code:

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

C'était un beau défi. Les paramètres de flou, de saturation et de contraste sont codés en dur mais peuvent être facilement modifiés si vous le souhaitez. Cependant, la zone mise au point est codée en dur comme une ligne horizontale au centre. Il ne peut pas être simplement modifié comme les autres paramètres. Il a été choisi car la plupart des images de test présentent des vues à travers une ville.

Après avoir effectué un flou gaussien, j'ai divisé l'image horizontalement en 5 régions. Les régions supérieure et inférieure recevront 100% du flou. La région du milieu recevra 0% du flou. Les deux régions restantes évolueront toutes deux proportionnellement au cube inverse de 0% à 100%.

Le code doit être utilisé comme script dans J et ce script sera dans le même dossier que celui input.bmpqui sera l'image d'entrée. Il créera output.bmpqui sera une fausse miniature de l'entrée.

Les performances sont bonnes et sur mon PC utilisant un i7-4770k, il faut environ 20 secondes pour traiter une image de l'ensemble OP. Auparavant, il fallait environ 70 secondes pour traiter une image à l'aide d'une convolution standard avec l' ;._3opérateur de sous-réseau. Les performances ont été améliorées en utilisant la FFT pour effectuer la convolution.

Boucle Loop-Mini Ville City-Mini

Ce code nécessite que les addons bmpet math/fftwsoient installés. Vous pouvez les installer en utilisant install 'bmp'et install 'math/fftw'. Votre système peut également nécessiter l' fftwinstallation de la bibliothèque.

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 ''
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.