American Gothic ในจานสีของ Mona Lisa: จัดเรียงพิกเซลใหม่


377

คุณจะได้รับสองภาพสีจริงแหล่งที่มาและจานสี พวกเขาไม่จำเป็นต้องมีขนาดเท่ากัน แต่รับประกันได้ว่าพื้นที่ของพวกเขาเหมือนกันคือพวกเขามีจำนวนพิกเซลเดียวกัน

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

สามารถใช้สคริปต์ Python นี้เพื่อให้แน่ใจว่าเป็นไปตามข้อ จำกัด เหล่านี้:

from PIL import Image
def check(palette, copy):
    palette = sorted(Image.open(palette).convert('RGB').getdata())
    copy = sorted(Image.open(copy).convert('RGB').getdata())
    print 'Success' if copy == palette else 'Failed'

check('palette.png', 'copy.png')

นี่คือภาพหลายภาพสำหรับการทดสอบ พวกเขาทั้งหมดมีพื้นที่เดียวกัน อัลกอริทึมของคุณควรทำงานกับภาพสองภาพที่มีขนาดเท่ากันไม่ใช่เฉพาะ American Gothic และ Mona Lisa แน่นอนคุณควรแสดงผลลัพธ์ของคุณ

โกธิคอเมริกัน Mona Lisa สตาร์รี่ไนท์ กรี๊ด แม่น้ำ รุ้ง

ขอบคุณ Wikipedia สำหรับรูปภาพของภาพวาดที่มีชื่อเสียง

เกณฑ์การให้คะแนน

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

นิเมชั่น

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

อันดับแรกคือ Mona Lisa เปลี่ยนเป็น American Gothic ของ aditsu ถัดไปคือ American Gothic ของ bitpwner (จาก Mona Lisa) กลายเป็น aditsu มันน่าทึ่งมากที่ทั้งสองรุ่นแบ่งปันจานสีเดียวกัน

โมนาลิซากับอนิเมชั่นอเมริกันกอธิค แอนิเมชั่นระหว่าง American Gothic สองรุ่นทำจาก Mona Lisa

ผลลัพธ์ค่อนข้างน่าประหลาดใจจริงๆ นี่คือรุ้ง Mona Lisa ของ aditsu (ชะลอการแสดงรายละเอียด)

ทรงกลมสีรุ้งไปเป็นภาพเคลื่อนไหว Mona Lisa

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

ภาพเคลื่อนไหวการหมุนต้นไม้


22
เพื่อเพิ่มความนิยมในคำถามของคุณคุณอาจต้องการพิจารณามัน "American Gothic ในจานสีของ Mona Lisa: จัดเรียงพิกเซล"
DavidC

14
สวัสดีฉันแค่อยากจะแสดงความยินดีกับคุณในความท้าทายดั้งเดิมนี้! สดชื่นและน่าสนใจมาก
bolov

6
ฉันดีใจที่นี่ไม่ใช่ [code-golf]
Ming-Tang

13
ขีด จำกัด ข้อมูลมือถือของฉันได้รับความเสียหายอย่างหนักทุกครั้งที่ฉันเข้าชมหน้านี้
Vectorized

5
เกี่ยวข้อง: github.com/jcjohnson/neural-style
Vi

คำตอบ:


159

Java - GUI พร้อมการแปลงแบบสุ่มแบบก้าวหน้า

ฉันลองหลายสิ่งหลายอย่างบางอย่างซับซ้อนมากจากนั้นในที่สุดฉันก็กลับมาที่โค้ดที่ค่อนข้างง่ายนี้:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class CopyColors extends JFrame {
    private static final String SOURCE = "spheres";
    private static final String PALETTE = "mona";
    private static final int COUNT = 10000;
    private static final int DELAY = 20;
    private static final int LUM_WEIGHT = 10;

    private static final double[] F = {0.114, 0.587, 0.299};
    private final BufferedImage source;
    protected final BufferedImage dest;
    private final int sw;
    private final int sh;
    private final int n;
    private final Random r = new Random();
    private final JLabel l;

    public CopyColors(final String sourceName, final String paletteName) throws IOException {
        super("CopyColors by aditsu");
        source = ImageIO.read(new File(sourceName + ".png"));
        final BufferedImage palette = ImageIO.read(new File(paletteName + ".png"));
        sw = source.getWidth();
        sh = source.getHeight();
        final int pw = palette.getWidth();
        final int ph = palette.getHeight();
        n = sw * sh;
        if (n != pw * ph) {
            throw new RuntimeException();
        }
        dest = new BufferedImage(sw, sh, BufferedImage.TYPE_INT_RGB);
        for (int i = 0; i < sh; ++i) {
            for (int j = 0; j < sw; ++j) {
                final int x = i * sw + j;
                dest.setRGB(j, i, palette.getRGB(x % pw, x / pw));
            }
        }
        l = new JLabel(new ImageIcon(dest));
        add(l);
        final JButton b = new JButton("Save");
        add(b, BorderLayout.SOUTH);
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                try {
                    ImageIO.write(dest, "png", new File(sourceName + "-" + paletteName + ".png"));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    protected double dist(final int x, final int y) {
        double t = 0;
        double lx = 0;
        double ly = 0;
        for (int i = 0; i < 3; ++i) {
            final double xi = ((x >> (i * 8)) & 255) * F[i];
            final double yi = ((y >> (i * 8)) & 255) * F[i];
            final double d = xi - yi;
            t += d * d;
            lx += xi;
            ly += yi;
        }
        double l = lx - ly;
        return t + l * l * LUM_WEIGHT;
    }

    public void improve() {
        final int x = r.nextInt(n);
        final int y = r.nextInt(n);
        final int sx = source.getRGB(x % sw, x / sw);
        final int sy = source.getRGB(y % sw, y / sw);
        final int dx = dest.getRGB(x % sw, x / sw);
        final int dy = dest.getRGB(y % sw, y / sw);
        if (dist(sx, dx) + dist(sy, dy) > dist(sx, dy) + dist(sy, dx)) {
            dest.setRGB(x % sw, x / sw, dy);
            dest.setRGB(y % sw, y / sw, dx);
        }
    }

    public void update() {
        l.repaint();
    }

    public static void main(final String... args) throws IOException {
        final CopyColors x = new CopyColors(SOURCE, PALETTE);
        x.setSize(800, 600);
        x.setLocationRelativeTo(null);
        x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        x.setVisible(true);
        new Timer(DELAY, new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                for (int i = 0; i < COUNT; ++i) {
                    x.improve();
                }
                x.update();
            }
        }).start();
    }
}

พารามิเตอร์ที่เกี่ยวข้องทั้งหมดจะถูกกำหนดเป็นค่าคงที่ที่จุดเริ่มต้นของชั้นเรียน

โปรแกรมแรกจะคัดลอกภาพพาเล็ตลงในมิติต้นฉบับจากนั้นเลือกสุ่มพิกเซล 2 พิกเซลและสลับมันหากมันจะเข้าใกล้ภาพต้นฉบับ "ใกล้ชิด" ถูกกำหนดโดยใช้ฟังก์ชั่นระยะทางสีที่คำนวณความแตกต่างระหว่างส่วนประกอบ r, g, b (น้ำหนักลูมา) พร้อมกับความแตกต่างลูม่าทั้งหมดด้วยน้ำหนักที่มากขึ้นสำหรับลูม่า

ใช้เวลาเพียงไม่กี่วินาทีในการสร้างรูปร่าง แต่อีกไม่นานที่สีจะมารวมกัน คุณสามารถบันทึกภาพปัจจุบันได้ตลอดเวลา ฉันมักจะรอประมาณ 1-3 นาทีก่อนบันทึก

ผล:

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

จานสีโกธิคอเมริกัน

mona โกธิค กรีดร้องโกธิค

Mona Lisa palette

โกธิค mona กรีดร้อง-Mona ทรงกลม-Mona

จานสี Starry Night

mona คืน กรีดร้องคืน ทรงกลมคืน

จานสี Scream

โกธิคเสียงกรีดร้อง mona-เสียงกรีดร้อง คืนเสียงกรีดร้อง ทรงกลม-เสียงกรีดร้อง

จานสีทรงกลม

ฉันคิดว่านี่คือการทดสอบที่ยากที่สุดและทุกคนควรโพสต์ผลลัพธ์ด้วยชุดแบบนี้:

โกธิคทรงกลม mona ทรงกลม กรีดร้องทรงกลม

ขออภัยฉันไม่พบภาพแม่น้ำที่น่าสนใจมากดังนั้นฉันจึงไม่ได้รวม

ฉันยังเพิ่มวิดีโอที่https://www.youtube.com/watch?v=_-w3cKL5teMมันแสดงให้เห็นว่าโปรแกรมทำอะไร (ไม่ตรงตามเวลาจริง แต่คล้ายกัน) จากนั้นจะแสดงการเคลื่อนไหวพิกเซลแบบค่อยเป็นค่อยไปโดยใช้ไพ ธ อนของ Calvin ต้นฉบับ น่าเสียดายที่คุณภาพวิดีโอได้รับความเสียหายอย่างมากจากการเข้ารหัส / บีบอัดของ youtube


2
@Quincunx และฉันไม่ได้เรียก invokeLater เช่นกันยิงฉัน: p ขอบคุณมากเช่นกัน :)
aditsu

16
คำตอบที่ดีที่สุดจนถึงตอนนี้ ...
Yuval Filmus

8
เมื่อมีข้อสงสัยเดรัจฉานบังคับมันได้หรือไม่ ดูเหมือนว่าเป็นทางออกที่ยอดเยี่ยมฉันชอบที่จะเห็นภาพเคลื่อนไหวสำหรับเรื่องนี้บางทีอาจเป็นวิดีโอแทนที่จะเป็น GIF
Lilienthal

3
คุณสามารถขยายอัลกอริทึมไปเป็นแบบจำลองการอบอ่อนเต็มรูปแบบเพื่อการปรับปรุงเล็กน้อย สิ่งที่คุณทำอยู่ใกล้มากแล้ว (แต่มันโลภมาก) การค้นหาการเรียงสับเปลี่ยนที่ลดระยะห่างให้น้อยที่สุดดูเหมือนจะเป็นปัญหาการปรับให้เหมาะสมอย่างหนักดังนั้นการแก้ปัญหาแบบนี้จึงเหมาะสม @Lilienthal นี่ไม่ใช่การบังคับเดรัจฉานมันใกล้เคียงกับเทคนิคการเพิ่มประสิทธิภาพที่ใช้กันทั่วไป
Szabolcs

3
อัลกอริทึมนี้มีผลลัพธ์ที่ดีที่สุดโดยไกล และมันง่ายมาก มันทำให้ผู้ชนะชัดเจนสำหรับฉัน
Leif

118

ชวา

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;

/**
 *
 * @author Quincunx
 */
public class PixelRearranger {

    public static void main(String[] args) throws IOException {
        BufferedImage source = ImageIO.read(resource("American Gothic.png"));
        BufferedImage palette = ImageIO.read(resource("Mona Lisa.png"));
        BufferedImage result = rearrange(source, palette);
        ImageIO.write(result, "png", resource("result.png"));
        validate(palette, result);
    }

    public static class MInteger {
        int val;

        public MInteger(int i) {
            val = i;
        }
    }

    public static BufferedImage rearrange(BufferedImage source, BufferedImage palette) {
        BufferedImage result = new BufferedImage(source.getWidth(),
                source.getHeight(), BufferedImage.TYPE_INT_RGB);

        //This creates a list of points in the Source image.
        //Then, we shuffle it and will draw points in that order.
        List<Point> samples = getPoints(source.getWidth(), source.getHeight());
        System.out.println("gotPoints");

        //Create a list of colors in the palette.
        rgbList = getColors(palette);
        Collections.sort(rgbList, rgb);
        rbgList = new ArrayList<>(rgbList);
        Collections.sort(rbgList, rbg);
        grbList = new ArrayList<>(rgbList);
        Collections.sort(grbList, grb);
        gbrList = new ArrayList<>(rgbList);
        Collections.sort(gbrList, gbr);
        brgList = new ArrayList<>(rgbList);
        Collections.sort(brgList, brg);
        bgrList = new ArrayList<>(rgbList);
        Collections.sort(bgrList, bgr);

        while (!samples.isEmpty()) {
            Point currentPoint = samples.remove(0);
            int sourceAtPoint = source.getRGB(currentPoint.x, currentPoint.y);
            int bestColor = search(new MInteger(sourceAtPoint));
            result.setRGB(currentPoint.x, currentPoint.y, bestColor);
        }
        return result;
    }

    public static List<Point> getPoints(int width, int height) {
        HashSet<Point> points = new HashSet<>(width * height);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                points.add(new Point(x, y));
            }
        }
        List<Point> newList = new ArrayList<>();
        List<Point> corner1 = new LinkedList<>();
        List<Point> corner2 = new LinkedList<>();
        List<Point> corner3 = new LinkedList<>();
        List<Point> corner4 = new LinkedList<>();

        Point p1 = new Point(width / 3, height / 3);
        Point p2 = new Point(width * 2 / 3, height / 3);
        Point p3 = new Point(width / 3, height * 2 / 3);
        Point p4 = new Point(width * 2 / 3, height * 2 / 3);

        newList.add(p1);
        newList.add(p2);
        newList.add(p3);
        newList.add(p4);
        corner1.add(p1);
        corner2.add(p2);
        corner3.add(p3);
        corner4.add(p4);
        points.remove(p1);
        points.remove(p2);
        points.remove(p3);
        points.remove(p4);

        long seed = System.currentTimeMillis();
        Random c1Random = new Random(seed += 179426549); //The prime number pushes the first numbers apart
        Random c2Random = new Random(seed += 179426549); //Or at least I think it does.
        Random c3Random = new Random(seed += 179426549);
        Random c4Random = new Random(seed += 179426549);

        Dir NW = Dir.NW;
        Dir N = Dir.N;
        Dir NE = Dir.NE;
        Dir W = Dir.W;
        Dir E = Dir.E;
        Dir SW = Dir.SW;
        Dir S = Dir.S;
        Dir SE = Dir.SE;
        while (!points.isEmpty()) {
            putPoints(newList, corner1, c1Random, points, NW, N, NE, W, E, SW, S, SE);
            putPoints(newList, corner2, c2Random, points, NE, N, NW, E, W, SE, S, SW);
            putPoints(newList, corner3, c3Random, points, SW, S, SE, W, E, NW, N, NE);
            putPoints(newList, corner4, c4Random, points, SE, S, SW, E, W, NE, N, NW);
        }
        return newList;
    }

    public static enum Dir {
        NW(-1, -1), N(0, -1), NE(1, -1), W(-1, 0), E(1, 0), SW(-1, 1), S(0, 1), SE(1, 1);
        final int dx, dy;

        private Dir(int dx, int dy) {
            this.dx = dx;
            this.dy = dy;
        }

        public Point add(Point p) {
            return new Point(p.x + dx, p.y + dy);
        }
    }

    public static void putPoints(List<Point> newList, List<Point> listToAddTo, Random rand,
                                 HashSet<Point> points, Dir... adj) {
        List<Point> newPoints = new LinkedList<>();
        for (Iterator<Point> iter = listToAddTo.iterator(); iter.hasNext();) {
            Point p = iter.next();
            Point pul = adj[0].add(p);
            Point pu = adj[1].add(p);
            Point pur = adj[2].add(p);
            Point pl = adj[3].add(p);
            Point pr = adj[4].add(p);
            Point pbl = adj[5].add(p);
            Point pb = adj[6].add(p);
            Point pbr = adj[7].add(p);
            int allChosen = 0;
            if (points.contains(pul)) {
                if (rand.nextInt(5) == 0) {
                    allChosen++;
                    newPoints.add(pul);
                    newList.add(pul);
                    points.remove(pul);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pu)) {
                if (rand.nextInt(5) == 0) {
                    allChosen++;
                    newPoints.add(pu);
                    newList.add(pu);
                    points.remove(pu);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pur)) {
                if (rand.nextInt(3) == 0) {
                    allChosen++;
                    newPoints.add(pur);
                    newList.add(pur);
                    points.remove(pur);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pl)) {
                if (rand.nextInt(5) == 0) {
                    allChosen++;
                    newPoints.add(pl);
                    newList.add(pl);
                    points.remove(pl);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pr)) {
                if (rand.nextInt(2) == 0) {
                    allChosen++;
                    newPoints.add(pr);
                    newList.add(pr);
                    points.remove(pr);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pbl)) {
                if (rand.nextInt(5) == 0) {
                    allChosen++;
                    newPoints.add(pbl);
                    newList.add(pbl);
                    points.remove(pbl);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pb)) {
                if (rand.nextInt(3) == 0) {
                    allChosen++;
                    newPoints.add(pb);
                    newList.add(pb);
                    points.remove(pb);
                }
            } else {
                allChosen++;
            }
            if (points.contains(pbr)) {
                newPoints.add(pbr);
                newList.add(pbr);
                points.remove(pbr);
            }
            if (allChosen == 7) {
                iter.remove();
            }
        }
        listToAddTo.addAll(newPoints);
    }

    public static List<MInteger> getColors(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();
        List<MInteger> colors = new ArrayList<>(width * height);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                colors.add(new MInteger(img.getRGB(x, y)));
            }
        }
        return colors;
    }

    public static int search(MInteger color) {
        int rgbIndex = binarySearch(rgbList, color, rgb);
        int rbgIndex = binarySearch(rbgList, color, rbg);
        int grbIndex = binarySearch(grbList, color, grb);
        int gbrIndex = binarySearch(gbrList, color, gbr);
        int brgIndex = binarySearch(brgList, color, brg);
        int bgrIndex = binarySearch(bgrList, color, bgr);

        double distRgb = dist(rgbList.get(rgbIndex), color);
        double distRbg = dist(rbgList.get(rbgIndex), color);
        double distGrb = dist(grbList.get(grbIndex), color);
        double distGbr = dist(gbrList.get(gbrIndex), color);
        double distBrg = dist(brgList.get(brgIndex), color);
        double distBgr = dist(bgrList.get(bgrIndex), color);

        double minDist = Math.min(Math.min(Math.min(Math.min(Math.min(
                distRgb, distRbg), distGrb), distGbr), distBrg), distBgr);

        MInteger ans;
        if (minDist == distRgb) {
            ans = rgbList.get(rgbIndex);
        } else if (minDist == distRbg) {
            ans = rbgList.get(rbgIndex);
        } else if (minDist == distGrb) {
            ans = grbList.get(grbIndex);
        } else if (minDist == distGbr) {
            ans = grbList.get(grbIndex);
        } else if (minDist == distBrg) {
            ans = grbList.get(rgbIndex);
        } else {
            ans = grbList.get(grbIndex);
        }
        rgbList.remove(ans);
        rbgList.remove(ans);
        grbList.remove(ans);
        gbrList.remove(ans);
        brgList.remove(ans);
        bgrList.remove(ans);
        return ans.val;
    }

    public static int binarySearch(List<MInteger> list, MInteger val, Comparator<MInteger> cmp){
        int index = Collections.binarySearch(list, val, cmp);
        if (index < 0) {
            index = ~index;
            if (index >= list.size()) {
                index = list.size() - 1;
            }
        }
        return index;
    }

    public static double dist(MInteger color1, MInteger color2) {
        int c1 = color1.val;
        int r1 = (c1 & 0xFF0000) >> 16;
        int g1 = (c1 & 0x00FF00) >> 8;
        int b1 = (c1 & 0x0000FF);

        int c2 = color2.val;
        int r2 = (c2 & 0xFF0000) >> 16;
        int g2 = (c2 & 0x00FF00) >> 8;
        int b2 = (c2 & 0x0000FF);

        int dr = r1 - r2;
        int dg = g1 - g2;
        int db = b1 - b2;
        return Math.sqrt(dr * dr + dg * dg + db * db);
    }

    //This method is here solely for my ease of use (I put the files under <Project Name>/Resources/ )
    public static File resource(String fileName) {
        return new File(System.getProperty("user.dir") + "/Resources/" + fileName);
    }

    static List<MInteger> rgbList;
    static List<MInteger> rbgList;
    static List<MInteger> grbList;
    static List<MInteger> gbrList;
    static List<MInteger> brgList;
    static List<MInteger> bgrList;
    static Comparator<MInteger> rgb = (color1, color2) -> color1.val - color2.val;
    static Comparator<MInteger> rbg = (color1, color2) -> {
        int c1 = color1.val;
        int c2 = color2.val;
        c1 = ((c1 & 0xFF0000)) | ((c1 & 0x00FF00) >> 8) | ((c1 & 0x0000FF) << 8);
        c2 = ((c2 & 0xFF0000)) | ((c2 & 0x00FF00) >> 8) | ((c2 & 0x0000FF) << 8);
        return c1 - c2;
    };
    static Comparator<MInteger> grb = (color1, color2) -> {
        int c1 = color1.val;
        int c2 = color2.val;
        c1 = ((c1 & 0xFF0000) >> 8) | ((c1 & 0x00FF00) << 8) | ((c1 & 0x0000FF));
        c2 = ((c2 & 0xFF0000) >> 8) | ((c2 & 0x00FF00) << 8) | ((c2 & 0x0000FF));
        return c1 - c2;
    };

    static Comparator<MInteger> gbr = (color1, color2) -> {
        int c1 = color1.val;
        int c2 = color2.val;
        c1 = ((c1 & 0xFF0000) >> 16) | ((c1 & 0x00FF00) << 8) | ((c1 & 0x0000FF) << 8);
        c2 = ((c2 & 0xFF0000) >> 16) | ((c2 & 0x00FF00) << 8) | ((c2 & 0x0000FF) << 8);
        return c1 - c2;
    };

    static Comparator<MInteger> brg = (color1, color2) -> {
        int c1 = color1.val;
        int c2 = color2.val;
        c1 = ((c1 & 0xFF0000) >> 8) | ((c1 & 0x00FF00) >> 8) | ((c1 & 0x0000FF) << 16);
        c2 = ((c2 & 0xFF0000) >> 8) | ((c2 & 0x00FF00) >> 8) | ((c2 & 0x0000FF) << 16);
        return c1 - c2;
    };

    static Comparator<MInteger> bgr = (color1, color2) -> {
        int c1 = color1.val;
        int c2 = color2.val;
        c1 = ((c1 & 0xFF0000) >> 16) | ((c1 & 0x00FF00)) | ((c1 & 0x0000FF) << 16);
        c2 = ((c2 & 0xFF0000) >> 16) | ((c2 & 0x00FF00)) | ((c2 & 0x0000FF) << 16);
        return c1 - c2;
    };

    public static void validate(BufferedImage palette, BufferedImage result) {
        List<Integer> paletteColors = getTrueColors(palette);
        List<Integer> resultColors = getTrueColors(result);
        Collections.sort(paletteColors);
        Collections.sort(resultColors);
        System.out.println(paletteColors.equals(resultColors));
    }

    public static List<Integer> getTrueColors(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();
        List<Integer> colors = new ArrayList<>(width * height);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                colors.add(img.getRGB(x, y));
            }
        }
        Collections.sort(colors);
        return colors;
    }
}

วิธีการของฉันทำงานโดยการค้นหาสีที่ใกล้เคียงที่สุดสำหรับแต่ละพิกเซล (น่าจะใกล้เคียงที่สุด) ในแบบ 3 ช่องว่างเนื่องจากสีเป็นแบบ 3 มิติ

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

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

อัปเดต 2:ฉันไม่สุ่มรายการอีกต่อไป แต่ฉันเลือก 4 คะแนนตามกฎข้อที่สามจากนั้นจัดเรียงคะแนนแบบสุ่มโดยเลือกที่จะเติมตรงกลาง

หมายเหตุ: ดูประวัติการแก้ไขสำหรับรูปภาพเก่า

Mona Lisa -> แม่น้ำ:

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

Mona Lisa -> American Gothic:

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

Mona Lisa -> ทรงกลม Raytraced:

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

Starry Night -> Mona Lisa:

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


นี่คือ Gif แบบเคลื่อนไหวที่แสดงวิธีสร้างภาพ:

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

และแสดงภาพที่ถ่ายจากโมนาลิซ่า:

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


11
มันช่างน่าอัศจรรย์จริงๆ ฉันไม่คิดว่าจะเป็นไปได้
AndoDaan

6
ฉันสงสัยว่ามันจะทำเรื่องไม่สำคัญ แต่มันวิเศษมากที่จะสามารถสร้างเวอร์ชันอนิเมชันที่แสดงพิกเซลที่ย้ายจากภาพต้นฉบับไปเป็นภาพสุดท้าย
millinon

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

7
@Quincunx ตามที่ปรากฎว่าสคริปต์ของฉันถูกต้อง (แม้ว่าฉันจะทำให้มันง่ายขึ้นสำหรับลูกหลาน) และเป็นโปรแกรมของคุณ สำหรับเหตุผลที่ฉันไม่แน่ใจว่าภาพ Mona Lisa จะเปลี่ยนไปเล็กน้อยเมื่ออัปโหลด ฉันสังเกตเห็นว่าพิกเซลที่ (177, 377) มี rgb จาก (0, 0, 16) ออนไลน์และ (0, 0, 14) บนคอมพิวเตอร์ที่บ้านของฉัน ฉันได้เปลี่ยน jpegs ด้วย pngs เพื่อหวังว่าจะหลีกเลี่ยงปัญหาเกี่ยวกับประเภทไฟล์ที่สูญหาย ข้อมูลพิกเซลในภาพไม่ควรเปลี่ยนแปลง แต่อาจเป็นการดีที่จะดาวน์โหลดรูปภาพใหม่
งานอดิเรกของ Calvin

8
นี่ไม่ใช่คำตอบยอดนิยม อัลกอริทึมมีความซับซ้อนโดยไม่จำเป็นและผลลัพธ์ไม่ดี เปรียบเทียบการเปลี่ยนแปลงจาก Mona Lisa กับ raytraced spheres กับผลลัพธ์ของarditsu
Leif

97

Perl พร้อมด้วยการเว้นวรรคสี Lab และการเปลี่ยนแปลง

