미니어처


26

아마추어 사진 작가가 말할 수 있듯이 극단적 인 후 처리는 항상 좋습니다. 이러한 기술 중 하나를 " 미니어처 페이 킹 "이라고합니다.

그 목적은 이미지를 소형 장난감 버전 자체의 사진처럼 보이게하는 것입니다. 이 기능은 피사체 높이의 편차가 적은 중간 / 높은 각도에서지면으로 촬영 한 사진에 가장 적합하지만 다른 이미지에 다양한 효과를 적용 할 수 있습니다.

도전 과제 : 사진을 찍고 미니어처 가짜 알고리즘을 적용하십시오. 이를 수행하는 방법은 여러 가지가 있지만이 과제의 목적을 위해 다음과 같이 요약됩니다.

  • 선택적 흐림

    얕은 피사계 심도를 시뮬레이션하려면 이미지의 일부를 흐리게 처리해야합니다. 이것은 일반적으로 선형이든 모양이든 약간의 기울기를 따라 수행됩니다. 원하는 블러 / 그라디언트 알고리즘을 선택하십시오. 그러나 이미지의 15-85 % 사이에 "눈에 띄는"블러가 있어야합니다.

  • 채도 향상

    손으로 칠한 물건이 보이도록 색상을 펌핑하십시오. 출력은 입력과 비교할 때 평균 채도 수준> + 5 % 여야합니다. ( HSV 채도 사용 )

  • 대비 부스트

    가혹한 조명 조건 (예 : 태양이 아닌 실내 / 스튜디오 조명으로 보는 경우)을 시뮬레이션하기 위해 대비를 증가시킵니다. 출력은 입력과 비교할 때> + 5 %의 대비를 가져야합니다. ( RMS 알고리즘 사용 )

이 세 가지 변경 구현 해야 하며 다른 개선 / 변경은 허용되지 않습니다. 자르기, 선명 화, 화이트 밸런스 조정 등이 없습니다.

  • 입력은 이미지이며 파일이나 메모리에서 읽을 수 있습니다. 외부 라이브러리를 사용 하여 이미지 를 읽고 쓸 수 는 있지만 이미지 를 처리 하는 데 사용할 수는 없습니다 . 제공된 기능도이 목적으로 허용되지 않습니다 ( 예를 들어 호출 할 수는 없습니다 )Image.blur()

  • 다른 입력이 없습니다. 가공 강도, 레벨 등은 사람이 아니라 프로그램에 의해 결정되어야합니다.

  • 출력을 표준화 된 이미지 형식 (PNG, BMP 등)으로 파일로 표시하거나 저장할 수 있습니다.

  • 일반화하십시오. 하나의 이미지 에서만 작동 하지는 않지만 모든 이미지에서 작동하지는 않습니다 . 일부 장면은 알고리즘이 아무리 우수하더라도이 기술에 잘 반응하지 않습니다. 답변과 투표에 모두 상식을 적용하십시오.

  • 유효하지 않은 입력 및 사양을 충족 할 수없는 이미지에 대해서는 동작이 정의되지 않습니다. 예를 들어, 회색조 이미지는 포화 될 수 없으며 (기본 색조는 없음) 순수한 흰색 이미지는 대비를 증가시킬 수 없습니다.

  • 답에 두 개 이상의 출력 이미지를 포함하십시오.

    이 dropbox 폴더 의 이미지 중 하나에서 하나를 생성해야 합니다 . 선택할 수있는 여섯 가지가 있지만, 나는 그것들을 다양한 정도로 실행할 수 있도록 노력했습니다. example-outputs하위 폴더 에서 각각에 대한 샘플 출력을 볼 수 있습니다 . 이 이미지는 카메라에서 곧바로 전체 10MP JPG 이미지이므로 작업해야 할 픽셀이 많습니다.

    다른 하나는 선택한 이미지 일 수 있습니다. 분명히 자유롭게 사용할 수있는 이미지를 선택하십시오. 또한 원본 이미지 또는 링크를 포함시켜 비교할 수 있습니다.


예를 들어,이 이미지에서 :

기발한

다음과 같이 출력 할 수 있습니다.

가공

참고로, 위의 예는 각도 상자 모양의 그라데이션 가우시안 블러, 채도 +80, 대비 +20으로 김프에서 처리되었습니다. (김프가 어떤 유닛을 사용하는지는 모르겠습니다)

