ミニチュアフェイク


26

アマチュアの写真家なら誰でも言うことができるように、極端な後処理は常に適切です。そのような手法の1つは「ミニチュアフェイク」と呼ばれます。

その目的は、画像をそれ自体の小型化された玩具バージョンの写真のように見せることです。これは、被写体の高さのばらつきが低く、地面に対して中程度/高い角度から撮影された写真に最適ですが、他の画像にさまざまな効果で適用できます。

課題:写真を撮り、それにミニチュア偽造アルゴリズムを適用します。これを行うには多くの方法がありますが、この課題の目的のために要約すると、次のようになります。

  • 選択的なぼかし

    浅い被写界深度をシミュレートするには、画像の一部をぼかす必要があります。これは通常、線形であれ形状であれ、何らかの勾配に沿って行われます。好きなぼかし/グラデーションアルゴリズムを選択しますが、画像の15〜85%に「認識可能な」ぼかしが必要です。

  • 彩度ブースト

    色を上げて、手で描いたように見えるようにします。入力と比較した場合、出力の平均飽和レベルは+ 5%を超える必要があります。(HSV飽和を使用)

  • コントラストブースト

    コントラストを上げて、より厳しい照明条件をシミュレートします(太陽ではなく屋内/スタジオの照明で見る場合など)。入力と比較すると、出力のコントラストは+ 5%を超える必要があります。(RMSアルゴリズムを使用)

これらの3つの変更を実装する必要があり、他の拡張/変更は許可されません。トリミング、シャープニング、ホワイトバランスの調整なし。

  • 入力は画像であり、ファイルまたはメモリから読み取ることができます。外部ライブラリを使用してイメージを読み書きできます、それらを使用してイメージを処理することはできません。提供された関数もこの目的のために許可されていません(Image.blur()たとえば、単に呼び出すことはできません)

  • 他の入力はありません。処理の強度、レベルなどは、人間ではなくプログラムによって決定される必要があります。

  • 出力は、標準化された画像形式(PNG、BMPなど)のファイルとして表示または保存できます。

  • 一般化してみてください。1つのイメージだけで機能するべきではありませんが、すべてのイメージで機能するわけではないことは理解できます。一部のシーンは、アルゴリズムがどれほど優れていても、この手法に単純に反応しません。答えるときも答えるときも、ここに常識を適用してください。

  • 無効な入力、および仕様を満たすことが不可能な画像の動作は未定義です。たとえば、グレースケール画像を飽和させることはできません(ベースの色相はありません)、純粋な白の画像はコントラストを上げることができません。

  • 回答に少なくとも2つの出力画像を含めます。

    このドロップボックスフォルダー内のいずれかの画像から生成する必要があります。選択肢は6つありますが、さまざまな程度ですべて実行可能にしようとしました。各example-outputsサブフォルダーのサンプル出力を見ることができます。これらは完全な10MP JPG画像であり、カメラから直接出ているため、作業するピクセルがたくさんあることに注意してください。

    もう1つは、任意の画像にすることができます。明らかに、自由に使用できる画像を選択してください。また、比較のために、元の画像またはそれへのリンクを含めます。


たとえば、この画像から:

元の

次のようなものを出力できます。

処理済み

参考までに、上記の例は、GIMPで処理され、角箱型の勾配ガウスぼかし、彩度+80、コントラスト+20で処理されています。(GIMPがそれらに使用する単位がわかりません)

より多くのインスピレーションを得るため、またはあなたが達成しようとしているものをより良く理解するために、このサイトまたはこのサイトをチェックしてください。例の検索miniature fakingや検索もできtilt shift photographyます。


これは人気コンテストです。投票者は、目的に忠実でありながら、最も見栄えが良いと思うエントリーに投票してください。


明確化:

許可されていない関数を明確にしましたが、数学関数を禁止するつもりはありませんでした。私の意図は、画像操作機能を禁止することでした。はい、そこにはいくつかの重複がありますが、FFT、畳み込み、行列演算などのようなものは、他の多くの分野で役立ちます。単に画像を取得してぼかしをかける関数を使用しないでください。ブラーを作成するための適切な方法が見つかった場合、その公正なゲームです。


この注目すべきデモンストレーションデモンストレーション .Yul-Sung Changによるデジタルチルトシフト画像処理のwolfram.com/DigitalTiltShiftPhotographyは、コントラスト、明るさ、ローカルフォーカス(写真の楕円形または長方形の領域内)を調整する方法に関する豊富なアイデアを伝えています)を用いて、内蔵のMathematicaの関数(GeometricTransformationDistanceTransformImageAddColorNegateImageMultiplyRasterize、およびImageAdjust。)であっても、このような高いレベルの画像処理機能の助けを借りて、コードが22 Kをとります。それにもかかわらず、ユーザーインターフェイスのコードは非常に小さいです。
DavidC 14

5
たった 22 k しか占有しない」と言うべきでした。上記の関数にカプセル化された舞台裏のコードが非常に多いため、専用の画像処理ライブラリを使用せずにほとんどの言語でこの課題に対する成功した応答を達成することは非常に困難であることがわかります。
DavidC 14

更新:2.5 k文字で行われたため、さらに効率的でした。
DavidC 14

1
@DavidCarraherそのため、仕様を明示的に制限しました。仕様をカバーするだけのものを書くことは難しくありません。以下の参照実装は、未開発のJavaの 4.3 k文字で示しています。私はプロのスタジオレベルの結果を期待していません。もちろん、仕様を超えるもの(より良い結果をもたらすもの)は心から賛成する必要があります、IMO。これは簡単なことではありませんが、そうするつもりはありませんでした。最小限の努力は基本的ですが、「良い」エントリは必然的により複雑になります。
ジオビット14

これらと組み合わせてさらに説得力のある「ミニチュア」を生成できる別のアルゴリズムは、ウェーブレット分解を使用して、大きな特徴を鮮明に保ちながら、画像から小さな特徴をフィルタリングすることです。
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-オリジナル 1-修正

別の...

2-オリジナル 2-修正

別の...

3-オリジナル 3-修正

彩度とコントラストの増加は、コードからかなり簡単です。これをHSLスペースで行い、RGBに変換し直します。

2Dガウシアンカーネルはサイズに基づいて生成される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%を受け取ります。残りの2つの領域は、両方とも0%から100%の逆立方体に比例してスケーリングします。

コードはJでスクリプトとして使用され、そのスクリプトはinput.bmp入力イメージと同じフォルダーにあります。入力の偽のミニチュアoutput.bmpなるものを作成します。

パフォーマンスは良好で、i7-4770kを使用する私のPCでは、OPのセットからイメージを処理するのに約20秒かかります。以前は、;._3サブアレイ演算子を使用した標準の畳み込みを使用して画像を処理するのに約70秒かかりました。FFTを使用して畳み込みを実行することにより、パフォーマンスが向上しました。

ループ ループミニ シティ シティミニ

このコードには、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 ''
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.