Amerikanische Gotik in der Palette von Mona Lisa: Ordne die Pixel neu an


377

Sie erhalten zwei Echtfarbenbilder, die Quelle und die Palette. Sie müssen nicht unbedingt die gleichen Abmessungen haben, aber es wird garantiert, dass ihre Flächen gleich sind, dh sie haben die gleiche Anzahl von Pixeln.

Ihre Aufgabe ist es, einen Algorithmus zu erstellen, der die genaueste Kopie der Quelle erstellt, indem nur die Pixel in der Palette verwendet werden. Jedes Pixel in der Palette muss in dieser Kopie genau einmal an einer eindeutigen Position verwendet werden. Die Kopie muss die gleichen Abmessungen wie die Quelle haben.

Dieses Python-Skript kann verwendet werden, um sicherzustellen, dass die folgenden Bedingungen erfüllt sind:

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')

Hier sind einige Bilder zum Testen. Sie haben alle die gleiche Fläche. Ihr Algorithmus sollte für zwei beliebige Bilder mit gleichen Flächen funktionieren, nicht nur für die amerikanische Gotik und die Mona Lisa. Sie sollten natürlich Ihre Ausgabe zeigen.

Amerikanische Gotik Mona Lisa Sternenklare Nacht Der Schrei Fluss Regenbogen

Dank an Wikipedia für die Bilder berühmter Gemälde.

Wertung

Dies ist ein Beliebtheitswettbewerb, bei dem die Antwort mit der höchsten Stimmenzahl gewinnt. Aber ich bin mir sicher, dass es viele Möglichkeiten gibt, damit kreativ zu sein!

Animation

millinon hatte die idee, dass es cool wäre zu sehen, wie sich die pixel neu ordnen. Das habe ich auch gedacht, also habe ich dieses Python-Skript geschrieben, das zwei Bilder mit den gleichen Farben nimmt und die Zwischenbilder dazwischen zeichnet. Update: Ich habe es gerade überarbeitet, damit sich jedes Pixel so weit wie möglich bewegt. Es ist nicht mehr zufällig.

Zuerst verwandelt sich die Mona Lisa in Aditsus amerikanische Gotik. Als nächstes verwandelt sich Bitpwners amerikanische Gotik (von Mona Lisa) in Aditsus. Es ist erstaunlich, dass die beiden Versionen genau dieselbe Farbpalette haben.

Mona Lisa zur amerikanischen Gothic Animation Animation zwischen zwei Versionen der amerikanischen Gotik von Mona Lisa

Die Ergebnisse sind wirklich erstaunlich. Hier ist Aditsus Regenbogen Mona Lisa (verlangsamt, um Details zu zeigen).

Regenbogenkugeln zur Animation von Mona Lisa

Diese letzte Animation ist nicht unbedingt mit dem Wettbewerb verbunden. Es zeigt, was passiert, wenn mein Skript verwendet wird, um ein Bild um 90 Grad zu drehen.

Animation der Baumrotation


22
Um die Anzahl der Treffer auf Ihre Frage zu erhöhen, sollten Sie in Betracht ziehen, "American Gothic in der Palette von Mona Lisa: Die Pixel neu
anordnen

14
Hallo, ich möchte dir nur zu dieser originellen Herausforderung gratulieren! Sehr erfrischend und interessant.
Bolov

6
Ich bin froh, dass dies kein [Code-Golf] ist.
Ming-Tang

13
Jedes Mal, wenn ich diese Seite besuche, wird mein mobiles Datenlimit fürchterlich verbrannt.
Vectorized

Antworten:


159

Java - GUI mit progressiver randomisierter Transformation

Ich habe VIELE Dinge ausprobiert, von denen einige sehr kompliziert sind, und bin dann zu diesem relativ einfachen Code zurückgekehrt:

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();
    }
}

Alle relevanten Parameter werden zu Beginn der Klasse als Konstanten definiert.

Das Programm kopiert zuerst das Palettenbild in die Quellabmessungen, wählt dann wiederholt 2 zufällige Pixel aus und tauscht sie aus, wenn sie dadurch näher am Quellbild wären. "Näher" wird mithilfe einer Farbabstandsfunktion definiert, die die Differenz zwischen den Komponenten r, g, b (luma-gewichtet) zusammen mit der gesamten Luma-Differenz berechnet, wobei die Luma-Differenz höher gewichtet wird.

Es dauert nur wenige Sekunden, bis sich die Formen bilden, aber eine Weile, bis die Farben zusammenkommen. Sie können das aktuelle Bild jederzeit speichern. Normalerweise habe ich 1-3 Minuten gewartet, bevor ich gespeichert habe.

Ergebnisse:

Im Gegensatz zu einigen anderen Antworten wurden diese Bilder alle mit genau den gleichen Parametern (außer den Dateinamen) erstellt.

Amerikanische gotische Palette

mona-gotisch schrei-gotisch

Mona Lisa-Palette

Gothic-Mona schrei mona Sphären-Mona

Sternennacht-Palette

Mona-Nacht Schreienacht Sphären-Nacht

Die Scream-Palette

Gothic-Schrei Monaschrei Nachtschrei Kugelschrei

Sphären-Palette

Ich denke, dies ist der härteste Test und jeder sollte seine Ergebnisse mit dieser Palette veröffentlichen:

gotische kugeln Mona-Kugeln Schrei-Kugeln

Tut mir leid, ich fand das Flussbild nicht sehr interessant, daher habe ich es nicht aufgenommen.

Ich habe auch ein Video unter https://www.youtube.com/watch?v=_-w3cKL5teM hinzugefügt , das zeigt, was das Programm macht (nicht genau in Echtzeit, aber ähnlich), dann zeigt es die schrittweise Pixelbewegung mit Calvins Python Skript. Leider wird die Videoqualität durch die Kodierung / Komprimierung von YouTube erheblich beeinträchtigt.


2
@ Quincunx Und ich rufe auch nicht invokeLater an, erschieß mich: p Auch danke :)
aditsu

16
Beste Antwort bisher ...
Yuval Filmus

8
Wenn Sie Zweifel haben, zwingen Sie es brutal? Scheint eine hervorragende Lösung zu sein, ich würde gerne eine Animation dafür sehen, vielleicht sogar ein Video anstelle eines Gifs.
Lilienthal

3
Sie könnten den Algorithmus ein wenig auf ein vollständig simuliertes Tempern erweitern, um eine kleine Verbesserung zu erzielen. Was Sie tun, ist bereits sehr nah (aber es ist gierig). Die Permutation zu finden, die den Abstand minimiert, scheint ein schwieriges Optimierungsproblem zu sein, daher ist diese Art von Heuristik geeignet. @Lilienthal das ist kein brachiales Zwingen, es ist eigentlich in der Nähe von häufig verwendeten Optimierungstechniken.
Szabolcs

3
Dieser Algorithmus hat bei weitem die besten Ergebnisse. Und es ist so einfach. Das macht es für mich zu einem klaren Gewinner.
Leif

118

Java

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;
    }
}

Mein Ansatz funktioniert, indem ich die Farbe im 3-Raum finde, die jedem Pixel am nächsten kommt (na ja, wahrscheinlich am nächsten), da die Farben 3D sind.

Dies funktioniert, indem eine Liste aller Punkte erstellt wird, die gefüllt werden müssen, und eine Liste aller möglichen Farben, die wir verwenden können. Wir sortieren die Liste der Punkte nach dem Zufallsprinzip (damit das Bild besser wird), gehen dann jeden Punkt durch und erhalten die Farbe des Quellbilds.

Update: Früher habe ich einfach binär gesucht, also passte Rot besser als Grün, was besser passte als Blau. Ich habe es jetzt geändert, um sechs binäre Suchen durchzuführen (alle möglichen Permutationen) und dann die nächstgelegene Farbe zu wählen. Es dauert nur ca. 6-mal so lange (dh 1 Minute). Während die Bilder noch körnig sind, stimmen die Farben besser überein.

Update 2: Ich verändere die Liste nicht mehr nach dem Zufallsprinzip. Stattdessen wähle ich 4 Punkte nach der Drittelregel aus und ordne die Punkte dann zufällig an, wobei ich es vorziehe, die Mitte auszufüllen.

Hinweis: Sehen Sie sich den Änderungsverlauf für die alten Bilder an.

Mona Lisa -> Fluss:

Bildbeschreibung hier eingeben

Mona Lisa -> Amerikanische Gotik:

Bildbeschreibung hier eingeben

Mona Lisa -> Raytraced Spheres:

Bildbeschreibung hier eingeben

Sternennacht -> Mona Lisa:

Bildbeschreibung hier eingeben


Hier ist ein animiertes GIF, das zeigt, wie das Bild erstellt wurde:

Bildbeschreibung hier eingeben

Und zeigt die Pixel, die von der Mona Lisa aufgenommen wurden:

Bildbeschreibung hier eingeben


11
Das ist verdammt erstaunlich. Ich hätte es nicht für möglich gehalten.
AndoDaan

6
Ich bezweifle, dass dies trivial wäre, aber es wäre erstaunlich, eine animierte Version zu erstellen, die die Pixel zeigt, die sich vom Originalbild in das endgültige Bild verlagern.
Millinon

2
Ich denke, Sie haben das Problem falsch verstanden. Sie müssen die Pixel in der Palette neu anordnen, um die Kopie zu erstellen, und nicht einfach Farben aus der Palette verwenden. Jede einzelne Farbe muss in der Kopie genauso oft verwendet werden, wie sie in der Palette vorkommt. Ihre Bilder passen nicht zu meinem Skript.
Calvins Hobbys

7
@Quincunx Wie sich herausstellte, war mein Skript korrekt (obwohl ich es für die Nachwelt vereinfacht habe), und Ihr Programm auch. Aus Gründen, bei denen ich mir nicht sicher bin, ob sich das Bild von Mona Lisa beim Hochladen geringfügig geändert hat. Mir ist aufgefallen, dass das Pixel bei (177, 377) einen RGB-Wert von (0, 0, 16) online und (0, 0, 14) auf meinem Heimcomputer aufweist. Ich habe die JPEGs durch PNGs ersetzt, um Probleme mit einem verlustbehafteten Dateityp zu vermeiden. Die Pixeldaten in den Bildern sollten sich nicht geändert haben, es kann jedoch sinnvoll sein, die Bilder erneut herunterzuladen.
Calvins Hobbys

8
Dies sollte nicht die beliebteste Antwort sein. Der Algorithmus ist unnötig kompliziert und die Ergebnisse sind schlecht, obwohl sie interessant aussehen. Vergleichen Sie die Transformation von Mona Lisa zu Raytracing-Kugeln mit dem Ergebnis von arditsu
Leif

97

Perl, mit Lab-Farbraum und Dithering

Hinweis: Jetzt habe ich auch eine C-Lösung .

Verwendet einen ähnlichen Ansatz wie bei Aditsu (wählen Sie zwei zufällige Positionen und tauschen Sie die Pixel an diesen Positionen aus, wenn das Bild dem Zielbild ähnlicher werden würde), mit zwei wesentlichen Verbesserungen:

  1. Verwendet den CIE L a b * -Farbraum zum Vergleichen von Farben - Die euklidische Metrik in diesem Raum ist eine sehr gute Annäherung an den wahrgenommenen Unterschied zwischen zwei Farben, daher sollten die Farbzuordnungen genauer sein als RGB oder sogar HSV / HSL.
  2. Nach einem ersten Durchlauf, bei dem die Pixel an der bestmöglichen Einzelposition platziert werden, wird ein zusätzlicher Durchlauf mit einem zufälligen Dither durchgeführt. Anstatt die Pixelwerte an den beiden Auslagerungspositionen zu vergleichen, wird der durchschnittliche Pixelwert einer 3 × 3-Nachbarschaft berechnet, die an den Auslagerungspositionen zentriert ist. Wenn ein Tausch die durchschnittlichen Farben der Nachbarschaften verbessert, ist dies zulässig, auch wenn einzelne Pixel dadurch ungenauer werden. Für einige Bildpaare hat dies einen zweifelhaften Effekt auf die Qualität (und macht den Paletteneffekt weniger auffällig), aber für einige (wie Kugeln -> alles) hilft es ziemlich viel. Der "Detail" -Faktor hebt das zentrale Pixel in unterschiedlichem Maße hervor. Durch Erhöhen des Werts wird das Dithering insgesamt verringert, das Zielbild erhält jedoch feinere Details. Die Dither-Optimierung ist langsamer.

Das Mitteln von Lab-Werten ist, wie das Zittern, nicht wirklich gerechtfertigt (sie sollten in XYZ konvertiert, gemittelt und zurückkonvertiert werden), funktioniert jedoch für diese Zwecke einwandfrei.

Diese Bilder haben Abbruchgrenzen von 100 und 200 (beenden Sie die erste Phase, wenn weniger als 1 von 5000 Tauschvorgängen akzeptiert wird, und die zweite Phase bei 1 von 2500) und einen Dithering-Detailfaktor von 12 (etwas stärkeres Dithering als der vorherige Satz) ). Bei dieser Einstellung mit super hoher Qualität dauert die Erstellung der Bilder sehr lange, aber mit der Parallelisierung ist der gesamte Auftrag auf meiner 6-Kern-Box noch innerhalb einer Stunde erledigt. Wenn die Werte auf etwa 500 erhöht werden, werden die Bilder innerhalb weniger Minuten fertiggestellt, und sie sehen nur etwas weniger poliert aus. Ich wollte hier den Algorithmus am besten zur Geltung bringen.

Code ist keineswegs hübsch:

#!/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);
  }
}

Ergebnisse

Amerikanische gotische Palette

Kleiner Unterschied hier mit oder ohne Dithering.

Mona Lisa-Palette

Dithering reduziert die Streifenbildung auf den Kugeln, ist aber nicht besonders hübsch.

Sternennacht-Palette

Mona Lisa behält ein bisschen mehr Details beim Dithering bei. Spheres ist ungefähr die gleiche Situation wie beim letzten Mal.

Schrei-Palette

Sternennacht ohne Dithering ist das Genialste, was es je gab. Durch das Dithering wird das Bild genauer, aber weitaus weniger interessant.

Sphären-Palette

Wie Aditsu sagt, der wahre Test. Ich glaube, ich gehe vorbei.

Dithering hilft immens bei der amerikanischen Gotik und Mona Lisa, indem einige Grautöne und andere Farben mit den intensiveren Pixeln gemischt werden, um halbgenaue Hauttöne anstelle von schrecklichen Flecken zu erzeugen. Der Schrei ist weit weniger betroffen.

Camaro - Mustang

Quellbilder aus dem Beitrag von flawr.

Camaro:

Mustang:

Camaro-Palette

Sieht ohne Dither ziemlich gut aus.

Ein "enges" Zittern (gleicher Detailfaktor wie oben) ändert nicht viel, fügt nur ein kleines Detail in die Highlights auf der Motorhaube und dem Dach ein.

Ein "loses" Dithering (Detailfaktor auf 6 gesunken) glättet die Tonalität wirklich und viel mehr Details sind durch die Windschutzscheibe sichtbar, aber Ditherng-Muster sind überall offensichtlicher.

Mustang-Palette

Teile des Autos sehen toll aus, aber die grauen Pixel sehen unordentlich aus. Was noch schlimmer ist, alle dunkleren gelben Pixel wurden über den roten Camaro-Körper verteilt, und der Algorithmus ohne Dithering kann nichts mit den helleren zu tun haben (wenn sie in das Auto geschoben werden, würde sich das Spiel verschlechtern und sie auf einen anderen verschieben) Fleck auf dem Hintergrund macht keinen Nettodifferenz), also gibt es einen Geister-Mustang im Hintergrund.

Durch das Dithering können diese zusätzlichen gelben Pixel so verteilt werden, dass sie sich nicht berühren, und dabei mehr oder weniger gleichmäßig über den Hintergrund verteilt werden. Die Lichter und Schatten auf dem Auto sehen etwas besser aus.

Auch hier weist der lose Dither die gleichmäßigste Tonalität auf und zeigt mehr Details an den Scheinwerfern und der Windschutzscheibe. Das Auto sieht fast wieder rot aus. Hintergrund ist aus irgendeinem Grund klumpiger. Ich bin mir nicht sicher, ob es mir gefällt.

Video

( HQ Link )


3
Ich mag dieses sehr, die stark verwackelten Bilder wirken wunderbar pointillistisch . Seurat macht Mona Lisa jemand?
Boris die Spinne

2
Ihr Algorithmus leistet auf jeden Fall hervorragende Arbeit mit der schrecklichen Spheres-Palette, gute Arbeit!
Snowbody