หมายเหตุ:ตอนนี้ฉันมีโซลูชัน Cเช่นกัน

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

  1. ใช้พื้นที่สีCIE L a b *เพื่อเปรียบเทียบสี - ตัวบ่งชี้แบบยุคลิดในพื้นที่นี้เป็นการประมาณที่ดีมากสำหรับความแตกต่างระหว่างการรับรู้ของสองสีดังนั้นการแมปสีจึงควรแม่นยำกว่า RGB หรือ HSV / HSL
  2. หลังจากผ่านจุดเริ่มต้นวางพิกเซลในตำแหน่งเดียวที่ดีที่สุดที่เป็นไปได้มันจะผ่านเพิ่มเติมด้วยการสุ่มแบบสองทิศทาง แทนที่จะเปรียบเทียบค่าพิกเซลที่ตำแหน่งการสลับทั้งสองจะคำนวณค่าพิกเซลเฉลี่ยของพื้นที่ใกล้เคียง 3x3 ที่กึ่งกลางที่ตำแหน่งการสลับ หากการสลับช่วยปรับปรุงสีเฉลี่ยของละแวกใกล้เคียงที่ได้รับอนุญาตถึงแม้ว่ามันจะทำให้แต่ละพิกเซลมีความแม่นยำน้อยลง สำหรับบางภาพคู่นี้มีผลกระทบที่น่าสงสัยกับคุณภาพ (และทำให้เอฟเฟ็กต์สีมีความโดดเด่นน้อยลง) แต่สำหรับบางคน (เช่นทรงกลม -> อะไรก็ได้) มันช่วยได้ไม่น้อย ปัจจัย "รายละเอียด" จะเน้นพิกเซลกลางเป็นระดับตัวแปร การเพิ่มจะลดจำนวน dither โดยรวม แต่ยังคงรายละเอียดที่ละเอียดกว่าจากภาพเป้าหมาย การเพิ่มประสิทธิภาพ dithered ช้าลง

ค่าเฉลี่ยของการทดลองในห้องแล็บเช่นเดียวกับที่ควรทำไม่ได้เป็นธรรมจริงๆ (ควรแปลงเป็น XYZ, โดยเฉลี่ยและแปลงกลับ) แต่ใช้ได้ดีสำหรับวัตถุประสงค์เหล่านี้

ภาพเหล่านี้มีข้อ จำกัด ในการยุติที่ 100 และ 200 (จบช่วงแรกเมื่อยอมรับน้อยกว่า 1 ใน 5000 swaps และขั้นตอนที่สองที่ 1 ใน 2500) และปัจจัยรายละเอียดที่แปรเปลี่ยนของ 12 (เล็กน้อยที่กระชับกว่าชุดก่อนหน้าเล็กน้อย ) ที่การตั้งค่าคุณภาพสูงพิเศษนี้รูปภาพใช้เวลาในการสร้างนาน แต่ด้วยการทำให้ขนานงานทั้งหมดยังคงเสร็จสิ้นภายในหนึ่งชั่วโมงในกล่อง 6-core ของฉัน การกระแทกค่ามากถึง 500 หรือมากกว่านั้นจะทำให้ภาพเสร็จภายในไม่กี่นาทีพวกเขาก็ดูขัดน้อยลงเล็กน้อย ฉันต้องการแสดงอัลกอริทึมให้ดีที่สุดที่นี่

โค้ดไม่ได้มีความหมายใด ๆ :

#!/usr/bin/perl
use strict;
use warnings;
use Image::Magick;
use Graphics::ColorObject 'RGB_to_Lab';
use List::Util qw(sum max);

my $source = Image::Magick->new;
$source->Read($ARGV[0]);
my $target = Image::Magick->new;
$target->Read($ARGV[1]);
my ($limit1, $limit2, $detail) = @ARGV[2,3,4];

my ($width, $height) = ($target->Get('width'), $target->Get('height'));

# Transfer the pixels of the $source onto a new canvas with the diemnsions of $target
$source->Set(magick => 'RGB');
my $img = Image::Magick->new(size => "${width}x${height}", magick => 'RGB', depth => 8);
$img->BlobToImage($source->ImageToBlob);

my ($made, $rejected) = (0,0);

system("rm anim/*.png");

my (@img_lab, @target_lab);
for my $x (0 .. $width) {
  for my $y (0 .. $height) {
    $img_lab[$x][$y] = RGB_to_Lab([$img->getPixel(x => $x, y => $y)], 'sRGB');
    $target_lab[$x][$y] = RGB_to_Lab([$target->getPixel(x => $x, y => $y)], 'sRGB');
  }
}

my $n = 0;
my $frame = 0;
my $mode = 1;

while (1) {
  $n++;

  my $swap = 0;
  my ($x1, $x2, $y1, $y2) = (int rand $width, int rand $width, int rand $height, int rand $height);
  my ($dist, $dist_swapped);

  if ($mode == 1) {
    $dist = (sum map { ($img_lab[$x1][$y1][$_] - $target_lab[$x1][$y1][$_])**2 } 0..2)
          + (sum map { ($img_lab[$x2][$y2][$_] - $target_lab[$x2][$y2][$_])**2 } 0..2);

    $dist_swapped = (sum map { ($img_lab[$x2][$y2][$_] - $target_lab[$x1][$y1][$_])**2 } 0..2)
                  + (sum map { ($img_lab[$x1][$y1][$_] - $target_lab[$x2][$y2][$_])**2 } 0..2);

  } else { # dither mode
    my $xoffmin = ($x1 == 0 || $x2 == 0 ? 0 : -1);
    my $xoffmax = ($x1 == $width - 1 || $x2 == $width - 1 ? 0 : 1);
    my $yoffmin = ($y1 == 0 || $y2 == 0 ? 0 : -1);
    my $yoffmax = ($y1 == $height - 1 || $y2 == $height - 1 ? 0 : 1);

    my (@img1, @img2, @target1, @target2, $points);
    for my $xoff ($xoffmin .. $xoffmax) {
      for my $yoff ($yoffmin .. $yoffmax) {
        $points++;
        for my $chan (0 .. 2) {
          $img1[$chan] += $img_lab[$x1+$xoff][$y1+$yoff][$chan];
          $img2[$chan] += $img_lab[$x2+$xoff][$y2+$yoff][$chan];
          $target1[$chan] += $target_lab[$x1+$xoff][$y1+$yoff][$chan];
          $target2[$chan] += $target_lab[$x2+$xoff][$y2+$yoff][$chan];
        }
      }
    }

    my @img1s = @img1;
    my @img2s = @img2;
    for my $chan (0 .. 2) {
      $img1[$chan] += $img_lab[$x1][$y1][$chan] * ($detail - 1);
      $img2[$chan] += $img_lab[$x2][$y2][$chan] * ($detail - 1);

      $target1[$chan] += $target_lab[$x1][$y1][$chan] * ($detail - 1);
      $target2[$chan] += $target_lab[$x2][$y2][$chan] * ($detail - 1);

      $img1s[$chan] += $img_lab[$x2][$y2][$chan] * $detail - $img_lab[$x1][$y1][$chan];
      $img2s[$chan] += $img_lab[$x1][$y1][$chan] * $detail - $img_lab[$x2][$y2][$chan];
    }

    $dist = (sum map { ($img1[$_] - $target1[$_])**2 } 0..2)
          + (sum map { ($img2[$_] - $target2[$_])**2 } 0..2);

    $dist_swapped = (sum map { ($img1s[$_] - $target1[$_])**2 } 0..2)
                  + (sum map { ($img2s[$_] - $target2[$_])**2 } 0..2);

  }

  if ($dist_swapped < $dist) {
    my @pix1 = $img->GetPixel(x => $x1, y => $y1);
    my @pix2 = $img->GetPixel(x => $x2, y => $y2);
    $img->SetPixel(x => $x1, y => $y1, color => \@pix2);
    $img->SetPixel(x => $x2, y => $y2, color => \@pix1);
    ($img_lab[$x1][$y1], $img_lab[$x2][$y2]) = ($img_lab[$x2][$y2], $img_lab[$x1][$y1]);
    $made ++;
  } else {
    $rejected ++;
  }

  if ($n % 50000 == 0) {
#    print "Made: $made Rejected: $rejected\n";
    $img->Write('png:out.png');
    system("cp", "out.png", sprintf("anim/frame%05d.png", $frame++));
    if ($mode == 1 and $made < $limit1) {
      $mode = 2;
      system("cp", "out.png", "nodither.png");
    } elsif ($mode == 2 and $made < $limit2) {
      last;
    }
    ($made, $rejected) = (0, 0);
  }
}

ผล

จานสีโกธิคอเมริกัน

ความแตกต่างเล็ก ๆ น้อย ๆ ที่นี่กับ dithering หรือไม่

Mona Lisa palette

การทำ Dithering ช่วยลดแถบคาดบนทรงกลม แต่ไม่สวยโดยเฉพาะ

จานสี Starry Night

โมนาลิซ่ายังคงรักษารายละเอียดได้มากขึ้นด้วยการทำสี Spheres เกี่ยวกับสถานการณ์เดียวกับครั้งที่แล้ว

จานสี

สตาร์รี่ไนท์โดยไม่ทำให้งุนงงเป็นสิ่งที่ยอดเยี่ยมที่สุด Dithering ทำให้ภาพถ่ายมีความแม่นยำมากขึ้น แต่ก็น่าสนใจน้อยลง

จานสีทรงกลม

ดังที่ aditsu กล่าวว่าการทดสอบจริง ฉันคิดว่าฉันผ่าน

Dithering ช่วยอย่างมากกับ American Gothic และ Mona Lisa ผสมสีเทาและสีอื่น ๆ เข้ากับพิกเซลที่เข้มข้นขึ้นเพื่อสร้างสีผิวที่มีความแม่นยำกึ่งกึ่งแทนรอยเปื้อนที่น่ากลัว กรีดร้องนั้นได้รับผลกระทบน้อยกว่ามาก

Camaro - Mustang

แหล่งรูปภาพจากโพสต์ของข้อบกพร่อง

Camaro:

มัสแตง:

จาน Camaro

ดูดีทีเดียวโดยไม่ต้องขุดคู

การขัน "อย่างแน่นหนา" (ปัจจัยรายละเอียดเดียวกันกับด้านบน) ไม่เปลี่ยนแปลงมากนักเพียงแค่เพิ่มรายละเอียดเล็กน้อยในไฮไลท์บนฝากระโปรงและหลังคา

การลดลงเล็กน้อย (ปัจจัยรายละเอียดลดลงถึง 6) ทำให้โทนเสียงนั้นนุ่มนวลขึ้นและรายละเอียดเพิ่มเติมสามารถมองเห็นได้ผ่านกระจกหน้ารถ แต่รูปแบบที่แตกต่างนั้นชัดเจนกว่า

จานมัสแตง

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

การทำ Dithering นั้นสามารถแพร่กระจายพิกเซลสีเหลืองพิเศษเหล่านั้นไปได้ดังนั้นพวกเขาจึงไม่แตะต้องทำให้พวกมันกระจัดกระจายไปทั่วพื้นหลังในกระบวนการ จุดเด่นและเงาบนรถดูดีขึ้นเล็กน้อย

อีกครั้งเสียงรบกวนเล็กน้อยที่มีระดับความดังสูงสุดเผยให้เห็นรายละเอียดเพิ่มเติมเกี่ยวกับไฟหน้าและกระจกหน้ารถ รถเกือบดูแดงอีกครั้ง พื้นหลังเป็น clumpier ด้วยเหตุผลบางอย่าง ไม่แน่ใจว่าฉันชอบมัน

วีดีโอ

( ลิงก์ HQ )


3
ฉันชอบภาพนี้มากภาพที่มีจุดแข็งมากมีความรู้สึกที่ยอดเยี่ยมมาก Seurat Mona Lisa ทำใครได้บ้าง
บอริสแมงมุม

2
อัลกอริทึมของคุณทำผลงานได้ยอดเยี่ยมด้วย Spheres pallette ที่น่ากลัวทำงานได้ดี!
Snowbody

1
@ ฮอบส์การใช้จานสีรุ้งที่ยอดเยี่ยมและรถยนต์ของคุณเกือบจะสมบูรณ์แบบ! จะเป็นไรไหมถ้าฉันใช้รูปบางรูปในวิดีโอ YouTube เพื่อแสดงสคริปต์ภาพเคลื่อนไหวของฉัน
งานอดิเรกของ Calvin

1
ฉันคิดว่าเหตุผลเดียวที่ dithering ของคุณให้รูปแบบนั้นก็เพราะคุณใช้พิกเซลแบบ 3x3 บล็อกโดยน้ำหนักนั้นเปลี่ยนไปเฉพาะตรงกลาง หากคุณถ่วงน้ำหนักพิกเซลตามระยะทางจากจุดศูนย์กลาง (ดังนั้นพิกเซลมุมจะมีส่วนร่วมน้อยกว่า 4 พิกเซลที่อยู่ติดกัน) และอาจขยายเป็นพิกเซลมากขึ้นเล็กน้อย มันเป็นเช่นนี้การปรับปรุงที่ดีสำหรับจานสีรุ้งดังนั้นอาจจะคุ้มค่าที่จะได้เห็นว่ามันสามารถทำอะไรได้อีกมาก ...
trichoplax

1
@githubphagocyte ฉันใช้เวลาครึ่งวันลองทำอย่างนั้น แต่ก็ไม่มีอะไรที่ฉันต้องการ มีตัวแปรหนึ่งตัวที่สร้างแบบสุ่มดูดีมาก แต่ก็ให้ระยะการเพิ่มประสิทธิภาพที่ฉันไม่เคยยกเลิก ตัวแปรอื่น ๆ นั้นมีการประดิษฐ์ที่แย่กว่าหรือมีแรงยึดติดที่มากเกินไป วิธีการแก้ปัญหา Cของฉันได้ดีขึ้นขอบคุณการแก้ไขเส้นโค้งของ ImageMagick มันเป็นลูกบาศก์อิสระดังนั้นฉันคิดว่าใช้พื้นที่ใกล้เคียง 5x5
ฮอบส์

79

หลาม

ความคิดนั้นง่าย: พิกเซลทุกจุดมีจุดในพื้นที่ 3D RGB เป้าหมายคือการจับคู่แต่ละพิกเซลของแหล่งที่มาและหนึ่งในภาพปลายทางโดยเฉพาะอย่างยิ่งพวกเขาควรจะ 'ปิด' (แทนสี 'เดียวกัน') เนื่องจากพวกมันสามารถกระจายในรูปแบบที่แตกต่างกันมากเราจึงไม่สามารถจับคู่เพื่อนบ้านที่ใกล้ที่สุดได้

กลยุทธ์

ปล่อย nเป็นจำนวนเต็ม (เล็ก 3-255 หรือมากกว่านั้น) ตอนนี้พิกเซลคลาวด์ในพื้นที่ RGB จะถูกจัดเรียงตามแกนแรก (R) ชุดพิกเซลนี้ถูกแบ่งพาร์ติชันออกเป็นพาร์ติชั่น n ตอนนี้ พาร์ติชั่นแต่ละอันจะถูกจัดเรียงตามแกนที่สอง (B) ซึ่งพาร์ติชั่นจะเรียงลำดับแบบเดียวกับที่แบ่งพาร์ติชัน เราทำสิ่งนี้กับภาพทั้งสองและตอนนี้ก็มีจุดต่าง ๆ มากมาย ตอนนี้เราก็สามารถจับคู่พิกเซลตามตำแหน่งของพวกเขาในอาเรย์, sice พิกเซลในตำแหน่งเดียวกันในแต่ละอาเรย์มีตำแหน่งที่คล้ายกันเมื่อเทียบกับแต่ละ pixelcloud ในพื้นที่ RGB

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

รหัส

คำเตือน: ฉันเป็นมือใหม่ที่แน่นอนในการหลาม

from PIL import Image

n = 5 #number of partitions per channel.

src_index = 3 #index of source image
dst_index = 2 #index of destination image

images =  ["img0.bmp","img1.bmp","img2.bmp","img3.bmp"];
src_handle = Image.open(images[src_index])
dst_handle = Image.open(images[dst_index])
src = src_handle.load()
dst = dst_handle.load()
assert src_handle.size[0]*src_handle.size[1] == dst_handle.size[0]*dst_handle.size[1],"images must be same size"

def makePixelList(img):
    l = []
    for x in range(img.size[0]):
        for y in range(img.size[1]):
            l.append((x,y))
    return l

lsrc = makePixelList(src_handle)
ldst = makePixelList(dst_handle)

def sortAndDivide(coordlist,pixelimage,channel): #core
    global src,dst,n
    retlist = []
    #sort
    coordlist.sort(key=lambda t: pixelimage[t][channel])
    #divide
    partitionLength = int(len(coordlist)/n)
    if partitionLength <= 0:
        partitionLength = 1
    if channel < 2:
        for i in range(0,len(coordlist),partitionLength):
            retlist += sortAndDivide(coordlist[i:i+partitionLength],pixelimage,channel+1)
    else:
        retlist += coordlist
    return retlist

print(src[lsrc[0]])

lsrc = sortAndDivide(lsrc,src,0)
ldst = sortAndDivide(ldst,dst,0)

for i in range(len(ldst)):
    dst[ldst[i]] = src[lsrc[i]]

dst_handle.save("exchange"+str(src_index)+str(dst_index)+".png")

ผลลัพธ์

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

การเปรียบเทียบผลลัพธ์ของฉัน

แกลเลอรี่เต็มรูปแบบที่นี่: https://imgur.com/a/hzaAm#6

รายละเอียดของแม่น้ำ

monalisa> แม่น้ำ

monalisa> แม่น้ำ

คน> แม่น้ำ

คน> แม่น้ำ

ลูกบอล> แม่น้ำ

ลูก> แม่น้ำ

กลางคืนที่เต็มไปด้วยดวงดาว> แม่น้ำ

Nocturne> แม่น้ำ

เสียงร้องไห้> แม่น้ำ

thecry> แม่น้ำ

ลูกบอล> MonaLisa, ต่างกัน n = 2,4,6, ... , 20

นี่เป็นงานที่ท้าทายที่สุดที่ฉันคิดว่าไกลจากรูปภาพที่สวยงามนี่คือ gif (ต้องลดเหลือ 256 สี) ของค่าพารามิเตอร์ differen n = 2,4,6, ... , 20 สำหรับฉันมันเป็นเรื่องที่น่าประหลาดใจที่ค่าที่ต่ำมากให้ภาพที่ดีกว่า (เมื่อดูที่หน้าของ Mme. Lisa): ลูกบอล> monalisa

ขอโทษฉันหยุดไม่ได้

คุณชอบอันไหนดีกว่า Chevy Camaro หรือ Ford Mustang? บางทีเทคนิคนี้อาจได้รับการปรับปรุงและใช้สำหรับระบายสีภาพ bw ตอนนี้ที่นี่: ก่อนอื่นฉันตัดรถออกจากพื้นหลังอย่างคร่าวๆโดยวาดเป็นสีขาว (เป็นสีไม่ใช่มืออาชีพมาก ... ) จากนั้นใช้โปรแกรมไพ ธ อนในแต่ละทิศทาง

ต้นฉบับ

เป็นต้นฉบับ เป็นต้นฉบับ

recoloured

มีสิ่งประดิษฐ์บางอย่างฉันคิดว่าเพราะพื้นที่ของรถคันหนึ่งใหญ่กว่าอีกคันหนึ่งเล็กน้อยและเนื่องจากทักษะทางศิลปะของฉันค่อนข้างแย่ =) จัดการ ป้อนคำอธิบายรูปภาพที่นี่


5
ว้าวฉันรักแม่น้ำ Starry Night มากและเสียงกรีดร้องของ The Scream ทำให้ดูเหมือนแม่น้ำไฟ
งานอดิเรกของ Calvin

@ งานอดิเรกของ Calvin ว้าวใช่แล้ว! พวกเขาเกือบจะดูฉันไม่ได้ดูพวกเขาอย่างใกล้ชิดตั้งแต่ฉันไม่ว่างอัพโหลดภาพใหม่ = P แต่ขอบคุณสำหรับความท้าทายที่ยิ่งใหญ่นี้!
ข้อบกพร่อง

3
ฉันรักการเปลี่ยนรถ ครั้งนี้อาจกลายเป็นการเปลี่ยนแปลงการแก้ไขภาพบางชนิดจริง ๆ !
tomsmeding

@tomsmeding ขอบคุณฉันคิดเกี่ยวกับการใช้เทคนิคในการปรับสีของภาพ b / w แล้ว แต่ถึงตอนนี้ก็ประสบความสำเร็จอย่าง จำกัด แต่บางทีเราต้องการแนวคิดเพิ่มเติมสำหรับการทำสิ่งนี้ให้สำเร็จ =)
ข้อผิดพลาด

@flawr มันจะเป็นไรไหมถ้าฉันใช้รูปของคุณบางรูปในวิดีโอ YouTube เพื่อแสดงสคริปต์ภาพเคลื่อนไหวของฉัน
งานอดิเรกของ Calvin

48

Python - ทางออกที่ดีที่สุดในทางทฤษฎี

ฉันบอกว่าเหมาะสมที่สุดในทางทฤษฎีเพราะทางออกที่ดีที่สุดอย่างแท้จริงนั้นไม่สามารถคำนวณได้ ฉันเริ่มต้นด้วยการอธิบายวิธีแก้ปัญหาเชิงทฤษฎีแล้วอธิบายวิธีที่ฉันบิดมันเพื่อให้สามารถคำนวณได้ทั้งในอวกาศและเวลา

ฉันพิจารณาโซลูชันที่เหมาะสมที่สุดเนื่องจากเป็นข้อผิดพลาดทั้งหมดที่ต่ำที่สุดในพิกเซลทั้งหมดระหว่างรูปภาพเก่าและใหม่ ข้อผิดพลาดระหว่างสองพิกเซลถูกกำหนดให้เป็นระยะทางยูคลิดระหว่างจุดในพื้นที่ 3 มิติซึ่งค่าสีแต่ละค่า (R, G, B) เป็นพิกัด ในทางปฏิบัติเนื่องจากมนุษย์มองเห็นสิ่งของต่าง ๆ ทางออกที่ดีที่สุดอาจไม่ใช่ทางออกที่ดีที่สุด อย่างไรก็ตามดูเหมือนว่าจะทำได้ค่อนข้างดีในทุกกรณี

การคำนวณการทำแผนที่ผมคิดว่าเรื่องนี้เป็นน้ำหนักต่ำสุดปัญหาการจับคู่สองฝ่าย กล่าวอีกนัยหนึ่งมีโหนดอยู่สองชุด: พิกเซลดั้งเดิมและพิกเซลจานสี ขอบถูกสร้างขึ้นระหว่างแต่ละพิกเซลในทั้งสองชุด (แต่ไม่มีการสร้างขอบภายในชุด) ค่าใช้จ่ายหรือน้ำหนักของขอบคือระยะ Euclidian ระหว่างสองพิกเซลดังที่อธิบายไว้ข้างต้น ยิ่งสีใกล้เคียงกับภาพมากเท่าไหร่ค่าใช้จ่ายระหว่างพิกเซลก็จะยิ่งต่ำลงเท่านั้น

ตัวอย่างการจับคู่สองฝ่าย

นี้จะสร้างเมทริกซ์ค่าใช้จ่ายของขนาด N 2 สำหรับอิมเมจเหล่านี้ที่ N = 123520 จำเป็นต้องมีหน่วยความจำประมาณ 40 GB เพื่อแสดงต้นทุนเป็นจำนวนเต็มและครึ่งหนึ่งเป็นจำนวนเต็มแบบสั้น ทั้งสองวิธีฉันมีหน่วยความจำไม่เพียงพอในเครื่องของฉันเพื่อพยายาม อีกประเด็นคืออัลกอริธึมของฮังการีหรือ Jonker-Volgenantซึ่งสามารถใช้ในการแก้ปัญหานี้ทำงานในเวลาN 3 ในขณะที่คำนวณได้แน่นอนการสร้างโซลูชันต่อภาพน่าจะใช้เวลาเป็นชั่วโมงหรือเป็นวัน

เพื่อแก้ไขปัญหานี้ฉันสุ่มเรียงลำดับรายการพิกเซลทั้งสองแยกรายการเป็นกลุ่ม C เรียกใช้C ++ของอัลกอริทึม Jonker-Volgenant ในแต่ละรายการย่อยคู่แล้วเข้าร่วมรายการกลับเพื่อสร้างการแมปสุดท้าย ดังนั้นรหัสด้านล่างจะอนุญาตให้หนึ่งค้นหาโซลูชันที่ดีที่สุดอย่างแท้จริงหากพวกเขาตั้งค่าขนาด chunk C เป็น 1 (ไม่มี chunking) และมีหน่วยความจำเพียงพอ สำหรับภาพเหล่านี้ฉันตั้งค่า C เป็น 16 ดังนั้น N จึงกลายเป็น 7720 ใช้เวลาเพียงไม่กี่นาทีต่อภาพ

วิธีคิดง่ายๆว่าทำไมสิ่งนี้ถึงได้ผลคือการเรียงลำดับรายการพิกเซลแบบสุ่มแล้วการเซตย่อยก็เหมือนกับการสุ่มภาพ ดังนั้นโดยการตั้งค่า C = 16 มันเหมือนกับการสุ่มตัวอย่างขนาด N / C 16 แบบที่แตกต่างจากทั้งต้นฉบับและจานสี ได้รับมีวิธีที่ดีกว่าในการแยกรายการ แต่วิธีการสุ่มให้ผลลัพธ์ที่ดี

import subprocess
import multiprocessing as mp
import sys
import os
import sge
from random import shuffle
from PIL import Image
import numpy as np
import LAPJV
import pdb

