แกล้งทำจิ๋ว


26

ช่างภาพมือสมัครเล่นคนใดสามารถบอกคุณได้การโพสต์โพสต์ที่ดีเสมอ หนึ่งในเทคนิคดังกล่าวเรียกว่า "การจำลองขนาดเล็ก "

เป้าหมายคือการทำให้รูปภาพดูเหมือนรูปถ่ายขนาดย่อส่วนรุ่นของเล่นของตัวเอง วิธีนี้ใช้ได้ผลดีที่สุดสำหรับภาพถ่ายที่ถ่ายจากมุมปานกลาง / สูงถึงพื้นดินที่มีความแตกต่างต่ำในเรื่องความสูงของวัตถุ แต่สามารถนำไปใช้กับประสิทธิภาพที่แตกต่างกันไปในภาพอื่น ๆ

ความท้าทาย:ถ่ายภาพและใช้อัลกอริทึมแบบแกล้งทำกับมัน มีหลายวิธีในการทำเช่นนี้ แต่สำหรับจุดประสงค์ของการท้าทายนี้

  • เลือกเบลอ

    บางส่วนของภาพควรเบลอเพื่อจำลองความชัดลึกที่ตื้น โดยทั่วไปจะทำตามการไล่ระดับสีไม่ว่าจะเป็นแบบเชิงเส้นหรือแบบ เลือกอัลกอริทึมเบลอ / ไล่ระดับสีที่คุณชอบ แต่ระหว่าง 15-85% ของรูปภาพจะต้องมี "เบลอ" ที่เห็นได้ชัดเจน

  • เพิ่มความอิ่มตัว

    เร่งสีเพื่อทำให้สิ่งต่าง ๆ ปรากฏขึ้นโดยใช้มือวาด เอาต์พุตจะต้องมีระดับความอิ่มตัวโดยเฉลี่ย> + 5% เมื่อเปรียบเทียบกับอินพุต (ใช้ความอิ่มตัวของ HSV )

  • เพิ่มความคมชัด

    เพิ่มความคมชัดเพื่อจำลองสภาพแสงที่รุนแรงยิ่งขึ้น (เช่นที่คุณเห็นด้วยแสงในร่ม / สตูดิโอแทนที่จะเป็นดวงอาทิตย์) ผลลัพธ์จะต้องมีความแตกต่าง> + 5% เมื่อเปรียบเทียบกับอินพุต (ใช้อัลกอริทึม RMS )

