Miniatur-Fälschung

26

Wie jeder Amateurfotograf Ihnen sagen kann, ist extreme Nachbearbeitung immer gut. Eine solche Technik wird " Miniatur-Fälschung " genannt.

Das Ziel ist es, ein Bild wie eine Fotografie einer miniaturisierten Spielzeugversion von sich selbst aussehen zu lassen. Dies funktioniert am besten für Fotos, die aus einem mittleren / hohen Winkel zum Boden mit einer geringen Abweichung in der Motivhöhe aufgenommen wurden, kann jedoch mit unterschiedlicher Effektivität auf andere Bilder angewendet werden.

Die Herausforderung: Nehmen Sie ein Foto auf und wenden Sie einen Miniatur-Fälschungsalgorithmus an. Es gibt viele Möglichkeiten, dies zu tun, aber für die Zwecke dieser Herausforderung läuft es auf Folgendes hinaus:

  • Selektive Unschärfe

    Ein Teil des Bildes sollte unscharf sein, um eine geringe Schärfentiefe zu simulieren. Dies geschieht normalerweise entlang eines linearen oder geformten Gefälles. Wählen Sie einen beliebigen Unschärfe- / Verlaufsalgorithmus aus, aber zwischen 15 und 85% des Bildes müssen "merkliche" Unschärfen aufweisen.

  • Sättigungsschub

    Pumpen Sie die Farbe auf, damit die Dinge so aussehen, als wären sie von Hand bemalt worden. Die Ausgabe muss im Vergleich zur Eingabe eine durchschnittliche Sättigung von> + 5% aufweisen. (mit HSV-Sättigung )

  • Kontrastverstärkung

    Erhöhen Sie den Kontrast, um härtere Lichtverhältnisse zu simulieren (z. B. wenn Sie mit Innen- / Studioleuchten anstatt mit Sonnenlicht sehen). Die Ausgabe muss im Vergleich zur Eingabe einen Kontrast von> + 5% aufweisen. (mit RMS-Algorithmus )

Diese drei Änderungen müssen implementiert werden, und es sind keine weiteren Verbesserungen / Änderungen zulässig. Kein Zuschneiden, Schärfen, Weißabgleichsanpassungen, nichts.

  • Die Eingabe ist ein Bild und kann aus einer Datei oder einem Speicher gelesen werden. Sie können externe Bibliotheken zum Lesen und Schreiben des Bildes verwenden, sie können jedoch nicht zum Verarbeiten des Bildes verwendet werden. Mitgelieferte Funktionen sind für diesen Zweck ebenfalls nicht zulässig (Sie können beispielsweise nicht einfach anrufen Image.blur()).

  • Es gibt keine weiteren Eingaben. Die Verarbeitungsstärken, -niveaus usw. müssen vom Programm und nicht von einem Menschen bestimmt werden.

  • Die Ausgabe kann als Datei in einem standardisierten Bildformat (PNG, BMP usw.) angezeigt oder gespeichert werden.

  • Versuchen Sie zu verallgemeinern. Es sollte nicht nur für ein Bild funktionieren , aber es ist verständlich, dass es nicht für alle Bilder funktioniert . Einige Szenen reagieren einfach nicht gut auf diese Technik, egal wie gut der Algorithmus ist. Wenden Sie hier Ihren gesunden Menschenverstand an, wenn Sie antworten oder über Antworten abstimmen.

  • Das Verhalten ist undefiniert für ungültige Eingaben und für Bilder, die die Spezifikation nicht erfüllen können. Zum Beispiel kann ein Graustufenbild nicht gesättigt werden (es gibt keinen Grundfarbton), ein reines Weißbild kann keinen erhöhten Kontrast aufweisen usw.

  • Fügen Sie Ihrer Antwort mindestens zwei Ausgabebilder hinzu:

    Aus einem der Bilder in diesem Dropbox-Ordner muss eines generiert werden . Es gibt sechs zur Auswahl, aber ich habe versucht, sie alle in unterschiedlichem Maße lebensfähig zu machen. Sie können Beispielausgaben für jede im example-outputsUnterordner sehen. Bitte beachten Sie, dass es sich um vollständige 10-Megapixel-JPG-Bilder handelt, die direkt aus der Kamera stammen, sodass Sie eine Menge Pixel zum Bearbeiten haben.

    Das andere kann ein beliebiges Bild Ihrer Wahl sein. Versuchen Sie natürlich, Bilder auszuwählen, die frei verwendbar sind. Fügen Sie außerdem entweder das Originalbild oder einen Link zum Vergleich hinzu.