def getError(p1, p2):
    return (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2 + (p1[2]-p2[2])**2

def getCostMatrix(pallete_list, source_list):
    num_pixels = len(pallete_list)
    matrix = np.zeros((num_pixels, num_pixels))

    for i in range(num_pixels):
        for j in range(num_pixels):
            matrix[i][j] = getError(pallete_list[i], source_list[j])

    return matrix

def chunks(l, n):
    if n < 1:
        n = 1
    return [l[i:i + n] for i in range(0, len(l), n)]

def imageToColorList(img_file):
    i = Image.open(img_file)

    pixels = i.load()
    width, height = i.size

    all_pixels = []
    for x in range(width):
        for y in range(height):
            pixel = pixels[x, y]
            all_pixels.append(pixel)

    return all_pixels

def colorListToImage(color_list, old_img_file, new_img_file, mapping):
    i = Image.open(old_img_file)

    pixels = i.load()
    width, height = i.size
    idx = 0

    for x in range(width):
        for y in range(height):
            pixels[x, y] = color_list[mapping[idx]]
            idx += 1

    i.save(new_img_file)

def getMapping(pallete_list, source_list):
    matrix = getCostMatrix(source_list, pallete_list)
    result = LAPJV.lap(matrix)[1]
    ret = []
    for i in range(len(pallete_list)):
        ret.append(result[i])
    return ret

def randomizeList(l):
    rdm_l = list(l)
    shuffle(rdm_l)
    return rdm_l

def getPartialMapping(zipped_chunk):
    pallete_chunk = zipped_chunk[0]
    source_chunk = zipped_chunk[1]
    subl_pallete = map(lambda v: v[1], pallete_chunk)
    subl_source = map(lambda v: v[1], source_chunk)
    mapping = getMapping(subl_pallete, subl_source)
    return mapping

def getMappingWithPartitions(pallete_list, source_list, C = 1):
    rdm_pallete = randomizeList(enumerate(pallete_list))
    rdm_source = randomizeList(enumerate(source_list))
    num_pixels = len(rdm_pallete)
    real_mapping = [0] * num_pixels

    chunk_size = int(num_pixels / C)

    chunked_rdm_pallete = chunks(rdm_pallete, chunk_size)
    chunked_rdm_source = chunks(rdm_source, chunk_size)
    zipped_chunks = zip(chunked_rdm_pallete, chunked_rdm_source)

    pool = mp.Pool(2)
    mappings = pool.map(getPartialMapping, zipped_chunks)

    for mapping, zipped_chunk in zip(mappings, zipped_chunks):
        pallete_chunk = zipped_chunk[0]
        source_chunk = zipped_chunk[1]
        for idx1,idx2 in enumerate(mapping):
            src_px = source_chunk[idx1]
            pal_px = pallete_chunk[idx2]
            real_mapping[src_px[0]] = pal_px[0]

    return real_mapping

def run(pallete_name, source_name, output_name):
    print("Getting Colors...")
    pallete_list = imageToColorList(pallete_name)
    source_list = imageToColorList(source_name)

    print("Creating Mapping...")
    mapping = getMappingWithPartitions(pallete_list, source_list, C = 16)

    print("Generating Image...");
    colorListToImage(pallete_list, source_name, output_name, mapping)

if __name__ == '__main__':
    pallete_name = sys.argv[1]
    source_name = sys.argv[2]
    output_name = sys.argv[3]
    run(pallete_name, source_name, output_name)

ผล:

เช่นเดียวกับโซลูชันของ aditsu รูปภาพเหล่านี้สร้างขึ้นโดยใช้พารามิเตอร์เดียวกันทั้งหมด พารามิเตอร์เดียวที่นี่คือ C ซึ่งควรตั้งให้ต่ำที่สุด สำหรับฉัน C = 16 เป็นความสมดุลระหว่างความเร็วและคุณภาพที่ดี

ภาพทั้งหมด: http://imgur.com/a/RCZiX#0

จานสีโกธิคอเมริกัน

mona โกธิค กรีดร้องโกธิค

Mona Lisa palette

โกธิค mona กรีดร้อง-Mona

จานสี Starry Night

mona คืน แม่น้ำคืน

จานสี

โกธิคเสียงกรีดร้อง mona-เสียงกรีดร้อง

จานแม่น้ำ

โกธิคทรงกลม mona ทรงกลม

จานสีทรงกลม

โกธิคทรงกลม mona ทรงกลม


4
ฉันชอบ (Scream -> Starry night) และ (Spheres -> Starry night) (Spheres -> Mona Lisa) ก็ไม่ได้เลวร้ายเกินไปเช่นกัน แต่ฉันอยากเห็นความคิดที่ดีขึ้น
John Dvorak

ฮ่า ๆ ผมก็คิดเหมือนกันเกี่ยวกับการจับคู่ฝ่ายกราฟ แต่ยกเลิกแล้วความคิดเพราะ N ^ 3 ..
RobAu

อัลกอริธึม "แบบเกือบจะกำหนด" นี้ชนะ IMO ที่กำหนดขึ้นทั้งหมดและยืนขึ้นด้วยวิธีการสุ่มที่ดี ฉันชอบมัน.
ฮอบส์

1
ฉันไม่เห็นด้วยกับความคิดของคุณเกี่ยวกับทางออกที่ดีที่สุด ทำไม? การทำ Dithering สามารถปรับปรุงคุณภาพการรับรู้ (สำหรับมนุษย์) แต่ให้คะแนนที่ต่ำกว่าโดยใช้คำจำกัดความของคุณ การใช้ RGB แทน CIELUV ก็เป็นความผิดพลาดเช่นกัน
Thomas Eding

39

หลาม

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

Rainbow -> Mona Lisa (เพิ่มความคมชัดของแหล่ง Mona Lisa, ความสว่างเท่านั้น)

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

Rainbow -> Mona Lisa (แหล่งที่ไม่มีความคมชัด, ถ่วงน้ำหนักด้วย Y = 10, I = 10, Q = 0)

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

Mona Lisa -> American Gothic (แหล่งที่ไม่รุนแรง, ความสว่างเท่านั้น)

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

Mona Lisa -> American Gothic (แหล่งที่ไม่มีความคมชัด, ถ่วงน้ำหนักด้วย Y = 1, I = 10, Q = 1)

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

แม่น้ำ -> Rainbow (แหล่งที่ไม่มีความคมชัด, ความส่องสว่างเท่านั้น)

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

โดยทั่วไปจะได้รับพิกเซลทั้งหมดจากภาพสองภาพเป็นสองรายการ

จัดเรียงพวกเขาด้วยความส่องสว่างเป็นกุญแจสำคัญ Y ใน YIQ หมายถึงความสว่าง

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

import Image, ImageFilter, colorsys

def getPixels(image):
    width, height = image.size
    pixels = []
    for x in range(width):
        for y in range(height):
            pixels.append([(x,y), image.getpixel((x,y))])
    return pixels

def yiq(pixel):
    # y is the luminance
    y,i,q = colorsys.rgb_to_yiq(pixel[1][0], pixel[1][6], pixel[1][7])
    # Change the weights accordingly to get different results
    return 10*y + 0*i + 0*q

# Open the images
source  = Image.open('ml.jpg')
pallete = Image.open('rainbow.png')

# Sharpen the source... It won't affect the palette anyway =D
source = source.filter(ImageFilter.SHARPEN)

# Sort the two lists by luminance
sourcePixels  = sorted(getPixels(source),  key=yiq)
palletePixels = sorted(getPixels(pallete), key=yiq)

copy = Image.new('RGB', source.size)

# Iterate through all the coordinates of source
# And set the new color
index = 0
for sourcePixel in sourcePixels:
    copy.putpixel(sourcePixel[0], palletePixels[index][8])
    index += 1

# Save the result
copy.save('copy.png')

เพื่อให้ทันกับแนวโน้มของภาพเคลื่อนไหว ...

พิกเซลในเสียงกรีดร้องนั้นถูกแยกอย่างรวดเร็วในคืนที่เต็มไปด้วยดาว

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


2
แนวคิดเรียบง่ายนั้นทำงานได้ดีจริงๆ ฉันสงสัยว่าสามารถยืดออกได้หรือไม่และใช้ความสว่างความอิ่มตัวและสี (เช่น 10 * L + S + H) เพื่อให้ได้การจับคู่สีพื้นที่เดียวกันที่ดีขึ้น
Moogie

1
@ bitpwnr ภาพของคุณไม่ผ่านสคริปต์ของฉัน แต่เกือบจะแน่นอนเพราะคุณกำลังใช้ jpegs ที่แตกต่างกันเล็กน้อยที่ฉันมีในตอนแรกดังนั้นจึงไม่ใช่เรื่องใหญ่ อย่างไรก็ตามฉันสามารถรันโค้ดของคุณหลังจากเปลี่ยน [6], [7] และ [8] ด้วย [1], [2] และ [1] ฉันได้ภาพเหมือนกัน แต่มันก็เป็นคำที่มีเอกลักษณ์มาก: P
งานอดิเรกของ Calvin

ภาพของคุณชัดเจนมาก แต่ก็ค่อนข้างจะ
แย่ไปกว่า

@ Calvin'sHobbies Opps แก้ไขความผิดพลาด
Vectorized

@bitpwner จะเป็นไรไหมถ้าฉันใช้รูปของคุณบางรูปในวิดีโอ YouTube เพื่อแสดงสคริปต์ภาพเคลื่อนไหวของฉัน
งานอดิเรกของ Calvin

39

C # Winform - Visual Studio 2010

แก้ไขเพิ่ม Dithering

นั่นเป็นเวอร์ชั่นอัลกอริทึมแบบสุ่มสลับของฉัน - @hobbs รส ฉันยังรู้สึกว่าการคิดแบบไม่สุ่มบางประเภททำได้ดีกว่า ...

การทำสีอย่างประณีตในพื้นที่ Y-Cb-Cr (เช่นเดียวกับการบีบอัด jpeg)

รายละเอียดสองเฟส:

  1. สำเนาของพิกเซลจากแหล่งกำเนิดตามลำดับความสว่าง ภาพนี้ให้ภาพที่ดี แต่ไม่อิ่มตัว - ระดับเกือบเทา - ใกล้เวลา 0
  2. สลับสุ่มพิกเซลซ้ำหลายครั้ง การสลับจะทำถ้าสิ่งนี้ให้เดลต้าที่ดีขึ้น (เคารพแหล่งที่มา) ในเซลล์ 3x3 ที่มีพิกเซล ดังนั้นมันจึงเป็นเอฟเฟกต์ที่แวววาว เดลต้าคำนวณบนพื้นที่ Y-Cr-Cb โดยไม่มีการถ่วงน้ำหนักของส่วนประกอบต่าง ๆ

นี่เป็นวิธีการเดียวกับที่ @hobbs ใช้โดยไม่มีการสลับแบบสุ่มครั้งแรกโดยไม่ทำให้งง เพียงแค่เวลาของฉันจะสั้นกว่า (ภาษานับ?) และฉันคิดว่าภาพของฉันจะดีกว่า (อาจใช้พื้นที่สีที่ถูกต้องมากขึ้น)

การใช้งานโปรแกรม: ใส่ภาพ. png ในโฟลเดอร์ c: \ temp ของคุณตรวจสอบองค์ประกอบในรายการเพื่อเลือกภาพจานสีเลือกองค์ประกอบในรายการเพื่อเลือกภาพต้นฉบับ (ไม่เป็นมิตรกับผู้ใช้) คลิกปุ่มเริ่มเพื่อเริ่มทำรายละเอียดการบันทึกเป็นไปโดยอัตโนมัติ (แม้ว่าคุณไม่ต้องการ - ระวัง)

เวลาการทำอย่างละเอียดต่ำกว่า 90 วินาที

อัปเดตผลลัพธ์

Palette: American Gothic

Monna Lisa รุ้ง แม่น้ำ กรี๊ด สตาร์รี่ไนท์

จานสี: Monna Lisa

โกธิคอเมริกัน รุ้ง แม่น้ำ กรี๊ด สตาร์รี่ไนท์

จานสี: รุ้ง

โกธิคอเมริกัน Monna Lisa แม่น้ำ กรี๊ด สตาร์รี่ไนท์

จานสี: แม่น้ำ

โกธิคอเมริกัน Monna Lisa รุ้ง กรี๊ด สตาร์รี่ไนท์

จานสี: กรีดร้อง

โกธิคอเมริกัน Monna Lisa รุ้ง แม่น้ำ สตาร์รี่ไนท์

จานสี: Starry Night

โกธิคอเมริกัน Monna Lisa รุ้ง แม่น้ำ กรี๊ด

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;

namespace Palette
{
    public struct YRB
    {
        public int y, cb, cr;

        public YRB(int r, int g, int b)
        {
            y = (int)(0.299 * r + 0.587 * g + 0.114 * b);
            cb = (int)(128 - 0.168736 * r - 0.331264 * g + 0.5 * b);
            cr = (int)(128 + 0.5 * r - 0.418688 * g - 0.081312 * b);
        }
    }

    public struct Pixel
    {
        private const int ARGBAlphaShift = 24;
        private const int ARGBRedShift = 16;
        private const int ARGBGreenShift = 8;
        private const int ARGBBlueShift = 0;

        public int px, py;
        private uint _color;
        public YRB yrb;

        public Pixel(uint col, int px = 0, int py = 0)
        {
            this.px = px;
            this.py = py;
            this._color = col;
            yrb = new YRB((int)(col >> ARGBRedShift) & 255, (int)(col >> ARGBGreenShift) & 255, (int)(col >> ARGBBlueShift) & 255); 
        }

        public uint color
        {
            get { 
                return _color; 
            }
            set {
                _color = color;
                yrb = new YRB((int)(color >> ARGBRedShift) & 255, (int)(color >> ARGBGreenShift) & 255, (int)(color >> ARGBBlueShift) & 255);
            }
        }

        public int y
        {
            get { return yrb.y; }
        }
        public int cr
        {
            get { return yrb.cr; }
        }
        public int cb
        {
            get { return yrb.cb; }
        }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            DirectoryInfo di = new System.IO.DirectoryInfo(@"c:\temp\");
            foreach (FileInfo file in di.GetFiles("*.png"))
            {
                ListViewItem item = new ListViewItem(file.Name);
                item.SubItems.Add(file.FullName);
                lvFiles.Items.Add(item);
            }
        }

        private void lvFiles_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            if (e.IsSelected)
            {
                string file = e.Item.SubItems[1].Text;
                GetImagePB(pbSource, file);
                pbSource.Tag = file; 
                DupImage(pbSource, pbOutput);

                this.Width = pbOutput.Width + pbOutput.Left + 20;
                this.Height = Math.Max(pbOutput.Height, pbPalette.Height)+lvFiles.Height*2;   
            }
        }

        private void lvFiles_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            foreach (ListViewItem item in lvFiles.CheckedItems)
            {
                if (item.Index != e.Index) item.Checked = false;
            }
            string file = lvFiles.Items[e.Index].SubItems[1].Text;
            GetImagePB(pbPalette, file);
            pbPalette.Tag = lvFiles.Items[e.Index].SubItems[0].Text; 

            this.Width = pbOutput.Width + pbOutput.Left + 20;
            this.Height = Math.Max(pbOutput.Height, pbPalette.Height) + lvFiles.Height * 2;   
        }

        Pixel[] Palette;
        Pixel[] Source;

        private void BtnStart_Click(object sender, EventArgs e)
        {
            lvFiles.Enabled = false;
            btnStart.Visible = false;
            progressBar.Visible = true; 
            DupImage(pbSource, pbOutput);

            Work(pbSource.Image as Bitmap, pbPalette.Image as Bitmap, pbOutput.Image as Bitmap);

            string newfile = (string)pbSource.Tag +"-"+ (string)pbPalette.Tag;
            pbOutput.Image.Save(newfile, ImageFormat.Png);   

            lvFiles.Enabled = true;
            btnStart.Visible = true;
            progressBar.Visible = false;
        }

        private void Work(Bitmap srcb, Bitmap palb, Bitmap outb)
        {
            GetData(srcb, out Source);
            GetData(palb, out Palette);

            FastBitmap fout = new FastBitmap(outb);
            FastBitmap fsrc = new FastBitmap(srcb);
            int pm = Source.Length;
            int w = outb.Width;
            int h = outb.Height;
            progressBar.Maximum = pm;

            fout.LockImage();
            for (int p = 0; p < pm; p++)
            {
                fout.SetPixel(Source[p].px, Source[p].py, Palette[p].color);
            }
            fout.UnlockImage();

            pbOutput.Refresh();

            var rnd = new Random();
            int totsw = 0;
            progressBar.Maximum = 200;
            for (int i = 0; i < 200; i++)
            {
                int nsw = 0;
                progressBar.Value = i;
                fout.LockImage();
                fsrc.LockImage();
                for (int j = 0; j < 200000; j++)
                {
                    nsw += CheckSwap(fsrc, fout, 1 + rnd.Next(w - 2), 1 + rnd.Next(h - 2), 1 + rnd.Next(w - 2), 1 + rnd.Next(h - 2));
                }
                totsw += nsw;
                lnCurSwap.Text = nsw.ToString();
                lnTotSwap.Text = totsw.ToString();
                fout.UnlockImage();
                fsrc.UnlockImage();
                pbOutput.Refresh();
                Application.DoEvents();
                if (nsw == 0)
                {
                    break;
                }
            }            
        }

        int CheckSwap(FastBitmap fsrc, FastBitmap fout, int x1, int y1, int x2, int y2)
        {
            const int fmax = 3;
            YRB ov1 = new YRB();
            YRB sv1 = new YRB();
            YRB ov2 = new YRB();
            YRB sv2 = new YRB();

            int f;
            for (int dx = -1; dx <= 1; dx++)
            {
                for (int dy = -1; dy <= 1; dy++)
                {
                    f = (fmax - Math.Abs(dx) - Math.Abs(dy));
                    {
                        Pixel o1 = new Pixel(fout.GetPixel(x1 + dx, y1 + dy));
                        ov1.y += o1.y * f;
                        ov1.cb += o1.cr * f;
                        ov1.cr += o1.cb * f;

                        Pixel s1 = new Pixel(fsrc.GetPixel(x1 + dx, y1 + dy));
                        sv1.y += s1.y * f;
                        sv1.cb += s1.cr * f;
                        sv1.cr += s1.cb * f;

                        Pixel o2 = new Pixel(fout.GetPixel(x2 + dx, y2 + dy));
                        ov2.y += o2.y * f;
                        ov2.cb += o2.cr * f;
                        ov2.cr += o2.cb * f;

                        Pixel s2 = new Pixel(fsrc.GetPixel(x2 + dx, y2 + dy));
                        sv2.y += s2.y * f;
                        sv2.cb += s2.cr * f;
                        sv2.cr += s2.cb * f;
                    }
                }
            }
            YRB ox1 = ov1;
            YRB ox2 = ov2;
            Pixel oc1 = new Pixel(fout.GetPixel(x1, y1));
            Pixel oc2 = new Pixel(fout.GetPixel(x2, y2));
            ox1.y += fmax * oc2.y - fmax * oc1.y;
            ox1.cb += fmax * oc2.cr - fmax * oc1.cr;
            ox1.cr += fmax * oc2.cb - fmax * oc1.cb;
            ox2.y += fmax * oc1.y - fmax * oc2.y;
            ox2.cb += fmax  * oc1.cr - fmax * oc2.cr;
            ox2.cr += fmax * oc1.cb - fmax * oc2.cb;

            int curd = Delta(ov1, sv1, 1) + Delta(ov2, sv2, 1);
            int newd = Delta(ox1, sv1, 1) + Delta(ox2, sv2, 1);
            if (newd < curd)
            {
                fout.SetPixel(x1, y1, oc2.color);
                fout.SetPixel(x2, y2, oc1.color);
                return 1;
            }
            return 0;
        }

        int Delta(YRB p1, YRB p2, int sf)
        {
            int dy = (p1.y - p2.y);
            int dr = (p1.cr - p2.cr);
            int db = (p1.cb - p2.cb);

            return dy * dy * sf + dr * dr + db * db;
        }

        Bitmap GetData(Bitmap bmp, out Pixel[] Output)
        {
            FastBitmap fb = new FastBitmap(bmp);
            BitmapData bmpData = fb.LockImage(); 

            Output = new Pixel[bmp.Width * bmp.Height];

            int p = 0;
            for (int y = 0; y < bmp.Height; y++)
            {
                uint col = fb.GetPixel(0, y);
                Output[p++] = new Pixel(col, 0, y);

                for (int x = 1; x < bmp.Width; x++)
                {
                    col = fb.GetNextPixel();
                    Output[p++] = new Pixel(col, x, y);
                }
            }
            fb.UnlockImage(); // Unlock the bits.

            Array.Sort(Output, (a, b) => a.y - b.y);

            return bmp;
        }

        void DupImage(PictureBox s, PictureBox d)
        {
            if (d.Image != null)
                d.Image.Dispose();
            d.Image = new Bitmap(s.Image.Width, s.Image.Height);  
        }

        void GetImagePB(PictureBox pb, string file)
        {
            Bitmap bms = new Bitmap(file, false);
            Bitmap bmp = bms.Clone(new Rectangle(0, 0, bms.Width, bms.Height), PixelFormat.Format32bppArgb);
            bms.Dispose(); 
            if (pb.Image != null)
                pb.Image.Dispose();
            pb.Image = bmp;
        }
    }

    //Adapted from Visual C# Kicks - http://www.vcskicks.com/
    unsafe public class FastBitmap
    {
        private Bitmap workingBitmap = null;
        private int width = 0;
        private BitmapData bitmapData = null;
        private Byte* pBase = null;

        public FastBitmap(Bitmap inputBitmap)
        {
            workingBitmap = inputBitmap;
        }

        public BitmapData LockImage()
        {
            Rectangle bounds = new Rectangle(Point.Empty, workingBitmap.Size);

            width = (int)(bounds.Width * 4 + 3) & ~3;

            //Lock Image
            bitmapData = workingBitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            pBase = (Byte*)bitmapData.Scan0.ToPointer();
            return bitmapData;
        }

        private uint* pixelData = null;

        public uint GetPixel(int x, int y)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            return *pixelData;
        }

        public uint GetNextPixel()
        {
            return *++pixelData;
        }

        public void GetPixelArray(int x, int y, uint[] Values, int offset, int count)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            while (count-- > 0)
            {
                Values[offset++] = *pixelData++;
            }
        }

        public void SetPixel(int x, int y, uint color)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            *pixelData = color;
        }

        public void SetNextPixel(uint color)
        {
            *++pixelData = color;
        }

        public void UnlockImage()
        {
            workingBitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
        }
    }

}

Form1.designer.cs

namespace Palette
{
    partial class Form1
    {
        /// <summary>
        /// Variabile di progettazione necessaria.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Liberare le risorse in uso.
        /// </summary>
        /// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Codice generato da Progettazione Windows Form