การแก้ไขทั้งสามนั้นจะต้องดำเนินการและไม่อนุญาตให้มีการปรับปรุง / แก้ไขอื่นใด ไม่มีการครอบตัดความคมชัดการปรับสมดุลสีขาวไม่มีอะไรเกิดขึ้น

  • อินพุตเป็นรูปภาพและสามารถอ่านได้จากไฟล์หรือหน่วยความจำ คุณสามารถใช้ไลบรารีภายนอกเพื่ออ่านและเขียนภาพ แต่ไม่สามารถใช้เพื่อประมวลผลภาพได้ ฟังก์ชั่นที่ให้มานั้นไม่ได้รับอนุญาตสำหรับจุดประสงค์นี้ (คุณไม่สามารถเรียกImage.blur()ตัวอย่างได้)

  • ไม่มีอินพุตอื่น จุดแข็งของการประมวลผลระดับ ฯลฯ จะต้องถูกกำหนดโดยโปรแกรมไม่ใช่โดยมนุษย์

  • เอาต์พุตสามารถแสดงหรือบันทึกเป็นไฟล์ในรูปแบบภาพมาตรฐาน (PNG, BMP, ฯลฯ )

  • พยายามที่จะพูดคุย ไม่ควรใช้กับภาพเดียวแต่เข้าใจได้ว่าจะไม่ทำงานกับภาพทั้งหมด บางฉากก็ตอบสนองไม่ดีกับเทคนิคนี้ไม่ว่าอัลกอริทึมจะดีแค่ไหน ใช้สามัญสำนึกที่นี่ทั้งในการตอบและลงคะแนนให้กับคำตอบ

  • พฤติกรรมไม่ได้ถูกกำหนดไว้สำหรับอินพุตที่ไม่ถูกต้องและรูปภาพเหล่านั้นซึ่งไม่สามารถตอบสนองความต้องการได้ ตัวอย่างเช่นภาพระดับสีเทาไม่สามารถอิ่มตัว (ไม่มีฐานสี), ภาพสีขาวบริสุทธิ์ไม่สามารถเพิ่มความคมชัด ฯลฯ

  • รวมภาพที่ส่งออกอย่างน้อยสองภาพในคำตอบของคุณ:

    ต้องสร้างหนึ่งภาพจากหนึ่งในรูปภาพในโฟลเดอร์ดรอปบ็อกซ์นี้ มีหกให้เลือก แต่ฉันพยายามที่จะทำให้พวกเขาทั้งหมดไปได้องศาที่แตกต่าง คุณสามารถดูตัวอย่างผลลัพธ์สำหรับแต่ละรายการในexample-outputsโฟลเดอร์ย่อย โปรดทราบว่ารูปภาพเหล่านี้เป็นภาพ JPG ขนาด 10MP เต็มรูปแบบซึ่งอยู่นอกกล้องดังนั้นคุณจึงมีพิกเซลจำนวนมากที่ใช้งานได้

    อื่น ๆ สามารถเป็นภาพที่คุณเลือกได้ เห็นได้ชัดว่าพยายามเลือกภาพที่สามารถใช้งานได้อย่างอิสระ รวมถึงรูปภาพต้นฉบับหรือลิงก์ไปยังรูปภาพเพื่อเปรียบเทียบ


ตัวอย่างเช่นจากภาพนี้:

เป็นต้นฉบับ

คุณอาจส่งออกสิ่งที่ชอบ:

การประมวลผล

สำหรับการอ้างอิงตัวอย่างข้างต้นได้รับการประมวลผลใน GIMP ที่มีการเอียงลาดแบบเกาส์แบบกล่องมุมเชิงมุมความอิ่มตัว +80 ความคมชัด +20 (ฉันไม่ทราบว่าหน่วย GIMP ใช้สำหรับหน่วยใด)

สำหรับแรงบันดาลใจมากขึ้นหรือเพื่อให้ได้ความคิดที่ดีสิ่งที่คุณกำลังพยายามที่จะบรรลุตรวจสอบเว็บไซต์นี้หรืออย่างใดอย่างหนึ่ง คุณยังสามารถค้นหาminiature fakingและtilt shift photographyเป็นตัวอย่างได้


นี่คือการประกวดความนิยม ผู้ลงคะแนนออกเสียงลงคะแนนสำหรับรายการที่คุณรู้สึกว่าดีที่สุดในขณะที่ยังคงความจริงกับวัตถุประสงค์


ชี้แจง:

ชี้แจงว่าหน้าที่ใดที่ไม่ได้รับอนุญาตฉันไม่ได้ตั้งใจที่จะห้ามฟังก์ชั่นคณิตศาสตร์ ฉันตั้งใจจะห้ามฟังก์ชั่นจัดการภาพ ใช่มีบางอย่างทับซ้อนกัน แต่สิ่งต่าง ๆ เช่น FFT, convolutions, คณิตศาสตร์เมทริกซ์ ฯลฯ มีประโยชน์ในด้านอื่น ๆ คุณไม่ควรใช้ฟังก์ชั่นที่ถ่ายภาพและพร่ามัว หากคุณพบวิธีที่เหมาะสมในการสร้างความเบลอเกมที่ยุติธรรม


