Мініатюрна підробка


26

Як може сказати будь-який фотограф- аматор , екстремальна постійна обробка завжди гарна. Один такий прийом називається " мініатюрним підробкою ".

Завдання полягає в тому, щоб зображення виглядало як фотографія мініатюрної іграшкової версії себе. Це найкраще підходить для фотографій, зроблених від помірного / високого кута до землі, з низькою дисперсією у висоті об'єкта, але їх можна застосовувати з різною ефективністю до інших зображень.

Завдання: сфотографуйте та застосуйте до нього алгоритм мініатюрного підробки. Існує багато способів зробити це, але для цілей цього виклику воно зводиться до:

  • Вибірковий розмиття

    Частина зображення повинна бути розмитою, щоб імітувати невелику глибину різкості. Зазвичай це робиться вздовж деякого градієнта, лінійного чи фасонного. Вибирайте алгоритм розмиття / градієнта, який вам подобається, але між 15-85% зображення повинно бути "помітне" розмиття.

  • Підвищення насичення

    Підкачайте колір, щоб речі виглядали, що їх малювали вручну. Вихід повинен мати середній рівень насичення> + 5% порівняно з вхідним. (з використанням насичення ВПГ )

  • Контрастне збільшення

    Збільште контраст, щоб імітувати суворіші умови освітлення (такі, як ви бачите, із світлом у приміщенні / студії, а не з сонцем). Вихід повинен мати контрастність> + 5% порівняно з вхідним. (з використанням алгоритму RMS )

Ці три зміни повинні бути здійснені, а інші вдосконалення / зміни не допускаються. Ніяких обрізань, різкості, коригування балансу білого, нічого.

  • Вхід - це зображення, яке може бути прочитане з файлу чи пам'яті. Ви можете використовувати зовнішні бібліотеки для читання та запису зображення, але ви не можете використовувати їх для обробки зображення. Для цього також заборонені функції (наприклад, ви не можете просто зателефонувати Image.blur())

  • Іншого вводу немає. Сильні сторони обробки, рівні тощо повинні визначатися програмою, а не людиною.

  • Вихід може бути відображений або збережений у вигляді файлу у стандартизованому форматі зображення (PNG, BMP тощо).

  • Спробуйте узагальнити. Він не повинен працювати лише на одному зображенні, але зрозуміло, що він працюватиме не на всіх зображеннях. Деякі сцени просто не реагують добре на цю техніку, незалежно від того, наскільки хороший алгоритм. Тут застосовуйте здоровий глузд як при відповіді, так і при голосуванні відповідей.

  • Поведінка не визначена для недійсних даних та зображень, які неможливо задовольнити специфікацією. Наприклад, зображення в градаціях сірого не може бути насиченим (немає базового відтінку), чистий білий малюнок не може мати підвищений контраст тощо.

  • Додайте у відповідь щонайменше два вихідні зображення:

    Потрібно створити одне із зображень цієї папки . На вибір можна шість, але я намагався зробити їх життєздатними в різній мірі. Ви можете побачити зразки виходів для кожного з example-outputsпідпапок. Зауважте, що це повні 10-мегапіксельні зображення у форматі JPG прямо з камери, тому у вас є багато пікселів для роботи.

    Іншим може бути будь-яке обране вами зображення. Очевидно, постарайтеся вибрати образи, які вільно користуються. Також для порівняння включіть оригінальне зображення або посилання на нього.


Наприклад, із цього зображення:

оригінальний

Ви можете вивести щось на кшталт:

обробляється

Для довідки, наведений вище приклад оброблявся в GIMP з кутовим градієнтом у формі кутової гаусії, насичення +80, контраст +20. (Я не знаю, які одиниці використовує GIMP для них)

Для отримання більшого натхнення або для того, щоб краще зрозуміти, чого ви намагаєтесь досягти, ознайомтесь із цим сайтом чи цим . Ви також можете шукати miniature fakingі tilt shift photographyприклади.


Це конкурс на популярність. Виборці, голосуйте за записи, які, на вашу думку, виглядають найкраще, залишаючись вірними цілі.


Пояснення:

Уточнюючи, які функції заборонені, я не мав наміру заборонити математичні функції. Це був мій намір заборонити функції маніпулювання зображеннями . Так, там є деякі перекриття, але такі речі, як FFT, згортки, матрична математика тощо, корисні у багатьох інших сферах. Ви не повинні використовувати функцію, яка просто знімає зображення і розмивається. Якщо ви знайдете відповідний матовий спосіб створити розмиття, це чесна гра.