        /// <summary>
        /// Metodo necessario per il supporto della finestra di progettazione. Non modificare
        /// il contenuto del metodo con l'editor di codice.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.panel = new System.Windows.Forms.FlowLayoutPanel();
            this.pbSource = new System.Windows.Forms.PictureBox();
            this.pbPalette = new System.Windows.Forms.PictureBox();
            this.pbOutput = new System.Windows.Forms.PictureBox();
            this.btnStart = new System.Windows.Forms.Button();
            this.progressBar = new System.Windows.Forms.ProgressBar();
            this.imageList1 = new System.Windows.Forms.ImageList(this.components);
            this.lvFiles = new System.Windows.Forms.ListView();
            this.lnTotSwap = new System.Windows.Forms.Label();
            this.lnCurSwap = new System.Windows.Forms.Label();
            this.panel.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.pbSource)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbPalette)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbOutput)).BeginInit();
            this.SuspendLayout();
            // 
            // panel
            // 
            this.panel.AutoScroll = true;
            this.panel.AutoSize = true;
            this.panel.Controls.Add(this.pbSource);
            this.panel.Controls.Add(this.pbPalette);
            this.panel.Controls.Add(this.pbOutput);
            this.panel.Dock = System.Windows.Forms.DockStyle.Top;
            this.panel.Location = new System.Drawing.Point(0, 0);
            this.panel.Name = "panel";
            this.panel.Size = new System.Drawing.Size(748, 266);
            this.panel.TabIndex = 3;
            this.panel.WrapContents = false;
            // 
            // pbSource
            // 
            this.pbSource.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbSource.Location = new System.Drawing.Point(3, 3);
            this.pbSource.Name = "pbSource";
            this.pbSource.Size = new System.Drawing.Size(157, 260);
            this.pbSource.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbSource.TabIndex = 1;
            this.pbSource.TabStop = false;
            // 
            // pbPalette
            // 
            this.pbPalette.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbPalette.Location = new System.Drawing.Point(166, 3);
            this.pbPalette.Name = "pbPalette";
            this.pbPalette.Size = new System.Drawing.Size(172, 260);
            this.pbPalette.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbPalette.TabIndex = 3;
            this.pbPalette.TabStop = false;
            // 
            // pbOutput
            // 
            this.pbOutput.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbOutput.Location = new System.Drawing.Point(344, 3);
            this.pbOutput.Name = "pbOutput";
            this.pbOutput.Size = new System.Drawing.Size(172, 260);
            this.pbOutput.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbOutput.TabIndex = 4;
            this.pbOutput.TabStop = false;
            // 
            // btnStart
            // 
            this.btnStart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.btnStart.Location = new System.Drawing.Point(669, 417);
            this.btnStart.Name = "btnStart";
            this.btnStart.Size = new System.Drawing.Size(79, 42);
            this.btnStart.TabIndex = 4;
            this.btnStart.Text = "Start";
            this.btnStart.UseVisualStyleBackColor = true;
            this.btnStart.Click += new System.EventHandler(this.BtnStart_Click);
            // 
            // progressBar
            // 
            this.progressBar.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.progressBar.Location = new System.Drawing.Point(0, 465);
            this.progressBar.Name = "progressBar";
            this.progressBar.Size = new System.Drawing.Size(748, 16);
            this.progressBar.TabIndex = 5;
            // 
            // imageList1
            // 
            this.imageList1.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit;
            this.imageList1.ImageSize = new System.Drawing.Size(16, 16);
            this.imageList1.TransparentColor = System.Drawing.Color.Transparent;
            // 
            // lvFiles
            // 
            this.lvFiles.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.lvFiles.CheckBoxes = true;
            this.lvFiles.HideSelection = false;
            this.lvFiles.Location = new System.Drawing.Point(12, 362);
            this.lvFiles.MultiSelect = false;
            this.lvFiles.Name = "lvFiles";
            this.lvFiles.Size = new System.Drawing.Size(651, 97);
            this.lvFiles.Sorting = System.Windows.Forms.SortOrder.Ascending;
            this.lvFiles.TabIndex = 7;
            this.lvFiles.UseCompatibleStateImageBehavior = false;
            this.lvFiles.View = System.Windows.Forms.View.List;
            this.lvFiles.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.lvFiles_ItemCheck);
            this.lvFiles.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.lvFiles_ItemSelectionChanged);
            // 
            // lnTotSwap
            // 
            this.lnTotSwap.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.lnTotSwap.Location = new System.Drawing.Point(669, 362);
            this.lnTotSwap.Name = "lnTotSwap";
            this.lnTotSwap.Size = new System.Drawing.Size(58, 14);
            this.lnTotSwap.TabIndex = 8;
            this.lnTotSwap.Text = "label1";
            // 
            // lnCurSwap
            // 
            this.lnCurSwap.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.lnCurSwap.Location = new System.Drawing.Point(669, 385);
            this.lnCurSwap.Name = "lnCurSwap";
            this.lnCurSwap.Size = new System.Drawing.Size(58, 14);
            this.lnCurSwap.TabIndex = 9;
            this.lnCurSwap.Text = "label1";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.SystemColors.ControlDark;
            this.ClientSize = new System.Drawing.Size(748, 481);
            this.Controls.Add(this.lnCurSwap);
            this.Controls.Add(this.lnTotSwap);
            this.Controls.Add(this.lvFiles);
            this.Controls.Add(this.progressBar);
            this.Controls.Add(this.btnStart);
            this.Controls.Add(this.panel);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.panel.ResumeLayout(false);
            this.panel.PerformLayout();
            ((System.ComponentModel.ISupportInitialize)(this.pbSource)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbPalette)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbOutput)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.FlowLayoutPanel panel;
        private System.Windows.Forms.PictureBox pbSource;
        private System.Windows.Forms.PictureBox pbPalette;
        private System.Windows.Forms.PictureBox pbOutput;
        private System.Windows.Forms.Button btnStart;
        private System.Windows.Forms.ProgressBar progressBar;
        private System.Windows.Forms.ImageList imageList1;
        private System.Windows.Forms.ListView lvFiles;
        private System.Windows.Forms.Label lnTotSwap;
        private System.Windows.Forms.Label lnCurSwap;
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace Palette
{
    static class Program
    {
        /// <summary>
        /// Punto di ingresso principale dell'applicazione.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

ทำเครื่องหมาย 'รหัสที่ไม่ปลอดภัย' ในคุณสมบัติโครงการเพื่อคอมไพล์


4
IMO อันนี้ให้ผลลัพธ์ที่ดีที่สุด
figgycity50

9
นั่นเป็นเรื่องเหลือเชื่ออย่างยิ่งกับจานสีรุ้งที่น่ากลัว
Michael B

1
น่าอัศจรรย์ผู้ชนะ!
jjrv

25

JS

เพียงใช้ URL ภาพสองภาพ

ในฐานะแพ็คเกจ JS คุณสามารถเรียกใช้ด้วยตัวเองในเบราว์เซอร์ ให้เป็นซอที่เล่นด้วยการตั้งค่าที่แตกต่างกัน โปรดทราบว่าซอนี้: http://jsfiddle.net/eithe/J7jEk/จะเป็นข้อมูลล่าสุดเสมอ (มีการตั้งค่าทั้งหมด) เนื่องจากมีการเติบโต (มีการเพิ่มตัวเลือกใหม่) ฉันจะไม่อัปเดตซอดก่อนหน้าทั้งหมด

โทร

  • f("string to image (palette)", "string to image", {object of options});
  • f([[palette pixel], [palette pixel], ..., "string to image", {object of options});

ตัวเลือก

  • อัลกอริทึม: 'balanced', 'surrounding', 'reverse', 'hsv', 'yiq','lab'
  • ความเร็ว: ความเร็วของภาพเคลื่อนไหว
  • การเคลื่อนไหว: true- ภาพเคลื่อนไหวควรแสดงการเคลื่อนไหวตั้งแต่ต้นจนจบตำแหน่ง
  • รอบ: ถ้า 'surrounding'เลือกอัลกอริธึมนี่คือน้ำหนักของสภาพแวดล้อมที่จะนำมาพิจารณาเมื่อคำนวณน้ำหนักของพิกเซลที่กำหนด
  • hsv: ถ้า 'hsv'เลือกอัลกอริทึมพารามิเตอร์เหล่านี้จะควบคุมว่าสีสันความอิ่มตัวและค่ามีผลต่อน้ำหนักมากน้อยเพียงใด
  • yiq: ถ้า 'qiv'เลือกอัลกอริทึมพารามิเตอร์เหล่านี้จะควบคุมว่า yiq มีผลต่อน้ำหนักมากแค่ไหน
  • ห้องปฏิบัติการ: ถ้า 'lab'เลือกอัลกอริทึมพารามิเตอร์เหล่านี้จะควบคุมว่าห้องปฏิบัติการมีผลต่อน้ำหนักมากเพียงใด
  • เสียงรบกวน: การเพิ่มน้ำหนักแบบสุ่มจะเพิ่มน้ำหนักเท่าไร
  • ไม่ซ้ำกัน: ควรใช้พิกเซลจากจานสีเพียงครั้งเดียว (ดู: Photomosaics หรือ: ใช้โปรแกรมเมอร์จำนวนเท่าใดในการเปลี่ยนหลอดไฟ? )
  • pixel_1 / pixel_2 {ความกว้างความสูง}: ขนาดของพิกเซล (เป็นพิกเซล: D)

แกลเลอรี่ (สำหรับการนำเสนอฉันมักจะใช้ Mona Lisa & American Gothic ยกเว้นที่ระบุไว้):


แอนิเมชั่นดูดีมาก! แต่ภาพของคุณสั้นกว่าปกติหนึ่งพิกเซล
งานอดิเรกของ Calvin

@ งานอดิเรกของ Calvin - ต้องตัดมันด้วยสี: P น่าจะเป็นที่มาของความแตกต่าง Updated!
eithed

ฉันชอบอันนี้: jsfiddle.net/q865W/4
Justin

@Quincunx ไชโย! ด้วยเวอร์ชันถ่วงน้ำหนักมันใช้งานได้ดียิ่งขึ้น
eithed

ว้าว. 0_0 มันดีจริงๆ jsfiddle.net/q865W/6
Justin

24

C พร้อมพื้นที่สี Lab และปรับปรุงการเพิ่มขึ้น

ฉันว่าฉันทำไปแล้วเหรอ? ฉันโกหก. ฉันคิดว่าอัลกอริทึมในโซลูชันอื่นของฉันนั้นดีที่สุด แต่ Perl ก็ไม่เร็วพอสำหรับการทำตัวเลขซ้ำซ้อนดังนั้นฉันจึงนำงานของฉันไปใช้ใหม่ใน C ตอนนี้ทำงานแล้วทั้งหมดของภาพในโพสต์นี้, ที่มีคุณภาพสูงขึ้น กว่าต้นฉบับที่ประมาณ 3 นาทีต่อภาพและคุณภาพต่ำกว่า (ระดับ 0.5%) ทำงานใน 20-30 วินาทีต่อภาพ โดยพื้นฐานแล้วงานทั้งหมดจะทำด้วย ImageMagick และการทำ dithering นั้นทำได้โดยใช้การประมาณลูกบาศก์ของการแบ่งเป็นแนวโค้งของ ImageMagick ซึ่งให้ผลลัพธ์ที่มีลวดลายดีกว่า / น้อยกว่า

รหัส

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <wand/MagickWand.h>

#define ThrowWandException(wand) \
{ \
  char \
  *description; \
  \
  ExceptionType \
  severity; \
  \
  description=MagickGetException(wand,&severity); \
  (void) fprintf(stderr,"%s %s %lu %s\n",GetMagickModule(),description); \
  description=(char *) MagickRelinquishMemory(description); \
  abort(); \
  exit(-1); \
}

int width, height; /* Target image size */
MagickWand *source_wand, *target_wand, *img_wand, *target_lab_wand, *img_lab_wand;
PixelPacket *source_pixels, *target_pixels, *img_pixels, *target_lab_pixels, *img_lab_pixels;
Image *img, *img_lab, *target, *target_lab;
CacheView *img_lab_view, *target_lab_view;
ExceptionInfo *e;

MagickWand *load_image(const char *filename) {
  MagickWand *img = NewMagickWand();
  if (!MagickReadImage(img, filename)) {
    ThrowWandException(img);
  }
  return img;
}

PixelPacket *get_pixels(MagickWand *wand) {
  PixelPacket *ret = GetAuthenticPixels(
      GetImageFromMagickWand(wand), 0, 0,
      MagickGetImageWidth(wand), MagickGetImageHeight(wand), e);
  CatchException(e);
  return ret;
}

void sync_pixels(MagickWand *wand) {
  SyncAuthenticPixels(GetImageFromMagickWand(wand), e);
  CatchException(e);
}

MagickWand *transfer_pixels() {
  if (MagickGetImageWidth(source_wand) * MagickGetImageHeight(source_wand)
      != MagickGetImageWidth(target_wand) * MagickGetImageHeight(target_wand)) {
    perror("size mismtch");
  }

  MagickWand *img_wand = CloneMagickWand(target_wand);
  img_pixels = get_pixels(img_wand);
  memcpy(img_pixels, source_pixels, 
      MagickGetImageWidth(img_wand) * MagickGetImageHeight(img_wand) * sizeof(PixelPacket));

  sync_pixels(img_wand);
  return img_wand;
}

MagickWand *image_to_lab(MagickWand *img) {
  MagickWand *lab = CloneMagickWand(img);
  TransformImageColorspace(GetImageFromMagickWand(lab), LabColorspace);
  return lab;
}

int lab_distance(PixelPacket *a, PixelPacket *b) {
  int l_diff = (GetPixelL(a) - GetPixelL(b)) / 256,
      a_diff = (GetPixela(a) - GetPixela(b)) / 256,
      b_diff = (GetPixelb(a) - GetPixelb(b)) / 256;

  return (l_diff * l_diff + a_diff * a_diff + b_diff * b_diff);
}

int should_swap(int x1, int x2, int y1, int y2) {
  int dist = lab_distance(&img_lab_pixels[width * y1 + x1], &target_lab_pixels[width * y1 + x1])
           + lab_distance(&img_lab_pixels[width * y2 + x2], &target_lab_pixels[width * y2 + x2]);
  int swapped_dist = lab_distance(&img_lab_pixels[width * y2 + x2], &target_lab_pixels[width * y1 + x1])
                   + lab_distance(&img_lab_pixels[width * y1 + x1], &target_lab_pixels[width * y2 + x2]);

  return swapped_dist < dist;
}

void pixel_multiply_add(MagickPixelPacket *dest, PixelPacket *src, double mult) {
  dest->red += (double)GetPixelRed(src) * mult;
  dest->green += ((double)GetPixelGreen(src) - 32768) * mult;
  dest->blue += ((double)GetPixelBlue(src) - 32768) * mult;
}

#define min(x,y) (((x) < (y)) ? (x) : (y))
#define max(x,y) (((x) > (y)) ? (x) : (y))

double mpp_distance(MagickPixelPacket *a, MagickPixelPacket *b) {
  double l_diff = QuantumScale * (a->red - b->red),
         a_diff = QuantumScale * (a->green - b->green),
         b_diff = QuantumScale * (a->blue - b->blue);
  return (l_diff * l_diff + a_diff * a_diff + b_diff * b_diff);
}

void do_swap(PixelPacket *pix, int x1, int x2, int y1, int y2) {
  PixelPacket tmp = pix[width * y1 + x1];
  pix[width * y1 + x1] = pix[width * y2 + x2];
  pix[width * y2 + x2] = tmp;
}

int should_swap_dither(double detail, int x1, int x2, int y1, int y2) {
//  const InterpolatePixelMethod method = Average9InterpolatePixel;
  const InterpolatePixelMethod method = SplineInterpolatePixel;

  MagickPixelPacket img1, img2, img1s, img2s, target1, target2;
  GetMagickPixelPacket(img, &img1);
  GetMagickPixelPacket(img, &img2);
  GetMagickPixelPacket(img, &img1s);
  GetMagickPixelPacket(img, &img2s);
  GetMagickPixelPacket(target, &target1);
  GetMagickPixelPacket(target, &target2);
  InterpolateMagickPixelPacket(img, img_lab_view, method, x1, y1, &img1, e);
  InterpolateMagickPixelPacket(img, img_lab_view, method, x2, y2, &img2, e);
  InterpolateMagickPixelPacket(target, target_lab_view, method, x1, y1, &target1, e);
  InterpolateMagickPixelPacket(target, target_lab_view, method, x2, y2, &target2, e);
  do_swap(img_lab_pixels, x1, x2, y1, y2);
//  sync_pixels(img_wand);
  InterpolateMagickPixelPacket(img, img_lab_view, method, x1, y1, &img1s, e);
  InterpolateMagickPixelPacket(img, img_lab_view, method, x2, y2, &img2s, e);
  do_swap(img_lab_pixels, x1, x2, y1, y2);
//  sync_pixels(img_wand);

  pixel_multiply_add(&img1, &img_lab_pixels[width * y1 + x1], detail);
  pixel_multiply_add(&img2, &img_lab_pixels[width * y2 + x2], detail);
  pixel_multiply_add(&img1s, &img_lab_pixels[width * y2 + x2], detail);
  pixel_multiply_add(&img2s, &img_lab_pixels[width * y1 + x1], detail);
  pixel_multiply_add(&target1, &target_lab_pixels[width * y1 + x1], detail);
  pixel_multiply_add(&target2, &target_lab_pixels[width * y2 + x2], detail);

  double dist = mpp_distance(&img1, &target1)
              + mpp_distance(&img2, &target2);
  double swapped_dist = mpp_distance(&img1s, &target1)
                      + mpp_distance(&img2s, &target2);

  return swapped_dist + 1.0e-4 < dist;
}

int main(int argc, char *argv[]) {
  if (argc != 7) {
    fprintf(stderr, "Usage: %s source.png target.png dest nodither_pct dither_pct detail\n", argv[0]);
    return 1;
  }
  char *source_filename = argv[1];
  char *target_filename = argv[2];
  char *dest = argv[3];
  double nodither_pct = atof(argv[4]);
  double dither_pct = atof(argv[5]);
  double detail = atof(argv[6]) - 1;
  const int SWAPS_PER_LOOP = 1000000;
  int nodither_limit = ceil(SWAPS_PER_LOOP * nodither_pct / 100);
  int dither_limit = ceil(SWAPS_PER_LOOP * dither_pct / 100);
  int dither = 0, frame = 0;
  char outfile[256], cmdline[1024];
  sprintf(outfile, "out/%s.png", dest);

  MagickWandGenesis();
  e = AcquireExceptionInfo();
  source_wand = load_image(source_filename);
  source_pixels = get_pixels(source_wand);
  target_wand = load_image(target_filename);
  target_pixels = get_pixels(target_wand);
  img_wand = transfer_pixels();
  img_pixels = get_pixels(img_wand);
  target_lab_wand = image_to_lab(target_wand);
  target_lab_pixels = get_pixels(target_lab_wand);
  img_lab_wand = image_to_lab(img_wand);
  img_lab_pixels = get_pixels(img_lab_wand);
  img = GetImageFromMagickWand(img_lab_wand);
  target = GetImageFromMagickWand(target_lab_wand);
  img_lab_view = AcquireAuthenticCacheView(img, e);
  target_lab_view = AcquireAuthenticCacheView(target,e);
  CatchException(e);

  width = MagickGetImageWidth(img_wand);
  height = MagickGetImageHeight(img_wand);

  while (1) {
    int swaps_made = 0;
    for (int n = 0 ; n < SWAPS_PER_LOOP ; n++) {
      int x1 = rand() % width,
          x2 = rand() % width,
          y1 = rand() % height,
          y2 = rand() % height;

      int swap = dither ?
        should_swap_dither(detail, x1, x2, y1, y2)
        : should_swap(x1, x2, y1, y2);

      if (swap) {
        do_swap(img_pixels, x1, x2, y1, y2);
        do_swap(img_lab_pixels, x1, x2, y1, y2);
        swaps_made ++;
      }
    }

    sync_pixels(img_wand);
    if (!MagickWriteImages(img_wand, outfile, MagickTrue)) {
      ThrowWandException(img_wand);
    }
    img_pixels = get_pixels(img_wand);
    sprintf(cmdline, "cp out/%s.png anim/%s/%05i.png", dest, dest, frame++);
    system(cmdline);

    if (!dither && swaps_made < nodither_limit) {
      sprintf(cmdline, "cp out/%s.png out/%s-nodither.png", dest, dest);
      system(cmdline);
      dither = 1;
    } else if (dither && swaps_made < dither_limit)
      break;
  }

  return 0;
}

รวบรวมกับ

gcc -std=gnu99 -O3 -march=native -ffast-math \
  -o transfer `pkg-config --cflags MagickWand` \
  transfer.c `pkg-config --libs MagickWand` -lm

ผล

ส่วนใหญ่เหมือนกับรุ่น Perl ดีขึ้นเล็กน้อย แต่มีข้อยกเว้นเล็กน้อย โดยทั่วไปการสังเกตเห็นได้ชัดเจนน้อยกว่า Scream -> Starry Night ไม่มีเอฟเฟกต์ "ภูเขาเพลิง" และ Camaro ก็ดูมีพิกเซลสีเทาน้อยลง ฉันคิดว่ารหัส colorspace ของ Perl มีข้อผิดพลาดที่มีพิกเซลความอิ่มตัวต่ำ

จานสีโกธิคอเมริกัน

Mona Lisa palette

จานสี Starry Night

จานสี

จานสีทรงกลม

มัสแตง (จาน Camaro)

Camaro (มัสแตงจานสี)


ใช่แล้วท่านเป็นคนที่ดีที่สุด ทำไมใน C จึงสร้าง. 5% แย่ลง?
RMalke

@Ralke มันเลวร้ายกว่าตอนที่เขาปล่อยให้มันวิ่ง 20-30 วินาทีเท่านั้น
trlkly

คุณช่วยกรุณาโพสต์ค่าที่คุณใช้เป็นnodither_pct, dither_pctและdetailในตัวอย่างนี้? ฉันกำลังรันโปรแกรมของคุณด้วยชุดค่าผสมที่แตกต่างกัน แต่สำหรับภาพของฉันดูเหมือนว่าจะเหมาะสมที่สุดและพาเล็ตก็ใกล้เคียงกับของคุณดังนั้น ... ได้โปรด?
Andreï Kostyrka

@ AndreïKostyrka 0.1 0.1 1.6เป็นค่าที่ฉันใช้ในการสร้างภาพเหล่านี้
ฮอบส์

@ AndreïKostyrkaน่า0.5 0.5 1.6จะส่งผลให้มีคุณภาพที่ดีเกือบจะเร็วกว่ามาก
ฮอบส์

23

ค่าที่ใกล้ที่สุดของ HSL พร้อมข้อผิดพลาดและการกระจาย

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

สำหรับ AllRGB ฉันปรับแต่งเวฟเล็ตด้วยตนเองซึ่งให้ความสำคัญกับบางพื้นที่ของภาพ สำหรับการใช้งานที่ไม่มีการชี้นำนี้ฉันกำลังเลือกเวฟเล็ตหนึ่งตัวซึ่งถือว่าเป็นกฎของเลย์เอาท์ที่สามซึ่งให้ความสนใจหลักเป็นหนึ่งในสามของการลงมาจากด้านบน

American Gothic พร้อมจานสีจาก Mona Lisa Mona Lisa พร้อมจานสีจาก American Gothic

ฉันชอบ 36:

แม่น้ำพร้อมจานสีจาก Mona Lisa

ผลิตภัณฑ์คาร์ทีเซียนเต็มรูปแบบของ (ภาพ, จานสี)

package org.cheddarmonk.graphics;

import org.cheddarmonk.util.*;
import java.awt.Point;
import java.awt.image.*;
import java.io.File;
import java.util.Random;
import javax.imageio.ImageIO;

public class PaletteApproximator {
    public static void main(String[] args) throws Exception {
        // Adjust this to fine-tune for the areas which are most important.
        float[] waveletDefault = new float[] {0.5f, 0.333f, 0.5f, 0.5f, 1};

        generateAndSave(args[0], args[1], args[2], waveletDefault);
    }

    private static void generateAndSave(String paletteFile, String fileIn, String fileOut, float[]... wavelets) throws Exception {
        BufferedImage imgIn = ImageIO.read(new File(fileIn));
        int w = imgIn.getWidth(), h = imgIn.getHeight();

        int[] buf = new int[w * h];
        imgIn.getRGB(0, 0, w, h, buf, 0, w);

        SimpleOctTreeInt palette = loadPalette(paletteFile);
        generate(palette, buf, w, h, wavelets);

        // Masks for R, G, B, A.
        final int[] off = new int[]{0xff0000, 0xff00, 0xff, 0xff000000};
        // The corresponding colour model.
        ColorModel colourModel = ColorModel.getRGBdefault();
        DataBufferInt dbi = new DataBufferInt(buf, buf.length);
        Point origin = new Point(0, 0);
        WritableRaster raster = Raster.createPackedRaster(dbi, w, h, w, off, origin);
        BufferedImage imgOut = new BufferedImage(colourModel, raster, false, null);

        ImageIO.write(imgOut, "PNG", new File(fileOut));
    }

    private static SimpleOctTreeInt loadPalette(String paletteFile) throws Exception {
        BufferedImage img = ImageIO.read(new File(paletteFile));
        int w = img.getWidth(), h = img.getHeight();

        int[] buf = new int[w * h];
        img.getRGB(0, 0, w, h, buf, 0, w);

        // Parameters tuned for 4096x4096
        SimpleOctTreeInt octtree = new SimpleOctTreeInt(0, 1, 0, 1, 0, 1, 16, 12);
        for (int i = 0; i < buf.length; i++) {
            octtree.add(buf[i], transform(buf[i]));
        }

        return octtree;
    }

    private static void generate(SimpleOctTreeInt octtree, int[] buf, int w, int h, float[]... wavelets) {
        int m = w * h;

        LeanBinaryHeapInt indices = new LeanBinaryHeapInt();
        Random rnd = new Random();
        for (int i = 0; i < m; i++) {
            float x = (i % w) / (float)w, y = (i / w) / (float)w;

            float weight = 0;
            for (float[] wavelet : wavelets) {
                weight += wavelet[4] * Math.exp(-Math.pow((x - wavelet[0]) / wavelet[2], 2) - Math.pow((y - wavelet[1]) / wavelet[3], 2));
            }

            // Random element provides some kind of dither
            indices.insert(i, -weight + 0.2f * rnd.nextFloat());
        }

        // Error diffusion buffers.
        float[] errx = new float[m], erry = new float[m], errz = new float[m];

        for (int i = 0; i < m; i++) {
            int idx = indices.pop();
            int x = idx % w, y = idx / w;

            // TODO Bicubic interpolation? For the time being, prefer to scale the input image externally...
            float[] tr = transform(buf[x + w * y]);
            tr[0] += errx[idx]; tr[1] += erry[idx]; tr[2] += errz[idx];

            int pixel = octtree.nearestNeighbour(tr, 2);
            buf[x + y * w] = 0xff000000 | pixel;

            // Don't reuse pixels.
            float[] trPix = transform(pixel);
            boolean ok = octtree.remove(pixel, trPix);
            if (!ok) throw new IllegalStateException("Failed to remove from octtree");

            // Propagate error in 4 directions, not caring whether or not we've already done that pixel.
            // This will lose some error, but that might be a good thing.
            float dx = (tr[0] - trPix[0]) / 4, dy = (tr[1] - trPix[1]) / 4, dz = (tr[2] - trPix[2]) / 4;
            if (x > 0) {
                errx[idx - 1] += dx;
                erry[idx - 1] += dy;
                errz[idx - 1] += dz;
            }
            if (x < w - 1) {
                errx[idx + 1] += dx;
                erry[idx + 1] += dy;
                errz[idx + 1] += dz;
            }
            if (y > 0) {
                errx[idx - w] += dx;
                erry[idx - w] += dy;
                errz[idx - w] += dz;
            }
            if (y < h - 1) {
                errx[idx + w] += dx;
                erry[idx + w] += dy;
                errz[idx + w] += dz;
            }
        }
    }

    private static final float COS30 = (float)Math.sqrt(3) / 2;
    private static float[] transform(int rgb) {
        float r = ((rgb >> 16) & 0xff) / 255.f;
        float g = ((rgb >> 8) & 0xff) / 255.f;
        float b = (rgb & 0xff) / 255.f;

        // HSL cone coords
        float cmax = (r > g) ? r : g; if (b > cmax) cmax = b;
        float cmin = (r < g) ? r : g; if (b < cmin) cmin = b;
        float[] cone = new float[3];
        cone[0] = (cmax + cmin) / 2;
        cone[1] = 0.5f * (1 + r - (g + b) / 2);
        cone[2] = 0.5f * (1 + (g - b) * COS30);
        return cone;
    }
}

22

หลาม

ไม่ใช่โค้ดสวยหรือโดยผลลัพธ์

from blist import blist
from PIL import Image
import random

def randpop(colors):
    j = random.randrange(len(colors))
    return colors.pop(j)

colors = blist(Image.open('in1.png').getdata())
random.shuffle(colors)
target = Image.open('in2.png')

out = target.copy()
data = list(list(i) for i in out.getdata())

assert len(data) == len(colors)

w, h = out.size

coords = []
for i in xrange(h):
    for j in xrange(w):
        coords.append((i, j))

# Adjust color balance
dsum = [sum(d[i] for d in data) for i in xrange(3)]
csum = [sum(c[i] for c in colors) for i in xrange(3)]
adjust = [(csum[i] - dsum[i]) // len(data) for i in xrange(3)]
for i, j in coords:
    for k in xrange(3):
        data[i*w + j][k] += adjust[k]

random.shuffle(coords)

# larger value here gives better results but take longer
choose = 100
threshold = 10

done = set()
while len(coords):
    if not len(coords) % 1000:
        print len(coords) // 1000
    i, j = coords.pop()
    ind = i*w + j
    done.add(ind)
    t = data[ind]
    dmin = 255*3
    kmin = 0
    choices = []
    while colors and len(choices) < choose:
        k = len(choices)
        choices.append(randpop(colors))
        c = choices[-1]
        d = sum(abs(t[l] - c[l]) for l in xrange(3))
        if d < dmin:
            dmin = d
            kmin = k
            if d < threshold:
                break
    c = choices.pop(kmin)
    data[ind] = c
    colors.extend(choices)

    # Push the error to nearby pixels for dithering
    if ind + 1 < len(data) and ind + 1 not in done:
        ind2 = ind + 1
    elif ind + w < len(data) and ind + w not in done:
        ind2 = ind + w
    elif ind > 0 and ind - 1 not in done:
        ind2 = ind - 1
    elif ind - w > 0 and ind - w not in done:
        ind2 = ind - w
    else:
        ind2 = None
    if ind2 is not None:
        for k in xrange(3):
            err = abs(t[k] - c[k])
            data[ind2][k] += err

out.putdata(data)
out.save('out.png')

การปรับปรุงที่เป็นไปได้:

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

น่าเกลียด (1-> 2): 1-> 2

ดีขึ้นเล็กน้อย (2-> 1): 2-> 1

มีคุณค่า (2-> 3): 2-> 3

เหมือน raytracer ที่ไม่ดี (3-> 4): 3-> 4

การโกง - ใช้พิกเซลที่ดีทั้งหมดในช่วงบนและเรียกใช้สีที่หมด: 1-> 2


3
สุดท้ายคือ ... ความคิดที่น่าสนใจ แต่ก็ยังไม่ upvoting
John Dvorak

20

Python (โดยใช้ kd-tree และ luminosity)

ความท้าทายที่ดี ฉันตัดสินใจใช้วิธี kd-tree ดังนั้นแนวคิดพื้นฐานที่อยู่เบื้องหลังโดยใช้วิธี kd-tree คือมันแบ่งสีและความส่องสว่างตามที่ปรากฏในภาพ

ดังนั้นสำหรับ kd-tree การเรียงลำดับแรกขึ้นอยู่กับสีแดง มันแยกสีทั้งหมดเป็นสองกลุ่มเท่ากันโดยประมาณของสีแดง (แสงสีแดงและสีแดงเข้ม) ถัดไปก็แยกพาร์ติชั่นทั้งสองนี้ออกเป็นสีเขียว ถัดไปสีน้ำเงินแล้วความสว่างแล้วสีแดงอีกครั้ง เป็นต้นจนกว่าต้นไม้จะถูกสร้างขึ้น ด้วยวิธีนี้ฉันสร้าง kd-tree สำหรับอิมเมจต้นทางและอิมเมจปลายทาง หลังจากนั้นฉันแมปทรีจากแหล่งไปยังปลายทางและเขียนทับข้อมูลสีของไฟล์ปลายทาง ทั้งหมดของผลที่จะแสดงที่นี่

ตัวอย่างบางส่วน:

Mona Lisa -> โกธิคอเมริกัน

Mona Lisa กอธิคอเมริกัน (สไตล์ mona_lisa)

โกธิคอเมริกัน -> Mona Lisa

กอธิคอเมริกัน mona_lisa (สไตล์โกธิคอเมริกัน)

Starry Night -> เสียงกรีดร้อง

คืนเต็มไปด้วยดวงดาว กรีดร้องเต็มไปด้วยดวงดาว

The Scream -> Starry Night

กรี๊ด ดาวกรีดร้อง

ทรงกลมสีรุ้ง

ป้อนคำอธิบายรูปภาพที่นี่ ลูกโมนาลิซ่า ลูกกรีดร้อง

นี่คือหนังสั้น ๆ โดยใช้ผู้สร้างภาพยนตร์ของ @ Calvin's Hobbies:

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

และตอนนี้สำหรับรหัส :-)

from PIL import Image

""" Computation of hue, saturation, luminosity.
Based on http://stackoverflow.com/questions/3732046/how-do-you-get-the-hue-of-a-xxxxxx-colour
"""
def rgbToLsh(t):
    r = t[0]
    g = t[1]
    b = t[2]
    r /= 255.
    g /= 255.
    b /= 255.
    vmax = max([r, g, b])
    vmin = min([r, g, b]);
    h = s = l = (vmax + vmin) / 2.;

    if (vmax == vmin):
        h = s = 0.  # achromatic
    else:
        d = vmax - vmin;
        if l > 0.5:
            s = d / (2. - vmax - vmin)
        else:
            s = d / (vmax + vmin);
        if vmax == r:
            if g<b: 
                m = 6. 
            else: 
                m = 0. 
            h = (g - b) / d + m
        elif vmax == g: 
            h = (b - r) / d + 2.
        elif vmax == b: 
            h = (r - g) / d + 4.
        h /= 6.;
    return [l,s,h];



""" KDTree implementation.
Based on https://code.google.com/p/python-kdtree/ 
"""
__version__ = "1r11.1.2010"
__all__ = ["KDTree"]

def square_distance(pointA, pointB):
    # squared euclidean distance
    distance = 0
    dimensions = len(pointA) # assumes both points have the same dimensions
    for dimension in range(dimensions):
        distance += (pointA[dimension] - pointB[dimension])**2
    return distance

class KDTreeNode():
    def __init__(self, point, left, right):
        self.point = point
        self.left = left
        self.right = right

    def is_leaf(self):
        return (self.left == None and self.right == None)

class KDTreeNeighbours():
    """ Internal structure used in nearest-neighbours search.
    """
    def __init__(self, query_point, t):
        self.query_point = query_point
        self.t = t # neighbours wanted
        self.largest_distance = 0 # squared
        self.current_best = []

    def calculate_largest(self):
        if self.t >= len(self.current_best):
            self.largest_distance = self.current_best[-1][1]
        else:
            self.largest_distance = self.current_best[self.t-1][1]

    def add(self, point):
        sd = square_distance(point, self.query_point)
        # run through current_best, try to find appropriate place
        for i, e in enumerate(self.current_best):
            if i == self.t:
                return # enough neighbours, this one is farther, let's forget it
            if e[1] > sd:
                self.current_best.insert(i, [point, sd])
                self.calculate_largest()
                return
        # append it to the end otherwise
        self.current_best.append([point, sd])
        self.calculate_largest()

    def get_best(self):
        return [element[0] for element in self.current_best[:self.t]]



class KDTree():
    """ KDTree implementation.

        Example usage:

            from kdtree import KDTree

            data = <load data> # iterable of points (which are also iterable, same length)
            point = <the point of which neighbours we're looking for>

            tree = KDTree.construct_from_data(data)
            nearest = tree.query(point, t=4) # find nearest 4 points
    """

    def __init__(self, data):

        self.data_listing = []
        def build_kdtree(point_list, depth):

            # code based on wikipedia article: http://en.wikipedia.org/wiki/Kd-tree
            if not point_list:
                return None

            # select axis based on depth so that axis cycles through all valid values
            axis = depth % 4 #len(point_list[0]) # assumes all points have the same dimension

            # sort point list and choose median as pivot point,
            # TODO: better selection method, linear-time selection, distribution
            point_list.sort(key=lambda point: point[axis])
            median = len(point_list)/2 # choose median

            # create node and recursively construct subtrees
            node = KDTreeNode(point=point_list[median],
                              left=build_kdtree(point_list[0:median], depth+1),
                              right=build_kdtree(point_list[median+1:], depth+1))

            # add point to listing                   
            self.data_listing.append(point_list[median])
            return node

        self.root_node = build_kdtree(data, depth=0)

    @staticmethod
    def construct_from_data(data):
        tree = KDTree(data)
        return tree

    def query(self, query_point, t=1):
        statistics = {'nodes_visited': 0, 'far_search': 0, 'leafs_reached': 0}

        def nn_search(node, query_point, t, depth, best_neighbours):
            if node == None:
                return

            #statistics['nodes_visited'] += 1

            # if we have reached a leaf, let's add to current best neighbours,
            # (if it's better than the worst one or if there is not enough neighbours)
            if node.is_leaf():
                #statistics['leafs_reached'] += 1
                best_neighbours.add(node.point)
                return

            # this node is no leaf

            # select dimension for comparison (based on current depth)
            axis = depth % len(query_point)

            # figure out which subtree to search
            near_subtree = None # near subtree
            far_subtree = None # far subtree (perhaps we'll have to traverse it as well)

            # compare query_point and point of current node in selected dimension
            # and figure out which subtree is farther than the other
            if query_point[axis] < node.point[axis]:
                near_subtree = node.left
                far_subtree = node.right
            else:
                near_subtree = node.right
                far_subtree = node.left

            # recursively search through the tree until a leaf is found
            nn_search(near_subtree, query_point, t, depth+1, best_neighbours)

            # while unwinding the recursion, check if the current node
            # is closer to query point than the current best,
            # also, until t points have been found, search radius is infinity
            best_neighbours.add(node.point)

            # check whether there could be any points on the other side of the
            # splitting plane that are closer to the query point than the current best
            if (node.point[axis] - query_point[axis])**2 < best_neighbours.largest_distance:
                #statistics['far_search'] += 1
                nn_search(far_subtree, query_point, t, depth+1, best_neighbours)

            return

        # if there's no tree, there's no neighbors
        if self.root_node != None:
            neighbours = KDTreeNeighbours(query_point, t)
            nn_search(self.root_node, query_point, t, depth=0, best_neighbours=neighbours)
            result = neighbours.get_best()
        else:
            result = []

        #print statistics
        return result


#List of files: 
files = ['JXgho.png','N6IGO.png','c5jq1.png','itzIe.png','xPAwA.png','y2VZJ.png']

#Loop over source files 
for im_orig in range(len(files)):
    srch = Image.open(files[im_orig])   #Open file handle 
    src = srch.load();                  #Load file  

    # Build data structure (R,G,B,lum,xpos,ypos) for source file
    srcdata =  [(src[i,j][0],src[i,j][1],src[i,j][2],rgbToLsh(src[i,j])[0],i,j) \
                     for i in range(srch.size[0]) \
                     for j in range(srch.size[1])]  

    # Build kd-tree for source
    srctree = KDTree.construct_from_data(srcdata)

    for im in range(len(files)):
        desh = Image.open(files[im])
        des = desh.load();

        # Build data structure (R,G,B,lum,xpos,ypos) for destination file
        desdata =  [(des[i,j][0],des[i,j][1],des[i,j][2],rgbToLsh(des[i,j]),i,j) \
                     for i in range(desh.size[0]) \
                     for j in range(desh.size[1])]  

        # Build kd-tree for destination
        destree = KDTree.construct_from_data(desdata)

        # Switch file mode
        desh.mode = srch.mode
        for k in range(len(srcdata)):
            # Get locations from kd-tree sorted data
            i   = destree.data_listing[k][-2]
            j   = destree.data_listing[k][-1]
            i_s = srctree.data_listing[k][-2]
            j_s = srctree.data_listing[k][-1]

            # Overwrite original colors with colors from source file 
            des[i,j] = src[i_s,j_s]

        # Save to disk  
        desh.save(files[im_orig].replace('.','_'+`im`+'.'))

ฉันไม่ได้สังเกตเห็นเมื่อหนึ่งปีก่อน แต่มันค่อนข้างดี!
ฮอบส์

16

หลาม

เพียงเพื่อให้ลูกบอลกลิ้งต่อไปนี้เป็นคำตอบที่ง่ายและช้าของฉันเอง

import Image

def countColors(image):
    colorCounts = {}
    for color in image.getdata():
        if color in colorCounts:
            colorCounts[color] += 1
        else:
            colorCounts[color] = 1
    return colorCounts

def colorDist(c1, c2):
    def ds(c1, c2, i):
        return (c1[i] - c2[i])**2
    return (ds(c1, c2, 0) + ds(c1, c2, 1) + ds(c1, c2, 2))**0.5

def findClosestColor(palette, color):
    closest = None
    minDist = (3*255**2)**0.5
    for c in palette:
        dist = colorDist(color, c)
        if dist < minDist:
            minDist = dist
            closest = c
    return closest

def removeColor(palette, color):
    if palette[color] == 1:
        del palette[color]
    else:
        palette[color] -= 1

def go(paletteFile, sourceFile):
    palette = countColors(Image.open(paletteFile).convert('RGB'))
    source = Image.open(sourceFile).convert('RGB')
    copy = Image.new('RGB', source.size)
    w, h = copy.size

    for x in range(w):
        for y in range(h):
            c = findClosestColor(palette, source.getpixel((x, y)))
            removeColor(palette, c)
            copy.putpixel((x, y), c)
        print x #print progress
    copy.save('copy.png')

#the respective file paths go here
go('../ag.png', '../r.png')

สำหรับแต่ละพิกเซลในแหล่งที่มามันจะมองหาพิกเซลที่ไม่ได้ใช้ในจานสีที่ใกล้เคียงที่สุดในลูกบาศก์สี RGB มันเป็นพื้นฐานเหมือนกับอัลกอริทึมของ Quincunx แต่ไม่มีการสุ่มและฟังก์ชั่นการเปรียบเทียบสีที่แตกต่างกัน

คุณสามารถบอกได้ว่าฉันย้ายจากซ้ายไปขวาเนื่องจากด้านขวาของภาพมีรายละเอียดน้อยกว่ามากเนื่องจากการลดลงของสีที่คล้ายกัน

แม่น้ำจากโกธิคอเมริกัน

แม่น้ำจากโกธิคอเมริกัน

Mona Lisa จาก Rainbow Spheres

Mona Lisa จาก Rainbow Spheres


1
Mme ลิซ่าเป็นบิตเหลือง ...
tomsmeding

4
ฉันชอบการเปลี่ยนแปลงในแม่น้ำจากโกธิคของชาวอเมริกันจากซ้าย 'ดี' เป็นขวา 'นามธรรม' =)
ข้อผิดพลาด

12

Haskell

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

module Main where

import System.Environment    (getArgs)
import System.Exit           (exitSuccess, exitFailure)
import System.Console.GetOpt (getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..))
import Data.List             (sortBy)

import Codec.Picture
import Codec.Picture.Types

import qualified Data.Vector as V

main :: IO ()
main = do
    (ioOpts, _) <- getArgs >>= getOpts
    opts        <- ioOpts
    image       <- loadImage $ imageFile opts
    palette     <- loadImage $ paletteFile opts
    case swapPalette image palette of
      Nothing -> do
          putStrLn "Error: image and palette dimensions do not match"
          exitFailure
      Just img ->
          writePng (outputFile opts) img

swapPalette :: Image PixelYCbCr8 -> Image PixelYCbCr8 -> Maybe (Image PixelRGB8)
swapPalette img pal
    | area1 == area2 =
        let cmpCr (_, (PixelYCbCr8 _ _ r1)) (_, (PixelYCbCr8 _ _ r2)) = r1 `compare` r2
            cmpCb (_, (PixelYCbCr8 _ c1 _)) (_, (PixelYCbCr8 _ c2 _)) = c1 `compare` c2
            cmpY  (_, (PixelYCbCr8 y1 _ _)) (_, (PixelYCbCr8 y2 _ _)) = y2 `compare` y1
            w       = imageWidth  img
            h       = imageHeight img
            imgData = sortBy cmpY $ sortBy cmpCr $ sortBy cmpCb $ zip [1 :: Int ..] $ getPixelList img
            palData = sortBy cmpY $ sortBy cmpCr $ sortBy cmpCb $ zip [1 :: Int ..] $ getPixelList pal
            newData = zipWith (\(n, _) (_, p) -> (n, p)) imgData palData
            pixData = map snd $ sortBy (\(n1, _) (n2, _) -> n1 `compare` n2) newData
            dataVec = V.reverse $ V.fromList pixData
        in  Just $ convertImage $ generateImage (lookupPixel dataVec w h) w h
    | otherwise = Nothing
    where area1 = (imageWidth img) * (imageHeight img)
          area2 = (imageWidth pal) * (imageHeight pal)

lookupPixel :: V.Vector PixelYCbCr8 -> Int -> Int -> Int -> Int -> PixelYCbCr8
lookupPixel vec w h x y = vec V.! i
    where i = flattenIndex w h x y

getPixelList :: Image PixelYCbCr8 -> [PixelYCbCr8]
getPixelList img = foldl (\ps (x, y) -> (pixelAt img x y):ps) [] coords
    where coords = [(x, y) | x <- [0..(imageWidth img) - 1], y <- [0..(imageHeight img) - 1]]

flattenIndex :: Int -> Int -> Int -> Int -> Int
flattenIndex _ h x y = y + (x * h)

-------------------------------------------------
-- Command Line Option Functions
-------------------------------------------------

getOpts :: [String] -> IO (IO Options, [String])
getOpts args = case getOpt Permute options args of
    (opts, nonOpts, []) -> return (foldl (>>=) (return defaultOptions) opts, nonOpts)
    (_, _, errs)        -> do
        putStrLn $ concat errs
        printUsage
        exitFailure

data Options = Options
  { imageFile   :: Maybe FilePath
  , paletteFile :: Maybe FilePath
  , outputFile  :: FilePath
  }

defaultOptions :: Options
defaultOptions = Options
  { imageFile   = Nothing
  , paletteFile = Nothing
  , outputFile  = "out.png"
  }

options :: [OptDescr (Options -> IO Options)]
options = [ Option ['i'] ["image"]   (ReqArg setImage   "FILE") "",
            Option ['p'] ["palette"] (ReqArg setPalette "FILE") "",
            Option ['o'] ["output"]  (ReqArg setOutput  "FILE") "",
            Option ['v'] ["version"] (NoArg showVersion)        "",
            Option ['h'] ["help"]    (NoArg exitPrintUsage)     ""]

setImage :: String -> Options -> IO Options
setImage image opts = return $ opts { imageFile = Just image }

setPalette :: String -> Options -> IO Options
setPalette palette opts = return $ opts { paletteFile = Just palette }

setOutput :: String -> Options -> IO Options
setOutput output opts = return $ opts { outputFile = output }

printUsage :: IO ()
printUsage = do
    putStrLn "Usage: repix [OPTION...] -i IMAGE -p PALETTE [-o OUTPUT]"
    putStrLn "Rearrange pixels in the palette file to closely resemble the given image."
    putStrLn ""
    putStrLn "-i, --image    specify the image to transform"
    putStrLn "-p, --palette  specify the image to use as the palette"
    putStrLn "-o, --output   specify the output image file"
    putStrLn ""
    putStrLn "-v, --version  display version information and exit"
    putStrLn "-h, --help     display this help and exit"

exitPrintUsage :: a -> IO Options
exitPrintUsage _ = do
    printUsage
    exitSuccess

showVersion :: a -> IO Options
showVersion _ = do
    putStrLn "Pixel Rearranger v0.1"
    exitSuccess

-------------------------------------------------
-- Image Loading Util Functions
-------------------------------------------------

loadImage :: Maybe FilePath -> IO (Image PixelYCbCr8)
loadImage Nothing     = do
    printUsage
    exitFailure
loadImage (Just path) = do
    rdImg <- readImage path
    case rdImg of
      Left err -> do
          putStrLn err
          exitFailure
      Right img -> getRGBImage img

getRGBImage :: DynamicImage -> IO (Image PixelYCbCr8)
getRGBImage dynImg =
    case dynImg of
      ImageYCbCr8 img -> return img
      ImageRGB8   img -> return $ convertImage img
      ImageY8     img -> return $ convertImage (promoteImage img :: Image PixelRGB8)
      ImageYA8    img -> return $ convertImage (promoteImage img :: Image PixelRGB8)
      ImageCMYK8  img -> return $ convertImage (convertImage img :: Image PixelRGB8)
      ImageRGBA8  img -> return $ convertImage (pixelMap dropTransparency img :: Image PixelRGB8)
      _               -> do
          putStrLn "Error: incompatible image type."
          exitFailure

ผล

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

นี่คือลิงค์ไปยังอัลบั้มเต็ม

โกธิคอเมริกัน -> Mona Lisa

Mona Lisa -> โกธิคอเมริกัน

ทรงกลม -> โมนาลิซา

The Scream -> Starry Night

The Scream -> Spheres


3
ฉันชอบ dithering on (Spheres -> Mona Lisa) แต่สิ่งประดิษฐ์เหล่านั้นน่าเกลียดบน (Scream -> Spheres) มาจากไหน
John Dvorak

1
สิ่งประดิษฐ์เป็นผลข้างเคียงของอัลกอริทึมของฉันในการจัดเรียงพิกเซล ตอนนี้ความแตกต่างสีแดงของแต่ละพิกเซลมีความสำคัญเหนือกว่าความแตกต่างสีน้ำเงินในขั้นตอนการเรียงลำดับซึ่งหมายความว่าสีที่คล้ายกันในภาพอินพุตสามารถจับคู่กับสีที่แตกต่างกันมากจากภาพสี อย่างไรก็ตามฉันเกือบจะแน่ใจว่าเอฟเฟกต์แบบเดียวกันนี้เป็นสิ่งที่ทำให้เกิดความแตกต่างอย่างชัดเจนในภาพเช่น Spheres -> Mona Lisa ดังนั้นฉันจึงตัดสินใจที่จะเก็บมันไว้
ChaseC

9

ชวา

แรงบันดาลใจจากคำตอบจาวาก่อนหน้านี้จาก Quincunx

     package paletteswap;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.imageio.ImageIO;

public class Test
{
    public static class Bits
    {

        public static BitSet convert( int value )
        {
            BitSet bits = new BitSet();
            int index = 0;
            while ( value != 0L )
            {
                if ( value % 2 != 0 )
                {
                    bits.set( index );
                }
                ++index;
                value = value >>> 1;
            }
            return bits;
        }

        public static int convert( BitSet bits )
        {
            int value = 0;
            for ( int i = 0; i < bits.length(); ++i )
            {
                value += bits.get( i ) ? ( 1 << i ) : 0;
            }
            return value;
        }
    }

    public static void main( String[] args ) throws IOException
    {
        BufferedImage source = ImageIO.read( resource( "river.png" ) ); // My names
                                                                            // for the
                                                                            // files
        BufferedImage palette = ImageIO.read( resource( "farmer.png" ) );
        BufferedImage result = rearrange( source, palette );
        ImageIO.write( result, "png", resource( "result.png" ) );
    }

    public static BufferedImage rearrange( BufferedImage source, BufferedImage palette )
    {
        BufferedImage result = new BufferedImage( source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_RGB );

        // This creates a list of points in the Source image.
        // Then, we shuffle it and will draw points in that order.
        List<Point> samples = getPoints( source.getWidth(), source.getHeight() );
        Collections.sort( samples, new Comparator<Point>()
        {

            @Override
            public int compare( Point o1, Point o2 )
            {
                int c1 = getRGB( source, o1.x, o1.y );
                int c2 = getRGB( source, o2.x, o2.y );
                return c1 -c2;
            }
        } );

        // Create a list of colors in the palette.
        List<Integer> colors = getColors( palette );

        while ( !samples.isEmpty() )
        {
            Point currentPoint = samples.remove( 0 );
            int sourceAtPoint = getRGB( source, currentPoint.x, currentPoint.y );
            int colorIndex = binarySearch( colors, sourceAtPoint );
            int bestColor = colors.remove( colorIndex );
            setRGB( result, currentPoint.x, currentPoint.y, bestColor );
        }
        return result;
    }

    public static int unpack( int rgbPacked )
    {
        BitSet packed = Bits.convert( rgbPacked );
        BitSet rgb = Bits.convert( 0 );
        for (int i=0; i<8; i++)
        {
            rgb.set( i,    packed.get( i*3 )  );
            rgb.set( i+16,    packed.get( i*3+1 )  );
            rgb.set( i+8,    packed.get( i*3+2 )  );
        }
        return Bits.convert( rgb);
    }

    public static int pack( int rgb )
    {
        int myrgb = rgb & 0x00FFFFFF;

        BitSet bits = Bits.convert( myrgb );
        BitSet packed = Bits.convert( 0 );

        for (int i=0; i<8; i++)
        {
            packed.set( i*3,    bits.get( i )  );
            packed.set( i*3+1,  bits.get( i+16 )  );
            packed.set( i*3+2,  bits.get( i+8 )  );
        }
        return Bits.convert( packed);

    }

    public static int getRGB( BufferedImage image, int x, int y )
    {
        return pack( image.getRGB( x, y ) );
    }

    public static void setRGB( BufferedImage image, int x, int y, int color )
    {
        image.setRGB( x, y, unpack( color ) );
    }

    public static List<Point> getPoints( int width, int height )
    {
        List<Point> points = new ArrayList<>( width * height );
        for ( int x = 0; x < width; x++ )
        {
            for ( int y = 0; y < height; y++ )
            {
                points.add( new Point( x, y ) );
            }
        }
        return points;
    }

    public static List<Integer> getColors( BufferedImage img )
    {
        int width = img.getWidth();
        int height = img.getHeight();
        List<Integer> colors = new ArrayList<>( width * height );
        for ( int x = 0; x < width; x++ )
        {
            for ( int y = 0; y < height; y++ )
            {
                colors.add( getRGB( img, x, y ) );
            }
        }
        Collections.sort( colors );
        return colors;
    }

    public static int binarySearch( List<Integer> toSearch, int obj )
    {
        int index = toSearch.size() >> 1;
        for ( int guessChange = toSearch.size() >> 2; guessChange > 0; guessChange >>= 1 )
        {
            int value = toSearch.get( index );
            if ( obj == value )
            {
                return index;
            }
            else if ( obj < value )
            {
                index -= guessChange;
            }
            else
            {
                index += guessChange;
            }
        }
        return index;
    }

    public static File resource( String fileName )
    { // This method is here solely
        // for my ease of use (I put
        // the files under <Project
        // Name>/Resources/ )
        return new File( System.getProperty( "user.home" ) + "/pictureswap/" + fileName );
    }
}

Mona lisa -> เกษตรกร

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

มันจะเรียงลำดับจุดที่ต้องแทนที่ด้วยความเข้มแทนที่จะเป็นแบบสุ่ม


8

ทับทิม

ข้อมูลทั่วไป:

วิธีการง่าย ๆ จริงๆ แต่ดูเหมือนว่าจะได้ผลลัพธ์ที่ดีงาม

  1. ใช้จานสีและเป้าหมายจัดเรียงพิกเซลของพวกเขาทั้งสองโดยฟังก์ชั่นบางอย่าง; เรียกใช้อาร์เรย์ "อ้างอิง" เหล่านี้ ฉันเลือกที่จะจัดเรียงตาม HSLA แต่เลือกใช้ Luminance to Saturation to Hue (aka "LSHA")
  2. จัดทำอิมเมจเอาต์พุตโดยวนซ้ำแต่ละพิกเซลของอิมเมจเป้าหมายค้นหาตำแหน่งที่จะถูกจัดเรียงในอาร์เรย์อ้างอิงเป้าหมายและนำพิกเซลจากพาเล็ตที่เรียงลำดับเป็นดัชนีเดียวกันในอาเรย์อ้างอิงจานสี

รหัส:

require 'rubygems'
require 'chunky_png'
require 'rmagick' # just for the rgba => hsla converter, feel free to use something lighter-weight you have on hand

def pixel_array_for_image(image)
  # [r, b, g, a]
  image.pixels.map{|p| ChunkyPNG::Color.to_truecolor_alpha_bytes(p)}
end

def sorted_pixel_references(pixel_array)
  pixel_array.map{|a| yield(a)}.map.with_index.sort_by(&:first).map(&:last)
end

def sort_by_lsha(pixel_array)
  sorted_pixel_references(pixel_array) {|p|
    # feel free to drop in any sorting function you want here!
    hsla = Magick::Pixel.new(*p).to_hsla # [h, s, l, a]
    [hsla[2], hsla[1], hsla[0], hsla[3]]
  }
end

def make_target_out_of_palette(target_filename, palette_filename, output_filename)
  puts "making #{target_filename} out of #{palette_filename}"

  palette = ChunkyPNG::Image.from_file(palette_filename)
  target = ChunkyPNG::Image.from_file(target_filename)
  puts "  loaded images"

  palette_array = pixel_array_for_image(palette)
  target_array = pixel_array_for_image(target)
  puts "  have pixel arrays"

  palette_spr = sort_by_lsha(palette_array)
  target_spr = sort_by_lsha(target_array)
  puts "  have sorted-pixel reference arrays"

  output = ChunkyPNG::Image.new(target.dimension.width, target.dimension.height, ChunkyPNG::Color::TRANSPARENT)
  (0...target_array.count).each { |index|
    spr_index = target_spr.index(index)
    index_in_palette = palette_spr[spr_index]
    palette_pixel = palette_array[index_in_palette]
    index_as_x = (index % target.dimension.width)
    index_as_y = (index / target.dimension.width)
    output[index_as_x, index_as_y] = ChunkyPNG::Color.rgba(*palette_pixel)
  }
  output.save(output_filename)
  puts "  saved to #{output_filename}"
end

palette_filename, target_filename, output_filename = ARGV
make_target_out_of_palette(target_filename, palette_filename, output_filename)

ผล:

http://imgur.com/a/Iu7Ds

ไฮไลท์:

Starry Night ผลิตจาก Scream โกธิคอเมริกันทำจาก Mona Lisa โมนาลิซาทำจากรูปแม่น้ำ รูปแม่น้ำทำจากสตาร์รี่ไนท์


2
คุณสามารถเพิ่มจานสีต้นฉบับสำหรับแต่ละภาพได้หรือไม่?
PlasmaHH

7

Perl

นี่เป็นวิธีการที่ค่อนข้างง่าย ใช้เวลาประมาณห้าวินาทีในการสร้าง 100 เฟรมต่อภาพคู่หนึ่งในMacBook Pro ของฉันโดยมีพื้นที่หน่วยความจำประมาณ 120 MB

แนวคิดคือการเรียงลำดับพิกเซลในทั้งภาพและจานสีด้วย RGB แบบ 24 บิตที่บรรจุและแทนที่สีในแหล่งที่มาด้วยสีจากจานสีตามลำดับ

#!/usr/bin/env perl

use 5.020; # just because
use strict;
use warnings;

use Const::Fast;
use GD;
GD::Image->trueColor(1);

use Path::Class;

const my $COLOR => 0;
const my $COORDINATES => 1;
const my $RGB => 2;
const my $ANIMATION_FRAMES => 100;

const my %MASK => (
    RED => 0x00ff0000,
    GREEN => 0x0000ff00,
    BLUE => 0x000000ff,
);

run(@ARGV);

sub run {
    unless (@_ == 2) {
        die "Need source and palette images\n";
    }
    my $source_file = file(shift)->resolve;
    my $palette_file = file(shift)->resolve;

    my $source = GD::Image->new("$source_file")
        or die "Failed to create source image from '$source_file'";
    my $palette = GD::Image->new("$palette_file")
        or die "Failed to create palette image from '$palette_file'";

    my %source =  map { $_ => $source->$_ } qw(width height);
    my %palette = map { $_ => $palette->$_ } qw(width height);
    my ($frame_prefix) = ($source_file->basename =~ /\A([^.]+)/);

    unless (
        (my $source_area = $source{width} * $source{height}) <=
        (my $palette_area = $palette{width} * $source{height})
    ) {
        die "Source area ($source_area) is greater than palette area ($palette_area)";
    }

    my ($last_frame, $png) = recreate_source_image_from_palette(
        \%source,
        get_source_pixels( get_pixels_by_color($source, \%source) ),
        get_palette_colors( get_pixels_by_color($palette, \%palette) ),
        sub { save_frame($frame_prefix, @_) }
    );

    save_frame($frame_prefix, $last_frame, $png);
    return;
}

sub save_frame {
    my $frame_prefix = shift;
    my $frame = shift;
    my $png = shift;
    file(
        sprintf("${frame_prefix}-%d.png", $frame)
    )->spew(iomode => '>:raw', $$png);
    return;
}

sub recreate_source_image_from_palette {
    my $dim = shift;
    my $source_pixels = shift;
    my $palette_colors = shift;
    my $callback = shift;
    my $frame = 0;

    my %colors;
    $colors{$_} = undef for @$palette_colors;

    my $gd = GD::Image->new($dim->{width}, $dim->{height}, 1);
    for my $x (keys %colors) {
          $colors{$x} = $gd->colorAllocate(unpack_rgb($x));
    }

    my $period = sprintf '%.0f', @$source_pixels / $ANIMATION_FRAMES;
    for my $i (0 .. $#$source_pixels) {
        $gd->setPixel(
            @{ $source_pixels->[$i] },
            $colors{ $palette_colors->[$i] }
        );
        if ($i % $period == 0) {
            $callback->($frame, \ $gd->png);
            $frame += 1;
        }
    }
    return ($frame, \ $gd->png);
}

sub get_palette_colors { [ map sprintf('%08X', $_->[$COLOR]), @{ $_[0] } ] }

sub get_source_pixels { [ map $_->[$COORDINATES], @{ $_[0] } ] }

sub get_pixels_by_color {
    my $gd = shift;
    my $dim = shift;
    return [
        sort { $a->[$COLOR] <=> $b->[$COLOR] }
        map {
            my $y = $_;
            map {
                [ pack_rgb( $gd->rgb( $gd->getPixel($_, $y) ) ), [$_, $y] ];
            } 0 .. $dim->{width}
        } 0 .. $dim->{height}
    ];
}

sub pack_rgb { $_[0] << 16 | $_[1] << 8 | $_[2] }

sub unpack_rgb {
    my ($r, $g, $b) = map $MASK{$_} & hex($_[0]), qw(RED GREEN BLUE);
    return ($r >> 16, $g >> 8, $b);
}

เอาท์พุต

กรีดร้องโดยใช้จาน Starry Night

กรีดร้องโดยใช้จาน Starry Night

American Gothic ใช้สี Mona Lisa

American Gothic ใช้สี Mona Lisa

Mona Lisa ใช้สี Scream

Mona Lisa ใช้สี Scream

แม่น้ำใช้สีหินอ่อน

แม่น้ำใช้สีหินอ่อน

ภาพเคลื่อนไหว

ผมขี้เกียจดังนั้นฉันใส่ภาพเคลื่อนไหวบน YouTube: โมนาลิซ่าใช้สีจาก Starry Nightและอเมริกันกอธิคโดยใช้สีจากโมนาลิซ่า


7

หลาม

ฉันคิดว่าฉันใช้โอกาสเล็กน้อยนี้ในการทำโค้ดกอล์ฟและใช้เป็นข้ออ้างในการทำงานกับ Python ของฉันเนื่องจากมันกำลังจะมาบ่อยขึ้นในที่ทำงานทุกวันนี้ ฉันวิ่งผ่านอัลกอริธึมสองสามอันรวมถึงเวลาไม่กี่นาทีด้วย O (n ^ 2) และ O (nlog (n)) เพื่อลองและปรับสีให้เหมาะสม แต่ก็เห็นได้ชัดว่านี่ทั้งคู่มีราคาแพงและมีน้อยมาก ผลกระทบต่อผลที่เห็นได้ชัด ดังนั้นด้านล่างเป็นสิ่งที่ฉันทำในสิ่งที่ทำงานในเวลา O (n) (โดยทั่วไปในระบบของฉัน) ที่ได้รับองค์ประกอบภาพที่สำคัญที่สุด (ความส่องสว่าง) ถูกต้องตามที่สมเหตุสมผลและให้ Chroma Land ที่มันอาจ

from PIL import Image
def check(palette, copy):
    palette = sorted(palette.getdata())
    copy = sorted(copy.getdata())
    print "Master says it's good!" if copy == palette else "The master disapproves."

def GetLuminance(pixel):
    # Extract the pixel channel data
    b, g, r = pixel
    # and used the standard luminance curve to get luminance.
    return .3*r+.59*g+.11*b

print "Putting pixels on the palette..."
# Open up the image and get all of the pixels out of it. (Memory intensive!)
palette = Image.open("2.png").convert(mode="RGB")

pixelsP = [] # Allocate the array
width,height = palette.size # Unpack the image size
for y in range(height): # Scan the lines
    for x in range(width): # within the line, scan the pixels
        curpixel = palette.getpixel((x,y)) # get the pixel
        pixelsP.append((GetLuminance(curpixel),curpixel)) # and add a (luminance, color) tuple to the array.


# sort the pixels by the calculated luminescence
pixelsP.sort()

print "Getting the reference picture..."
# Open up the image and get all of the pixels out of it. (Memory intensive!)
source = Image.open("6.png").convert(mode="RGB")
pixelsR = [] # Allocate the array
width,height = source.size # Unpack the image size
for y in range(height): # Scan the lines
    for x in range(width): # within the line, scan the pixels
        curpixel = source.getpixel((x,y)) # get the pixel
        pixelsR.append((GetLuminance(curpixel),(x,y))) # and add a (luminance, position) tuple

# Sort the Reference pixels by luminance too
pixelsR.sort()

# Now for the neat observation. Luminance matters more to humans than chromanance,
# given this then we want to match luminance as well as we can. However, we have
# a finite luminance distribution to work with. Since we can't change that, it's best
# just to line the two images up, sorted by luminance, and just simply assign the
# luminance directly. The chrominance will be all kinds of whack, but fixing that
# by way of loose sorting possible chrominance errors takes this algorithm from O(n)
# to O(n^2), which just takes forever (trust me, I've tried it.)

print "Painting reference with palette..."
for p in range(len(pixelsP)): # For each pixel in the palette
    pl,pixel = pixelsP[p] # Grab the pixel from the palette
    l,cord = pixelsR[p] # Grab the location from the reference
    source.putpixel(cord,pixel) # and assign the pallet pixel to the refrence pixels place

print "Applying fixative..."
# save out the result.
source.save("o.png","PNG")

print "Handing it to the master to see if he approves..."
check(palette, source)
print "Done!"

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

ทุกอย่าง -> Mona Lisa

โกธิคอเมริกัน -> Mona Lisa Starry Night -> โมนาลิซ่า Scream -> Mona Lisa แม่น้ำ -> Mona Lisa ทรงกลม -> โมนาลิซา

Mona Lisa -> ทรงกลม

Mona Lisa -> ทรงกลม


6

Mathematica - การเรียงสับเปลี่ยนแบบสุ่ม

ความคิด

เลือกสองพิกเซลในภาพต้นฉบับและตรวจสอบว่าข้อผิดพลาดของภาพปลายทางจะลดลงหรือไม่หากพิกเซลสองเหล่านี้ถูกเปลี่ยน เราเพิ่มตัวเลขสุ่มขนาดเล็ก (-d | + d) ลงในผลลัพธ์เพื่อหลีกเลี่ยงการใช้งานในท้องถิ่นน้อยที่สุด ทำซ้ำ สำหรับความเร็วทำได้ด้วย 10,000 พิกเซลในครั้งเดียว

มันเป็นเหมือนเชนสุ่มของมาร์คอฟ อาจเป็นการดีที่จะลดการสุ่มระหว่างกระบวนการปรับให้เหมาะสมซึ่งคล้ายกับการหลอมจำลอง

รหัส

colorSpace = "RGB";
\[Delta] = 0.05;
ClearAll[loadImgur, imgToList, listToImg, improveN, err, rearrange, \
rearrangeImg]
loadImgur[tag_] := 
 RemoveAlphaChannel@
  Import["http://i.stack.imgur.com/" <> tag <> ".png"]
imgToList[img_] := Flatten[ImageData[ColorConvert[img, colorSpace]], 1]
listToImg[u_, w_] := Image[Partition[u, w], ColorSpace -> colorSpace]
err[{x_, y_, z_}] := x^2 + y^2 + z^2
improveN[a_, t_, n_] := Block[{i, j, ai, aj, ti, tj},
  {i, j} = Partition[RandomSample[Range@Length@a, 2 n], n];
  ai = a[[i]];
  aj = a[[j]];
  ti = t[[i]];
  tj = t[[j]];
  ReplacePart[
   a, (#1 -> #3) & @@@ 
    Select[Transpose[{i, 
       err /@ (ai - ti) + err /@ (aj - tj) - err /@ (ai - tj) - 
        err /@ (aj - ti) + RandomReal[\[Delta]^2 {-1, +1}, n], aj}], #[[2]] > 0 &]]
  ]
rearrange[ua_, ub_, iterations_: 100] := Block[{tmp = ua},
  Do[tmp = improveN[tmp, ub, Floor[.1 Length@ua]];, {i, iterations}]; 
  tmp]
rearrangeImg[a_, b_, iterations_: 100] := With[{imgdst = loadImgur[b]},
  listToImg[rearrange[
    RandomSample@imgToList@loadImgur[a],
    imgToList@imgdst, iterations], First@ImageDimensions@imgdst]]
rearrangeImg["JXgho","itzIe"]

ผล

โกธิคไป Mona Lisa ซ้าย: การใช้พื้นที่สี LAB (เดลต้า = 0) ขวา: การใช้พื้นที่สี RBG (เดลต้า = 0) img7 img8

โกธิคไป Mona Lisa ซ้าย: พื้นที่สี RGB, เดลต้า = 0.05 ขวา: พื้นที่สี RGB, เดลต้า = 0.15 img9 img10

ภาพต่อไปนี้แสดงภาพเคลื่อนไหวประมาณ 3,500,000 swaps ด้วยพื้นที่สี RGB และเดลต้า = 0

img1 img2 img3 img4 img5 img6


ดูเหมือนว่าทางของ aditsu แต่ฉันหวังว่าจะได้ผลลัพธ์ของคุณ
Leif

5

การประมวลผล

แหล่งที่มาและจานสีจะแสดงแบบเคียงข้างกันและมีภาพเคลื่อนไหวของพิกเซลที่นำมาจากจานสี

ในบรรทัดint i = chooseIndexIncremental();คุณสามารถเปลี่ยนchooseIndex*ฟังก์ชั่นเพื่อดูลำดับการเลือกของพิกเซล

int scanRate = 20; // pixels per frame

// image filenames
String source = "N6IGO.png";
String palette = "JXgho.png";

PImage src, pal, res;
int area;
int[] lut;
boolean[] processed;
boolean[] taken;
int count = 0;

void start() {
  //size(800, 600);

  src = loadImage(source);
  pal = loadImage(palette);

  size(src.width + pal.width, max(src.height, pal.height));

  src.loadPixels();
  pal.loadPixels();

  int areaSrc = src.pixels.length;
  int areaPal = pal.pixels.length;

  if (areaSrc != areaPal) {
    println("Areas mismatch: src: " + areaSrc + ", pal: " + areaPal);
    return;
  }

  area = areaSrc;

  println("Area: " + area);

  lut = new color[area];
  taken = new boolean[area];
  processed = new boolean[area];

  randomSeed(1);
}

void draw() {
  background(0);
  image(src, 0, 0);
  image(pal, src.width, 0);

  for (int k = 0; k < scanRate; k ++)
  if (count < area) {
    // choose from chooseIndexRandom, chooseIndexSkip and chooseIndexIncremental
    int i = chooseIndexIncremental();
    process(i);

    processed[i] = true;
    count ++;
  }
}

int chooseIndexRandom() {
  int i = 0;
  do i = (int) random(area); while (processed[i]);
  return i;
}

int chooseIndexSkip(int n) {
  int i = (n * count) % area;
  while (processed[i] || i >= area) i = (int) random(area);
  return i;
}

int chooseIndexIncremental() {
  return count;
}

void process(int i) {
  lut[i] = findPixel(src.pixels[i]);
  taken[lut[i]] = true;

  src.loadPixels();
  src.pixels[i] = pal.pixels[lut[i]];
  src.updatePixels();

  pal.loadPixels();
  pal.pixels[lut[i]] = color(0);
  pal.updatePixels();

  stroke(src.pixels[i]);
  int sy = i / src.width;
  int sx = i % src.width;

  int j = lut[i];
  int py = j / pal.width;
  int px = j % pal.width;
  line(sx, sy, src.width + px, py);
}

int findPixel(color c) {
  int best;
  do best = (int) random(area); while (taken[best]);
  float bestDist = colorDist(c, pal.pixels[best]);

  for (int k = 0; k < area; k ++) {
    if (taken[k]) continue;
    color c1 = pal.pixels[k];
    float dist = colorDist(c, c1);
    if (dist < bestDist) {
      bestDist = dist;
      best = k;
    }
  }
  return best;
}

float colorDist(color c1, color c2) {
  return S(red(c1) - red(c2)) + S(green(c1) - green(c2)) + S(blue(c1) - blue(c2));
}

float S(float x) { return x * x; }

American Gothic -> Mona Lisa แบบเพิ่มหน่วย

ที่เพิ่มขึ้น

American Gothic -> Mona Lisa สุ่ม

สุ่ม


2
มันจะดูเป็นอย่างไรถ้าคุณใช้จานสีรุ้งทรงกลม?
phyzome

5

C-Sharp

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

แก้ไข: เพิ่มรุ่นที่สั้นกว่า

using System;
using System.Drawing;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        Bitmap sourceImg = new Bitmap("TheScream.png"),
            arrangImg = new Bitmap("StarryNight.png"),
            destImg = new Bitmap(arrangImg.Width, arrangImg.Height);

        List<Pix> sourcePixels = new List<Pix>(), arrangPixels = new List<Pix>();

        for (int i = 0; i < sourceImg.Width; i++)
            for (int j = 0; j < sourceImg.Height; j++)
                sourcePixels.Add(new Pix(sourceImg.GetPixel(i, j), i, j));

        for (int i = 0; i < arrangImg.Width; i++)
            for (int j = 0; j < arrangImg.Height; j++)
                arrangPixels.Add(new Pix(arrangImg.GetPixel(i, j), i, j));

        sourcePixels.Sort();
        arrangPixels.Sort();

        for (int i = 0; i < arrangPixels.Count; i++)
            destImg.SetPixel(arrangPixels[i].x,
                             arrangPixels[i].y,
                             sourcePixels[i].col);

        destImg.Save("output.png");
    }
}

class Pix : IComparable<Pix>
{
    public Color col;
    public int x, y;
    public Pix(Color col, int x, int y)
    {
        this.col = col;
        this.x = x;
        this.y = y;
    }

    public int CompareTo(Pix other)
    {
        return(int)(255 * 255 * 255 * (col.GetBrightness() - other.col.GetBrightness())
                + (255 * (col.GetHue() - other.col.GetHue()))
                + (255 * 255 * (col.GetSaturation() - other.col.GetSaturation())));
    }
}

ตัวอย่าง:

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

+

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

=

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


5

ชวา

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;

/**
 *
 * @author Quincunx
 */
public class PixelRearrangerMK2 {

    public static void main(String[] args) throws IOException {
        BufferedImage source = ImageIO.read(resource("Raytraced Spheres.png"));
        BufferedImage palette = ImageIO.read(resource("American Gothic.png"));
        BufferedImage result = rearrange(source, palette);
        ImageIO.write(result, "png", resource("result.png"));
        validate(palette, result);
    }

    public static BufferedImage rearrange(BufferedImage source, BufferedImage palette) {
        List<Color> sColors = Color.getColors(source);
        List<Color> pColors = Color.getColors(palette);
        Collections.sort(sColors);
        Collections.sort(pColors);

        BufferedImage result = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_RGB);
        Iterator<Color> sIter = sColors.iterator();
        Iterator<Color> pIter = pColors.iterator();

        while (sIter.hasNext()) {
            Color s = sIter.next();
            Color p = pIter.next();

            result.setRGB(s.x, s.y, p.rgb);
        }
        return result;
    }

    public static class Color implements Comparable {
        int x, y;
        int rgb;
        double hue;

        private int r, g, b;

        public Color(int x, int y, int rgb) {
            this.x = x;
            this.y = y;
            this.rgb = rgb;
            r = (rgb & 0xFF0000) >> 16;
            g = (rgb & 0x00FF00) >> 8;
            b = rgb & 0x0000FF;
            hue = Math.atan2(Math.sqrt(3) * (g - b), 2 * r - g - b);
        }

        @Override
        public int compareTo(Object o) {
            Color c = (Color) o;
            return hue < c.hue ? -1 : hue == c.hue ? 0 : 1;
        }

        public static List<Color> getColors(BufferedImage img) {
            List<Color> result = new ArrayList<>();
            for (int y = 0; y < img.getHeight(); y++) {
                for (int x = 0; x < img.getWidth(); x++) {
                    result.add(new Color(x, y, img.getRGB(x, y)));
                }
            }
            return result;
        }
    }

    //Validation and util methods follow
    public static void validate(BufferedImage palette, BufferedImage result) {
        List<Integer> paletteColors = getColorsAsInt(palette);
        List<Integer> resultColors = getColorsAsInt(result);
        Collections.sort(paletteColors);
        Collections.sort(resultColors);
        System.out.println(paletteColors.equals(resultColors));
    }

    public static List<Integer> getColorsAsInt(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();
        List<Integer> colors = new ArrayList<>(width * height);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                colors.add(img.getRGB(x, y));
            }
        }
        Collections.sort(colors);
        return colors;
    }

    public static File resource(String fileName) {
        return new File(System.getProperty("user.dir") + "/Resources/" + fileName);
    }
}

