Fake miniaturowe


26

Jak każdy fotograf amator może powiedzieć, ekstremalna obróbka końcowa jest zawsze dobra. Jedną z takich technik jest „ oszustwo miniaturowe ”.

Celem jest sprawienie, aby obraz wyglądał jak fotografia jego zminiaturyzowanej, zabawkowej wersji. Działa to najlepiej w przypadku zdjęć wykonanych pod średnim / wysokim kątem do podłoża, z niewielkim zróżnicowaniem wysokości obiektu, ale można go stosować z różną skutecznością do innych zdjęć.

Wyzwanie: Zrób zdjęcie i zastosuj do niego algorytm sfałszowania miniatur. Można to zrobić na wiele sposobów, ale na potrzeby tego wyzwania sprowadza się to do:

  • Selektywne rozmycie

    Część obrazu powinna być zamazana, aby zasymulować płytką głębię ostrości. Zwykle odbywa się to wzdłuż pewnego gradientu, liniowego lub kształtowego. Wybierz dowolny algorytm rozmycia / gradientu, który Ci się podoba, ale między 15-85% obrazu musi mieć „zauważalne” rozmycie.

  • Zwiększenie nasycenia

    Podkręć kolor, aby rzeczy wyglądały na pomalowane ręcznie. Wyjściowy musi mieć średni poziom nasycenia> + 5% w porównaniu do wejściowego. (przy użyciu nasycenia HSV )

  • Wzmocnienie kontrastu

    Zwiększ kontrast, aby zasymulować trudniejsze warunki oświetleniowe (takie jak światło wewnętrzne / studyjne zamiast słońca). Wyjście musi mieć kontrast> + 5% w porównaniu z wejściem. (przy użyciu algorytmu RMS )

Te trzy zmiany muszą zostać zaimplementowane i żadne inne ulepszenia / zmiany nie są dozwolone. Bez kadrowania, wyostrzania, regulacji balansu bieli, nic.

  • Dane wejściowe to obraz, który można odczytać z pliku lub pamięci. Możesz używać zewnętrznych bibliotek do odczytu i zapisu obrazu, ale nie możesz ich użyć do przetworzenia obrazu. W tym celu niedozwolone są również dostarczone funkcje (nie można po prostu zadzwonić Image.blur())

  • Nie ma innych danych wejściowych. Siły przetwarzania, poziomy itp. Muszą być określone przez program, a nie przez człowieka.

  • Dane wyjściowe mogą być wyświetlane lub zapisywane jako plik w standardowym formacie obrazu (PNG, BMP itp.).

  • Spróbuj uogólnić. Nie powinno działać tylko na jednym obrazie, ale zrozumiałe jest, że nie będzie działać na wszystkich obrazach. Niektóre sceny po prostu nie reagują dobrze na tę technikę, bez względu na to, jak dobry jest algorytm. Stosuj zdrowy rozsądek, zarówno podczas odpowiadania, jak i głosowania na odpowiedzi.

  • Zachowanie jest niezdefiniowane dla niepoprawnych danych wejściowych i obrazów, których spełnienie nie jest możliwe. Na przykład obraz w skali szarości nie może być nasycony (nie ma odcienia bazowego), czysty biały obraz nie może mieć zwiększonego kontrastu itp.

  • Uwzględnij co najmniej dwa obrazy wyjściowe w swojej odpowiedzi:

    Należy wygenerować jeden z obrazów w tym folderze skrzynki odbiorczej . Do wyboru jest sześć, ale starałem się, aby wszystkie były wykonalne w różnym stopniu. Możesz zobaczyć przykładowe wyniki dla każdego w example-outputspodfolderze. Pamiętaj, że są to pełne obrazy JPG 10 MP prosto z aparatu, więc masz dużo pikseli do pracy.

    Drugim może być dowolny obraz do wyboru. Oczywiście staraj się wybierać obrazy, które można swobodnie wykorzystywać. Dołącz także oryginalny obraz lub link do niego w celu porównania.


Na przykład z tego obrazu:

oryginalny

Możesz wypisać coś takiego:

obrobiony

Dla porównania powyższy przykład został przetworzony w GIMP z kątowym rozmytym gradientem gaussowskim w kształcie pudełka, nasyceniem +80, kontrastem +20. (Nie wiem, jakich jednostek używa do nich GIMP)

Aby uzyskać więcej inspiracji lub uzyskać lepszy pomysł na to, co próbujesz osiągnąć, sprawdź tę witrynę lub . Możesz także wyszukiwać miniature fakingi wyszukiwać tilt shift photographyprzykłady.


To konkurs popularności. Głosujący głosują na wpisy, które według ciebie wyglądają najlepiej, pozostając wiernymi celowi.


Wyjaśnienie:

Wyjaśniając, które funkcje są niedozwolone, moim zamiarem nie było blokowanie funkcji matematycznych . Moim zamiarem było zablokowanie funkcji manipulacji obrazem . Tak, niektóre elementy się pokrywają, ale rzeczy takie jak FFT, zwoje, matematyka macierzy itp. Są przydatne w wielu innych obszarach. Państwo powinno nie być przy użyciu funkcji, które po prostu potrzebny obraz i zaciera. Jeśli okaże się, odpowiednio Mathy drogę do tworzenia rozmycie, że uczciwą grę.


Ta niezwykła demonstracja. Wolfram.com/DigitalTiltShiftPhotography na temat cyfrowego przetwarzania obrazu Tilt-Shift, autorstwa Yu-Sung Chang, zawiera wiele pomysłów na temat regulacji kontrastu, jasności i lokalnego ustawiania ostrości (w owalnym lub prostokątnym obszarze zdjęcia ) przy użyciu wbudowanej funkcji Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, i ImageAdjust.) Nawet z pomocą takich funkcji przetwarzania obrazu wysokiego szczebla, kod zajmuje 22 K. Kod interfejsu użytkownika jest jednak bardzo mały.
DavidC

5
Powinienem powiedzieć „zajmuje tylko 22 tys.”. We wspomnianych wyżej funkcjach jest tyle kodu zakulisowego, że udana odpowiedź na to wyzwanie powinna okazać się bardzo, bardzo trudna do osiągnięcia w większości języków bez użycia dedykowanych bibliotek przetwarzania obrazu.
DavidC

Aktualizacja: zrobiono to przy użyciu 2,5 k znaków, dzięki czemu było jeszcze bardziej wydajne.
DavidC

1
@DavidCarraher Właśnie dlatego wyraźnie ograniczyłem specyfikację. Nie jest trudno napisać coś, co tylko by zawierało specyfikację, ponieważ moja implementacja poniżej pokazuje 4,3 tys. Znaków nieogolonej Javy . Absolutnie nie oczekuję wyników na poziomie profesjonalnym. Oczywiście wszystko, co wykracza poza specyfikację (co prowadzi do lepszych wyników), powinno być serdecznie ocenione, IMO. Zgadzam się, że nie jest to proste wyzwanie excel na, ale to nie miało być. Minimalny wysiłek jest podstawowy, ale „dobre” wpisy będą z konieczności bardziej zaangażowane.
Geobity

Innym algorytmem, który można łączyć z nimi, aby stworzyć jeszcze bardziej przekonujące „miniatury”, jest zastosowanie rozkładu falkowego w celu odfiltrowania małych elementów z obrazu, przy jednoczesnym zachowaniu ostrości większych elementów.
AJMansfield

Odpowiedzi:


15

Java: Implementacja referencyjna

Oto podstawowa implementacja referencyjna w Javie. Działa najlepiej na ujęciach pod dużym kątem i jest okropnie nieefektywny.

Rozmycie jest bardzo prostym rozmyciem ramki, więc zapętla się nad tymi samymi pikselami znacznie więcej niż to konieczne. Kontrast i nasycenie można również połączyć w jedną pętlę, ale ogromna większość czasu spędzonego jest na rozmyciu, więc nie zobaczyłby z tego wiele korzyści. To powiedziawszy, działa dość szybko na obrazach poniżej 2MP lub mniej więcej. Wykonanie zdjęcia 10 MP zajęło trochę czasu.

Jakość rozmycia można łatwo poprawić, używając praktycznie wszystkiego oprócz rozmycia płaskiego pola. Algorytmy kontrastu / nasycenia wykonują swoją pracę, więc nie ma tam prawdziwych skarg.

W programie nie ma prawdziwej inteligencji. Wykorzystuje stałe współczynniki rozmycia, nasycenia i kontrastu. Bawiłem się nim, by znaleźć szczęśliwe średnie ustawienia. W rezultacie, nie niektóre sceny, że nie robi się bardzo dobrze. Na przykład, pompuje kontrast / nasycenie tak bardzo, że obrazy z dużymi obszarami o podobnych kolorach (jak niebo) rozpadają się na kolorowe pasy.

Jest prosty w użyciu; wystarczy podać nazwę pliku jako jedyny argument. Wyprowadza w formacie PNG niezależnie od tego, jaki był plik wejściowy.

Przykłady:

Z listy rozwijanej:

Te pierwsze obrazy są zmniejszane w celu ułatwienia publikowania. Kliknij obraz, aby zobaczyć w pełnym rozmiarze.

Po:

wprowadź opis zdjęcia tutaj

Przed:

wprowadź opis zdjęcia tutaj

Wybór różnych:

Po:

wprowadź opis zdjęcia tutaj

Przed:

wprowadź opis zdjęcia tutaj

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

import javax.imageio.ImageIO;

