Pemalsuan Miniatur


26

Seperti yang bisa dikatakan oleh fotografer amatir kepada Anda, pasca-pemrosesan ekstrem selalu baik. Salah satu teknik semacam itu disebut " pemalsuan miniatur ".

Tujuannya adalah untuk membuat gambar tampak seperti foto versi mainan miniatur itu sendiri. Ini berfungsi baik untuk foto yang diambil dari sudut sedang / tinggi ke tanah, dengan varians rendah pada tinggi subjek, tetapi dapat diterapkan dengan berbagai efektivitas ke gambar lain.

Tantangannya: Ambil foto dan terapkan algoritma pemalsuan miniatur. Ada banyak cara untuk melakukan ini, tetapi untuk tujuan tantangan ini, intinya adalah:

  • Kabur selektif

    Beberapa bagian dari gambar harus buram untuk mensimulasikan kedalaman-bidang-dangkal. Ini biasanya dilakukan sepanjang beberapa gradien, baik linier atau berbentuk. Pilih algoritma blur / gradien apa pun yang Anda suka, tetapi antara 15-85% gambar harus memiliki blur "nyata".

  • Meningkatkan saturasi

    Pompa warna untuk membuat hal-hal tampak mereka dilukis dengan tangan. Output harus memiliki tingkat saturasi rata-rata> + 5% bila dibandingkan dengan input. (menggunakan saturasi HSV )

  • Meningkatkan Kontras

    Tingkatkan kontras untuk mensimulasikan kondisi pencahayaan yang lebih keras (seperti yang Anda lihat dengan lampu indoor / studio daripada matahari). Output harus memiliki kontras> + 5% bila dibandingkan dengan input. (menggunakan algoritma RMS )

Ketiga perubahan itu harus diimplementasikan, dan tidak ada peningkatan / perubahan lain yang diizinkan. Tidak ada pemotongan, penajaman, penyesuaian white balance, tidak ada.

  • Input adalah gambar, dan dapat dibaca dari file atau memori. Anda dapat menggunakan perpustakaan eksternal untuk membaca dan menulis gambar, tetapi Anda tidak dapat menggunakannya untuk memproses gambar. Fungsi yang disediakan juga tidak diizinkan untuk tujuan ini (Anda tidak bisa hanya menelepon Image.blur()misalnya)

  • Tidak ada input lain. Kekuatan pemrosesan, level, dll, harus ditentukan oleh program, bukan oleh manusia.

  • Output dapat ditampilkan atau disimpan sebagai file dalam format gambar standar (PNG, BMP, dll).

  • Cobalah untuk menggeneralisasi. Seharusnya tidak hanya bekerja pada satu gambar, tetapi dapat dimengerti bahwa itu tidak akan bekerja pada semua gambar. Beberapa adegan tidak merespon dengan baik untuk teknik ini, tidak peduli seberapa bagus algoritma tersebut. Terapkan akal sehat di sini, baik ketika menjawab dan memilih jawaban.

  • Perilaku tidak ditentukan untuk input yang tidak valid, dan gambar-gambar yang tidak mungkin memenuhi spesifikasi. Misalnya, gambar skala abu-abu tidak dapat dijenuhkan (tidak ada rona dasar), gambar putih murni tidak dapat meningkatkan kontras, dll.

  • Sertakan setidaknya dua gambar keluaran dalam jawaban Anda:

    Satu harus dihasilkan dari salah satu gambar di folder dropbox ini . Ada enam untuk dipilih, tetapi saya mencoba membuat semuanya layak untuk berbagai tingkatan. Anda dapat melihat output sampel untuk masing-masing dalam example-outputssubfolder. Harap dicatat bahwa ini adalah gambar JPG 10MP penuh langsung dari kamera, sehingga Anda memiliki banyak piksel untuk bekerja.

    Yang lain bisa berupa gambar pilihan Anda. Jelas, cobalah untuk memilih gambar yang dapat digunakan secara bebas. Juga, sertakan baik gambar asli atau tautan untuk perbandingan.


Misalnya, dari gambar ini:

asli

Anda mungkin menampilkan sesuatu seperti:

diproses

Untuk referensi, contoh di atas diproses dalam GIMP dengan gradien gaussian blur berbentuk kotak sudut, saturasi +80, kontras +20. (Saya tidak tahu unit apa yang digunakan GIMP untuk itu)

Untuk inspirasi lebih banyak, atau untuk mendapatkan ide yang lebih baik tentang apa yang ingin Anda capai, lihat situs ini , atau yang ini . Anda juga dapat mencari miniature fakingdan tilt shift photographyuntuk contoh.


Ini adalah kontes popularitas. Pemilih, pilih entri yang Anda rasa terlihat terbaik sambil tetap setia pada tujuan.


Klarifikasi:

Mengklarifikasi fungsi apa yang dilarang, itu bukan maksud saya untuk melarang fungsi matematika . Itu maksud saya untuk melarang fungsi manipulasi gambar . Ya, ada beberapa tumpang tindih di sana, tetapi hal-hal seperti FFT, konvolusi, matrik matematika, dll, berguna di banyak bidang lainnya. Anda seharusnya tidak menggunakan fungsi yang hanya mengambil gambar dan mengaburkan. Jika Anda menemukan cara matematis yang sesuai untuk membuat buram, itu permainan yang adil.


Demonstrasi demonstrasi yang luar biasa ini.wolfram.com/DigitalTiltShiftFotografi tentang Pemrosesan Gambar Tilt-Shift Digital, oleh Yu-Sung Chang, menyampaikan banyak ide tentang cara menyesuaikan kontras, kecerahan, dan fokus lokal (dalam wilayah oval atau persegi panjang foto) ) menggunakan built-in fungsi Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, dan ImageAdjust.) Bahkan dengan bantuan fungsi gambar-pengolahan tingkat tinggi seperti, kode memakan 22 k. Kode untuk antarmuka pengguna masih sangat kecil.
DavidC

5
Saya seharusnya mengatakan " hanya membutuhkan 22 k". Ada begitu banyak kode di belakang layar yang dienkapsulasi dalam fungsi-fungsi yang disebutkan di atas sehingga respons yang berhasil terhadap tantangan ini harus dibuktikan sangat, sangat sulit untuk dicapai dalam sebagian besar bahasa tanpa menggunakan perpustakaan pemrosesan gambar khusus.
DavidC

Pembaruan: dilakukan dalam karakter 2,5 k sehingga lebih efisien.
DavidC

1
@DavidCarraher Itu sebabnya saya secara eksplisit membatasi spec. Tidak sulit untuk menulis sesuatu hanya untuk menutupi spesifikasi, seperti implementasi referensi saya di bawah ini yang ditampilkan dalam karakter 4.3 k dari Jawa yang tidak diserang . Saya sama sekali tidak mengharapkan hasil tingkat studio profesional. Tentu saja, apa pun yang melebihi spesifikasi (mengarah ke hasil yang lebih baik) harus diunggulkan, IMO. Saya setuju bahwa ini bukan tantangan sederhana untuk diunggulkan , tetapi itu tidak dimaksudkan. Upaya minimum adalah dasar, tetapi entri "baik" tentu akan lebih banyak terlibat.
Geobits

Algoritma lain yang dapat dikombinasikan dengan ini untuk menghasilkan "miniatur" yang lebih meyakinkan adalah dengan menggunakan dekomposisi wavelet untuk menyaring fitur kecil dari gambar, sambil menjaga fitur yang lebih besar tetap tajam.
AJMansfield

Jawaban:


15

Java: Implementasi Referensi

Berikut ini adalah implementasi referensi dasar di Jawa. Ini bekerja paling baik pada bidikan sudut tinggi, dan itu sangat tidak efisien.

Blur adalah blur kotak yang sangat mendasar, sehingga loop lebih dari piksel yang sama lebih dari yang diperlukan. Kontras dan saturasi bisa digabungkan menjadi satu loop juga, tetapi sebagian besar waktu yang dihabiskan adalah blur, sehingga tidak akan melihat banyak keuntungan dari itu. Yang sedang berkata, itu bekerja cukup cepat pada gambar di bawah 2MP atau lebih. Gambar 10MP membutuhkan waktu untuk diselesaikan.

Kualitas blur dapat dengan mudah ditingkatkan dengan menggunakan apa pun kecuali blur kotak datar. Algoritma kontras / saturasi melakukan tugasnya, jadi tidak ada keluhan nyata di sana.

Tidak ada kecerdasan nyata dalam program ini. Ini menggunakan faktor konstan untuk blur, saturasi, dan kontras. Saya bermain-main dengannya untuk menemukan pengaturan menengah yang menyenangkan. Akibatnya, ada yang beberapa adegan yang tidak melakukannya dengan sangat baik. Sebagai contoh, ia memompa kontras / saturasi sedemikian rupa sehingga gambar dengan area besar berwarna sama (think sky) pecah menjadi pita warna.

Mudah digunakan; cukup berikan nama file sebagai satu-satunya argumen. Ini output dalam PNG terlepas dari apa file input itu.

Contoh:

Dari pilihan dropbox:

Gambar-gambar pertama ini diperkecil untuk memudahkan pengiriman. Klik gambar untuk melihat ukuran penuh.

Setelah:

masukkan deskripsi gambar di sini

Sebelum:

masukkan deskripsi gambar di sini

Pilihan lain-lain:

Setelah:

masukkan deskripsi gambar di sini

Sebelum:

masukkan deskripsi gambar di sini

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 #

Alih-alih melakukan iterative box blur, saya memutuskan untuk melakukan semua cara dan menulis blur Gaussian. ItuGetPixel panggilan benar-benar memperlambatnya ketika menggunakan kernel yang besar, tapi itu tidak benar-benar berharga untuk mengubah metode untuk penggunaan LockBitskecuali kami memproses beberapa gambar yang lebih besar.

Beberapa contoh di bawah ini, yang menggunakan parameter penyetelan standar yang saya tetapkan (saya tidak terlalu banyak bermain dengan parameter penyetelan, karena tampaknya berfungsi dengan baik untuk gambar uji).

Untuk test case disediakan ...

1-Asli 1-Dimodifikasi

Lain...

2-Asli 2-Dimodifikasi

Lain...

3-Asli 3-Dimodifikasi

Peningkatan saturasi dan kontras harus cukup mudah dari kode. Saya melakukan ini di ruang HSL dan mengubahnya kembali ke RGB.

The 2D Gaussian kernel yang dihasilkan didasarkan pada ukuran ntertentu, dengan:

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

... dan dinormalisasi setelah semua nilai kernel ditetapkan. Catat itu A=sigma_x=sigma_y=1.

Untuk mengetahui di mana harus menerapkan kernel, saya menggunakan bobot yang kabur, dihitung dengan:

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

... yang memberikan respons yang layak, pada dasarnya menciptakan elips nilai yang dilindungi dari keburaman yang secara bertahap memudar lebih jauh. Filter band-pass yang dikombinasikan dengan persamaan lain (mungkin beberapa varian y=-x^2) berpotensi bekerja lebih baik di sini untuk gambar tertentu. Saya menggunakan cosine karena memberikan respon yang baik untuk base case yang saya uji.

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

Menggunakan blur kotak dua arah rata-rata yang berjalan cepat agar cukup cepat untuk menjalankan beberapa lintasan, meniru blur Gaussian. Blur adalah gradien elips bukan bi-linear juga.

Secara visual, ini bekerja paling baik pada gambar besar. Memiliki tema grungier yang lebih gelap. Saya senang dengan bagaimana hasil blur pada gambar berukuran tepat, itu cukup bertahap dan sulit untuk membedakan mana itu "dimulai."

Semua perhitungan dilakukan pada array bilangan bulat atau ganda (untuk HSV).

Mengharapkan jalur file sebagai argumen, menampilkan file ke lokasi yang sama dengan akhiran "miniaturized.png" Juga menampilkan input dan output dalam JFrame untuk dilihat segera.

(klik untuk melihat versi besar, mereka jauh lebih baik)

Sebelum:

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

Setelah:

masukkan deskripsi gambar di sini

Saya mungkin harus menambahkan beberapa pemetaan nada yang lebih cerdas atau pelestarian luma, karena ini bisa menjadi sangat gelap:

Sebelum:

masukkan deskripsi gambar di sini

Setelah:

masukkan deskripsi gambar di sini

Namun tetap menarik, menempatkannya dalam suasana yang sama sekali baru.

Kode:

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

Ini adalah tantangan yang bagus. Pengaturan untuk blur, saturasi, dan kontras adalah kode-keras tetapi dapat dengan mudah diubah jika diinginkan. Namun, area dalam fokus dikodekan sebagai garis horizontal di tengah. Itu tidak dapat dengan mudah dimodifikasi seperti pengaturan lainnya. Itu dipilih karena sebagian besar gambar uji fitur tampilan di kota.

Setelah melakukan blur Gaussian, saya membagi gambar secara horizontal menjadi 5 wilayah. Daerah atas dan bawah akan menerima 100% blur. Wilayah tengah akan menerima 0% blur. Dua wilayah yang tersisa akan berskala proporsional dengan kubus terbalik dari 0% hingga 100%.

Kode ini akan digunakan sebagai skrip dalam J dan skrip itu akan berada di folder yang sama dengan input.bmpyang akan menjadi gambar input. Ini akan membuat output.bmpyang akan menjadi miniatur palsu input.

Performanya bagus dan di pc saya menggunakan i7-4770k, dibutuhkan sekitar 20 detik untuk memproses gambar dari perangkat OP. Sebelumnya, butuh sekitar 70 detik untuk memproses gambar menggunakan konvolusi standar dengan ;._3operator subarray. Kinerja ditingkatkan dengan menggunakan FFT untuk melakukan konvolusi.

Loop Loop-Mini Kota City-Mini

Kode ini mengharuskan bmpdan math/fftwaddon diinstal. Anda dapat menginstalnya menggunakan install 'bmp'dan install 'math/fftw'. Sistem Anda juga mungkin perlu fftwpustaka untuk diinstal.

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 ''
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.