นี่เป็นแนวคิดที่แตกต่างอย่างสิ้นเชิง ฉันสร้างรายการสีของภาพแต่ละภาพจากนั้นฉันเรียงลำดับตามสีซึ่งคำนวณโดยสูตรของวิกิพีเดีย:

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

ต่างจากคำตอบอื่น ๆ ของฉันนี่เร็วมาก ใช้เวลาประมาณ 2 วินาทีรวมถึงการตรวจสอบความถูกต้อง

ผลที่ได้คือศิลปะนามธรรม นี่คือภาพบางส่วน (วางเมาส์เพื่อดู / จาก):

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


5
ดูเหมือนว่าสิ่งที่ Predator จะเห็น o_O
eithed

สิ่งเหล่านี้ค่อนข้างน่ากลัว แต่จริง ๆ แล้วถูกต้อง!
งานอดิเรกของ Calvin

1
@ Calvin'sHobbies เรื่องนี้น่ากลัวแค่ไหน? ฉันเรียกว่าความงาม
Justin

3
ใบหน้าของพวกเขาว่างเปล่าและน่าขนลุก ... แต่พวกเขาก็มีความงามตามหลอกหลอน
งานอดิเรกของ Calvin

1
ทรงกลมนั้นยอดเยี่ยม
siledh

