Giả mạo thu nhỏ


26

Như bất kỳ nhiếp ảnh gia nghiệp dư nào cũng có thể nói với bạn, xử lý hậu kỳ cực kỳ tốt. Một kỹ thuật như vậy được gọi là " giả mạo thu nhỏ ".

Đối tượng là làm cho một hình ảnh trông giống như một bức ảnh của một phiên bản đồ chơi thu nhỏ của chính nó. Điều này hoạt động tốt nhất cho các bức ảnh được chụp từ góc trung bình / cao so với mặt đất, với độ chênh lệch thấp về chiều cao chủ thể, nhưng có thể được áp dụng với hiệu quả khác nhau đối với các hình ảnh khác.

Thách thức: Chụp ảnh và áp dụng thuật toán giả mạo thu nhỏ cho nó. Có nhiều cách để làm điều này, nhưng với mục đích của thử thách này, nó thực hiện theo:

  • Chọn lọc mờ

    Một phần của hình ảnh nên được làm mờ để mô phỏng độ sâu trường ảnh nông. Điều này thường được thực hiện dọc theo một số gradient, cho dù tuyến tính hoặc hình. Chọn bất kỳ thuật toán làm mờ / độ dốc nào bạn thích, nhưng từ 15-85% hình ảnh phải có độ mờ "đáng chú ý".

  • Tăng độ bão hòa

    Bơm lên màu sắc để làm cho mọi thứ xuất hiện chúng được vẽ bằng tay. Đầu ra phải có mức bão hòa trung bình> + 5% khi so sánh với đầu vào. (sử dụng bão hòa HSV )

  • Tương phản Boost

    Tăng độ tương phản để mô phỏng các điều kiện ánh sáng khắc nghiệt hơn (như bạn nhìn thấy với ánh sáng trong nhà / phòng thu hơn là mặt trời). Đầu ra phải có độ tương phản> + 5% khi so sánh với đầu vào. (sử dụng thuật toán RMS )

Ba thay đổi đó phải được thực hiện và không được phép cải tiến / thay đổi nào khác. Không cắt xén, làm sắc nét, điều chỉnh cân bằng trắng, không có gì.

  • Đầu vào là một hình ảnh và có thể được đọc từ một tập tin hoặc bộ nhớ. Bạn có thể sử dụng các thư viện bên ngoài để đọc và viết hình ảnh, nhưng bạn không thể sử dụng chúng để xử lý hình ảnh. Các chức năng được cung cấp cũng không được phép cho mục đích này ( Image.blur()ví dụ bạn không thể gọi )

  • Không có đầu vào khác. Các cường độ xử lý, cấp độ, v.v., phải được xác định bởi chương trình, không phải bởi con người.

  • Đầu ra có thể được hiển thị hoặc lưu dưới dạng tệp ở định dạng hình ảnh được tiêu chuẩn hóa (PNG, BMP, v.v.).

  • Cố gắng khái quát hóa. Nó không chỉ hoạt động trên một hình ảnh, nhưng có thể hiểu rằng nó sẽ không hoạt động trên tất cả các hình ảnh. Một số cảnh chỉ đơn giản là không đáp ứng tốt với kỹ thuật này, cho dù thuật toán có tốt đến đâu. Áp dụng ý thức chung ở đây, cả khi trả lời và bỏ phiếu cho câu trả lời.

  • Hành vi không được xác định cho các đầu vào không hợp lệ và những hình ảnh không thể đáp ứng thông số kỹ thuật. Ví dụ, hình ảnh thang độ xám không thể bão hòa (không có màu sắc cơ sở), hình ảnh màu trắng tinh khiết không thể có độ tương phản tăng, v.v.

  • Bao gồm ít nhất hai hình ảnh đầu ra trong câu trả lời của bạn:

    Một phải được tạo từ một trong những hình ảnh trong thư mục dropbox này . Có sáu để lựa chọn, nhưng tôi đã cố gắng làm cho tất cả chúng khả thi ở các mức độ khác nhau. Bạn có thể thấy các đầu ra mẫu cho từng trong example-outputsthư mục con. Xin lưu ý rằng đây là những hình ảnh JPG 10MP đầy đủ ngay từ máy ảnh, vì vậy bạn có rất nhiều pixel để làm việc.

    Cái khác có thể là bất kỳ hình ảnh của sự lựa chọn của bạn. Rõ ràng, cố gắng chọn hình ảnh có thể sử dụng tự do. Ngoài ra, bao gồm hình ảnh gốc hoặc liên kết đến nó để so sánh.