Ця чудова демонстраційна демонстрація. Wolfram.com/DigitalTiltShiftPhotography про цифрову обробку зображення з нахилом (Yu-Sung Chang) передає безліч ідей про те, як регулювати контраст, яскравість та локальний фокус (в овальній чи прямокутній області фотографії). ) з використанням вбудованих функцій Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, і ImageAdjust.) Навіть за допомогою таких обробок зображень функцій високого рівня, код займає до 22 K. Код інтерфейсу користувача, тим не менш, дуже малий.
DavidC

5
Я повинен був сказати, «займає всього 22 к». Код за кадром, укладений у вищезазначені функції, настільки багато, що успішна відповідь на цей виклик повинна виявитись дуже і дуже складно досягти на більшості мов без використання виділених бібліотек для обробки зображень.
DavidC

Оновлення: це робилося в 2,5 к символів, щоб було ще ефективніше.
DavidC

1
@DavidCarraher Тому я чітко обмежив специфікацію. Не важко щось написати, щоб просто покрити специфікацію, як показано моєю базовою реалізацією нижче, розміщеною в 4,3 к символів нествореної Java . Я абсолютно не чекаю результатів на рівні професійної студії. Звичайно, все, що перевищує специфікацію (що призводить до кращих результатів), повинно бути відвертим, ІМО. Я погоджуюсь, що це не простий виклик, щоб досягти успіху , але цього не передбачалося. Мінімальні зусилля є основними, але "хороші" записи обов'язково будуть задіяні більше.
Геобіц

Інший алгоритм, який може поєднуватися з ними, щоб отримати ще більш переконливі "мініатюри", - це використовувати розкладання вейвлетів для фільтрації дрібних функцій із зображення, зберігаючи при цьому більші функції.
AJMansfield

Відповіді:


15

Java: Реалізація довідок

Ось основна посилання на реалізацію в Java. Він найкраще працює на знімках під високим кутом, і це жахливо неефективно.

Розмиття є дуже базовим розмиттям поля, тому воно перетинає ті самі пікселі набагато більше, ніж потрібно. Контраст і насиченість також можна поєднувати в одну петлю, але переважна більшість часу витрачається на розмиття, тому від цього не буде багато користі. Як було сказано, він працює досить швидко на зображеннях менше 2 Мп. 10MP-зображення зайняло деякий час.

Якість розмиття можна було легко поліпшити, використовуючи в основному все, крім розмивання плоскої коробки. Алгоритми контрастності / насичення виконують свою роботу, тому реальних скарг немає.

У програмі немає реального інтелекту. Він використовує постійні фактори для розмиття, насичення та контрасту. Я пограв з цим, щоб знайти щасливі середні налаштування. Як результат, є деякі сцени, які не дуже добре. Наприклад, він накачує контраст / насиченість настільки, що зображення з великими областями однакового кольору (думаю, небо) розпадаються на кольорові смуги.

Це простий у використанні; просто передайте ім'я файлу як єдиний аргумент. Він виводить у PNG незалежно від того, яким був вхідний файл.

Приклади:

З вибору меню, що випадає:

Ці перші зображення зменшуються для зручності публікації. Клацніть на зображення, щоб побачити його в повному розмірі.

Після:

введіть тут опис зображення

Перед:

введіть тут опис зображення

Інший вибір:

Після:

введіть тут опис зображення

Перед:

введіть тут опис зображення

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 #

Замість того, щоб робити будь-які ітеративні розмиття поля, я вирішив пройти весь шлях і написати розмиття Гаусса. Ці GetPixelвиклики дійсно уповільнити при використанні великих ядер, але це на самому справі не варто , щоб перетворити методи використання , LockBitsякщо ми не були обробляти кілька великих зображень.

Нижче наведено декілька прикладів, у яких використовуються встановлені я параметри настройки за замовчуванням (я не дуже грав з параметрами настройки, тому що вони, здається, добре працюють для тестового зображення).

Для тестового випадку передбачено ...

1-Оригінал 1-модифікований

Ще ...

2-Оригінал 2-модифікований

Ще ...

3-оригінал 3-модифіковані

Насиченість та збільшення контрасту повинні бути досить зрозумілими від коду. Я роблю це в просторі HSL і перетворюю його назад в RGB.

Ядро 2D Gaussian генерується на основі розміру nвказано, з:

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

... і нормалізується після призначення всіх значень ядра. Зауважте, що A=sigma_x=sigma_y=1.

Для того, щоб визначити, де застосувати ядро, я використовую вагу розмиття, обчислену:

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