5

หลาม

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

from PIL import Image
from optparse import OptionParser


def key_func(arr):
    # Sort the pixels by luminance
    r = 0.2126*arr[0] + 0.7152*arr[1] + 0.0722*arr[2]
    return r


def main():
    # Parse options from the command line
    parser = OptionParser()
    parser.add_option("-p", "--pixels", dest="pixels",
                      help="use pixels from FILE", metavar="FILE")
    parser.add_option("-i", "--input", dest="input", metavar="FILE",
                      help="recreate FILE")
    parser.add_option("-o", "--out", dest="output", metavar="FILE",
                      help="output to FILE", default="output.png")

    (options, args) = parser.parse_args()

    if not options.pixels or not options.input:
        raise Exception("Missing arguments. See help for more info.")

    # Load the images
    im1 = Image.open(options.pixels)
    im2 = Image.open(options.input)

    # Get the images into lists
    px1 = list(im1.getdata())
    px2 = list(im2.getdata())
    w1, h1 = im1.size
    w2, h2 = im2.size

    if w1*h1 != w2*h2:
        raise Exception("Images must have the same number of pixels.")

    # Sort the pixels lists by luminance
    px1_s = sorted(px1, key=key_func)
    px2_s = sorted(px2, key=key_func)

    # Create an array of nothing but black pixels
    arr = [(0, 0, 0)]*w2*h2

    # Create a dict that contains a list of locations with pixel value as key
    # This speeds up the process a lot, since before it was O(n^2)
    locations_cache = {}
    for index, val in enumerate(px2):
        v = str(val)
        if v in locations_cache:
            locations_cache[v].append(index)
        else:
            locations_cache[v] = [index]

    # Loop through each value of the sorted pixels
    for index, val in enumerate(px2_s):
        # Find the original location of the pixel
        # v = px2.index(val)
        v = locations_cache[str(val)].pop(0)
        # Set the value of the array at the given location to the pixel of the
        # equivalent luminance from the source image
        arr[v] = px1_s[index]
        # v2 = px1.index(px1_s[index])
        # Set the value of px2 to an arbitrary value outside of the RGB range
        # This prevents duplicate pixel locations
        # I would use "del px2[v]", but it wouldn't work for some reason
        px2[v] = (512, 512, 512)
        # px1[v2] = (512, 512, 512)
        # Print the percent progress
        print("%f%%" % (index/len(px2)*100))
        """if index % 500 == 0 or index == len(px2_s)-1:
            if h1 > h2:
                size = (w1+w2, h1)
            else:
                size = (w1+w2, h2)
            temp_im1 = Image.new("RGB", im2.size)
            temp_im1.putdata(arr)

            temp_im2 = Image.new("RGB", im1.size)
            temp_im2.putdata(px1)

            temp_im3 = Image.new("RGB", size)
            temp_im3.paste(temp_im1, (0, 0))
            temp_im3.paste(temp_im2, (w2, 0))
            temp_im3.save("still_frames/img_%04d.png" % (index/500))"""

    # Save the image
    im3 = Image.new('RGB', im2.size)
    im3.putdata(arr)
    im3.save(options.output)

if __name__ == '__main__':
    main()

ผล

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

Starry Night กับ Scream Pixels

Scream + Starry Night

Starry Night กับ Rainbow Pixels

Rainbow + Starry Night

Rainbow กับ Starry Night Pixels

Starry Night + Rainbow

Mona Lisa พร้อม Scream Pixels

Scream + Mona Lisa

แม่น้ำกับ Starry Night Pixels

Starry Night + River

Mona Lisa กับ American Gothic Pixels

Gothic + Mona Lisa

มัสแตงกับ Chevy Pixels

ฉันควรจะลดขนาดรูปภาพลงเนื่องจากข้อ จำกัด ด้านฮาร์ดแวร์ แต่ก็ดี

Chevy + มัสแตง

Chevy กับ Mustang Pixels

มัสแตง + Chevy