Ví dụ: từ hình ảnh này:

nguyên

Bạn có thể xuất ra một cái gì đó như:

xử lý

Để tham khảo, ví dụ trên đã được xử lý trong GIMP với độ mờ gaussian hình hộp góc, độ bão hòa +80, độ tương phản +20. (Tôi không biết GIMP sử dụng đơn vị nào cho những đơn vị đó)

Để có thêm cảm hứng, hoặc để có được ý tưởng tốt hơn những gì bạn đang cố gắng đạt được, hãy xem trang web này hoặc trang này . Bạn cũng có thể tìm kiếm miniature fakingtilt shift photographycho ví dụ.


Đây là một cuộc thi phổ biến. Cử tri, bỏ phiếu cho các mục bạn cảm thấy tốt nhất trong khi vẫn trung thực với mục tiêu.


Làm rõ:

Làm rõ những chức năng nào không được phép, đó không phải là ý định của tôi để cấm các chức năng toán học . Đó là ý định của tôi để cấm chức năng thao tác hình ảnh . Vâng, có một số chồng chéo ở đó, nhưng những thứ như FFT, kết luận, toán ma trận, v.v., rất hữu ích trong nhiều lĩnh vực khác. Bạn không nên sử dụng một chức năng chỉ đơn giản là chụp ảnh và làm mờ. Nếu bạn tìm thấy một cách toán học phù hợp để tạo ra một vệt mờ, trò chơi công bằng đó.


Đây trình diễn đáng chú ý demonstrations.wolfram.com/DigitalTiltShiftPhotography trên Digital Nghiêng-Shift Xử lý hình ảnh, bởi Yu-Sung Chang, truyền đạt một sự giàu có của những ý tưởng về làm thế nào để điều chỉnh độ tương phản, độ sáng, và tập trung địa phương (trong một khu vực hình bầu dục hoặc hình chữ nhật của bức ảnh ) sử dụng built-in chức năng của Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, Rasterize, và ImageAdjust.) Ngay cả với sự giúp đỡ của các chức năng xử lý hình ảnh cao cấp như vậy, mã chiếm 22 k. Mã cho giao diện người dùng dù sao cũng rất nhỏ.
DavidC

5
Tôi nên nói " chỉ mất 22 k". Có rất nhiều mã hậu trường được gói gọn trong các chức năng được đề cập ở trên mà một phản ứng thành công cho thách thức này sẽ chứng minh rất, rất khó đạt được trong hầu hết các ngôn ngữ mà không sử dụng các thư viện xử lý hình ảnh chuyên dụng.
DavidC

Cập nhật: nó được thực hiện trong 2,5 k ký tự để nó thậm chí còn hiệu quả hơn.
DavidC

1
@DavidCarraher Đó là lý do tại sao tôi giới hạn rõ ràng thông số kỹ thuật. Không khó để viết một cái gì đó chỉ để trình bày thông số kỹ thuật, vì triển khai tham chiếu của tôi dưới đây cho thấy trong 4,3 k ký tự của Java không được mã hóa . Tôi hoàn toàn không mong đợi kết quả cấp độ chuyên nghiệp. Tất nhiên, bất cứ điều gì vượt quá thông số kỹ thuật (dẫn đến kết quả tốt hơn) nên được nâng cấp một cách chân thành, IMO. Tôi đồng ý rằng đây không phải là một thử thách đơn giản để vượt trội , nhưng nó không có nghĩa là như vậy. Nỗ lực tối thiểu là cơ bản, nhưng các mục "tốt" sẽ nhất thiết phải được tham gia nhiều hơn.
Geobits