1
@hobbs Fantastische Nutzung der Regenbogen-Palette, und Ihre Autos sind fast perfekt! Wäre es in Ordnung, wenn ich einige Ihrer Bilder in einem YouTube-Video verwenden würde, um mein Animationsskript zu präsentieren?
Calvins Hobbys

1
Ich denke, der einzige Grund, warum Ihr Dithering dieses Muster ergibt, ist, dass Sie einen 3x3-Pixelblock verwenden, dessen Gewicht nur für die Mitte geändert wurde. Wenn Sie die Pixel nach dem Abstand von der Mitte gewichtet haben (sodass die Eckpixel weniger als die 4 benachbarten Pixel beitragen) und möglicherweise auf etwas mehr Pixel ausgedehnt haben, sollte das Dithering weniger auffällig sein. Es ist bereits eine so große Verbesserung für die Regenbogen-Palette, dass es sich lohnen könnte zu sehen, was noch alles möglich ist ...
Trichoplax

1
@githubphagocyte Ich habe einen halben Tag damit verbracht, solche Sachen auszuprobieren, aber nichts davon hat geklappt, wie ich wollte. Eine Variante lieferte einen sehr schönen, zufällig aussehenden Dither, gab mir aber auch eine Optimierungsphase, die nie beendet wurde. Andere Varianten hatten entweder schlechtere Artefakte oder zu starkes Dithering. Meine C-Lösung bietet dank der Spline-Interpolation von ImageMagick jedoch ein besseres Dithering. Da es sich um einen kubischen Spline handelt, wird meiner Meinung nach ein 5x5-Viertel verwendet.
Hobbs

79

Python

Die Idee ist einfach: Jedes Pixel hat einen Punkt im 3D-RGB-Raum. Das Ziel besteht darin, jeweils ein Pixel des Quell- und eines des Zielbilds abzugleichen. Vorzugsweise sollten sie "nah" sein (die "gleiche" Farbe darstellen). Da sie auf sehr unterschiedliche Weise verteilt werden können, können wir nicht einfach den nächsten Nachbarn finden.

Strategie

Sei neine ganze Zahl (klein, 3-255 oder so). Nun wird die Pixelwolke im RGB-Raum nach der ersten Achse (R) sortiert. Diese Gruppe von Pixeln ist jetzt in n Partitionen unterteilt. Jede der Partitionen ist nun entlang der zweiten Achse (B) sortiert, die wiederum auf die gleiche Weise partitioniert ist. Wir machen das mit beiden Bildern und haben nun für beide ein Array der Punkte. Jetzt können wir die Pixel nur noch an ihrer Position im Array abgleichen, da ein Pixel an derselben Position in jedem Array eine ähnliche Position relativ zu jeder Pixelcloud im RGB-Raum hat.

Wenn die Verteilung der Pixel im RGB-Raum der beiden Bilder ähnlich ist (dh nur verschoben und / oder gestreckt entlang der 3-Achse), ist das Ergebnis ziemlich vorhersehbar. Wenn die Verteilungen völlig anders aussehen, liefert dieser Algorithmus nicht so gute Ergebnisse (wie im letzten Beispiel gezeigt), aber dies ist auch einer der schwierigeren Fälle, die ich denke. Was es nicht tut, ist die Verwendung von Effekten der Interaktion benachbarter Pixel in der Wahrnehmung.

Code

Haftungsausschluss: Ich bin ein absoluter Neuling in Python.

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")

Ergebnis

Ich denke, es stellte sich angesichts der einfachen Lösung als nicht schlecht heraus. Sie können natürlich bessere Ergebnisse erzielen, wenn Sie mit dem Parameter herumspielen, die Farben zuerst in einen anderen Farbraum transformieren oder sogar die Partitionierung optimieren.

Vergleich meiner Ergebnisse

Vollständige Galerie hier: https://imgur.com/a/hzaAm#6

Detailliert für River

monalisa> fluss

monalisa> fluss

Menschen> Fluss

Menschen> Fluss

Bälle> Fluss

Bälle> Fluss

Sternennacht> Fluss

Nocturne> Fluss

der Schrei Fluss

thecry> river

Bälle> MonaLisa, variierend n = 2,4,6, ..., 20

Dies war meiner Meinung nach die herausforderndste Aufgabe, weit entfernt von schönen Bildern, hier ein GIF (musste auf 256 Farben reduziert werden) der verschiedenen Parameterwerte n = 2,4,6, ..., 20. Für mich war es überraschend, dass sehr niedrige Werte bessere Bilder hervorbringen (wenn man Madame Lisa ins Gesicht schaut): Bälle> monalisa

Entschuldigung, ich kann nicht aufhören

Welches magst du lieber? Chevy Camaro oder Ford Mustang? Vielleicht könnte diese Technik verbessert und zum Ausmalen von s / w-Bildern verwendet werden. Jetzt hier: Zuerst schneide ich die Autos grob aus dem Hintergrund, indem ich sie weiß male (in Farbe, nicht sehr professionell ...) und benutze dann das Python-Programm in jede Richtung.

Originale

Original Original

Umgefärbt

Es gibt einige Artefakte, ich denke, weil die Fläche eines Autos etwas größer war als die des anderen und weil meine künstlerischen Fähigkeiten ziemlich schlecht sind =) manipuliert Bildbeschreibung hier eingeben


5
Wow, ich liebe den Fluss Starry Night wirklich und wie The Scream ihn wie einen Feuerfluss aussehen lässt.
Calvins Hobbys

@ Calvin'sHobbies wow ja! Sie sehen beinahe gezeichnet aus, ich habe sie mir nicht einmal genau angesehen, da ich gerade damit beschäftigt war, die neuen Bilder hochzuladen = P Aber danke für diese großartige Herausforderung!
Fehler

3
Ich liebe die Autotransformationen. Dies könnte einmal eine Art Bildbearbeitungstransformation werden, wirklich!
Tomsmeding

@tomsmeding Vielen Dank, ich habe bereits darüber nachgedacht, die Technik zum Einfärben von Schwarzweißbildern zu verwenden, aber bisher nur mit begrenztem Erfolg. Aber vielleicht brauchen wir noch ein paar Ideen, um das zu erledigen =)
flawr

@flawr Wäre es in Ordnung, wenn ich einige Ihrer Bilder in einem YouTube-Video verwenden würde, um mein Animationsskript zu präsentieren?
Calvins Hobbys

48

Python - Eine theoretisch optimale Lösung

Ich sage theoretisch optimal, weil die wirklich optimale Lösung nicht ganz berechenbar ist. Zunächst beschreibe ich die theoretische Lösung und erkläre dann, wie ich sie optimiert habe, um sie räumlich und zeitlich rechnerisch realisierbar zu machen.

Ich betrachte die optimalste Lösung als diejenige, die den niedrigsten Gesamtfehler über alle Pixel zwischen dem alten und dem neuen Bild ergibt. Der Fehler zwischen zwei Pixeln ist definiert als der euklidische Abstand zwischen den Punkten im 3D-Raum, an denen jeder Farbwert (R, G, B) eine Koordinate ist. In der Praxis ist die optimale Lösung aufgrund der Sichtweise des Menschen möglicherweise nicht die am besten aussehende Lösung. Es scheint jedoch in allen Fällen ziemlich gut zu funktionieren.

Um die Abbildung zu berechnen, betrachtete ich dies als ein zweigliedriges Matching-Problem mit minimalem Gewicht . Mit anderen Worten, es gibt zwei Knotensätze: die Originalpixel und die Palettenpixel. Zwischen jedem Pixel wird über die beiden Sätze hinweg eine Kante erstellt (innerhalb eines Satzes werden jedoch keine Kanten erstellt). Die Kosten oder das Gewicht einer Kante sind der euklidische Abstand zwischen den beiden Pixeln, wie oben beschrieben. Je näher zwei Farben optisch sind, desto geringer sind die Kosten zwischen den Pixeln.

Beispiel für ein zweiteiliges Matching

Dies erzeugt eine Kostenmatrix der Größe N 2 . Für diese Images mit N = 123520 sind ca. 40 GB Arbeitsspeicher erforderlich, um die Kosten als Ganzzahlen und die Hälfte als kurze Ganzzahlen darzustellen. In beiden Fällen verfügte mein Computer nicht über genügend Speicher, um einen Versuch zu starten. Ein weiteres Problem ist, dass der ungarische Algorithmus oder der Jonker-Volgenant-Algorithmus , der zur Lösung dieses Problems verwendet werden kann, in N 3 -Zeit ausgeführt wird. Obwohl definitiv berechenbar, hätte die Erstellung einer Lösung pro Bild wahrscheinlich Stunden oder Tage gedauert.