แม่น้ำกับเรนโบว์พิกเซล

เรนโบว์ + แม่น้ำ

โมนาลิซ่ากับ Rainbow Pixels

Rainbow + Mona Lisa

American Gothic พร้อมเรนโบว์พิกเซล

เรนโบว์ + กอธิค


อัปเดตฉันได้เพิ่มรูปภาพอีกสองสามภาพและนี่เป็นภาพเคลื่อนไหวสองสามภาพ วิธีแรกแสดงวิธีการทำงานของฉันและวิธีที่สองใช้สคริปต์ @ Calvin'sHobbies ที่โพสต์

วิธีการของฉัน

@ สคริปต์ของ Calvin


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


5

C ++ 11

ในที่สุดฉันก็ตัดสินด้วยอัลกอริทึมโลภที่ค่อนข้างง่าย นี่เป็นเธรดเดี่ยว แต่ทำงานในเครื่องของฉันนานกว่า 4 วินาที

อัลกอริทึมพื้นฐานทำงานโดยการจัดเรียงพิกเซลทั้งหมดในจานสีและภาพเป้าหมายโดยการลดความสว่าง (L ของL a b * ) จากนั้นสำหรับแต่ละพิกเซลเป้าหมายที่สั่งซื้อจะค้นหาการจับคู่ที่ใกล้เคียงที่สุดใน 75 รายการแรกของจานสีโดยใช้ตารางของการวัดระยะทางCIEDE2000พร้อมความส่องสว่างของสีจานสีที่ยึดกับเป้าหมาย (สำหรับการนำไปใช้และแก้ไขจุดบกพร่องของ CIEDE2000 หน้านี้มีประโยชน์มาก) การจับคู่ที่ดีที่สุดจะถูกลบออกจากจานสีกำหนดให้กับผลลัพธ์และอัลกอริทึมไปยังพิกเซลที่เบาที่สุดถัดไปในภาพเป้าหมาย

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

รหัส

ในการรวบรวมสิ่งนี้คุณจะต้องมีไลบรารีการพัฒนา ImageMagick ++ ไฟล์ CMake ขนาดเล็กเพื่อรวบรวมมันยังรวมอยู่ด้านล่าง

palette.cpp

#include <Magick++.h>
#include <algorithm>
#include <functional>
#include <utility>
#include <set>

using namespace std;
using namespace Magick;

struct Lab
{
    PixelPacket rgb;
    float L, a, b;

    explicit Lab(
        PixelPacket rgb )
        : rgb( rgb )
    {
        auto R_srgb = static_cast< float >( rgb.red ) / QuantumRange;
        auto G_srgb = static_cast< float >( rgb.green ) / QuantumRange;
        auto B_srgb = static_cast< float >( rgb.blue ) / QuantumRange;
        auto R_lin = R_srgb < 0.04045f ? R_srgb / 12.92f :
            powf( ( R_srgb + 0.055f ) / 1.055f, 2.4f );
        auto G_lin = G_srgb < 0.04045f ? G_srgb / 12.92f :
            powf( ( G_srgb + 0.055f ) / 1.055f, 2.4f );
        auto B_lin = B_srgb < 0.04045f ? B_srgb / 12.92f :
            powf( ( B_srgb + 0.055f ) / 1.055f, 2.4f );
        auto X = 0.4124f * R_lin + 0.3576f * G_lin + 0.1805f * B_lin;
        auto Y = 0.2126f * R_lin + 0.7152f * G_lin + 0.0722f * B_lin;
        auto Z = 0.0193f * R_lin + 0.1192f * G_lin + 0.9502f * B_lin;
        auto X_norm = X / 0.9505f;
        auto Y_norm = Y / 1.0000f;
        auto Z_norm = Z / 1.0890f;
        auto fX = ( X_norm > 216.0f / 24389.0f ?
                    powf( X_norm, 1.0f / 3.0f ) :
                    X_norm * ( 841.0f / 108.0f ) + 4.0f / 29.0f );
        auto fY = ( Y_norm > 216.0f / 24389.0f ?
                    powf( Y_norm, 1.0f / 3.0f ) :
                    Y_norm * ( 841.0f / 108.0f ) + 4.0f / 29.0f );
        auto fZ = ( Z_norm > 216.0f / 24389.0f ?
                    powf( Z_norm, 1.0f / 3.0f ) :
                    Z_norm * ( 841.0f / 108.0f ) + 4.0f / 29.0f );
        L = 116.0f * fY - 16.0f;
        a = 500.0f * ( fX - fY );
        b = 200.0f * ( fY - fZ );
    }

    bool operator<(
        Lab const that ) const
    {
        return ( L > that.L ? true :
                 L < that.L ? false :
                 a > that.a ? true :
                 a < that.a ? false :
                 b > that.b );
    }

    Lab clampL(
        Lab const that ) const
    {
        auto result = Lab( *this );
        if ( result.L > that.L )
            result.L = that.L;
        return result;
    }

    float cieDe2000(
        Lab const that,
        float const k_L = 1.0f,
        float const k_C = 1.0f,
        float const k_H = 1.0f ) const
    {
        auto square = []( float value ){ return value * value; };
        auto degs = []( float rad ){ return rad * 180.0f / 3.14159265359f; };
        auto rads = []( float deg ){ return deg * 3.14159265359f / 180.0f; };
        auto C_1 = hypot( a, b );
        auto C_2 = hypot( that.a, that.b );
        auto C_bar = ( C_1 + C_2 ) * 0.5f;
        auto C_bar_7th = square( square( C_bar ) ) * square( C_bar ) * C_bar;
        auto G = 0.5f * ( 1.0f - sqrtf( C_bar_7th / ( C_bar_7th + 610351562.0f ) ) );
        auto a_1_prime = ( 1.0f + G ) * a;
        auto a_2_prime = ( 1.0f + G ) * that.a;
        auto C_1_prime = hypot( a_1_prime, b );
        auto C_2_prime = hypot( a_2_prime, that.b );
        auto h_1_prime = C_1_prime == 0.0f ? 0.0f : degs( atan2f( b, a_1_prime ) );
        if ( h_1_prime < 0.0f )
            h_1_prime += 360.0f;
        auto h_2_prime = C_2_prime == 0.0f ? 0.0f : degs( atan2f( that.b, a_2_prime ) );
        if ( h_2_prime < 0.0f )
            h_2_prime += 360.0f;
        auto delta_L_prime = that.L - L;
        auto delta_C_prime = C_2_prime - C_1_prime;
        auto delta_h_prime =
            C_1_prime * C_2_prime == 0.0f ? 0 :
            fabs( h_2_prime - h_1_prime ) <= 180.0f ? h_2_prime - h_1_prime :
            h_2_prime - h_1_prime > 180.0f ? h_2_prime - h_1_prime - 360.0f :
            h_2_prime - h_1_prime + 360.0f;
        auto delta_H_prime = 2.0f * sqrtf( C_1_prime * C_2_prime ) *
            sinf( rads( delta_h_prime * 0.5f ) );
        auto L_bar_prime = ( L + that.L ) * 0.5f;
        auto C_bar_prime = ( C_1_prime + C_2_prime ) * 0.5f;
        auto h_bar_prime =
            C_1_prime * C_2_prime == 0.0f ? h_1_prime + h_2_prime :
            fabs( h_1_prime - h_2_prime ) <= 180.0f ? ( h_1_prime + h_2_prime ) * 0.5f :
            h_1_prime + h_2_prime < 360.0f ? ( h_1_prime + h_2_prime + 360.0f ) * 0.5f :
            ( h_1_prime + h_2_prime - 360.0f ) * 0.5f;
        auto T = ( 1.0f
                   - 0.17f * cosf( rads( h_bar_prime - 30.0f ) )
                   + 0.24f * cosf( rads( 2.0f * h_bar_prime ) )
                   + 0.32f * cosf( rads( 3.0f * h_bar_prime + 6.0f ) )
                   - 0.20f * cosf( rads( 4.0f * h_bar_prime - 63.0f ) ) );
        auto delta_theta = 30.0f * expf( -square( ( h_bar_prime - 275.0f ) / 25.0f ) );
        auto C_bar_prime_7th = square( square( C_bar_prime ) ) *
            square( C_bar_prime ) * C_bar_prime;
        auto R_C = 2.0f * sqrtf( C_bar_prime_7th / ( C_bar_prime_7th + 610351562.0f ) );
        auto S_L = 1.0f + ( 0.015f * square( L_bar_prime - 50.0f ) /
                            sqrtf( 20.0f + square( L_bar_prime - 50.0f ) ) );
        auto S_C = 1.0f + 0.045f * C_bar_prime;
        auto S_H = 1.0f + 0.015f * C_bar_prime * T;
        auto R_T = -sinf( rads( 2.0f * delta_theta ) ) * R_C;
        return (
            square( delta_L_prime / ( k_L * S_L ) ) +
            square( delta_C_prime / ( k_C * S_C ) ) +
            square( delta_H_prime / ( k_H * S_H ) ) +
            R_T * delta_C_prime * delta_H_prime / ( k_C * S_C * k_H * S_H ) );
    }

};

Image read_image(
    char * const filename )
{
    auto result = Image( filename );
    result.type( TrueColorType );
    result.matte( true );
    result.backgroundColor( Color( 0, 0, 0, QuantumRange ) );
    return result;
}

template< typename T >
multiset< T > map_image(
    Image const &image,
    function< T( unsigned, PixelPacket ) > const transform )
{
    auto width = image.size().width();
    auto height = image.size().height();
    auto result = multiset< T >();
    auto pixels = image.getConstPixels( 0, 0, width, height );
    for ( auto index = 0; index < width * height; ++index, ++pixels )
        result.emplace( transform( index, *pixels ) );
    return result;
}

int main(
    int argc,
    char **argv )
{
    auto palette = map_image(
        read_image( argv[ 1 ] ),
        function< Lab( unsigned, PixelPacket ) >(
            []( unsigned index, PixelPacket rgb ) {
                return Lab( rgb );
            } ) );

    auto target_image = read_image( argv[ 2 ] );
    auto target_colors = map_image(
        target_image,
        function< pair< Lab, unsigned >( unsigned, PixelPacket ) >(
            []( unsigned index, PixelPacket rgb ) {
                return make_pair( Lab( rgb ), index );
            } ) );

    auto pixels = target_image.setPixels(
        0, 0,
        target_image.size().width(),
        target_image.size().height() );
    for ( auto &&target : target_colors )
    {
        auto best_color = palette.begin();
        auto best_difference = 1.0e38f;
        auto count = 0;
        for ( auto candidate = palette.begin();
              candidate != palette.end() && count < 75;
              ++candidate, ++count )
        {
            auto difference = target.first.cieDe2000(
                candidate->clampL( target.first ) );
            if ( difference < best_difference )
            {
                best_color = candidate;
                best_difference = difference;
            }
        }
        pixels[ target.second ] = best_color->rgb;
        palette.erase( best_color );
    }
    target_image.syncPixels();
    target_image.write( argv[ 3 ] );

    return 0;
}

CMakeList.txt

cmake_minimum_required( VERSION 2.8.11 )
project( palette )
add_executable( palette palette.cpp)
find_package( ImageMagick COMPONENTS Magick++ )
if( ImageMagick_FOUND )
    include_directories( ${ImageMagick_INCLUDE_DIRS} )
    target_link_libraries( palette ${ImageMagick_LIBRARIES} )
endif( ImageMagick_FOUND )

ผลลัพธ์

อัลบั้มเต็มอยู่ที่นี่ จากผลลัพธ์ด้านล่างรายการโปรดของฉันอาจเป็น American Gothic พร้อมจานสี Mona Lisa และ Starry Night พร้อมจาน Spheres

American Gothic Palette

Mona Lisa Palette

ริเวอร์เพลท

The Scream Palette

จานสี Spheres

จาน Starry Night


มันดูน่าอัศจรรย์! คุณคิดอย่างไรเกี่ยวกับเรื่องนี้สามารถเร่งได้เร็วแค่ไหน? มีโอกาสตามเวลาจริงเช่น 60fps กับฮาร์ดแวร์เฉลี่ยหรือไม่
danijar

4

C ++

ไม่ใช่รหัสที่สั้นที่สุด แต่สร้างคำตอบ 'ทันที' แม้ว่าจะเป็นเธรดเดี่ยวและไม่ได้รับการปรับให้เหมาะสม ฉันพอใจกับผลลัพธ์ที่ได้

ฉันสร้างรายการพิกเซลที่เรียงลำดับสองรายการหนึ่งรายการสำหรับแต่ละภาพและการเรียงลำดับนั้นขึ้นอยู่กับค่าถ่วงน้ำหนักของ 'ความสว่าง' ฉันใช้สีเขียว 100%, สีแดง 50% และสีน้ำเงิน 10% เพื่อคำนวณความสว่างโดยให้น้ำหนักกับตามนุษย์ (มากหรือน้อย) จากนั้นฉันสลับพิกเซลในภาพต้นฉบับสำหรับพิกเซลที่จัดทำดัชนีเดียวกันของพวกเขาในภาพสีและเขียนภาพปลายทาง

ฉันใช้ไลบรารี FreeImage เพื่ออ่าน / เขียนไฟล์รูปภาพ

รหัส

/* Inputs: 2 image files of same area
Outputs: image1 made from pixels of image2*/
#include <iostream>
#include <stdlib.h>
#include "FreeImage.h"
#include <vector>
#include <algorithm>

class pixel
{
public:
    int x, y;
    BYTE r, g, b;
    float val;  //color value; weighted 'brightness'
};

bool sortByColorVal(const pixel &lhs, const pixel &rhs) { return lhs.val > rhs.val; }

FIBITMAP* GenericLoader(const char* lpszPathName, int flag) 
{
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;

    // check the file signature and deduce its format
    // (the second argument is currently not used by FreeImage)
    fif = FreeImage_GetFileType(lpszPathName, 0);
    if (fif == FIF_UNKNOWN) 
    {
        // no signature ?
        // try to guess the file format from the file extension
        fif = FreeImage_GetFIFFromFilename(lpszPathName);
    }
    // check that the plugin has reading capabilities ...
    if ((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) 
    {
        // ok, let's load the file
        FIBITMAP *dib = FreeImage_Load(fif, lpszPathName, flag);
        // unless a bad file format, we are done !
        return dib;
    }
    return NULL;
}

bool GenericWriter(FIBITMAP* dib, const char* lpszPathName, int flag) 
{
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    BOOL bSuccess = FALSE;

    if (dib) 
    {
        // try to guess the file format from the file extension
        fif = FreeImage_GetFIFFromFilename(lpszPathName);
        if (fif != FIF_UNKNOWN) 
        {
            // check that the plugin has sufficient writing and export capabilities ...
            WORD bpp = FreeImage_GetBPP(dib);
            if (FreeImage_FIFSupportsWriting(fif) && FreeImage_FIFSupportsExportBPP(fif, bpp)) 
            {
                // ok, we can save the file
                bSuccess = FreeImage_Save(fif, dib, lpszPathName, flag);
                // unless an abnormal bug, we are done !
            }
        }
    }
    return (bSuccess == TRUE) ? true : false;
}

void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) 
{
    std::cout << std::endl << "*** ";
    if (fif != FIF_UNKNOWN) 
    {
        std::cout << "ERROR: " << FreeImage_GetFormatFromFIF(fif) << " Format" << std::endl;
    }
    std::cout << message;
    std::cout << " ***" << std::endl;
}

FIBITMAP* Convert24BPP(FIBITMAP* dib)
{
    if (FreeImage_GetBPP(dib) == 24) return dib;

    FIBITMAP *dib2 = FreeImage_ConvertTo24Bits(dib);
    FreeImage_Unload(dib);
    return dib2;
}
// ----------------------------------------------------------

int main(int argc, char **argv)
{
    // call this ONLY when linking with FreeImage as a static library
#ifdef FREEIMAGE_LIB
    FreeImage_Initialise();
#endif

    FIBITMAP *src = NULL, *pal = NULL;
    int result = EXIT_FAILURE;

    // initialize my own FreeImage error handler
    FreeImage_SetOutputMessage(FreeImageErrorHandler);

    // print version
    std::cout << "FreeImage version : " << FreeImage_GetVersion() << std::endl;

    if (argc != 4) 
    {
        std::cout << "USAGE : Pic2Pic <source image> <palette image> <output file name>" << std::endl;
        return EXIT_FAILURE;
    }

    // Load the src image
    src = GenericLoader(argv[1], 0);
    if (src) 
    {
        // load the palette image
        pal = GenericLoader(argv[2], 0);

        if (pal) 
        {
            //compare areas
            // if(!samearea) return EXIT_FAILURE;
            unsigned int width_src = FreeImage_GetWidth(src);
            unsigned int height_src = FreeImage_GetHeight(src);
            unsigned int width_pal = FreeImage_GetWidth(pal);
            unsigned int height_pal = FreeImage_GetHeight(pal);

            if (width_src * height_src != width_pal * height_pal)
            {
                std::cout << "ERROR: source and palette images do not have the same pixel area." << std::endl;
                result = EXIT_FAILURE;
            }
            else
            {
                //go to work!

                //first make sure everything is 24 bit:
                src = Convert24BPP(src);
                pal = Convert24BPP(pal);

                //retrieve the image data
                BYTE *bits_src = FreeImage_GetBits(src);
                BYTE *bits_pal = FreeImage_GetBits(pal);

                //make destination image
                FIBITMAP *dst = FreeImage_ConvertTo24Bits(src);
                BYTE *bits_dst = FreeImage_GetBits(dst);

                //make a vector of all the src pixels that we can sort by color value
                std::vector<pixel> src_pixels;
                for (unsigned int y = 0; y < height_src; ++y)
                {
                    for (unsigned int x = 0; x < width_src; ++x)
                    {
                        pixel p;
                        p.x = x;
                        p.y = y;

                        p.b = bits_src[y*width_src * 3 + x * 3];
                        p.g = bits_src[y*width_src * 3 + x * 3 + 1];
                        p.r = bits_src[y*width_src * 3 + x * 3 + 2];

                        //calculate color value using a weighted brightness for each channel
                        //p.val = 0.2126f * p.r + 0.7152f * p.g + 0.0722f * p.b; //from http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html
                        p.val = 0.5f * p.r + p.g + 0.1f * p.b;                      

                        src_pixels.push_back(p);
                    }
                }

                //sort by color value
                std::sort(src_pixels.begin(), src_pixels.end(), sortByColorVal);

                //make a vector of all palette pixels we can use
                std::vector<pixel> pal_pixels;

                for (unsigned int y = 0; y < height_pal; ++y)
                {
                    for (unsigned int x = 0; x < width_pal; ++x)
                    {
                        pixel p;

                        p.b = bits_pal[y*width_pal * 3 + x * 3];
                        p.g = bits_pal[y*width_pal * 3 + x * 3 + 1];
                        p.r = bits_pal[y*width_pal * 3 + x * 3 + 2];

                        p.val = 0.5f * p.r + p.g + 0.1f * p.b;

                        pal_pixels.push_back(p);
                    }
                }

                //sort by color value
                std::sort(pal_pixels.begin(), pal_pixels.end(), sortByColorVal);

                //for each src pixel, match it with same index palette pixel and copy to destination
                for (unsigned int i = 0; i < width_src * height_src; ++i)
                {
                    bits_dst[src_pixels[i].y * width_src * 3 + src_pixels[i].x * 3] = pal_pixels[i].b;
                    bits_dst[src_pixels[i].y * width_src * 3 + src_pixels[i].x * 3 + 1] = pal_pixels[i].g;
                    bits_dst[src_pixels[i].y * width_src * 3 + src_pixels[i].x * 3 + 2] = pal_pixels[i].r;
                }

                // Save the destination image
                bool bSuccess = GenericWriter(dst, argv[3], 0);
                if (!bSuccess)
                {
                    std::cout << "ERROR: unable to save " << argv[3] << std::endl;
                    std::cout << "This format does not support 24-bit images" << std::endl;
                    result = EXIT_FAILURE;
                }
                else result = EXIT_SUCCESS;

                FreeImage_Unload(dst);
            }

            // Free pal
            FreeImage_Unload(pal);
        }

        // Free src
        FreeImage_Unload(src);
    }

#ifdef FREEIMAGE_LIB
    FreeImage_DeInitialise();
#endif

    if (result == EXIT_SUCCESS) std::cout << "SUCCESS!" << std::endl;
    else std::cout << "FAILURE!" << std::endl;
    return result;
}

ผล

American Gothic ใช้จาน Mona Lisa American Gothic ใช้ชุด Mona Lisa American Gothic ใช้ชุดสายรุ้ง American Gothic ใช้ชุดสายรุ้ง Mona Lisa ใช้ชุด Scream Mona Lisa ใช้ชุด Scream Mona Lisa ใช้จานสายรุ้ง Mona Lisa ใช้ชุดจานสายรุ้ง กรีดร้องโดยใช้จาน Starry Night Scream ใช้ชุด Starry Night


4

C #

คะแนนจะถูกจัดเรียงในการเดินสุ่มเริ่มต้นจากศูนย์ ได้สีที่ใกล้เคียงที่สุดในภาพพาเล็ทเสมอ ดังนั้นพิกเซลสุดท้ายจึงค่อนข้างแย่มาก

ผล

จานสีโกธิค

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

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

และคู่ชาวอเมริกันที่มาเยี่ยมชมวิกิพีเดีย

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

Mona Palette

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

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

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

รหัส:

ฉันไม่รู้ว่าทำไม แต่รหัสค่อนข้างช้า ...

public class PixelExchanger
{
    public class ProgressInfo
    {
        public readonly Pixel NewPixel;
        public readonly int Percentage;

        public ProgressInfo(Pixel newPixel, int percentage)
        {
            this.NewPixel = newPixel;
            this.Percentage = percentage;
        }
    }

    public class Pixel
    {
        public readonly int X;
        public readonly int Y;
        public readonly Color Color;

        public Pixel(int x, int y, Color color)
        {
            this.X = x;
            this.Y = y;
            this.Color = color;
        }
    }

    private static Random r = new Random(0);

    private readonly Bitmap Pallete;
    private readonly Bitmap Image;

    private readonly int Width;
    private readonly int Height;

    private readonly Action<ProgressInfo> ProgressCallback;
    private System.Drawing.Image image1;
    private System.Drawing.Image image2;

    private int Area { get { return Width * Height; } }

    public PixelExchanger(Bitmap pallete, Bitmap image, Action<ProgressInfo> progressCallback = null)
    {
        this.Pallete = pallete;
        this.Image = image;

        this.ProgressCallback = progressCallback;

        Width = image.Width;
        Height = image.Height;

        if (Area != pallete.Width * pallete.Height)
            throw new ArgumentException("Image and Pallete have different areas!");
    }

    public Bitmap DoWork()
    {
        var array = GetColorArray();
        var map = GetColorMap(Image);
        var newMap = Go(array, map);

        var bm = new Bitmap(map.Length, map[0].Length);

        for (int i = 0; i < Width; i++)
        {
            for (int j = 0; j < Height; j++)
            {
                bm.SetPixel(i, j, newMap[i][j]);
            }
        }

        return bm;
    }

    public Color[][] Go(List<Color> array, Color[][] map)
    {
        var centralPoint = new Point(Width / 2, Height / 2);

        var q = OrderRandomWalking(centralPoint).ToArray();

        Color[][] newMap = new Color[map.Length][];
        for (int i = 0; i < map.Length; i++)
        {
            newMap[i] = new Color[map[i].Length];
        }

        double pointsDone = 0;

        foreach (var p in q)
        {
            newMap[p.X][p.Y] = Closest(array, map[p.X][p.Y]);

            pointsDone++;

            if (ProgressCallback != null)
            {
                var percent = 100 * (pointsDone / (double)Area);

                var progressInfo = new ProgressInfo(new Pixel(p.X, p.Y, newMap[p.X][p.Y]), (int)percent);

                ProgressCallback(progressInfo);
            }
        }

        return newMap;
    }

    private int[][] GetCardinals()
    {
        int[] nn = new int[] { -1, +0 };
        // int[] ne = new int[] { -1, +1 };
        int[] ee = new int[] { +0, +1 };
        // int[] se = new int[] { +1, +1 };
        int[] ss = new int[] { +1, +0 };
        // int[] sw = new int[] { +1, -1 };
        int[] ww = new int[] { +0, -1 };
        // int[] nw = new int[] { -1, -1 };

        var dirs = new List<int[]>();

        dirs.Add(nn);
        // dirs.Add(ne);
        dirs.Add(ee);
        // dirs.Add(se);
        dirs.Add(ss);
        // dirs.Add(sw);
        dirs.Add(ww);
        // dirs.Add(nw);

        return dirs.ToArray();
    }

    private Color Closest(List<Color> array, Color c)
    {
        int closestIndex = -1;

        int bestD = int.MaxValue;

        int[] ds = new int[array.Count];
        Parallel.For(0, array.Count, (i, state) =>
        {
            ds[i] = Distance(array[i], c);

            if (ds[i] <= 50)
            {
                closestIndex = i;
                state.Break();
            }
            else if (bestD > ds[i])
            {
                bestD = ds[i];
                closestIndex = i;
            }
        });

        var closestColor = array[closestIndex];

        array.RemoveAt(closestIndex);

        return closestColor;
    }

    private int Distance(Color c1, Color c2)
    {
        var r = Math.Abs(c1.R - c2.R);
        var g = Math.Abs(c1.G - c2.G);
        var b = Math.Abs(c1.B - c2.B);
        var s = Math.Abs(c1.GetSaturation() - c1.GetSaturation());

        return (int)s + r + g + b;
    }

    private HashSet<Point> OrderRandomWalking(Point p)
    {
        var points = new HashSet<Point>();

        var dirs = GetCardinals();
        var dir = new int[] { 0, 0 };

        while (points.Count < Width * Height)
        {
            bool inWidthBound = p.X + dir[0] < Width && p.X + dir[0] >= 0;
            bool inHeightBound = p.Y + dir[1] < Height && p.Y + dir[1] >= 0;

            if (inWidthBound && inHeightBound)
            {
                p.X += dir[0];
                p.Y += dir[1];

                points.Add(p);
            }

            dir = dirs.Random(r);
        }

        return points;
    }

    private static Color[][] GetColorMap(Bitmap b1)
    {
        int hight = b1.Height;
        int width = b1.Width;

        Color[][] colorMatrix = new Color[width][];
        for (int i = 0; i < width; i++)
        {
            colorMatrix[i] = new Color[hight];
            for (int j = 0; j < hight; j++)
            {
                colorMatrix[i][j] = b1.GetPixel(i, j);
            }
        }
        return colorMatrix;
    }