더 많은 영감을 얻거나 달성하려는 목표를 더 잘 이해하려면 이 사이트 또는 사이트를 확인하십시오 . 또한 검색 할 수 miniature fakingtilt shift photography예제.


이것은 인기 콘테스트입니다. 유권자 여러분, 목표에 충실하면서 가장 좋아 보이는 항목에 투표하십시오.


설명:

어떤 기능이 허용되지 않는지 명확히하는 것은 수학 기능 을 금지하려는 의도가 아닙니다 . 이미지 조작 기능 을 금지하려는 의도였습니다 . 예, 겹치는 부분이 있지만 FFT, 컨볼 루션, 행렬 수학 등과 같은 다른 많은 영역에서 유용합니다. 단순히 이미지를 찍고 흐리게하는 기능을 사용 해서는 안됩니다 . 블러 를 만드는 적절한 방법을 찾으면 공정한 게임입니다.


장 유성 (Yu-Sung Chang)의 디지털 틸트-시프트 이미지 처리에 관한 이 놀라운 데모 데모 는 사진의 타원형 또는 직사각형 영역 내에서 대비, 밝기 및 로컬 초점을 조정하는 방법에 대한 풍부한 아이디어를 전달합니다. )를 이용하여 내장 매쓰의 함수 ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, 및 ImageAdjust.)에도 높은 수준의 화상 처리 기능을 통해, 코드 22 (k)를 차지한다. 그럼에도 불구하고 사용자 인터페이스의 코드는 매우 작습니다.
DavidC

5
나는 " 22 k 차지한다"고 말 했어야했다 . 위에서 언급 한 기능으로 캡슐화 된 수많은 비하인드 코드가 있기 때문에 이러한 문제에 대한 성공적인 대응은 전용 이미지 처리 라이브러리를 사용하지 않고 대부분의 언어에서 달성하기가 매우 어렵습니다.
DavidC

업데이트 : 2.5k 문자로 수행되어 훨씬 효율적이었습니다.
DavidC

1
@DavidCarraher 이것이 스펙을 명시 적으로 제한 한 이유입니다. 아래의 참조 구현이 4.3k 문자의 ungolfed Java로 표시되므로 사양을 다루기 위해 무언가를 작성하는 것은 어렵지 않습니다 . 나는 전문 스튜디오 수준의 결과를 기대하지 않습니다. 물론, 사양을 초과하는 것 (더 나은 결과로 이어짐)은 IMO에 진심으로 찬성해야합니다. 나는 이것이 아니라고 동의 간단한 하는 도전 엑셀 에 있지만 될 운명이되지 않았다. 최소한의 노력이 기본이지만 "좋은"항목이 더 많이 관여합니다.
Geobits

이것들을 결합하여 훨씬 더 설득력있는 "미니어처"를 생성 할 수있는 또 다른 알고리즘은 잔물결 분해를 사용하여 이미지에서 작은 특징을 걸러 내고 더 큰 특징을 선명하게 유지하는 것입니다.
AJMansfield

답변:


15

자바 : 참조 구현

다음은 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

기음#

반복적 인 박스 블러를 수행하는 대신, 나는 길을 가고 가우시안 블러를 작성하기로 결정했습니다. 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

자바

빠른 달리기 평균 양방향 상자 흐림 효과를 사용하여 여러 패스를 실행할 수있을만큼 빠르므로 가우시안 흐림 효과를 모방합니다. 흐림은 이중 선형 대신 타원형 그라디언트입니다.

시각적으로 큰 이미지에서 가장 잘 작동합니다. 더 어둡고 지저분한 테마가 있습니다. 적절한 크기의 이미지에서 흐림 효과가 어떻게 나타 났는지에 만족합니다. "시작"위치를 식별하는 것은 매우 점진적이고 어렵습니다.

모든 계산은 정수 또는 이중 배열에서 수행됩니다 (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를 사용하는 PC에서는 OP 세트의 이미지를 처리하는 데 약 20 초가 걸립니다. 이전에는 ;._3하위 배열 연산자로 표준 컨벌루션을 사용하여 이미지를 처리하는 데 약 70 초가 걸렸습니다 . FFT를 사용하여 컨볼 루션을 수행하여 성능이 향상되었습니다.

고리 루프 미니 시티 시티 미니

이 코드를 사용하려면 bmpmath/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.