Um dieses Problem zu umgehen, sortiere ich beide Pixellisten nach dem Zufallsprinzip, teile die Listen in C -Blöcke auf, führe eine C ++ - Implementierung des Jonker-Volgenant-Algorithmus für jedes Unterlistenpaar aus und füge die Listen dann wieder zusammen, um die endgültige Zuordnung zu erstellen. Der folgende Code würde es daher ermöglichen, die wirklich optimale Lösung zu finden, vorausgesetzt, sie setzen die Blockgröße C auf 1 (kein Blockieren) und verfügen über genügend Speicher. Für diese Bilder setze ich C auf 16, so dass N zu 7720 wird, was nur einige Minuten pro Bild dauert.

Eine einfache Möglichkeit, sich vorzustellen, warum dies funktioniert, besteht darin, die Liste der Pixel nach dem Zufallsprinzip zu sortieren und dann eine Teilmenge davon zu nehmen, als würde man das Bild abtasten. Wenn Sie also C = 16 einstellen, werden 16 verschiedene Zufallsstichproben der Größe N / C sowohl vom Original als auch von der Palette entnommen. Zugegeben, es gibt wahrscheinlich bessere Möglichkeiten, die Listen aufzuteilen, aber ein zufälliger Ansatz liefert anständige Ergebnisse.

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)

Ergebnisse:

Wie bei der Lösung von aditsu wurden diese Bilder alle mit genau den gleichen Parametern erstellt. Der einzige Parameter hier ist C, der so niedrig wie möglich eingestellt werden sollte. Für mich war C = 16 eine gute Balance zwischen Geschwindigkeit und Qualität.

Alle Bilder: http://imgur.com/a/RCZiX#0

Amerikanische gotische Palette

mona-gotisch schrei-gotisch

Mona Lisa-Palette

Gothic-Mona schrei mona

Sternennacht-Palette

Mona-Nacht Flussnacht

Schrei-Palette

Gothic-Schrei Monaschrei

Fluss-Palette

gotische kugeln Mona-Kugeln

Sphären-Palette

gotische kugeln Mona-Kugeln


4
Ich mag (Scream -> Starry Night) und (Spheres -> Starry Night) sehr. (Kugeln -> Mona Lisa) ist auch nicht schlecht, aber ich würde gerne mehr Dithering sehen.
John Dvorak

Lol, ich habe dasselbe über den bipartiten Graphenvergleich gedacht, aber die Idee aufgegeben, weil der N ^ 3 ..
RobAu

Dieser "fast deterministische" Algorithmus schlägt alle deterministischen IMO und steht den gut randomisierten gegenüber. Ich mag das.
Hobbs

1
Ich stimme Ihrer Vorstellung einer optimalen Lösung nicht zu. Warum? Durch Dithering kann die Wahrnehmungsqualität (für Menschen) verbessert werden, jedoch wird mit Ihrer Definition eine niedrigere Punktzahl erzielt. Es ist ein Fehler, RGB über etwas wie CIELUV zu verwenden.
Thomas Eding

39

Python

Bearbeiten: Ich habe gerade festgestellt, dass Sie die Quelle mit ImageFilter schärfen können, um die Ergebnisse klarer zu definieren.

Regenbogen -> Mona Lisa (geschärfte Mona Lisa-Quelle, nur Leuchtdichte)

Bildbeschreibung hier eingeben

Regenbogen -> Mona Lisa (ungeschärfte Quelle, gewichtet mit Y = 10, I = 10, Q = 0)

Bildbeschreibung hier eingeben

Mona Lisa -> American Gothic (ungeschärfte Quelle, nur Leuchtdichte)

Bildbeschreibung hier eingeben

Mona Lisa -> American Gothic (ungeschärfte Quelle, gewichtet mit Y = 1, I = 10, Q = 1)

Bildbeschreibung hier eingeben

Fluss -> Regenbogen (nicht geschärfte Quelle, nur Leuchtdichte)

Bildbeschreibung hier eingeben

Grundsätzlich werden alle Pixel der beiden Bilder in zwei Listen zusammengefasst.

Sortieren Sie sie mit der Leuchtdichte als Schlüssel. Y in YIQ steht für die Luminanz.

Ermitteln Sie dann für jedes Pixel in der Quelle (in aufsteigender Luminanzreihenfolge) den RGB-Wert von Pixel desselben Indexes in der Palettenliste.

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')

Um mit dem Trend der Animationen Schritt zu halten ...

Pixel im Schrei werden schnell in die sternenklare Nacht sortiert und umgekehrt

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben


2
Diese einfache Idee funktioniert wirklich gut. Ich frage mich, ob es erweitert werden kann und gewichtete Luminanz, Sättigung und Farbton verwenden. (ZB 10 * L + S + H), um eine bessere Farbanpassung für den gleichen Bereich zu erzielen.
Moogie

1
@bitpwnr Ihre Bilder passen nicht zu meinem Skript, aber das liegt mit ziemlicher Sicherheit daran, dass Sie die etwas anderen JPEGs verwenden, die ich anfangs habe, also keine große Sache. Ich konnte Ihren Code jedoch erst ausführen, nachdem ich [6], [7] und [8] durch [1], [2] und [1] ersetzt hatte. Ich bekomme die gleichen Bilder, aber das ist ein sehr einzigartiger Tippfehler: P
Calvins Hobbys

Ihre Bilder sind sehr klar, aber irgendwie entsättigt: p
aditsu

@ Calvin'sHobbies Opps, korrigierte die Tippfehler.
Vectorized

@bitpwner Wäre es in Ordnung, wenn ich einige Ihrer Bilder in einem YouTube-Video verwenden würde, um mein Animationsskript zu präsentieren?
Calvins Hobbys

39

C # Winform - Visual Studio 2010

Dithering bearbeiten hinzugefügt.

Das ist meine Version des Random-Swap-Algorithmus - @hobbs Flavour. Ich habe immer noch das Gefühl, dass eine Art nicht zufälliges Dithering besser funktioniert ...

Farbausarbeitung im Y-Cb-Cr-Raum (wie bei der JPEG-Komprimierung)

Zwei-Phasen-Ausarbeitung:

  1. Kopie des Pixels von der Quelle in Luminanzreihenfolge. Dies ergibt bereits ein gutes Bild, aber in der Nähe von 0-mal - fast Graustufen - entsättigt
  2. Wiederholtes zufälliges Vertauschen von Pixeln. Der Austausch wird durchgeführt, wenn dies ein besseres Delta (in Bezug auf die Quelle) in der 3 × 3-Zelle ergibt, die das Pixel enthält. Es ist also ein Dithering-Effekt. Das Delta wird im Y-Cr-Cb-Raum ohne Gewichtung der verschiedenen Komponenten berechnet.

Dies ist im Wesentlichen die gleiche Methode, die von @hobbs ohne den ersten zufälligen Tausch ohne Dithering verwendet wird. Nur, meine Zeiten sind kürzer (die Sprache zählt?) Und ich denke, meine Bilder sind besser (wahrscheinlich ist der verwendete Farbraum genauer).

Verwendung des Programms: Legen Sie PNG-Bilder in Ihren Ordner c: \ temp, markieren Sie das Element in der Liste, um das Palettenbild auszuwählen, und wählen Sie das Element in der Liste aus, um das Quellbild auszuwählen (nicht so benutzerfreundlich). Klicken Sie auf die Schaltfläche Start, um die Ausarbeitung zu starten. Das Speichern erfolgt automatisch (auch wenn Sie dies nicht möchten - seien Sie vorsichtig).

Ausarbeitungszeit unter 90 Sekunden.

Aktualisierte Ergebnisse

Palette: Amerikanische Gotik

Monna Lisa Regenbogen Fluss Schrei Sternenklare Nacht

Palette: Monna Lisa

Amerikanische Gotik Regenbogen Fluss Schrei Sternenklare Nacht

Palette: Regenbogen

Amerikanische Gotik Monna Lisa Fluss Schrei Sternenklare Nacht

Palette: Fluss

Amerikanische Gotik Monna Lisa Regenbogen Schrei Sternenklare Nacht

Palette: Schrei

Amerikanische Gotik Monna Lisa Regenbogen Fluss Sternenklare Nacht

Palette: Sternennacht