    private List<Color> GetColorArray()
    {
        var map = GetColorMap(Pallete);

        List<Color> colors = new List<Color>();

        foreach (var line in map)
        {
            colors.AddRange(line);
        }

        return colors;
    }
}

2
พวกนี้ยอดเยี่ยมมาก พวกมันดูเหมือนภาพถ่ายที่ถูกไฟไหม้หรือทิ้งไว้ที่ไหนซักแห่งเน่า

ขอบคุณ A ทำหลายอัลกอริธึม แต่คำตอบอื่น ๆ ก็เหมือนกันมาก ดังนั้นฉันโพสต์ที่โดดเด่นมากขึ้น
RMalke

3

C #

เปรียบเทียบสีตามความห่างของสี เริ่มจากตรงกลาง

แก้ไข: อัปเดตตอนนี้ควรเร็วขึ้นประมาณ 1.5x

American Gothic
ป้อนคำอธิบายรูปภาพที่นี่
The Scream
ป้อนคำอธิบายรูปภาพที่นี่
Starry Night
ป้อนคำอธิบายรูปภาพที่นี่
Marbles
ป้อนคำอธิบายรูปภาพที่นี่
River
ป้อนคำอธิบายรูปภาพที่นี่
และนี่คือ Chevy สีเหลือง:
ป้อนคำอธิบายรูปภาพที่นี่

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Pixel
    {
        public int X = 0;
        public int Y = 0;
        public Color Color = new Color();
        public Pixel(int x, int y, Color clr)
        {
            Color = clr;
            X = x;
            Y = y;
        }
        public Pixel()
        {
        }
    }
    class Vector2
    {
        public int X = 0;
        public int Y = 0;
        public Vector2(int x, int y)
        {
            X = x;
            Y = y;
        }
        public Vector2()
        {
        }
        public double Diagonal()
        {
            return Math.Sqrt((X * X) + (Y * Y));
        }
    }
    class ColorCollection
    {
        Dictionary<Color, int> dict = new Dictionary<Color, int>();
        public ColorCollection()
        {
        }
        public void AddColor(Color color)
        {
            if (dict.ContainsKey(color))
            {
                dict[color]++;
                return;
            }
            dict.Add(color, 1);
        }
        public void UseColor(Color color)
        {
            if (dict.ContainsKey(color))
                dict[color]--;
            if (dict[color] < 1)
                dict.Remove(color);
        }
        public Color FindBestColor(Color color)
        {
            Color ret = dict.First().Key;
            int p = this.CalculateDifference(ret, color);
            foreach (KeyValuePair<Color, int> pair in dict)
            {
                int points = CalculateDifference(pair.Key, color);
                if (points < p)
                {
                    ret = pair.Key;
                    p = points;
                }
            }
            this.UseColor(ret);
            return ret;
        }
        int CalculateDifference(Color c1, Color c2)
        {
            int ret = 0;
            ret = ret + Math.Abs(c1.R - c2.R);
            ret = ret + Math.Abs(c1.G - c2.G);
            ret = ret + Math.Abs(c1.B - c2.B);
            return ret;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string img1 = "";
            string img2 = "";
            if (args.Length != 2)
            {
                Console.Write("Where is the first picture located? ");
                img1 = Console.ReadLine();
                Console.Write("Where is the second picture located? ");
                img2 = Console.ReadLine();
            }
            else
            {
                img1 = args[0];
                img2 = args[1];
            }
            Bitmap bmp1 = new Bitmap(img1);
            Bitmap bmp2 = new Bitmap(img2);
            Console.WriteLine("Getting colors....");
            ColorCollection colors = GetColors(bmp1);
            Console.WriteLine("Getting pixels....");
            List<Pixel> pixels = GetPixels(bmp2);
            int centerX = bmp2.Width / 2;
            int centerY = bmp2.Height / 2;
            pixels.Sort((p1, p2) =>
            {
                Vector2 p1_v = new Vector2(Math.Abs(p1.X - centerX), Math.Abs(p1.Y - centerY));
                Vector2 p2_v = new Vector2(Math.Abs(p2.X - centerX), Math.Abs(p2.Y - centerY));
                double d1 = p1_v.Diagonal();
                double d2 = p2_v.Diagonal();
                if (d1 > d2)
                    return 1;
                else if (d1 == d2)
                    return 0;
                return -1;
            });
            Console.WriteLine("Calculating...");
            int k = 0;
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < pixels.Count; i++)
            {
                if (i % 100 == 0 && i != 0)
                {
                    float percentage = ((float)i / (float)pixels.Count) * 100;
                    Console.WriteLine(percentage.ToString("0.00") + "% completed(" + i + "/" + pixels.Count + ")");
                    Console.SetCursorPosition(0, Console.CursorTop - 1);
                }
                Color set = colors.FindBestColor(pixels[i].Color);
                pixels[i].Color = set;
                k++;
            }
            sw.Stop();
            Console.WriteLine("Saving...");
            Bitmap result = WritePixelsToBitmap(pixels, bmp2.Width, bmp2.Height);
            result.Save(img1 + ".png");
            Console.WriteLine("Completed in " + sw.Elapsed.TotalSeconds + " seconds. Press a key to exit.");
            Console.ReadKey();
        }
        static Bitmap WritePixelsToBitmap(List<Pixel> pixels, int width, int height)
        {
            Bitmap bmp = new Bitmap(width, height);
            foreach (Pixel pixel in pixels)
            {
                bmp.SetPixel(pixel.X, pixel.Y, pixel.Color);
            }
            return bmp;
        }

        static ColorCollection GetColors(Bitmap bmp)
        {
            ColorCollection ret = new ColorCollection();
            for (int x = 0; x < bmp.Width; x++)
            {
                for (int y = 0; y < bmp.Height; y++)
                {
                    Color clr = bmp.GetPixel(x, y);
                    ret.AddColor(clr);
                }
            }
            return ret;
        }
        static List<Pixel> GetPixels(Bitmap bmp)
        {
            List<Pixel> ret = new List<Pixel>();
            for (int x = 0; x < bmp.Width; x++)
            {
                for (int y = 0; y < bmp.Height; y++)
                {
                    Color clr = bmp.GetPixel(x, y);
                    ret.Add(new Pixel(x, y, clr));
                }
            }
            return ret;
        }
    }
}

3

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

ฉันชอบผลลัพธ์บางอย่างจริงๆ!

American Gothic พร้อมจานสีของแม่น้ำ:

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

Mona Lisa พร้อมชุดแบบโกธิคอเมริกัน:

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

Mona Lisa พร้อมจานสีแม่น้ำ:

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

ฉันลอง 4x4 ด้วยและนี่คือรายการโปรดของฉัน!

Starry Night พร้อมชุด Scream:

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

Mona Lisa พร้อมชุดแบบโกธิคอเมริกัน:

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

The Scream with Mona Lisa palette:

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

American Gothic พร้อมจาน Mona Lisa:

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


1
กำลังคิดเกี่ยวกับการทำสิ่งเดียวกัน + คำนวณน้ำหนักพิกเซลตามบล็อกสี่เหลี่ยม ฉันชอบผล Mona Lisa มาก - พวกเขาทำให้ฉันนึกถึงภาพจากสิ่งต่าง ๆ ของภาพ คุณสามารถบล็อก 4x4 โดยบังเอิญได้ไหม?
eithed

1
@eithedog ฉันลอง 4x4 และมันก็ดูค่อนข้างดี ดูคำตอบที่อัปเดตของฉัน!
LVBen

3

C #

มันช้ามาก ๆ แต่มันก็ใช้งานได้ดีโดยเฉพาะเมื่อใช้จานทรงกลมแบบ raytraced

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

จาน Scream:

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

Mona Lisa palette:

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

จานสีโกธิคอเมริกัน:

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

จานสีแม่น้ำ:

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

จานสี Starry Night:

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

   class Program
   {
      class Pixel
      {
         public int x;
         public int y;
         public Color color;
         public Pixel(int x, int y, Color color)
         {
            this.x = x;
            this.y = y;
            this.color = color;
         }
      }

      static Pixel BaselineColor = new Pixel(0, 0, Color.FromArgb(0, 0, 0, 0));

      static void Main(string[] args)
      {
         string sourceDirectory = "pic" + args[0] + ".png";
         string paletteDirectory = "pic" + args[1] + ".png";

         using (Bitmap source = Bitmap.FromFile(sourceDirectory) as Bitmap)
         {
            List<Pixel> sourcePixels = GetPixels(source).ToList();
            LinkedList<Pixel> palettePixels;

            using (Bitmap palette = Bitmap.FromFile(paletteDirectory) as Bitmap)
            {
               palettePixels = GetPixels(palette) as LinkedList<Pixel>;
            }

            if (palettePixels.Count != sourcePixels.Count)
            {
               throw new Exception("OH NO!!!!!!!!");
            }

            sourcePixels.Sort((x, y) => GetDiff(y, BaselineColor) - GetDiff(x, BaselineColor));

            LinkedList<Pixel> newPixels = new LinkedList<Pixel>();
            foreach (Pixel p in sourcePixels)
            {
               Pixel newPixel = GetClosestColor(palettePixels, p);
               newPixels.AddLast(newPixel);
            }

            foreach (var p in newPixels)
            {
               source.SetPixel(p.x, p.y, p.color);
            }
            source.Save("Out" + args[0] + "to" + args[1] + ".png");
         }
      }

      private static IEnumerable<Pixel> GetPixels(Bitmap source)
      {
         List<Pixel> newList = new List<Pixel>();
         for (int x = 0; x < source.Width; x++)
         {
            for (int y = 0; y < source.Height; y++)
            {
               newList.Add(new Pixel(x, y, source.GetPixel(x, y)));
            }
         }
         return newList;
      }

      private static Pixel GetClosestColor(LinkedList<Pixel> palettePixels, Pixel p)
      {
         Pixel minPixel = palettePixels.First();
         int diff = GetDiff(minPixel, p);
         foreach (var pix in palettePixels)
         {
            int current = GetDiff(pix, p);
            if (current < diff)
            {
               diff = current;
               minPixel = pix;
               if (diff == 0)
               {
                  return minPixel;
               }
            }
         }
         palettePixels.Remove(minPixel);
         return new Pixel(p.x, p.y, minPixel.color);
      }

      private static int GetDiff(Pixel a, Pixel p)
      {
         return GetProx(a.color, p.color);
      }

      private static int GetProx(Color a, Color p)
      {
         int red = (a.R - p.R) * (a.R - p.R);
         int green = (a.G - p.G) * (a.G - p.G);
         int blue = (a.B - p.B) * (a.B - p.B);
         return red + blue + green;
      }
   }

3

Java - แนวทางการทำแผนที่อื่น

แก้ไข 1:หลังจากนั้นถูกแชร์ในสภาพแวดล้อม "คณิตศาสตร์" ใน G + เราทุกคนดูเหมือนจะใช้วิธีการจับคู่ด้วยวิธีการต่าง ๆ เพื่อหลีกเลี่ยงความซับซ้อน

แก้ไข 2:ฉันทำภาพในไดรฟ์ google และรีสตาร์ทดังนั้นลิงก์เก่าจึงไม่ทำงานอีกต่อไป ขออภัยฉันยังคงทำงานเกี่ยวกับชื่อเสียงมากขึ้นสำหรับการเชื่อมโยงเพิ่มเติม

แก้ไข 3:อ่านโพสต์อื่น ๆ ที่ฉันได้แรงบันดาลใจ ฉันได้โปรแกรมเร็วขึ้นและนำ CPU ไปลงทุนใหม่เพื่อทำการเปลี่ยนแปลงบางอย่างขึ้นอยู่กับตำแหน่งภาพเป้าหมาย

แก้ไข 4:เวอร์ชันโปรแกรมใหม่ ได้เร็วขึ้น! การรักษาพิเศษของทั้งสองพื้นที่ที่มีมุมคมและการเปลี่ยนแปลงที่ราบรื่นมาก (ช่วยได้มากกับการติดตามรังสี แต่ให้ดวงตาสีแดงเป็นครั้งคราวของ Mona Lisa)! ความสามารถในการสร้างเฟรมกลางจากภาพเคลื่อนไหว!

ฉันชอบแนวคิดนี้มากและการแก้ปัญหาของ Quincunx ทำให้ฉันรู้สึกทึ่ง ดังนั้นฉันคิดว่าฉันน่าจะเพิ่มค่าเงินยูโร 2 ยูโรได้

แนวคิดคือเราต้องการการแมป (ใกล้เคียง) ระหว่างสองชุดสี

ด้วยความคิดนี้ฉันใช้เวลาในคืนแรกพยายามที่จะใช้อัลกอริทึมการแต่งงานที่เสถียรเพื่อเรียกใช้อย่างรวดเร็วและด้วยหน่วยความจำของพีซีของฉันในผู้สมัคร 123520 คน ในขณะที่ฉันอยู่ในช่วงหน่วยความจำฉันพบว่าปัญหา runtime ไม่สามารถแก้ไขได้

คืนที่สองฉันตัดสินใจที่จะก้าวต่อไปและกระโดดลงสู่อัลกอริทึมของฮังการีซึ่งสัญญาว่าจะให้คุณสมบัติการประมาณเช่นระยะห่างขั้นต่ำระหว่างสีในภาพใดภาพหนึ่ง โชคดีที่ฉันพบว่า 3 พร้อมที่จะเสียบการใช้งาน Java ของสิ่งนี้ (ไม่นับการมอบหมายนักเรียนกึ่งสำเร็จรูปจำนวนมากซึ่งเริ่มทำให้ Google ยากสำหรับอัลกอริทึมพื้นฐาน) แต่อย่างที่เราคาดหวังไว้อัลกอริทึมของฮังการีก็ยิ่งแย่ลงในแง่ของเวลาในการทำงานและการใช้หน่วยความจำ ยิ่งกว่านั้นการใช้งานทั้ง 3 อย่างที่ฉันทดสอบได้ส่งคืนผลลัพธ์ที่ผิดพลาดเป็นครั้งคราว ฉันตัวสั่นเมื่อฉันนึกถึง programms อื่น ๆ ซึ่งอาจเป็นไปตามเหล่านี้

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

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

ดังนั้นนี่คือรหัส Java บางส่วนอาจมีลักษณะคล้ายกับรหัส Java อื่น ๆ ที่โพสต์ที่นี่ ชาวฮังการีที่ใช้เป็นรุ่นที่ได้รับการแก้ไขของ John Millers แต่เดิมในโครงการ ontologySimilariy นี่เป็นวิธีที่เร็วที่สุดที่ฉันพบและแสดงข้อบกพร่องที่น้อยที่สุด

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import javax.imageio.ImageIO;

/**
 *
 */
public class PixelRearranger {

    private final String mode;

    public PixelRearranger(String mode)
    {
        this.mode = mode;
    }

    public final static class Pixel {
        final BufferedImage img;
        final int val;
        final int r, g, b;
        final int x, y;

        public Pixel(BufferedImage img, int x, int y) {
            this.x = x;
            this.y = y;
            this.img = img;
            if ( img != null ) {
                val = img.getRGB(x,y);
                r = ((val & 0xFF0000) >> 16);
                g = ((val & 0x00FF00) >> 8);
                b = ((val & 0x0000FF));
            } else {
                val = r = g = b = 0;
            }
        }

        @Override
        public int hashCode() {
            return x + img.getWidth() * y + img.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if ( !(o instanceof Pixel) ) return false;
            Pixel p2 = (Pixel) o;
            return p2.x == x && p2.y == y && p2.img == img;
        }

        public double cd() {
            double x0 = 0.5 * (img.getWidth()-1);
            double y0 = 0.5 * (img.getHeight()-1);
            return Math.sqrt(Math.sqrt((x-x0)*(x-x0)/x0 + (y-y0)*(y-y0)/y0));
        }

        @Override
        public String toString() { return "P["+r+","+g+","+b+";"+x+":"+y+";"+img.getWidth()+":"+img.getHeight()+"]"; }
    }

    public final static class Pair
        implements Comparable<Pair>
    {   
        public Pixel palette, from;
        public double d;

        public Pair(Pixel palette, Pixel from)
        {
            this.palette = palette;
            this.from = from;
            this.d = distance(palette, from);
        }

        @Override
        public int compareTo(Pair e2)
        {
            return sgn(e2.d - d);
        }

        @Override
        public String toString() { return "E["+palette+from+";"+d+"]"; }
    }

    public static int sgn(double d) { return d > 0.0 ? +1 : d < 0.0 ? -1 : 0; }

    public final static int distance(Pixel p, Pixel q)
    {
        return 3*(p.r-q.r)*(p.r-q.r) + 6*(p.g-q.g)*(p.g-q.g) + (p.b-q.b)*(p.b-q.b);
    }

    public final static Comparator<Pixel> LUMOSITY_COMP = (p1,p2) -> 3*(p1.r-p2.r)+6*(p1.g-p2.g)+(p1.b-p2.b);


    public final static class ArrangementResult
    {
        private List<Pair> pairs;

        public ArrangementResult(List<Pair> pairs)
        {
            this.pairs = pairs;
        }

        /** Provide the output image */
        public BufferedImage finalImage()
        {
            BufferedImage target = pairs.get(0).from.img;
            BufferedImage res = new BufferedImage(target.getWidth(),
                target.getHeight(), BufferedImage.TYPE_INT_RGB);
            for(Pair p : pairs) {
                Pixel left = p.from;
                Pixel right = p.palette;
                res.setRGB(left.x, left.y, right.val);
            }
            return res;
        }

        /** Provide an interpolated image. 0 le;= alpha le;= 1 */
        public BufferedImage interpolateImage(double alpha)
        {
            BufferedImage target = pairs.get(0).from.img;
            int wt = target.getWidth(), ht = target.getHeight();
            BufferedImage palette = pairs.get(0).palette.img;
            int wp = palette.getWidth(), hp = palette.getHeight();
            int w = Math.max(wt, wp), h = Math.max(ht, hp);
            BufferedImage res = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            int x0t = (w-wt)/2, y0t = (h-ht)/2;
            int x0p = (w-wp)/2, y0p = (h-hp)/2;
            double a0 = (3.0 - 2.0*alpha)*alpha*alpha;
            double a1 = 1.0 - a0;
            for(Pair p : pairs) {
                Pixel left = p.from;
                Pixel right = p.palette;
                int x = (int) (a1 * (right.x + x0p) + a0 * (left.x + x0t));
                int y = (int) (a1 * (right.y + y0p) + a0 * (left.y + y0t));
                if ( x < 0 || x >= w ) System.out.println("x="+x+", w="+w+", alpha="+alpha);
                if ( y < 0 || y >= h ) System.out.println("y="+y+", h="+h+", alpha="+alpha);
                res.setRGB(x, y, right.val);
            }
            return res;
        }
    }

    public ArrangementResult rearrange(BufferedImage target, BufferedImage palette)
    {
        List<Pixel> targetPixels = getColors(target);
        int n = targetPixels.size();
        System.out.println("total Pixels "+n);
        Collections.sort(targetPixels, LUMOSITY_COMP);

        final double[][] energy = energy(target);

        List<Pixel> palettePixels = getColors(palette);
        Collections.sort(palettePixels, LUMOSITY_COMP);

        ArrayList<Pair> pairs = new ArrayList<>(n);
        for(int i = 0; i < n; i++) {
            Pixel pal = palettePixels.get(i);
            Pixel to = targetPixels.get(i);
            pairs.add(new Pair(pal, to));
        }
        correct(pairs, (p1,p2) -> sgn(p2.d*p2.from.b - p1.d*p1.from.b));
        correct(pairs, (p1,p2) -> sgn(p2.d*p2.from.r - p1.d*p1.from.r));
        // generates visible circular artifacts: correct(pairs, (p1,p2) -> sgn(p2.d*p2.from.cd() - p1.d*p1.from.cd()));
        correct(pairs, (p1,p2) -> sgn(energy[p2.from.x][p2.from.y]*p2.d - energy[p1.from.x][p1.from.y]*p1.d));
        correct(pairs, (p1,p2) -> sgn(p2.d/(1+energy[p2.from.x][p2.from.y]) - p1.d/(1+energy[p1.from.x][p1.from.y])));
        // correct(pairs, null);
        return new ArrangementResult(pairs);

    }

    /**
     * derive an energy map, to detect areas of lots of change.
     */
    public double[][] energy(BufferedImage img)
    {
        int n = img.getWidth();
        int m = img.getHeight();
        double[][] res = new double[n][m];
        for(int x = 0; x < n; x++) {
            for(int y = 0; y < m; y++) {
                int rgb0 = img.getRGB(x,y);
                int count = 0, sum = 0;
                if ( x > 0 ) {
                    count++; sum += dist(rgb0, img.getRGB(x-1,y));
                    if ( y > 0 ) { count++; sum += dist(rgb0, img.getRGB(x-1,y-1)); }
                    if ( y < m-1 ) { count++; sum += dist(rgb0, img.getRGB(x-1,y+1)); }
                }
                if ( x < n-1 ) {
                    count++; sum += dist(rgb0, img.getRGB(x+1,y));
                    if ( y > 0 ) { count++; sum += dist(rgb0, img.getRGB(x+1,y-1)); }
                    if ( y < m-1 ) { count++; sum += dist(rgb0, img.getRGB(x+1,y+1)); }
                }
                if ( y > 0 ) { count++; sum += dist(rgb0, img.getRGB(x,y-1)); }
                if ( y < m-1 ) { count++; sum += dist(rgb0, img.getRGB(x,y+1)); }
                res[x][y] = Math.sqrt((double)sum/count);
            }
        }
        return res;
    }

    public int dist(int rgb0, int rgb1) {
        int r0 = ((rgb0 & 0xFF0000) >> 16);
        int g0 = ((rgb0 & 0x00FF00) >> 8);
        int b0 = ((rgb0 & 0x0000FF));
        int r1 = ((rgb1 & 0xFF0000) >> 16);
        int g1 = ((rgb1 & 0x00FF00) >> 8);
        int b1 = ((rgb1 & 0x0000FF));
        return 3*(r0-r1)*(r0-r1) + 6*(g0-g1)*(g0-g1) + (b0-b1)*(b0-b1);
    }

    private void correct(ArrayList<Pair> pairs, Comparator<Pair> comp)
    {
        Collections.sort(pairs, comp);
        int n = pairs.size();
        int limit = Math.min(n, 133); // n / 1000;
        int limit2 = Math.max(1, n / 3 - limit);
        int step = (2*limit + 2)/3;
        for(int base = 0; base < limit2; base += step ) {
            List<Pixel> list1 = new ArrayList<>();
            List<Pixel> list2 = new ArrayList<>();
            for(int i = base; i < base+limit; i++) {
                list1.add(pairs.get(i).from);
                list2.add(pairs.get(i).palette);
            }
            Map<Pixel, Pixel> connection = rematch(list1, list2);
            int i = base;
            for(Pixel p : connection.keySet()) {
                pairs.set(i++, new Pair(p, connection.get(p)));
            }
        }
    }

    /**
     * Glue code to do an hungarian algorithm distance optimization.
     */
    public Map<Pixel,Pixel> rematch(List<Pixel> liste1, List<Pixel> liste2)
    {
        int n = liste1.size();
        double[][] cost = new double[n][n];
        Set<Pixel> s1 = new HashSet<>(n);
        Set<Pixel> s2 = new HashSet<>(n);
        for(int i = 0; i < n; i++) {
            Pixel ii = liste1.get(i);
            for(int j = 0; j < n; j++) {
                Pixel ij = liste2.get(j);
                cost[i][j] = -distance(ii,ij);
            }
        }
        Map<Pixel,Pixel> res = new HashMap<>();
        int[] resArray = Hungarian.hungarian(cost);
        for(int i = 0; i < resArray.length; i++) {
            Pixel ii = liste1.get(i);
            Pixel ij = liste2.get(resArray[i]);
            res.put(ij, ii);
        }
        return res;
    }

    public static List<Pixel> getColors(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();
        List<Pixel> colors = new ArrayList<>(width * height);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                colors.add(new Pixel(img, x, y));
            }
        }
        return colors;
    }

    public static List<Integer> getSortedTrueColors(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();
        List<Integer> colors = new ArrayList<>(width * height);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                colors.add(img.getRGB(x, y));
            }
        }
        Collections.sort(colors);
        return colors;
    }

    public static void main(String[] args) throws Exception {
        int i = 0;
        String mode = args[i++];
        PixelRearranger pr = new PixelRearranger(mode);
        String a1 = args[i++];
        File in1 = new File(a1);
        String a2 = args[i++];
        File in2 = new File(a2);
        File out = new File(args[i++]);
        //
        BufferedImage target = ImageIO.read(in1);
        BufferedImage palette = ImageIO.read(in2);
        long t0 = System.currentTimeMillis();
        ArrangementResult result = pr.rearrange(target, palette);
        BufferedImage resultImg = result.finalImage();
        long t1 = System.currentTimeMillis();
        System.out.println("took "+0.001*(t1-t0)+" s");
        ImageIO.write(resultImg, "png", out);
        // Check validity
        List<Integer> paletteColors = getSortedTrueColors(palette);
        List<Integer> resultColors = getSortedTrueColors(resultImg);
        System.out.println("validate="+paletteColors.equals(resultColors));
        // In Mode A we do some animation!
        if ( "A".equals(mode) ) {
            for(int j = 0; j <= 50; j++) {
                BufferedImage stepImg = result.interpolateImage(0.02 * j);
                File oa = new File(String.format("anim/%s-%s-%02d.png", a1, a2, j));
                ImageIO.write(stepImg, "png", oa);
            }
        }
    }
}

เวลาในการทำงานปัจจุบันคือ 20 ถึง 30 วินาทีต่อคู่ภาพด้านบน แต่มีการปรับแต่งมากมายเพื่อให้เร็วขึ้นหรืออาจได้คุณภาพที่ดีขึ้นเล็กน้อย

ดูเหมือนว่าชื่อเสียงของมือใหม่ของฉันไม่พอสำหรับลิงก์ / รูปภาพจำนวนมากดังนั้นนี่คือทางลัดไปยังโฟลเดอร์ Google ไดรฟ์ของฉันสำหรับตัวอย่างรูปภาพ: http://goo.gl/qZHTao

ตัวอย่างที่ฉันต้องการแสดงก่อน:

ผู้คน -> Mona Lisa http://goo.gl/mGvq9h

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.