Zum Beispiel von diesem Bild:

Original

Sie könnten etwas ausgeben wie:

verarbeitet

Als Referenz wurde das obige Beispiel in GIMP mit einer kastenförmigen Gradienten-Gaußschen Unschärfe, Sättigung +80, Kontrast +20 verarbeitet. (Ich weiß nicht, welche Einheiten GIMP für diese verwendet)

Weitere Inspirationen oder eine bessere Vorstellung davon, was Sie erreichen möchten, finden Sie auf dieser oder dieser Website . Sie können auch für die Suche miniature fakingund tilt shift photographyfür Beispiele.


Dies ist ein Beliebtheitswettbewerb. Stimmen Sie für die Beiträge ab, von denen Sie glauben, dass sie am besten aussehen, während Sie dem Ziel treu bleiben.


Klärung:

Es war nicht meine Absicht, mathematische Funktionen zu verbieten, um zu klären, welche Funktionen nicht zulässig sind . Es war meine Absicht, Bildmanipulationsfunktionen zu verbieten . Ja, da gibt es einige Überlappungen, aber Dinge wie FFT, Faltungen, Matrixmathematik usw. sind in vielen anderen Bereichen nützlich. Sie sollten keine Funktion verwenden, die einfach ein Bild aufnimmt und verwischt. Wenn Sie einen geeigneten mathematischen Weg finden, um eine Unschärfe zu erzeugen , dann dieses faire Spiel.

Geobits
quelle
Diese bemerkenswerte Demonstration demonstrations.wolfram.com/DigitalTiltShiftPhotography on Digital Tilt-Shift Image Processing von Yu-Sung Chang vermittelt eine Fülle von Ideen zum Anpassen von Kontrast, Helligkeit und lokalem Fokus (innerhalb eines ovalen oder rechteckigen Bereichs des Fotos) ) unter Verwendung von integrierten Funktionen von Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, und ImageAdjust.) Auch mit Hilfe eines solchen Hochpegel - Bildverarbeitungsfunktionen, setzt sich der Code 22 K auf. Der Code für die Benutzeroberfläche ist dennoch sehr klein.
DavidC
5
Ich hätte sagen sollen "nimmt nur 22 k auf". In den oben genannten Funktionen ist so viel Code hinter den Kulissen enthalten, dass sich eine erfolgreiche Antwort auf diese Herausforderung in den meisten Sprachen als sehr, sehr schwierig erweisen dürfte, wenn keine dedizierten Bildverarbeitungsbibliotheken verwendet werden.
DavidC
Update: Es wurde in 2,5 k Zeichen geschrieben, um noch effizienter zu sein.
DavidC
1
@DavidCarraher Deshalb habe ich die Spezifikation ausdrücklich eingeschränkt. Es ist nicht schwer, etwas zu schreiben, das nur die Spezifikation abdeckt, wie meine folgende Referenzimplementierung in 4,3 k Zeichen ungolfed Java zeigt . Ich erwarte absolut keine professionellen Studioergebnisse. Natürlich sollte alles, was über die Spezifikation hinausgeht (was zu besseren Ergebnissen führt), nachdrücklich befürwortet werden, IMO. Ich bin damit einverstanden, dass dies keine einfache Herausforderung ist, an der man sich auszeichnen kann , aber es sollte nicht sein. Der minimale Aufwand ist grundlegend, aber "gute" Einträge werden notwendigerweise mehr involviert sein.
Geobits
Ein anderer Algorithmus, der mit diesen kombiniert werden kann, um noch überzeugendere "Miniaturen" zu erzeugen, ist die Verwendung der Wavelet-Zerlegung, um kleine Merkmale aus dem Bild herauszufiltern, während größere Merkmale scharf bleiben.
AJMansfield