Amerikanische Gotik Monna Lisa Regenbogen Fluss Schrei

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());
        }
    }
}

Überprüfen Sie 'Unsicherer Code' in der Projekteigenschaft zum Kompilieren.


4
IMO dieser produziert die besten Ergebnisse
figgycity50

9
Das ist absolut unglaublich mit dieser schrecklichen Regenbogen-Palette.
Michael B

1
Erstaunlich, Gewinner!
JJRV

25

JS

Führen Sie einfach zwei Bild-URLs aus.

Als JS-Paket können Sie es selbst im Browser ausführen. Vorgesehen sind Geigen, die mit unterschiedlichen Einstellungen herumspielen. Bitte bedenken Sie, dass diese Fiedel: http://jsfiddle.net/eithe/J7jEk/ immer auf dem neuesten Stand ist (enthält alle Einstellungen). Da dies zunimmt (neue Optionen werden hinzugefügt), werde ich nicht alle vorherigen Fiddles aktualisieren.

Anrufe

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

Optionen

  • Algorithmus: 'balanced', 'surrounding', 'reverse', 'hsv', 'yiq','lab'
  • Geschwindigkeit: Animationsgeschwindigkeit
  • bewegung: true- sollte die animation bewegung von anfang bis ende zeigen
  • Umgebung: Wenn der 'surrounding'Algorithmus ausgewählt ist, wird das Gewicht der Umgebung bei der Berechnung des Gewichts des angegebenen Pixels berücksichtigt
  • hsv: Wenn der 'hsv'Algorithmus ausgewählt ist, steuern diese Parameter, wie stark sich Farbton, Sättigung und Wert auf die Gewichte auswirken
  • yiq: Wenn der 'qiv'Algorithmus ausgewählt ist, steuern diese Parameter, wie stark yiq die Gewichte beeinflusst
  • Labor: Wenn der 'lab'Algorithmus ausgewählt ist, bestimmen diese Parameter, wie stark sich das Labor auf die Gewichte auswirkt
  • Lärm: Wie viel Zufälligkeit wird zu Gewichten hinzugefügt
  • einzigartig: Sollen die Pixel aus der Palette nur einmal verwendet werden (siehe: Fotomosaik oder: Wie viele Programmierer sind erforderlich, um eine Glühbirne auszutauschen? )
  • pixel_1 / pixel_2 {width, height}: Größe des Pixels (in Pixel: D)

Galerie (für die Vitrinen verwende ich immer Mona Lisa & American Gothic, sofern nicht anders angegeben):


Die Animation sieht gut aus! Ihr Bild ist jedoch ein Pixel kürzer als normal.
Calvins Hobbys

@ Calvin Hobbys - Musste es in Farbe schneiden: P Wahrscheinlich ist das, woher der Unterschied kommt. Aktualisiert!
mitte

Ich mag dieses: jsfiddle.net/q865W/4
Justin

@ Quincunx Prost! Mit der gewichteten Version funktioniert es sogar noch besser
9.

Beeindruckend. 0_0 Das ist wirklich gut. jsfiddle.net/q865W/6
Justin

24

C, mit Lab-Farbraum und verbessertem Dithering

Habe ich gesagt, ich war fertig? Ich habe gelogen. Ich denke, der Algorithmus in meiner anderen Lösung ist der beste auf dem Markt, aber Perl ist nicht schnell genug für die Eingabe von Zahlen. Deshalb habe ich meine Arbeit in C neu implementiert. Jetzt werden alle Bilder in diesem Beitrag in einer höheren Qualität ausgeführt Bei einer Geschwindigkeit von ca. 3 Minuten pro Bild und einer etwas geringeren Qualität (0,5%) dauert es 20 bis 30 Sekunden pro Bild. Grundsätzlich erfolgt die gesamte Arbeit mit ImageMagick, und das Dithering erfolgt mit der kubischen Spline-Interpolation von ImageMagick, die ein besseres / weniger strukturiertes Ergebnis liefert.

Code

#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;
}

Kompilieren mit

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

Ergebnisse

Meistens die gleiche wie die Perl-Version, nur etwas besser, aber es gibt einige Ausnahmen. Dithering ist im Allgemeinen weniger auffällig. Schrei -> Sternennacht hat nicht den "flammenden Berg" -Effekt und der Camaro sieht mit den grauen Pixeln weniger glitschig aus. Ich denke, der Farbraumcode der Perl-Version hat einen Fehler mit Pixeln mit niedriger Sättigung.

Amerikanische gotische Palette

Mona Lisa-Palette

Sternennacht-Palette

Schrei-Palette

Sphären-Palette

Mustang (Camaro-Palette)

Camaro (Mustang-Palette)


Ja, Sir, Ihre ist in der Tat die beste, die es gibt. Warum in C es 0,5% schlechter erzeugen?
RMalke

@RMalke Es ist nur schlimmer, wenn er es nur 20-30 Sekunden laufen lässt.
Trlkly

Könnten Sie bitte die Werte, als die Sie verwendet haben nodither_pct, dither_pctund detailin diesem Beispiel posten ? Ich starte Ihr Programm mit verschiedenen Kombinationen, aber für meine Bilder scheinen sie nicht optimal zu sein, und die Paletten sind nah an Ihren, also ... bitte?
Andreï Kostyrka

@ AndreïKostyrka 0.1 0.1 1.6sind die Werte, mit denen ich diese Bilder erstellt habe.
Hobbs

@ AndreïKostyrka 0.5 0.5 1.6sollte in fast genauso guter Qualität mit viel schnellerer Geschwindigkeit resultieren .
Hobbs

23

HSL nächster Wert mit Fehlerausbreitung und Dithering

Ich habe den Code, den ich für meine AllRGB-Bilder verwendet habe, geringfügig angepasst . Dies ist für die Verarbeitung von 16-Megapixel-Bildern mit angemessener Zeit- und Speicherbeschränkung ausgelegt. Daher werden einige Datenstrukturklassen verwendet, die nicht in der Standardbibliothek enthalten sind. Ich habe diese jedoch weggelassen, da hier bereits viel Code vorhanden ist und dies der interessante Code ist.

Für AllRGB stelle ich die Wavelets manuell ein, die bestimmten Bildbereichen Priorität einräumen. Für diese ungeführte Verwendung wähle ich ein Wavelet aus, das das Layout eines Drittels annimmt und das Hauptinteresse ein Drittel von oben nach unten ausrichtet.

Amerikanische Gotik mit Palette von Mona Lisa Mona Lisa mit einer Palette aus der amerikanischen Gotik

Mein Favorit von den 36:

Fluss mit Palette von Mona Lisa

Volles kartesisches Produkt von (Bild, Palette)

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

Python

Weder codeweise noch von den Ergebnissen her.

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')

Mögliche Verbesserungen:

  • intelligentere Farbkorrektur?
  • bessere Qualitätsmetrik?
  • Schieben Sie den Fehler auf alle umliegenden Pixel anstatt auf eines

Hässlich (1-> 2): 1-> 2

Ein bisschen besser (2-> 1): 2-> 1

Anständig (2-> 3): 2-> 3

Wie ein schlechter Raytracer (3-> 4): 3-> 4

Betrug - benutze alle guten Pixel in der oberen Hälfte und behaupte, die Farbe sei ausgegangen: 1-> 2


3
Der letzte ist ... eine interessante Idee. Aber immer noch nicht abstimmen.
John Dvorak

20

Python (mit kd-tree und Helligkeit)

Schöne Herausforderung. Ich habe mich für einen kd-tree-Ansatz entschieden. Die Grundidee eines kd-tree-Ansatzes besteht darin, die Farben und die Leuchtkraft entsprechend ihrer Präsenz im Bild zu unterteilen.

Für den kd-Baum basiert die erste Sortierung auf dem Rot. Es teilt alle Farben in zwei ungefähr gleiche Gruppen von Rottönen auf (hellrot und dunkelrot). Als nächstes werden diese beiden Partitionen entlang der Grüns aufgeteilt. Weiter blau und dann hell und dann wieder rot. Und so weiter, bis der Baum gebaut wurde. In diesem Ansatz habe ich einen kd-Baum für das Quellbild und das Zielbild erstellt. Danach habe ich den Baum von der Quelle zum Ziel gemappt und die Farbdaten der Zieldatei überschrieben. Alle Ergebnisse werden hier angezeigt .

Einige Beispiele:

Mona Lisa -> Amerikanische Gotik