Một thuật toán khác có thể được kết hợp với những thuật toán này để tạo ra các "thu nhỏ" thậm chí còn thuyết phục hơn là sử dụng phân tách sóng con để lọc các tính năng nhỏ khỏi hình ảnh, trong khi vẫn giữ các tính năng lớn hơn sắc nét.
AJMansfield

Câu trả lời:


15

Java: Tham chiếu thực hiện

Đây là một triển khai tham chiếu cơ bản trong Java. Nó hoạt động tốt nhất trên các góc chụp cao, và nó không hiệu quả khủng khiếp.

Làm mờ là làm mờ hộp rất cơ bản, do đó, nó lặp lại trên cùng các pixel nhiều hơn mức cần thiết. Độ tương phản và độ bão hòa cũng có thể được kết hợp thành một vòng lặp duy nhất, nhưng phần lớn thời gian sử dụng là mờ, vì vậy nó sẽ không thu được nhiều lợi ích từ đó. Điều đó đang được nói, nó hoạt động khá nhanh trên hình ảnh dưới 2MP hoặc hơn. Hình ảnh 10MP mất một thời gian để hoàn thành.

Chất lượng mờ có thể dễ dàng được cải thiện bằng cách sử dụng bất cứ thứ gì về cơ bản ngoại trừ mờ hộp phẳng. Các thuật toán tương phản / bão hòa làm công việc của họ, vì vậy không có khiếu nại thực sự ở đó.

Không có trí thông minh thực sự trong chương trình. Nó sử dụng các yếu tố không đổi cho độ mờ, độ bão hòa và độ tương phản. Tôi chơi xung quanh nó với nó để tìm các thiết lập trung bình hạnh phúc. Kết quả là, có rất một số cảnh mà nó không làm rất tốt. Chẳng hạn, nó bơm độ tương phản / bão hòa đến mức các hình ảnh có diện tích lớn tương tự (nghĩ bầu trời) bị vỡ thành các dải màu.

Thật đơn giản để sử dụng; chỉ cần truyền tên tập tin vào làm đối số duy nhất. Nó xuất ra ở định dạng PNG bất kể tập tin đầu vào là gì.

Ví dụ:

Từ lựa chọn dropbox:

Những hình ảnh đầu tiên được thu nhỏ lại để dễ đăng. Nhấn vào tấm ảnh để có thể nhìn thấy kích cỡ lớn nhất.

Sau:

nhập mô tả hình ảnh ở đây

Trước:

nhập mô tả hình ảnh ở đây

Lựa chọn linh tinh:

Sau:

nhập mô tả hình ảnh ở đây

Trước:

nhập mô tả hình ảnh ở đây

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 #

Thay vì làm mờ bất kỳ hộp lặp đi lặp lại, tôi quyết định đi toàn bộ con đường và viết một vệt mờ Gaussian. Các GetPixelcuộc gọi thực sự làm chậm nó khi sử dụng các hạt nhân lớn, nhưng nó không thực sự đáng để chuyển đổi các phương thức để sử dụng LockBitstrừ khi chúng tôi đang xử lý một số hình ảnh lớn hơn.

Một số ví dụ dưới đây, sử dụng các tham số điều chỉnh mặc định mà tôi đã đặt (Tôi không chơi nhiều với các tham số điều chỉnh, vì chúng dường như hoạt động tốt cho hình ảnh thử nghiệm).

Đối với trường hợp thử nghiệm được cung cấp ...

1-Bản gốc 1-Sửa đổi

Khác...

2-Bản gốc 2-Sửa đổi

Khác...

3-Bản gốc 3-Sửa đổi

Độ bão hòa và độ tương phản tăng khá đơn giản từ mã. Tôi làm điều này trong không gian HSL và chuyển đổi nó trở lại RGB.

Các kernel 2D Gaussian được tạo ra dựa trên kích thước nquy định, với:

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

... và được chuẩn hóa sau khi tất cả các giá trị kernel được gán. Lưu ý rằng A=sigma_x=sigma_y=1.

