Миниатюрная подделка


26

Любой фотограф- любитель может сказать, что экстремальная пост-обработка всегда хороша. Одна из таких техник называется « миниатюрная фальсификация ».

Цель состоит в том, чтобы сделать изображение похожим на фотографию миниатюрной, игрушечной версии самого себя. Это лучше всего подходит для фотографий, снятых под умеренным / большим углом к ​​земле, с малой дисперсией высоты объекта, но может применяться с различной эффективностью к другим изображениям.

Задача: сделать фотографию и применить к ней миниатюрный алгоритм подделки. Есть много способов сделать это, но для целей этой задачи, это сводится к:

  • Выборочное размытие

    Некоторая часть изображения должна быть размытой, чтобы имитировать малую глубину резкости. Это обычно делается вдоль некоторого градиента, линейного или в форме. Выберите любой алгоритм размытия / градиента, который вам нравится, но между 15-85% изображения должно быть "заметное" размытие.

  • Повышение насыщенности

    Увеличьте цвет, чтобы вещи выглядели так, как будто они были нарисованы вручную. Выход должен иметь средний уровень насыщения> + 5% по сравнению с входом. (с использованием HSV насыщения )

  • Повышение контрастности

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

Эти три изменения должны быть реализованы, и никакие другие улучшения / изменения не допускаются. Нет обрезки, резкости, регулировки баланса белого, ничего.

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

  • Там нет другого ввода. Сила обработки, уровни и т. Д. Должны определяться программой, а не человеком.

  • Вывод может быть отображен или сохранен как файл в стандартном формате изображения (PNG, BMP и т. Д.).

  • Попробуйте обобщить. Он не должен работать только на одном изображении, но понятно, что он не будет работать на всех изображениях. Некоторые сцены просто плохо реагируют на эту технику, независимо от того, насколько хорош алгоритм. Примените здравый смысл здесь, как при ответе, так и при голосовании за ответы.

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

  • Включите в ответ как минимум два выходных изображения:

    Один должен быть создан из одного из изображений в этой папке Dropbox . Есть шесть на выбор, но я постарался сделать их жизнеспособными в разной степени. Вы можете увидеть примеры выходных данных для каждого в example-outputsподпапке. Обратите внимание, что это полные 10-мегапиксельные изображения JPG прямо из камеры, поэтому у вас есть много пикселей для работы.

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


Например, из этого изображения:

оригинал

Вы можете вывести что-то вроде:

обработанный

Для справки, приведенный выше пример был обработан в GIMP с угловым градиентом в виде прямоугольника размытия по Гауссу, насыщенность +80, контрастность +20. (Я не знаю, какие единицы использует GIMP для них)

Чтобы получить больше вдохновения или получить лучшее представление о том, чего вы хотите достичь, посетите этот сайт или этот . Вы также можете искать miniature fakingи tilt shift photographyдля примеров.


Это конкурс популярности. Избиратели, проголосуйте за записи, которые выглядят лучше всего, оставаясь верными цели.


Разъяснение:

Уточняя, какие функции запрещены, я не собирался запрещать математические функции. Моим намерением было запретить функции манипулирования изображениями . Да, там есть некоторые совпадения, но такие вещи, как БПФ, свертки, матричная математика и т. Д., Полезны во многих других областях. Вы не должны использовать функцию, которая просто берет изображение и размывает. Если вы найдете подходящий математический способ создать размытие, то это честная игра.


Эта замечательная демонстрация showrations.wolfram.com/DigitalTiltShiftPhotography Ю-Суна Чанга, посвященная цифровой обработке изображений с наклоном и сдвигом, дает множество идей о том, как регулировать контраст, яркость и локальную фокусировку (в овальной или прямоугольной области фотографии ) с использованием встроенных функций Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, и ImageAdjust.) Даже с помощью таких обработок изображений функций высокого уровня, код занимает до 22 K. Код для пользовательского интерфейса, тем не менее, очень маленький.
DavidC

5
Я должен был сказать "занимает всего 22 к". Существует так много закулисного кода, инкапсулированного в вышеупомянутых функциях, что успешный ответ на этот вызов окажется очень, очень трудным для достижения в большинстве языков без использования специальных библиотек обработки изображений.
DavidC

Обновление: это было сделано в 2.5 k символов, так что это было еще более эффективным.
DavidC

1
@DavidCarraher Вот почему я явно ограничил спецификацию. Нетрудно написать что-то, чтобы просто покрыть спецификацию, как показано ниже в моей эталонной реализации из 4,3 тыс. Символов неуправляемой Java . Я абсолютно не ожидаю профессиональных результатов на уровне студии. Разумеется, за все, что превышает технические требования (что ведет к лучшим результатам), следует горячо голосовать, ИМО. Я согласен с тем, что это непростая задача для достижения успеха , но это не так. Минимальные усилия являются базовыми, но «хорошие» записи обязательно будут более сложными.
Geobits

Другой алгоритм, который может быть объединен с ними для получения еще более убедительных «миниатюр», заключается в использовании вейвлет-разложения для фильтрации небольших объектов на изображении, сохраняя при этом большие объекты четкими.
AJMansfield

Ответы:


15

Java: эталонная реализация

Вот базовая справочная реализация в Java. Лучше всего работает на снимках под большим углом, и это ужасно неэффективно.

Размытие - это очень простое размытие в рамке, поэтому оно повторяет одни и те же пиксели гораздо больше, чем необходимо. Контрастность и насыщенность также могут быть объединены в один цикл, но подавляющее большинство времени затрачивается на размытие, поэтому от этого не получится большой выгоды. При этом, он работает довольно быстро на изображениях под 2MP или около того. Изображение 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-Original 1-Modified

Другая...

2-Original 2-Modified

Другая...

3-Original 3-Modified

Увеличение насыщенности и контрастности должно быть довольно простым из кода. Я делаю это в пространстве 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

Джава

Использует быструю среднюю двухстороннюю размытость, чтобы быть достаточно быстрой, чтобы выполнить несколько проходов, эмулируя размытие по Гауссу. Размытие - это эллиптический градиент, а не билинейный.

Визуально это лучше всего работает на больших изображениях. Имеет более темную, грубую тему. Я доволен тем, как размытие получилось на изображениях соответствующего размера, оно довольно постепенное и трудно различить, где оно «начинается».

Все вычисления выполняются на массивах целых или двойных чисел (для 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 секунд. Раньше обработка изображения с использованием стандартной свертки с ;._3оператором subarray занимала около 70 секунд . Производительность была улучшена с помощью FFT для выполнения свертки.

петля Loop-Mini город Город-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.