Antworten:

15

Java: Referenzimplementierung

Hier ist eine grundlegende Referenzimplementierung in Java. Es funktioniert am besten bei High Angle Shots und ist schrecklich ineffizient.

Bei der Unschärfe handelt es sich um eine sehr einfache Rahmenunschärfe, sodass sie viel mehr als nötig über dieselben Pixel verläuft. Der Kontrast und die Sättigung könnten auch in einer einzigen Schleife kombiniert werden, aber die überwiegende Mehrheit der aufgewendeten Zeit entfällt auf Unschärfe, sodass davon kein großer Nutzen zu erwarten ist. Davon abgesehen funktioniert es ziemlich schnell bei Bildern mit weniger als 2 Megapixeln. Die Fertigstellung des 10-Megapixel-Images hat einige Zeit in Anspruch genommen.

Die Unschärfe-Qualität kann leicht verbessert werden, indem grundsätzlich alles andere als eine flache Rahmenunschärfe verwendet wird. Die Kontrast- / Sättigungsalgorithmen machen ihren Job, also keine wirklichen Beschwerden.

Es gibt keine wirkliche Intelligenz im Programm. Dabei werden konstante Faktoren für Unschärfe, Sättigung und Kontrast verwendet. Ich habe damit herumgespielt, um glückliche mittlere Einstellungen zu finden. Infolgedessen gibt es einige Szenen, die nicht sehr gut funktionieren. Beispielsweise pumpt es den Kontrast / die Sättigung so stark, dass Bilder mit großen, ähnlich gefärbten Bereichen (etwa Himmel) in Farbbänder aufbrechen.

Es ist einfach zu bedienen; Übergeben Sie einfach den Dateinamen als einziges Argument. Die Ausgabe erfolgt in PNG, unabhängig von der Eingabedatei.

Beispiele:

Aus der Dropbox-Auswahl:

Diese ersten Bilder werden zur Erleichterung der Veröffentlichung verkleinert. Klicken Sie auf das Bild, um es in voller Größe zu sehen.

Nach:

Bildbeschreibung hier eingeben

Vor:

Bildbeschreibung hier eingeben

Sonstiges Auswahl:

Nach:

Bildbeschreibung hier eingeben

Vor:

Bildbeschreibung hier eingeben

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
quelle
12

C #

Anstatt iterative Box-Unschärfen zu machen, habe ich beschlossen, den gesamten Weg zu gehen und eine Gaußsche Unschärfe zu schreiben. Die GetPixelAufrufe verlangsamen den Vorgang bei Verwendung großer Kernel erheblich, aber es lohnt sich nicht, die verwendeten Methoden zu konvertieren, es LockBitssei denn, wir haben einige größere Bilder verarbeitet.

Im Folgenden sind einige Beispiele aufgeführt, die die von mir festgelegten Standard-Optimierungsparameter verwenden (ich habe nicht viel mit den Optimierungsparametern gespielt, da sie für das Testbild gut zu funktionieren schienen).

Für den Testfall vorgesehen ...

1-Original 1-modifiziert

Ein weiterer...

2-original 2-modifiziert

Ein weiterer...

3-original 3-modifiziert

Die Erhöhung der Sättigung und des Kontrasts sollte im Code relativ einfach sein. Ich mache das im HSL-Raum und konvertiere es zurück in RGB.

Der 2D-Gauß-Kernel wird basierend auf der nangegebenen Größe generiert , mit:

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

... und normalisiert, nachdem alle Kernelwerte zugewiesen wurden. Beachten Sie das A=sigma_x=sigma_y=1.