การสาธิตการที่โดดเด่นdemonstrations.wolfram.com/DigitalTiltShiftPhotography ประมวลผลภาพดิจิตอล Tilt-Shift โดย Yu-Sung ช้างบ่งบอกถึงความมั่งคั่งของความคิดเกี่ยวกับวิธีการปรับความสว่างคมชัดและมุ่งเน้นในท้องถิ่น (ภายในภูมิภาครูปไข่หรือรูปสี่เหลี่ยมของภาพ ) โดยใช้ฟังก์ชั่นของ Mathematica ( GeometricTransformation, DistanceTransform, ImageAdd, ColorNegate, ImageMultiply, RasterizeและImageAdjust.) ถึงแม้จะมีความช่วยเหลือของระดับสูงฟังก์ชั่นการประมวลผลภาพดังกล่าวรหัสจะใช้เวลาถึง 22 k รหัสสำหรับส่วนต่อประสานผู้ใช้นั้นมีขนาดเล็กมาก
DavidC

5
ฉันควรจะพูดว่า "ใช้เวลาเพียง 22 k เท่านั้น " มีโค้ดเบื้องหลังที่ห่อหุ้มอยู่ในฟังก์ชั่นที่กล่าวมาข้างต้นซึ่งการตอบสนองที่ประสบความสำเร็จต่อความท้าทายนี้ควรพิสูจน์ได้ยากมากในภาษาส่วนใหญ่โดยไม่ต้องใช้ไลบรารี่ประมวลผลภาพเฉพาะ
DavidC

อัปเดต: ทำได้ด้วยอักขระ 2.5 k ดังนั้นจึงมีประสิทธิภาพยิ่งขึ้น
DavidC

1
@DavidCarraher นั่นเป็นเหตุผลที่ฉัน จำกัด รายละเอียดไว้อย่างชัดเจน มันไม่ยากที่จะเขียนสิ่งที่จะครอบคลุมข้อมูลจำเพาะเนื่องจากการใช้งานอ้างอิงด้านล่างของฉันแสดงใน 4.3 k อักขระของJava ที่ไม่ได้รับ ฉันไม่คาดหวังถึงผลลัพธ์ระดับสตูดิโอระดับมืออาชีพอย่างแน่นอน แน่นอนว่าสิ่งใดก็ตามที่เกินกว่ามาตรฐาน (นำไปสู่ผลลัพธ์ที่ดีกว่า) ควรได้รับการสนับสนุนอย่างเต็มที่ IMO ฉันยอมรับว่านี่ไม่ใช่ความท้าทายที่ง่ายที่จะทำให้เก่งแต่มันไม่ได้หมายความว่าจะเป็น ความพยายามขั้นต่ำเป็นพื้นฐาน แต่รายการที่ "ดี" จะต้องเกี่ยวข้องมากกว่า
Geobits

อัลกอริธึมอื่นที่สามารถใช้ร่วมกับสิ่งเหล่านี้เพื่อสร้าง "เพชรประดับ" ที่น่าเชื่อยิ่งขึ้นคือการใช้การสลายตัวของเวฟเล็ตเพื่อกรองคุณสมบัติขนาดเล็กออกจากภาพในขณะที่ยังคงคุณสมบัติที่มีขนาดใหญ่ขึ้น
AJMansfield

คำตอบ:


15

Java: การดำเนินการอ้างอิง

นี่คือการดำเนินการอ้างอิงขั้นพื้นฐานใน Java มันทำงานได้ดีที่สุดในการถ่ายภาพมุมสูงและมันไม่มีประสิทธิภาพอย่างน่ากลัว

เบลอเป็นกล่องเบลอขั้นพื้นฐานมากดังนั้นจึงวนซ้ำพิกเซลเดียวกันมากกว่าที่จำเป็น ความคมชัดและความอิ่มตัวของสีสามารถรวมกันเป็นวงเดียวได้เช่นกัน แต่เวลาส่วนใหญ่ที่ใช้นั้นเบลอดังนั้นมันจึงไม่ได้รับประโยชน์มากนัก อย่างที่กล่าวมามันทำงานได้ค่อนข้างเร็วบนภาพที่ต่ำกว่า 2MP หรือมากกว่านั้น อิมเมจ 10MP ใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์