Mona Lisa amerikanische gotik (mona_lisa style)

Amerikanische Gotik -> Mona Lisa

amerikanische gotik mona_lisa (amerikanischer gotischer Stil)

Sternennacht -> Der Schrei

sternenklare Nacht Sternenschrei

Der Schrei -> Sternennacht

Schrei schreiende Sterne

Regenbogen-Kugeln

Bildbeschreibung hier eingeben Mona Lisa Bälle schreiende Bälle

Hier ist ein Kurzfilm mit dem @ Calvin Hobbies Movie Frame Maker:

Bildbeschreibung hier eingeben

Und jetzt zum Code :-)

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`+'.'))

Ich habe das vor einem Jahr nicht bemerkt, aber es ist ziemlich gut!
Hobbs

16

Python

Um den Ball am Laufen zu halten, hier meine einfache und schmerzhaft langsame Antwort.

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')

Für jedes Pixel in der Quelle wird nach dem nicht verwendeten Pixel in der Palette gesucht, das im RGB-Farbwürfel am nächsten liegt. Es ist im Grunde dasselbe wie der Quincunx-Algorithmus, jedoch ohne Zufälligkeit und mit einer anderen Farbvergleichsfunktion.

Sie können erkennen, dass ich mich von links nach rechts bewege, da die rechte Seite des Bildes aufgrund der Erschöpfung ähnlicher Farben weniger Details aufweist.

Fluss aus der amerikanischen Gotik

Fluss aus der amerikanischen Gotik

Mona Lisa von Rainbow Spheres

Mona Lisa von Rainbow Spheres


1
Mme. Lisa ist ein bisschen gelblich ...
Tomsmeding

4
Ich mag den Übergang im Fluss von der amerikanischen Gotik von links 'schön' nach rechts 'abstrakt' =)
Fehler

12

Haskell

Ich habe ein paar verschiedene Ansätze mit der Suche nach dem nächsten Nachbarn ausprobiert, bevor ich mich für diese Lösung entschieden habe (was eigentlich meine erste Idee war). Zuerst konvertiere ich die Pixelformate der Bilder in YCbCr und erstelle zwei Listen mit ihren Pixeldaten. Dann werden die Listen sortiert, wobei der Luminanzwert Vorrang hat. Danach ersetze ich einfach die sortierte Pixelliste des Eingabebildes durch die der Palette und setze sie dann in die ursprüngliche Reihenfolge zurück und konstruiere daraus ein neues Bild.

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

Ergebnisse

Die Bilder, die mit meinem Programm erstellt werden, sind in der Regel weniger lebendig als bei vielen anderen Lösungen, und große Flächen oder Farbverläufe lassen sich nicht gut bearbeiten.

Hier ist ein Link zum vollständigen Album.

Amerikanische Gotik -> Mona Lisa

Mona Lisa -> Amerikanische Gotik

Kugeln -> Mona Lisa

Der Schrei -> Sternennacht

Der Schrei -> Kugeln


3
Ich mag das Dithering auf (Kugeln -> Mona Lisa), aber woher stammen diese hässlichen Artefakte auf (Schrei -> Kugeln)?
John Dvorak

1
Die Artefakte sind ein Nebeneffekt davon, wie mein Algorithmus die Pixel sortiert. Im Moment hat die Rotdifferenz jedes Pixels Vorrang vor der Blaudifferenz im Sortierschritt, was bedeutet, dass ähnliche Farben im Eingabebild mit sehr unterschiedlichen Farben aus dem Palettenbild abgeglichen werden können. Ich bin mir jedoch fast sicher, dass derselbe Effekt das scheinbare Zittern in Bildern wie den Sphären verursacht -> Mona Lisa, also habe ich beschlossen, ihn beizubehalten.
ChaseC

9

Java

Inspiriert von der vorherigen Java-Antwort von 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 -> Bauern

Bildbeschreibung hier eingeben

Was ist, sortiert die Punkte, die durch ihre Intensität ersetzt werden müssen, anstatt zufällig.


8

Rubin

Überblick:

Wirklich einfacher Ansatz, scheint aber ziemlich gute Ergebnisse zu erzielen:

  1. Nehmen Sie die Palette und das Ziel und sortieren Sie die Pixel nach Funktionen. Nennen Sie diese "Referenz" -Arrays. Ich habe mich entschieden, nach HSLA zu sortieren, bevorzuge jedoch die Helligkeit der Sättigung der Farbe (auch "LSHA" genannt).
  2. Machen Sie das Ausgabebild, indem Sie über jedes Pixel des Zielbildes iterieren, herausfinden, wohin es im Zielreferenzfeld sortiert wird, und das Pixel aus der Palette nehmen, das zum gleichen Index im Palettenreferenzfeld sortiert wurde.

Code:

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)

Ergebnisse:

http://imgur.com/a/Iu7Ds

Höhepunkte:

Sternennacht aus Schrei Amerikanische Gotik aus Mona Lisa Mona Lisa gemacht vom Flussfoto Das Flussfoto gemacht von der sternenklaren Nacht


2
Können Sie die Quellpaletten für jedes Bild hinzufügen?
PlasmaHH

7

Perl

Hier ist ein eher vereinfachter Ansatz. Das Generieren von 100 Bildern pro Bildpaar auf meinem MacBook Pro mit einer Speicherkapazität von ca. 120 MB dauert ungefähr fünf Sekunden .

Die Idee ist, die Pixel in beiden Bildern und in Palettenbildern nach 24-Bit-gepacktem RGB zu sortieren und die Farben in der Quelle nacheinander durch Farben aus der Palette zu ersetzen.

#!/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);
}

Ausgabe

Schreien Sie mit der Starry Night-Palette

Schreien Sie mit der Starry Night-Palette

Amerikanische Gotik mit Mona Lisa Farben

Amerikanische Gotik mit Mona Lisa Farben

Mona Lisa mit Scream Farben

Mona Lisa mit Scream Farben

Fluss mit Marmorfarben

Fluss mit Marmorfarben

Animationen

Ich war faul, also habe ich die Animationen auf YouTube gestellt: Mona Lisa mit Farben aus Starry Night und American Gothic mit Farben aus Mona Lisa .


7

Python

Ich nahm an, dass ich diese kleine Gelegenheit nutzen würde, um Codegolf zu spielen und es als Ausrede für die Arbeit an meinen Python-Chops zu nutzen, da es heutzutage häufiger bei der Arbeit auftaucht. Ich habe einige Algorithmen durchgearbeitet, darunter einige mit O (n ^ 2) und O (nlog (n)) Zeit, um zu versuchen, die Farben zu optimieren, aber es wurde sehr deutlich, dass dies sowohl rechenintensiv als auch tatsächlich sehr wenig war Auswirkung auf das scheinbare Ergebnis. Unten ist eine Einstellung, die ich für Dinge gemacht habe, die in O (n) -Zeit (im Grunde genommen augenblicklich auf meinem System) funktionieren, die das wichtigste visuelle Element (Luminanz) so richtig macht, wie es vernünftig ist, und die Chroma dort landen lässt, wo es mag.

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!"

Das Endergebnis hat einige nette Konsequenzen. Wenn die Bilder jedoch sehr unterschiedliche Luminanzverteilungen aufweisen, kann nicht viel getan werden, ohne voranzukommen und zu zittern. Dies ist möglicherweise irgendwann interessant, wird hier jedoch aus Gründen der Kürze ausgeschlossen.

Alles -> Mona Lisa

Amerikanische Gotik -> Mona Lisa Sternennacht -> Mona Lisa Schrei -> Mona Lisa Fluss -> Mona Lisa Kugeln -> Mona Lisa

Mona Lisa -> Kugeln

Mona Lisa -> Kugeln


6

Mathematica - zufällige Permutationen

Idee

Wählen Sie zwei Pixel im Quellbild aus und prüfen Sie, ob sich der Fehler zum Zielbild verringern würde, wenn diese zwei Pixel vertauscht würden. Wir fügen dem Ergebnis eine kleine Zufallszahl (-d | + d) hinzu, um lokale Minima zu vermeiden. Wiederholen. Für die Geschwindigkeit machen Sie dies mit 10000 Pixel auf einmal.

Es ist ein bisschen wie eine zufällige Markov-Kette. Es wäre wahrscheinlich gut, die Zufälligkeit während des Optimierungsprozesses ähnlich wie beim simulierten Tempern zu verringern.

Code

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"]

Ergebnisse

Gothic zu Mona Lisa. Links: LAB-Farbraum verwenden (Delta = 0). Rechts: Verwenden des RBG-Farbraums (Delta = 0) img7 img8

Gothic zu Mona Lisa. Links: RGB-Farbraum, Delta = 0,05. Rechts: RGB-Farbraum, Delta = 0,15. img9 img10

Die folgenden Bilder zeigen Animationen für rund 3.500.000 Swaps mit RGB-Farbraum und Delta = 0.

img1 img2 img3 img4 img5 img6


Sieht aus wie Aditsus Weg, aber ich freue mich auf Ihre Ergebnisse.
Leif

5

wird bearbeitet

Die Quelle und die Palette werden nebeneinander angezeigt, und es wird eine Animation der Pixel angezeigt, die aus der Palette entnommen werden.

In der Zeile int i = chooseIndexIncremental();können Sie die chooseIndex*Funktionen ändern , um die Auswahlreihenfolge der Pixel anzuzeigen.

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; }

Amerikanische Gotik -> Mona Lisa, inkrementell

inkrementell

Amerikanische Gotik -> Mona Lisa, zufällig

zufällig


2
Wie sieht es aus, wenn Sie die Palette "Regenbogenkugeln" verwenden?
Phyzome

5

C-Sharp

Keine neuen / aufregenden Ideen, aber ich dachte, ich würde es versuchen. Sortiert einfach die Pixel und priorisiert die Helligkeit vor der Sättigung vor dem Farbton. Der Code ist einigermaßen kurz, für was es sich lohnt.

BEARBEITEN: Eine noch kürzere Version hinzugefügt

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())));
    }
}

Stichprobe:

Bildbeschreibung hier eingeben

+

Bildbeschreibung hier eingeben

=

Bildbeschreibung hier eingeben


5

Java

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);
    }
}

Hier ist eine ganz andere Idee. Ich erstelle eine Liste der Farben für jedes Bild und sortiere dann nach dem Farbton, der nach der Formel von Wikipedia berechnet wird:

Bildbeschreibung hier eingeben

Im Gegensatz zu meiner anderen Antwort ist dies extrem schnell. Es dauert ungefähr 2 Sekunden, einschließlich der Validierung.

Das Ergebnis ist eine abstrakte Kunst. Hier sind einige Bilder (Mauszeiger bewegen, um zu / von zu sehen):

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben


5
Es sieht aus wie etwas Predator würde o_O sehen
eithed

Diese sind ziemlich beängstigend, aber sie sind in der Tat richtig!
Calvins Hobbys

1
@ Calvin'sHobbies Wie ist das unheimlich? Ich nenne es Schönheit.
Justin

3
Ihre Gesichter sind leer und unheimlich ... aber sie haben eine eindringliche Schönheit.
Calvins Hobbys

1
Die Kugeln sind großartig.
Siledh

5

Python

Nun, ich entschied, dass ich meine Lösung genauso gut posten könnte, da ich die Zeit dafür aufgewendet habe. Im Wesentlichen dachte ich, ich würde die rohen Pixeldaten der Bilder abrufen, die Daten nach Helligkeit sortieren und dann die Werte desselben Indexes in ein neues Bild einfügen. Ich habe meine Meinung über die Helligkeit geändert und stattdessen die Leuchtdichte verwendet. Ich habe damit ein paar ziemlich gute Ergebnisse erzielt.

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()

Ergebnisse

Ich war ziemlich zufrieden mit dem Ergebnis. Es schien für alle Bilder, die ich durchmachte, einheitlich zu funktionieren.

Sternennacht mit Scream Pixels

Schrei + Sternennacht

Sternennacht mit Regenbogenpixeln

Regenbogen + Sternennacht

Regenbogen mit sternenklaren Nachtpixeln

Sternennacht + Regenbogen

Mona Lisa mit Scream Pixels

Schrei + Mona Lisa

Fluss mit Sternenhimmel-Pixeln

Sternennacht + Fluss

Mona Lisa mit amerikanischen Gothic Pixels

Gothic + Mona Lisa

Mustang mit Chevy Pixels

Ich hätte die Bilder wahrscheinlich aufgrund meiner Hardwareeinschränkungen verkleinern sollen, aber na ja.

Chevy + Mustang

Chevy mit Mustang-Pixeln

Mustang + Chevy

Fluss mit Regenbogen-Pixeln

Regenbogen + Fluss

Mona Lisa mit Regenbogenpixeln

Regenbogen + Mona Lisa

Amerikanische Gotik mit Regenbogenpixeln

Regenbogen + Gothic


Update Ich habe noch ein paar Bilder hinzugefügt, und hier sind ein paar Animationen. Die erste zeigt, wie meine Methode funktioniert, und die zweite verwendet das Skript @ Calvin'sHobbies geschrieben.

Meine Methode

@ Calvin'sHobbies-Skript


Update 2 Ich habe ein Diktum hinzugefügt, in dem die Indizes der Pixel nach ihrer Farbe gespeichert sind. Dies machte das Skript effizienter. Überprüfen Sie den Revisionsverlauf, um das Original zu sehen.


5

C ++ 11

Am Ende habe ich mich für einen relativ einfachen deterministischen Greedy-Algorithmus entschieden. Dies ist ein Thread, läuft aber in einem Haar über 4 Sekunden auf meiner Maschine.

Der grundlegende Algorithmus sortiert alle Pixel in der Palette und im Zielbild, indem die Luminanz (das L von L a b * ) verringert wird . Dann sucht es für jedes dieser geordneten Zielpixel nach der engsten Übereinstimmung in den ersten 75 Einträgen der Palette unter Verwendung des Quadrats der CIEDE2000- Abstandsmetrik mit der Luminanz der auf die des Ziels geklemmten Palettenfarben. (Für die Implementierung und das Debuggen von CIEDE2000 war diese Seite sehr hilfreich.) Die beste Übereinstimmung wird dann aus der Palette entfernt, dem Ergebnis zugewiesen und der Algorithmus fährt mit dem nächsten hellsten Pixel im Zielbild fort.

Indem wir sortierte Luminanz sowohl für das Ziel als auch für die Palette verwenden, stellen wir sicher, dass die Gesamtluminanz (das visuell hervorstechendste Element) des Ergebnisses so gut wie möglich mit dem Ziel übereinstimmt. Wenn Sie ein kleines Fenster mit 75 Einträgen verwenden, können Sie eine passende Farbe mit etwa der richtigen Helligkeit finden, falls es eine gibt. Wenn es keine gibt, ist die Farbe ausgeschaltet, aber zumindest sollte die Helligkeit konstant sein. Infolgedessen verschlechtert es sich ziemlich elegant, wenn die Palettenfarben nicht gut übereinstimmen.

Code

Zum Kompilieren benötigen Sie die ImageMagick ++ - Entwicklungsbibliotheken. Eine kleine CMake-Datei zum Kompilieren finden Sie weiter unten.

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 )

Ergebnis

Das vollständige Album ist da. Von den folgenden Ergebnissen sind meine Favoriten wahrscheinlich American Gothic mit der Mona Lisa-Palette und Starry Night mit der Spheres-Palette.

Amerikanische gotische Palette

Mona Lisa Palette

Fluss-Palette

Die Schrei-Palette

Sphären-Palette

Sternennacht-Palette


Das sieht fantastisch aus! Was denkst du darüber, wie sehr dies beschleunigt werden könnte? Gibt es eine Chance für Echtzeit, dh 60fps bei durchschnittlicher Hardware?
Danijar

4

C ++

Nicht der kürzeste Code, generiert aber die Antwort "sofort", obwohl er Single-Threaded und nicht optimiert ist. Ich bin mit dem Ergebnis zufrieden.

Ich generiere zwei sortierte Listen von Pixeln, eine für jedes Bild, und die Sortierung basiert auf einem gewichteten Wert von "Helligkeit". Ich benutze 100% Grün, 50% Rot und 10% Blau, um die Helligkeit zu berechnen und gewichte sie für das menschliche Auge (mehr oder weniger). Ich tausche dann die Pixel im Quellbild gegen die gleichen indizierten Pixel im Palettenbild aus und schreibe das Zielbild aus.

Ich benutze die FreeImage-Bibliothek zum Lesen / Schreiben der Bilddateien.

Code

/* 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;
}

Ergebnisse

Amerikanische Gotik mit Mona Lisa Palette American Gothic mit Mona Lisa-Palette Amerikanische Gotik mit Regenbogen-Palette American Gothic mit Rainbow-Palette Mona Lisa mit Scream-Palette Mona Lisa mit Scream-Palette Mona Lisa mit Regenbogen-Palette Mona Lisa mit Rainbow-Palette Schreien Sie mit der Starry Night-Palette Scream mit Starry Night-Palette


4

C #

Die Punkte werden in zufälliger Reihenfolge ab der Mitte abgelegt. Immer die Farbe wählen, die dem Palettenbild am nächsten kommt. Die letzten Pixel sind also etwas sehr schlecht.

Ergebnisse

Gotische Palette

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Und die amerikanischen Paarbesucher aus Wikipedia

Bildbeschreibung hier eingeben

Mona-Palette

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Code:

Ich weiß nicht warum, aber Code ist ziemlich langsam ...

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
Das sind ziemlich großartig. Sie sehen aus wie Fotos, die verbrannt wurden oder irgendwo verrotten.

Danke, A hat mehrere Algorithmen ausgeführt, aber die anderen waren den anderen Antworten sehr ähnlich. Also habe ich das markantere gepostet
RMalke 16.07.14

3

C #

Vergleicht Farben nach Entfernung. Startet von der Mitte.

EDIT: Aktualisiert, sollte jetzt ca. 1,5x schneller sein.

American Gothic
Bildbeschreibung hier eingeben
Der Scream
Bildbeschreibung hier eingeben
Starry Night
Bildbeschreibung hier eingeben
Marbles
Bildbeschreibung hier eingeben
River
Bildbeschreibung hier eingeben
Auch hier ist der gelbe Chevy:
Bildbeschreibung hier eingeben

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

Ich habe beschlossen, einen sehr ähnlichen Algorithmus wie meine andere Antwort zu verwenden, aber statt einzelner Pixel nur 2x2 Pixelblöcke zu tauschen. Leider fügt dieser Algorithmus eine zusätzliche Einschränkung hinzu, dass die Bilddimensionen durch 2 teilbar sein müssen, was die Raytrace-Kugeln unbrauchbar macht, sofern ich die Größen nicht ändere.

Ich mag einige der Ergebnisse wirklich!

Amerikanische Gotik mit Flusspalette:

Bildbeschreibung hier eingeben

Mona Lisa mit amerikanischer Gothic-Palette:

Bildbeschreibung hier eingeben

Mona Lisa mit Flusspalette:

Bildbeschreibung hier eingeben

Ich habe auch 4x4 ausprobiert und hier sind meine Favoriten!

Sternennacht mit der Scream-Palette:

Bildbeschreibung hier eingeben

Mona Lisa mit amerikanischer Gothic-Palette:

Bildbeschreibung hier eingeben

Der Schrei mit der Mona Lisa Palette:

Bildbeschreibung hier eingeben

Amerikanische Gotik mit der Mona Lisa Palette:

Bildbeschreibung hier eingeben


1
Habe darüber nachgedacht, dasselbe zu tun und die Pixelgewichte basierend auf quadratischen Blöcken zu berechnen. Ich mag die Ergebnisse von Mona Lisa sehr - sie erinnern mich an Bilder von Bildern. Können Sie zufällig 4x4-Blöcke machen?
Zehnte

1
@eithedog Ich habe 4x4 ausprobiert und es sieht ziemlich gut aus. Siehe meine aktualisierte Antwort!
LVBen

3

C #

Das ist wirklich sehr, sehr langsam, aber es macht einen tollen Job, besonders wenn Sie die Raytrace-Sphären-Palette verwenden.

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Die Scream-Palette:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Mona Lisa-Palette:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Amerikanische Gotikpalette:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Flusspalette:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

Die Sternennacht-Palette:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

   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 - Ein weiterer Mapping-Ansatz

Bearbeiten 1: Nachdem dies in einer "mathematischen" Umgebung unter G + geteilt wurde, scheinen wir alle übereinstimmende Ansätze mit verschiedenen Möglichkeiten zu verwenden, um die Komplexität zu umgehen.

Bearbeiten 2: Ich habe die Bilder in meinem Google Drive durcheinander gebracht und neu gestartet, sodass die alten Links nicht mehr funktionieren. Entschuldigung, ich arbeite noch an mehr Reputation für mehr Links.

Edit 3: Lesen der anderen Beiträge Ich habe einige Inspirationen. Ich habe das Programm jetzt schneller bekommen und etwas CPU-Zeit reinvestiert, um einige Änderungen abhängig vom Ziel-Image-Speicherort vorzunehmen.

Bearbeiten 4: Neue Programmversion. Schneller! Spezielle Behandlung beider Bereiche mit scharfen Ecken und sehr sanften Veränderungen (hilft sehr beim Raytracing, bringt der Mona Lisa aber gelegentlich rote Augen)! Möglichkeit, Zwischenbilder aus Animationen zu generieren!

Ich war von der Idee wirklich begeistert und die Quincunx-Lösung hat mich irgendwie fasziniert. Also dachte ich, ich könnte meine 2 Cent gut addieren.

Die Idee war, dass wir offensichtlich eine (irgendwie enge) Zuordnung zwischen zwei Farbpaletten benötigen.

Mit dieser Idee verbrachte ich die erste Nacht damit, einen stabilen Heiratsalgorithmus zu finden zu finden, der schnell und mit dem Speicher meines PCs auf 123520 Kandidaten lief. Während ich in den Speicherbereich kam, fand ich das Laufzeitproblem unlösbar.

Die zweite Nacht beschloss ich, weiter zu gehen und in den ungarischen Algorithmus einzutauchen der versprach, sogar Annäherungseigenschaften, dh den minimalen Abstand zwischen den Farben in beiden Bildern, bereitzustellen. Glücklicherweise fand ich 3 einbaufertige Java-Implementierungen dieser Art (abgesehen von vielen halbfertigen Aufgaben, die es schwierig machen, nach elementaren Algorithmen zu suchen). Wie zu erwarten war, sind ungarische Algorithmen in Bezug auf Laufzeit und Speichernutzung sogar noch schlechter. Schlimmer noch, alle drei von mir getesteten Implementierungen lieferten gelegentlich falsche Ergebnisse. Ich zittere, wenn ich an andere Programme denke, die auf diesen basieren könnten.

Der dritte Ansatz (Ende der zweiten Nacht) war einfach, schnell, schnell und gar nicht so schlecht: Sortieren Sie die Farben in beiden Bildern nach Leuchtkraft und auf der einfachen Karte nach Rangfolge, dh dunkelste bis dunkelste, zweitdunkelste bis zweitdunkelste. Dies erzeugt sofort eine scharf aussehende Schwarz-Weiß-Rekonstruktion mit einer zufälligen Farbe, die herumgesprüht wird.

* Der bisherige Ansatz 4 und der letzte Ansatz (Morgen der zweiten Nacht) beginnen mit der obigen Helligkeitsabbildung und fügen lokale Korrekturen hinzu, indem ungarische Algorithmen auf verschiedene überlappende Sequenzen von Pixeln angewendet werden. Auf diese Weise erhielt ich eine bessere Zuordnung und arbeitete sowohl an der Komplexität des Problems als auch an den Fehlern in den Implementierungen.

Hier ist also ein Java-Code, einige Teile sehen möglicherweise ähnlich aus wie der hier veröffentlichte Java-Code. Der verwendete Ungar ist eine gepatchte Version von John Millers, die ursprünglich im ontologySimilariy-Projekt enthalten war. Dies war der schnellste Weg, den ich gefunden habe und der die wenigsten Fehler aufwies.

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);
            }
        }
    }
}

Die aktuelle Laufzeit beträgt 20 bis 30 Sekunden pro Bildpaar, aber es gibt viele Verbesserungen, die es entweder beschleunigen oder die Qualität verbessern.

Scheint, als würde mein Ruf als Neuling für so viele Links / Bilder nicht ausreichen. Hier ist eine Verknüpfung zu meinem Google Drive-Ordner für Bildbeispiele: http://goo.gl/qZHTao

Die Beispiele, die ich zuerst zeigen wollte:

Leute -> Mona Lisa http://goo.gl/mGvq9h

Das Programm verfolgt alle Punktkoordinaten, aber ich fühle mich jetzt erschöpft und plane vorerst keine Animationen. Wenn ich mehr Zeit aufwenden würde, könnte ich selbst einen ungarischen Algorithmus ausführen oder den lokalen Optimierungsplan meines Programms abrufen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.