Để tìm ra nơi áp dụng kernel, tôi sử dụng trọng số mờ, được tính bằng:

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

... cung cấp một phản hồi tốt, về cơ bản tạo ra một hình elip của các giá trị được bảo vệ khỏi mờ dần dần mờ dần. Bộ lọc thông dải kết hợp với các phương trình khác (có thể là một số biến thể của y=-x^2) có khả năng hoạt động tốt hơn ở đây đối với một số hình ảnh nhất định. Tôi đã đi với cosin vì nó đã cho một phản ứng tốt cho trường hợp cơ sở mà tôi đã thử nghiệm.

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

Sử dụng hiệu ứng làm mờ hộp hai chiều trung bình chạy nhanh để đủ tốc độ để chạy nhiều đường chuyền, mô phỏng độ mờ Gaussian. Blur là một gradient hình elip thay vì bi-linear là tốt.

Trực quan, nó hoạt động tốt nhất trên hình ảnh lớn. Có một chủ đề tối hơn, grungier. Tôi hài lòng với cách làm mờ các hình ảnh có kích thước phù hợp, nó khá từ từ và khó nhận ra nơi nó "bắt đầu".

Tất cả các tính toán được thực hiện trên mảng các số nguyên hoặc nhân đôi (đối với HSV).

Yêu cầu đường dẫn tệp làm đối số, xuất tệp ra cùng một vị trí với hậu tố "miniaturized.png" Cũng hiển thị đầu vào và đầu ra trong JFrame để xem ngay lập tức.

(bấm vào để xem các phiên bản lớn, chúng tốt hơn)

Trước:

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

Sau:

nhập mô tả hình ảnh ở đây

Tôi có thể phải thêm một số ánh xạ giai điệu thông minh hơn hoặc bảo toàn luma, vì nó có thể trở nên khá tối:

Trước:

nhập mô tả hình ảnh ở đây

Sau:

nhập mô tả hình ảnh ở đây

Mặc dù vậy, vẫn thú vị, đặt nó trong một bầu không khí hoàn toàn mới.

Mật mã:

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

Đây là một thử thách tốt đẹp. Các cài đặt cho mờ, bão hòa và độ tương phản được mã hóa cứng nhưng có thể dễ dàng thay đổi nếu muốn. Tuy nhiên, khu vực trong tiêu điểm được mã hóa cứng là một đường ngang ở trung tâm. Nó không thể được sửa đổi đơn giản như các cài đặt khác. Nó được chọn vì hầu hết các hình ảnh thử nghiệm đều có chế độ xem trên toàn thành phố.

Sau khi thực hiện làm mờ Gaussian, tôi chia hình ảnh theo chiều ngang thành 5 vùng. Các khu vực trên cùng và dưới cùng sẽ nhận được 100% mờ. Vùng giữa sẽ nhận được 0% mờ. Hai khu vực còn lại sẽ quy mô tỷ lệ thuận với khối nghịch đảo từ 0% đến 100%.

Mã này sẽ được sử dụng làm tập lệnh trong J và tập lệnh đó sẽ nằm trong cùng thư mục với input.bmphình ảnh đầu vào. Nó sẽ tạo ra output.bmpmột hình thu nhỏ giả của đầu vào.

Hiệu suất rất tốt và trên máy tính của tôi sử dụng i7-4770k, mất khoảng 20 giây để xử lý hình ảnh từ bộ OP. Trước đây, mất khoảng 70 giây để xử lý hình ảnh bằng cách sử dụng tích chập tiêu chuẩn với ;._3toán tử subarray. Hiệu suất đã được cải thiện bằng cách sử dụng FFT để thực hiện tích chập.

Vòng lặp Vòng lặp nhỏ Thành phố Thành phố nhỏ

Mã này yêu cầu bmpmath/fftwaddons được cài đặt. Bạn có thể cài đặt chúng bằng cách sử dụng install 'bmp'install 'math/fftw'. Hệ thống của bạn cũng có thể cầnfftw thư viện để được cài đặt.

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 ''
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.