สามารถปรับปรุงคุณภาพเบลอได้อย่างง่ายดายโดยใช้อะไรก็ได้ยกเว้นเบลอกล่องแบน อัลกอริทึมตัดกัน / ความอิ่มตัวทำงานของพวกเขาดังนั้นจึงไม่มีการร้องเรียนที่แท้จริง

ไม่มีปัญญาที่แท้จริงในโปรแกรม มันใช้ปัจจัยคงที่สำหรับความพร่ามัวความอิ่มตัวและความคมชัด ฉันเล่นไปรอบ ๆ เพื่อหาการตั้งค่าขนาดกลางที่มีความสุข เป็นผลให้มีมีบางฉากที่ว่ามันไม่ได้ทำดีมาก ตัวอย่างเช่นมันปั๊มความคมชัด / ความอิ่มตัวของสีมากจนภาพที่มีพื้นที่สีขนาดใหญ่คล้ายกัน (คิดว่าท้องฟ้า) แบ่งออกเป็นแถบสี

มันใช้งานง่าย เพียงส่งชื่อไฟล์เป็นอาร์กิวเมนต์เท่านั้น มันส่งออกใน PNG โดยไม่คำนึงถึงสิ่งที่เป็นไฟล์อินพุต

ตัวอย่าง:

จากการเลือกดรอปบ็อกซ์:

รูปภาพแรกเหล่านี้จะถูกย่อขนาดเพื่อให้ง่ายต่อการโพสต์ คลิกที่ภาพเพื่อดูขนาดเต็ม

หลังจาก:

ป้อนคำอธิบายรูปภาพที่นี่

ก่อน:

ป้อนคำอธิบายรูปภาพที่นี่

การเลือกเบ็ดเตล็ด:

หลังจาก:

ป้อนคำอธิบายรูปภาพที่นี่

ก่อน:

ป้อนคำอธิบายรูปภาพที่นี่

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class MiniFake {

    int maxBlur;
    int maxDist;
    int width;
    int height;

    public static void main(String[] args) {
        if(args.length < 1) return;
        new MiniFake().run(args[0]);
    }

    void run(String filename){
        try{
            BufferedImage in = readImage(filename);
            BufferedImage out = blur(in);
            out = saturate(out, 0.8);
            out = contrast(out, 0.6);

            String[] tokens = filename.split("\\.");
            String outname = tokens[0];
            for(int i=1;i<tokens.length-1;i++)
                outname += "." + tokens[i];
            ImageIO.write(out, "png", new File(outname + "_post.png"));
            System.out.println("done");
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    BufferedImage contrast(BufferedImage in, double level){
        BufferedImage out = copyImage(in);
        long lumens=0;
        for(int x=0;x<width;x++)
            for(int y=0;y<height;y++){
                int color = out.getRGB(x,y);
                lumens += lumen(getR(color), getG(color), getB(color));
            }
        lumens /= (width * height);

        for(int x=0;x<width;x++)
            for(int y=0;y<height;y++){
                int color = out.getRGB(x,y);
                int r = getR(color);
                int g = getG(color);
                int b = getB(color);
                double ratio = ((double)lumen(r, g, b) / (double)lumens) - 1d;
                ratio *= (1+level) * 0.1;
                r += (int)(getR(color) * ratio+1);
                g += (int)(getG(color) * ratio+1);
                b += (int)(getB(color) * ratio+1);
                out.setRGB(x,y,getColor(clamp(r),clamp(g),clamp(b)));
            }   
        return out;
    }

    BufferedImage saturate(BufferedImage in, double level){
        BufferedImage out = copyImage(in);
        for(int x=0;x<width;x++)
            for(int y=0;y<height;y++){
                int color = out.getRGB(x,y);
                int r = getR(color);
                int g = getG(color);
                int b = getB(color);
                int brightness = Math.max(r, Math.max(g, b));
                int grey = (int)(Math.min(r, Math.min(g,b)) * level);
                if(brightness == grey)
                    continue;
                r -= grey;
                g -= grey;
                b -= grey;
                double ratio = brightness / (double)(brightness - grey);
                r = (int)(r * ratio);
                g = (int)(g * ratio);
                b = (int)(b * ratio);
                out.setRGB(x, y, getColor(clamp(r),clamp(g),clamp(b)));
            }
        return out;
    }


    BufferedImage blur(BufferedImage in){
        BufferedImage out = copyImage(in);
        int[] rgb = in.getRGB(0, 0, width, height, null, 0, width);
        for(int i=0;i<rgb.length;i++){
            double dist = Math.abs(getY(i)-(height/2));
            dist = dist * dist / maxDist;
            int r=0,g=0,b=0,p=0;
            for(int x=-maxBlur;x<=maxBlur;x++)
                for(int y=-maxBlur;y<=maxBlur;y++){
                    int xx = getX(i) + x;
                    int yy = getY(i) + y;
                    if(xx<0||xx>=width||yy<0||yy>=height)
                        continue;
                    int color = rgb[getPos(xx,yy)];
                    r += getR(color);
                    g += getG(color);
                    b += getB(color);
                    p++;
                }

            if(p>0){
                r /= p;
                g /= p;
                b /= p;
                int color = rgb[i];
                r = (int)((r*dist) + (getR(color) * (1 - dist)));
                g = (int)((g*dist) + (getG(color) * (1 - dist)));
                b = (int)((b*dist) + (getB(color) * (1 - dist)));
            } else {
                r = in.getRGB(getX(i), getY(i));
            }
            out.setRGB(getX(i), getY(i), getColor(r,g,b));
        }
        return out;
    }

    BufferedImage readImage(String filename) throws IOException{
         BufferedImage image = ImageIO.read(new File(filename));
         width = image.getWidth();
         height = image.getHeight();
         maxBlur = Math.max(width, height) / 100;
         maxDist =  (height/2)*(height/2);
         return image;
    }

    public BufferedImage copyImage(BufferedImage in){
        BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics g = out.getGraphics();
        g.drawImage(in, 0, 0, null);
        g.dispose();
        return out;
    }

    static int clamp(int c){return c<0?0:c>255?255:c;}
    static int getColor(int a, int r, int g, int b){return (a << 24) | (r << 16) | (g << 8) | (b);}
    static int getColor(int r, int g, int b){return getColor(0xFF, r, g, b);}
    static int getR(int color){return color >> 16 & 0xFF;}
    static int getG(int color){return color >> 8 & 0xFF;}
    static int getB(int color){return color & 0xFF;}
    static int lumen(int r, int g, int b){return (r*299)+(g*587)+(b*114);}
    int getX(int pos){return pos % width;}
    int getY(int pos){return pos / width;}
    int getPos(int x, int y){return y*width+x;} 
}

12

C #

แทนที่จะทำกล่องซ้ำแบบเบลอฉันตัดสินใจไปตลอดทางและเขียนแบบเกาส์เบลอ การGetPixelโทรช้าลงเมื่อใช้เมล็ดขนาดใหญ่ แต่ก็ไม่คุ้มที่จะแปลงวิธีการใช้LockBitsเว้นแต่ว่าเรากำลังประมวลผลภาพขนาดใหญ่

ตัวอย่างด้านล่างซึ่งใช้พารามิเตอร์การปรับค่าเริ่มต้นที่ฉันตั้งไว้ (ฉันไม่ได้เล่นกับพารามิเตอร์การปรับแต่งมากนักเพราะดูเหมือนว่าจะทำงานได้ดีสำหรับภาพทดสอบ)

สำหรับกรณีทดสอบที่ให้ไว้ ...

1-Original 1-Modified

อื่น ...

2-Original 2-Modified

อื่น ...

3 เดิม 3-Modified

การเพิ่มความอิ่มตัวและความคมชัดควรตรงไปตรงมาจากโค้ด ฉันทำสิ่งนี้ในพื้นที่ 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)

... ซึ่งให้การตอบสนองที่ดีโดยพื้นฐานแล้วการสร้างวงรีของค่าที่ได้รับการปกป้องจากความพร่ามัวที่ค่อยๆจางหายไปอีก ตัวกรอง band-pass ที่รวมกับสมการอื่น ๆ (อาจจะแตกต่างกันบ้าง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

หลังจาก:

ป้อนคำอธิบายรูปภาพที่นี่

ฉันอาจต้องเพิ่มการจับคู่โทนเสียงที่ชาญฉลาดกว่าหรือการอนุรักษ์ luma เพราะมันอาจมืดมาก:

ก่อน:

ป้อนคำอธิบายรูปภาพที่นี่

หลังจาก:

ป้อนคำอธิบายรูปภาพที่นี่

ยังคงน่าสนใจแม้ว่าวางไว้ในบรรยากาศใหม่ทั้งหมด

รหัส:

import java.awt.*;
import java.awt.image.*;
import java.io.*;

import javax.imageio.*;
import javax.swing.*;

public class SceneMinifier {

    static final double CONTRAST_INCREASE = 8;
    static final double SATURATION_INCREASE = 7;

    public static void main(String[] args) throws IOException {

        if (args.length < 1) {
            System.out.println("Please specify an input image file.");
            return;
        }

        BufferedImage temp = ImageIO.read(new File(args[0]));

        BufferedImage input = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_INT_ARGB);
        input.getGraphics().drawImage(temp, 0, 0, null); // just want to guarantee TYPE_ARGB

        int[] pixels = ((DataBufferInt) input.getData().getDataBuffer()).getData();

        // saturation

        double[][] hsv = toHSV(pixels);
        for (int i = 0; i < hsv[1].length; i++)
            hsv[1][i] = Math.min(1, hsv[1][i] * (1 + SATURATION_INCREASE / 10));

        // contrast

        int[][] rgb = toRGB(hsv[0], hsv[1], hsv[2]);

        double c = (100 + CONTRAST_INCREASE) / 100;
        c *= c;

        for (int i = 0; i < pixels.length; i++)
            for (int q = 0; q < 3; q++)
                rgb[q][i] = (int) Math.max(0, Math.min(255, ((rgb[q][i] / 255. - .5) * c + .5) * 255));

        // blur

        int w = input.getWidth();
        int h = input.getHeight();

        int k = 5;
        int kd = 2 * k + 1;
        double dd = 1 / Math.hypot(w / 2, h / 2);

        for (int reps = 0; reps < 5; reps++) {

            int tmp[][] = new int[3][pixels.length];
            int vmin[] = new int[Math.max(w, h)];
            int vmax[] = new int[Math.max(w, h)];

            for (int y = 0, yw = 0, yi = 0; y < h; y++) {
                int[] sum = new int[3];
                for (int i = -k; i <= k; i++) {
                    int ii = yi + Math.min(w - 1, Math.max(i, 0));
                    for (int q = 0; q < 3; q++)
                        sum[q] += rgb[q][ii];
                }
                for (int x = 0; x < w; x++) {

                    int dx = x - w / 2;
                    int dy = y - h / 2;
                    double dist = Math.sqrt(dx * dx + dy * dy) * dd;
                    dist *= dist;

                    for (int q = 0; q < 3; q++)
                        tmp[q][yi] = (int) Math.min(255, sum[q] / kd * dist + rgb[q][yi] * (1 - dist));

                    if (y == 0) {
                        vmin[x] = Math.min(x + k + 1, w - 1);
                        vmax[x] = Math.max(x - k, 0);
                    }

                    int p1 = yw + vmin[x];
                    int p2 = yw + vmax[x];

                    for (int q = 0; q < 3; q++)
                        sum[q] += rgb[q][p1] - rgb[q][p2];
                    yi++;
                }
                yw += w;
            }

            for (int x = 0, yi = 0; x < w; x++) {
                int[] sum = new int[3];
                int yp = -k * w;
                for (int i = -k; i <= k; i++) {
                    yi = Math.max(0, yp) + x;
                    for (int q = 0; q < 3; q++)
                        sum[q] += tmp[q][yi];
                    yp += w;
                }
                yi = x;
                for (int y = 0; y < h; y++) {

                    int dx = x - w / 2;
                    int dy = y - h / 2;
                    double dist = Math.sqrt(dx * dx + dy * dy) * dd;
                    dist *= dist;

                    for (int q = 0; q < 3; q++)
                        rgb[q][yi] = (int) Math.min(255, sum[q] / kd * dist + tmp[q][yi] * (1 - dist));

                    if (x == 0) {
                        vmin[y] = Math.min(y + k + 1, h - 1) * w;
                        vmax[y] = Math.max(y - k, 0) * w;
                    }
                    int p1 = x + vmin[y];
                    int p2 = x + vmax[y];

                    for (int q = 0; q < 3; q++)
                        sum[q] += tmp[q][p1] - tmp[q][p2];

                    yi += w;
                }
            }
        }

        // pseudo-lighting pass

        for (int i = 0; i < pixels.length; i++) {
            int dx = i % w - w / 2;
            int dy = i / w - h / 2;
            double dist = Math.sqrt(dx * dx + dy * dy) * dd;
            dist *= dist;

            for (int q = 0; q < 3; q++) {
                if (dist > 1 - .375)
                    rgb[q][i] *= 1 + (Math.sqrt((1 - dist + .125) / 2) - (1 - dist) - .125) * .7;
                if (dist < .375 || dist > .375)
                    rgb[q][i] *= 1 + (Math.sqrt((dist + .125) / 2) - dist - .125) * dist > .375 ? 1 : .8;
                rgb[q][i] = Math.min(255, Math.max(0, rgb[q][i]));
            }
        }

        // reassemble image

        BufferedImage output = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB);

        pixels = ((DataBufferInt) output.getData().getDataBuffer()).getData();

        for (int i = 0; i < pixels.length; i++)
            pixels[i] = 255 << 24 | rgb[0][i] << 16 | rgb[1][i] << 8 | rgb[2][i];

        output.setRGB(0, 0, output.getWidth(), output.getHeight(), pixels, 0, output.getWidth());

        // display results

        display(input, output);

        // output image

        ImageIO.write(output, "PNG", new File(args[0].substring(0, args[0].lastIndexOf('.')) + " miniaturized.png"));

    }

    private static int[][] toRGB(double[] h, double[] s, double[] v) {
        int[] r = new int[h.length];
        int[] g = new int[h.length];
        int[] b = new int[h.length];

        for (int i = 0; i < h.length; i++) {
            double C = v[i] * s[i];
            double H = h[i];
            double X = C * (1 - Math.abs(H % 2 - 1));

            double ri = 0, gi = 0, bi = 0;

            if (0 <= H && H < 1) {
                ri = C;
                gi = X;
            } else if (1 <= H && H < 2) {
                ri = X;
                gi = C;
            } else if (2 <= H && H < 3) {
                gi = C;
                bi = X;
            } else if (3 <= H && H < 4) {
                gi = X;
                bi = C;
            } else if (4 <= H && H < 5) {
                ri = X;
                bi = C;
            } else if (5 <= H && H < 6) {
                ri = C;
                bi = X;
            }

            double m = v[i] - C;

            r[i] = (int) ((ri + m) * 255);
            g[i] = (int) ((gi + m) * 255);
            b[i] = (int) ((bi + m) * 255);
        }

        return new int[][] { r, g, b };
    }

    private static double[][] toHSV(int[] c) {
        double[] h = new double[c.length];
        double[] s = new double[c.length];
        double[] v = new double[c.length];

        for (int i = 0; i < c.length; i++) {
            double r = (c[i] & 0xFF0000) >> 16;
            double g = (c[i] & 0xFF00) >> 8;
            double b = c[i] & 0xFF;

            r /= 255;
            g /= 255;
            b /= 255;

            double M = Math.max(Math.max(r, g), b);
            double m = Math.min(Math.min(r, g), b);
            double C = M - m;

            double H = 0;

            if (C == 0)
                H = 0;
            else if (M == r)
                H = (g - b) / C % 6;
            else if (M == g)
                H = (b - r) / C + 2;
            else if (M == b)
                H = (r - g) / C + 4;

            h[i] = H;
            s[i] = C / M;
            v[i] = M;
        }
        return new double[][] { h, s, v };
    }

    private static void display(final BufferedImage original, final BufferedImage output) {

        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int wt = original.getWidth();
        int ht = original.getHeight();
        double ratio = (double) wt / ht;
        if (ratio > 1 && wt > d.width / 2) {
            wt = d.width / 2;
            ht = (int) (wt / ratio);
        }
        if (ratio < 1 && ht > d.getHeight() / 2) {
            ht = d.height / 2;
            wt = (int) (ht * ratio);
        }

        final int w = wt, h = ht;

        JFrame frame = new JFrame();
        JPanel pan = new JPanel() {
            BufferedImage buffer = new BufferedImage(w * 2, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D gg = buffer.createGraphics();

            {
                gg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            }

            @Override
            public void paint(Graphics g) {
                gg.drawImage(original, 0, 0, w, h, null);
                gg.drawImage(output, w, 0, w, h, null);
                g.drawImage(buffer, 0, 0, null);
            }
        };
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pan.setPreferredSize(new Dimension(w * 2, h));
        frame.setLayout(new BorderLayout());
        frame.add(pan, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }
}

8

J

นี่เป็นความท้าทายที่ดี การตั้งค่าสำหรับความเบลอความอิ่มตัวและความคมชัดนั้นมีการกำหนดค่าตายตัว แต่สามารถเปลี่ยนแปลงได้ง่ายหากต้องการ อย่างไรก็ตามพื้นที่ที่อยู่ในโฟกัสจะถูกกำหนดให้เป็นเส้นแนวนอนที่กึ่งกลาง มันไม่สามารถแก้ไขได้ง่ายเหมือนการตั้งค่าอื่น ๆ ถูกเลือกเนื่องจากภาพทดสอบส่วนใหญ่มีวิวทั่วเมือง

หลังจากทำการเบลอแบบเกาส์ฉันก็แบ่งภาพในแนวนอนออกเป็น 5 ภูมิภาค ภูมิภาคด้านบนและด้านล่างจะได้รับ 100% ของความพร่ามัว ภาคกลางจะได้รับ 0% ของความพร่ามัว ทั้งสองภูมิภาคที่เหลือจะปรับสัดส่วนตามก้อนผกผันจาก 0% ถึง 100%

รหัสจะใช้เป็นสคริปต์ใน J และสคริปต์นั้นจะอยู่ในโฟลเดอร์เดียวกับinput.bmpที่จะเป็นภาพอินพุต มันจะสร้างoutput.bmpซึ่งจะเป็นของปลอมขนาดเล็กของอินพุต

ประสิทธิภาพเป็นสิ่งที่ดีและบนพีซีของฉันที่ใช้ i7-4770k มันใช้เวลาประมาณ 20 วินาทีในการประมวลผลภาพจากชุดของ OP ก่อนหน้านี้ใช้เวลาประมาณ 70 วินาทีในการประมวลผลภาพโดยใช้การแปลงมาตรฐานกับ;._3ผู้ให้บริการย่อย ประสิทธิภาพได้รับการปรับปรุงโดยใช้ FFT เพื่อทำการแปลง

ห่วง วงมินิ เมือง เมืองขนาดเล็ก

รหัสนี้ต้องbmpและmath/fftwaddons ที่จะติดตั้ง คุณสามารถติดตั้งได้โดยใช้และ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.