Um herauszufinden, wo der Kernel angewendet werden soll, verwende ich ein Weichzeichnungsgewicht, das berechnet wird durch:

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

... was eine anständige Reaktion ergibt, im Wesentlichen eine Ellipse von Werten erzeugt, die vor der Unschärfe geschützt sind, die allmählich weiter nachlässt. Ein Bandpassfilter in Kombination mit anderen Gleichungen (möglicherweise einer Variante davon y=-x^2) könnte hier möglicherweise für bestimmte Bilder besser funktionieren. Ich habe mich für den Cosinus entschieden, weil er für den von mir getesteten Basisfall eine gute Antwort lieferte.

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;
        }
    }
}
HainenNL
quelle
9

Java

Verwendet eine schnelle, durchschnittliche Zwei-Wege-Box-Unschärfe, um schnell genug zu sein, um mehrere Durchläufe durchzuführen und eine Gaußsche Unschärfe zu emulieren. Die Unschärfe ist ein elliptischer Farbverlauf anstatt auch bi-linear.

Optisch funktioniert es am besten bei großen Bildern. Hat ein dunkleres, grungieres Thema. Ich bin glücklich darüber, wie die Unschärfe bei Bildern mit angemessener Größe auftrat. Es ist ziemlich allmählich und schwer zu erkennen, wo sie "beginnt".

Alle Berechnungen für Arrays aus ganzen oder doppelten Zahlen (für HSV).

Erwartet den Dateipfad als Argument und gibt die Datei an derselben Stelle mit dem Suffix "miniaturized.png" aus. Zeigt auch die Eingabe und Ausgabe in einem JFrame zur sofortigen Anzeige an.

(Klicken, um große Versionen zu sehen, sie sind viel besser)

Vor:

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

Nach:

Bildbeschreibung hier eingeben

Möglicherweise muss ich eine intelligentere Tonzuordnung oder Luma-Erhaltung hinzufügen, da es ziemlich dunkel werden kann:

Vor:

Bildbeschreibung hier eingeben

Nach:

Bildbeschreibung hier eingeben

Immer noch interessant, bringt es in eine ganz neue Atmosphäre.

Der 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);
    }
}
Gebrannte Pizza
quelle
8

J

Das war eine schöne Herausforderung. Die Einstellungen für Unschärfe, Sättigung und Kontrast sind fest programmiert, können jedoch bei Bedarf problemlos geändert werden. Der fokussierte Bereich ist jedoch als horizontale Linie in der Mitte fest codiert. Es kann nicht einfach wie die anderen Einstellungen geändert werden. Es wurde ausgewählt, da die meisten Testbilder Ansichten über eine Stadt bieten.

Nach einer Gaußschen Unschärfe habe ich das Bild horizontal in 5 Regionen aufgeteilt. Die oberen und unteren Bereiche erhalten 100% der Unschärfe. Der mittlere Bereich erhält 0% der Unschärfe. Die beiden verbleibenden Bereiche werden proportional zum inversen Würfel von 0% auf 100% skaliert.

Der Code soll in J als Skript verwendet werden, und dieses Skript befindet sich in demselben Ordner wie input.bmpdas Eingabebild. Es wird output.bmpeine gefälschte Miniatur der Eingabe erstellt.

Die Leistung ist gut und auf meinem PC mit einem i7-4770k dauert es ungefähr 20 Sekunden, um ein Bild vom OP-Set zu verarbeiten. Zuvor dauerte es ungefähr 70 Sekunden, um ein Bild unter Verwendung der Standardfaltung mit dem ;._3Subarray-Operator zu verarbeiten. Die Leistung wurde durch Verwendung von FFT zur Durchführung der Faltung verbessert.

Schleife Loop-Mini Stadt Stadt-Mini

Dieser Code erfordert die bmpund math/fftwAddons installiert werden. Sie können sie mit install 'bmp'und installieren install 'math/fftw'. Auf Ihrem System muss möglicherweise auch die fftwBibliothek installiert werden.

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 ''
Meilen
quelle