... що дає гідну реакцію, по суті створюючи еліпс значень, захищених від розмиття, який поступово згасає далі. Смуговий фільтр у поєднанні з іншими рівняннями (можливо, якийсь варіант y=-x^2) може потенційно працювати краще для певних зображень. Я поїхав з косинусом, тому що він дав хорошу відповідь для базового тесту, який я перевіряв.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace FakeMini
{
    static class Program
    {
        static void Main()
        {
            // Some tuning variables
            double saturationValue = 1.7;
            double contrastValue = 1.2;
            int gaussianSize = 13; // Must be odd and >1 (3, 5, 7...)

            // NxN Gaussian kernel
            int padding = gaussianSize / 2;
            double[,] kernel = GenerateGaussianKernel(gaussianSize);

            Bitmap src = null;
            using (var img = new Bitmap(File.OpenRead("in.jpg")))
            {
                src = new Bitmap(img);
            }

            // Bordering could be avoided by reflecting or wrapping instead
            // Also takes advantage of the fact that a new bitmap returns zeros from GetPixel
            Bitmap border = new Bitmap(src.Width + padding*2, src.Height + padding*2);

            // Get average intensity of entire image
            double intensity = 0;
            for (int x = 0; x < src.Width; x++)
            {
                for (int y = 0; y < src.Height; y++)
                {
                    intensity += src.GetPixel(x, y).GetBrightness();
                }
            }
            double averageIntensity = intensity / (src.Width * src.Height);

            // Modify saturation and contrast
            double brightness;
            double saturation;
            for (int x = 0; x < src.Width; x++)
            {
                for (int y = 0; y < src.Height; y++)
                {
                    Color oldPx = src.GetPixel(x, y);
                    brightness = oldPx.GetBrightness();
                    saturation = oldPx.GetSaturation() * saturationValue;

                    Color newPx = FromHSL(
                                oldPx.GetHue(),
                                Clamp(saturation, 0.0, 1.0),
                                Clamp(averageIntensity - (averageIntensity - brightness) * contrastValue, 0.0, 1.0));
                    src.SetPixel(x, y, newPx);
                    border.SetPixel(x+padding, y+padding, newPx);
                }
            }

            // Apply gaussian blur, weighted by corresponding sine value based on height
            double blurWeight;
            Color oldColor;
            Color newColor;
            for (int x = padding; x < src.Width+padding; x++)
            {
                for (int y = padding; y < src.Height+padding; y++)
                {
                    oldColor = border.GetPixel(x, y);
                    newColor = Convolve2D(
                        kernel,
                        GetNeighbours(border, gaussianSize, x, y)
                       );

                    // sqrt([cos(pi*x_norm)^2 + cos(pi*y_norm)^2]/2) gives a decent response
                    blurWeight = Clamp(Math.Sqrt(
                        Math.Pow(Math.Cos(Math.PI * (y - padding) / src.Height), 2) +
                        Math.Pow(Math.Cos(Math.PI * (x - padding) / src.Width), 2)/2.0), 0.0, 1.0);
                    src.SetPixel(
                        x - padding,
                        y - padding,
                        Color.FromArgb(
                            Convert.ToInt32(Math.Round(oldColor.R * (1 - blurWeight) + newColor.R * blurWeight)),
                            Convert.ToInt32(Math.Round(oldColor.G * (1 - blurWeight) + newColor.G * blurWeight)),
                            Convert.ToInt32(Math.Round(oldColor.B * (1 - blurWeight) + newColor.B * blurWeight))
                            )
                        );
                }
            }
            border.Dispose();

            // Configure some save parameters
            EncoderParameters ep = new EncoderParameters(3);
            ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
            ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int)EncoderValue.ScanMethodInterlaced);
            ep.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int)EncoderValue.RenderProgressive);
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
            ImageCodecInfo ici = null;
            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.MimeType == "image/jpeg")
                    ici = codec;
            }
            src.Save("out.jpg", ici, ep);
            src.Dispose();
        }

        // Create RGB from HSL
        // (C# BCL allows me to go one way but not the other...)
        private static Color FromHSL(double h, double s, double l)
        {
            int h0 = Convert.ToInt32(Math.Floor(h / 60.0));
            double c = (1.0 - Math.Abs(2.0 * l - 1.0)) * s;
            double x = (1.0 - Math.Abs((h / 60.0) % 2.0 - 1.0)) * c;
            double m = l - c / 2.0;
            int m0 = Convert.ToInt32(255 * m);
            int c0 = Convert.ToInt32(255*(c + m));
            int x0 = Convert.ToInt32(255*(x + m));
            switch (h0)
            {
                case 0:
                    return Color.FromArgb(255, c0, x0, m0);
                case 1:
                    return Color.FromArgb(255, x0, c0, m0);
                case 2:
                    return Color.FromArgb(255, m0, c0, x0);
                case 3:
                    return Color.FromArgb(255, m0, x0, c0);
                case 4:
                    return Color.FromArgb(255, x0, m0, c0);
                case 5:
                    return Color.FromArgb(255, c0, m0, x0);
            }
            return Color.FromArgb(255, m0, m0, m0);
        }

        // Just so I don't have to write "bool ? val : val" everywhere
        private static double Clamp(double val, double min, double max)
        {
            if (val >= max)
                return max;
            else if (val <= min)
                return min;
            else
                return val;
        }

        // Simple convolution as C# BCL doesn't appear to have any
        private static Color Convolve2D(double[,] k, Color[,] n)
        {
            double r = 0;
            double g = 0;
            double b = 0;
            for (int i=0; i<k.GetLength(0); i++)
            {
                for (int j=0; j<k.GetLength(1); j++)
                {
                    r += n[i,j].R * k[i,j];
                    g += n[i,j].G * k[i,j];
                    b += n[i,j].B * k[i,j];
                }
            }
            return Color.FromArgb(
                Convert.ToInt32(Math.Round(r)),
                Convert.ToInt32(Math.Round(g)),
                Convert.ToInt32(Math.Round(b)));
        }

        // Generates a simple 2D square (normalized) Gaussian kernel based on a size
        // No tuning parameters - just using sigma = 1 for each
        private static double [,] GenerateGaussianKernel(int n)
        {
            double[,] kernel = new double[n, n];
            double currentValue;
            double normTotal = 0;
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    currentValue = Math.Exp(-(Math.Pow(i - n / 2, 2) + Math.Pow(j - n / 2, 2)) / 2.0);
                    kernel[i, j] = currentValue;
                    normTotal += currentValue;
                }
            }
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    kernel[i, j] /= normTotal;
                }
            }
            return kernel;
        }

        // Gets the neighbours around the current pixel
        private static Color[,] GetNeighbours(Bitmap bmp, int n, int x, int y)
        {
            Color[,] neighbours = new Color[n, n];
            for (int i = -n/2; i < n-n/2; i++)
            {
                for (int j = -n/2; j < n-n/2; j++)
                {
                    neighbours[i+n/2, j+n/2] = bmp.GetPixel(x + i, y + j);
                }
            }
            return neighbours;
        }
    }
}