public class MiniFake {

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

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

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

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

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

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

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


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

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

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

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

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

12

DO#

Zamiast wykonywać iteracyjne rozmycia pudełkowe, postanowiłem przejść całą drogę i napisać rozmycie gaussowskie. Te GetPixelrozmowy naprawdę spowolnić go podczas korzystania z dużych ziaren, ale to naprawdę nie opłaca się konwertować metody do użytku LockBits, chyba że były jakieś większe przetwarzania obrazów.

Poniżej podano kilka przykładów, które używają domyślnych parametrów strojenia, które ustawiłem (nie grałem zbyt długo z parametrami strojenia, ponieważ wydawały się one działać dobrze dla obrazu testowego).

Dla dostarczonego przypadku testowego ...

1-oryginał 1-Zmodyfikowany

Inne...

2-Oryginał 2-Zmodyfikowane

Inne...

3-oryginalne 3-zmodyfikowany

Zwiększenie nasycenia i kontrastu powinno być dość proste z kodu. Robię to w przestrzeni HSL i przekształcam z powrotem w RGB.

Jądro 2D Gaussa jest generowany na podstawie rozmiaru nokreślony, z:

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

... i znormalizowany po przypisaniu wszystkich wartości jądra. Zauważ, że A=sigma_x=sigma_y=1.

Aby dowiedzieć się, gdzie zastosować jądro, używam wagi rozmycia obliczonej przez:

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

... co daje przyzwoitą odpowiedź, zasadniczo tworząc elipsę wartości, które są chronione przed rozmyciem, które stopniowo zanika. Filtr pasmowo-przepustowy w połączeniu z innymi równaniami (być może jakimś wariantem y=-x^2) mógłby potencjalnie lepiej działać tutaj w przypadku niektórych obrazów. Poszedłem z cosinusem, ponieważ dał dobrą odpowiedź na testowany przypadek podstawowy.

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

Jawa

Używa szybkiego dwukierunkowego rozmycia średniej ruchomej, aby był wystarczająco szybki, aby uruchomić wiele przejść, emulując rozmycie gaussowskie. Rozmycie jest również gradientem eliptycznym zamiast dwuliniowym.

Wizualnie działa najlepiej na dużych obrazach. Ma ciemniejszy, grubszy motyw. Cieszę się, jak rozmycie pojawiło się na obrazach o odpowiedniej wielkości, jest to dość stopniowe i trudne do rozpoznania, gdzie „zaczyna się”.

Wszystkie obliczenia wykonane na tablicach liczb całkowitych lub podwójnych (dla HSV).

Oczekuje ścieżki pliku jako argumentu, wysyła plik do tej samej lokalizacji z sufiksem „miniaturized.png” Wyświetla również dane wejściowe i wyjściowe w ramce JFrame do natychmiastowego przeglądania.

(kliknij, aby zobaczyć duże wersje, są o wiele lepsze)

Przed:

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

Po:

wprowadź opis zdjęcia tutaj

Być może będę musiał dodać bardziej inteligentne mapowanie tonów lub zachowanie Luma, ponieważ może być dość ciemno:

Przed:

wprowadź opis zdjęcia tutaj

Po:

wprowadź opis zdjęcia tutaj

Nadal jednak ciekawy, wprowadza go w zupełnie nową atmosferę.

Kod:

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

jot

To było miłe wyzwanie. Ustawienia rozmycia, nasycenia i kontrastu są zakodowane na stałe, ale w razie potrzeby można je łatwo zmienić. Jednak obszar ostrości jest zakodowany na stałe jako pozioma linia pośrodku. Nie można go po prostu zmodyfikować, podobnie jak inne ustawienia. Zostało wybrane, ponieważ większość zdjęć testowych przedstawia widoki miasta.

Po wykonaniu rozmycia gaussowskiego podzieliłem obraz poziomo na 5 regionów. Górne i dolne regiony otrzymają 100% rozmycia. Środkowy region otrzyma 0% rozmycia. Dwa pozostałe regiony będą skalowane proporcjonalnie do odwrotnej kostki od 0% do 100%.

Kod ma być używany jako skrypt w J, a ten skrypt będzie w tym samym folderze, w input.bmpktórym będzie obraz wejściowy. Stworzy, output.bmpktóra będzie fałszywą miniaturą danych wejściowych.

Wydajność jest dobra i na moim komputerze z i7-4770k przetworzenie obrazu z zestawu OP zajmuje około 20 sekund. Poprzednio przetworzenie obrazu przy użyciu standardowego splotu z ;._3operatorem podrzędnej pamięci trwało około 70 sekund . Wydajność została poprawiona dzięki zastosowaniu FFT do wykonania splotu.

Pętla Loop-Mini Miasto City-Mini

Ten kod wymaga zainstalowania dodatków bmpi math/fftw. Możesz je zainstalować za pomocą install 'bmp'i install 'math/fftw'. Twój system może także wymagać zainstalowania fftwbiblioteki.

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 ''
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.