9

Java

Використовує швидке середнє двостороннє розмиття, щоб бути досить швидким для запуску декількох проходів, імітуючи розмиття Гаусса. Розмиття - це еліптичний градієнт, а не дволінійний.

Візуально він найкраще працює на великих зображеннях. Має більш темну, грандіозну тему. Я задоволений тим, як розмиття виявилося на зображеннях відповідного розміру, досить поступово і важко розрізнити, де воно починається.

Всі обчислення, проведені на масивах цілих чисел або подвійних (для HSV).

Очікує шлях аргументу як аргумент, виводить файл у те саме місце із суфіксом "miniaturized.png" Також відображає вхід та вихід у JFrame для негайного перегляду.

(натисніть, щоб побачити великі версії, вони набагато кращі)

Перед:

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

Після:

введіть тут опис зображення

Можливо, мені доведеться додати більш розумне відображення тонів або збереження луми, оскільки воно може стати досить темним:

Перед:

введіть тут опис зображення

Після:

введіть тут опис зображення

І все-таки цікаво, але це створює абсолютно нову атмосферу.

Кодекс:

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

Це було приємним викликом. Налаштування розмитості, насиченості та контрастності жорстко закодовані, але їх можна легко змінити за бажанням. Однак область у фокусі жорстко кодована як горизонтальна лінія в центрі. Його не можна просто змінити, як інші налаштування. Він був обраний, оскільки більшість тестових зображень мають вигляд у місті.

Виконавши розмиття Гаусса, я розділив зображення по горизонталі на 5 областей. Верхня та нижня області отримають 100% розмиття. Середня область отримає 0% розмитості. Обидві інші області будуть масштабуватися пропорційно оберненому кубу від 0% до 100%.

Код повинен використовуватися як сценарій у J, і цей скрипт буде знаходитися в тій самій папці, input.bmpщо і вхідне зображення. Це створить, output.bmpщо буде фальшивою мініатюрою вводу.

Продуктивність хороша, і на моєму ПК, використовуючи i7-4770k, потрібно тривати приблизно 20 секунд, щоб обробити зображення з набору ОП. Раніше знадобилося приблизно 70 секунд, щоб обробити зображення, використовуючи стандартну згортку з ;._3оператором subarray. Продуктивність була покращена за допомогою FFT для проведення згортки.

Петля Loop-Mini Місто City-Mini

Цей код вимагає встановлення bmpта math/fftwаддонів. Ви можете встановити їх за допомогою install 'bmp'та install 'math/fftw'. Можливо, ваша система також потребує встановлення fftwбібліотеки.

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 ''
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.