American Gothic trong bảng màu của Mona Lisa: Sắp xếp lại các pixel


377

Bạn được cung cấp hai hình ảnh màu sắc trung thực, Nguồn và Bảng màu. Chúng không nhất thiết phải có cùng kích thước nhưng được đảm bảo rằng các khu vực của chúng giống nhau, tức là chúng có cùng số pixel.

Nhiệm vụ của bạn là tạo ra một thuật toán tạo ra bản sao Nguồn chính xác nhất bằng cách chỉ sử dụng các pixel trong Bảng màu. Mỗi pixel trong Bảng màu phải được sử dụng chính xác một lần ở một vị trí duy nhất trong bản sao này. Bản sao phải có cùng kích thước với Nguồn.

Tập lệnh Python này có thể được sử dụng để đảm bảo các ràng buộc này được đáp ứng:

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

Dưới đây là một số hình ảnh để thử nghiệm. Họ đều có cùng một khu vực. Thuật toán của bạn nên hoạt động cho bất kỳ hai hình ảnh có diện tích bằng nhau, không chỉ là American Gothic và Mona Lisa. Tất nhiên bạn nên hiển thị đầu ra của bạn.

Mỹ gothic nàng mô na Li Sa Đêm đầy sao Tiếng hét con sông cầu vồng

Cảm ơn Wikipedia cho những hình ảnh của bức tranh nổi tiếng.

Chấm điểm

Đây là một cuộc thi phổ biến vì vậy câu trả lời được bình chọn cao nhất sẽ chiến thắng. Nhưng tôi chắc chắn có rất nhiều cách để sáng tạo với điều này!

Hoạt hình

millinon có ý tưởng rằng sẽ rất tuyệt khi nhìn thấy các pixel sắp xếp lại chính chúng. Tôi cũng nghĩ như vậy nên tôi đã viết này kịch bản Python mà phải mất hai hình ảnh làm bằng các màu sắc tương tự và rút ra những hình ảnh trung gian giữa chúng. Cập nhật: Tôi vừa sửa đổi nó để mỗi pixel di chuyển số tiền tối thiểu phải có. Nó không còn là ngẫu nhiên.

Đầu tiên là Mona Lisa biến thành American Gothic của aditsu. Tiếp theo là American Gothic của bitpwner (từ Mona Lisa) biến thành aditsu. Thật đáng kinh ngạc khi hai phiên bản chia sẻ cùng một bảng màu.

Mona Lisa đến hoạt hình gothic Mỹ hoạt hình giữa hai phiên bản American Gothic được làm từ Mona Lisa

Kết quả thực sự khá đáng kinh ngạc. Đây là cầu vồng Mona Lisa của aditsu (làm chậm để hiển thị chi tiết).

cầu vồng cho hoạt hình Mona Lisa

Hoạt hình cuối cùng này không nhất thiết liên quan đến cuộc thi. Nó cho thấy những gì xảy ra khi tập lệnh của tôi được sử dụng để xoay hình ảnh 90 độ.

cây hoạt hình xoay


22
Để tăng lượt truy cập cho câu hỏi của bạn, bạn có thể muốn xem xét việc lôi kéo nó, "American Gothic trong bảng màu của Mona Lisa: Sắp xếp lại các pixel"
DavidC

14
Xin chào, tôi chỉ muốn chúc mừng bạn về thử thách ban đầu này! Rất mới mẻ và thú vị.
bolov

6
Tôi rất vui vì đây không phải là [golf-code].
Ming-Tang

13
Giới hạn dữ liệu di động của tôi bị bỏng khủng khiếp mỗi khi tôi truy cập trang này.
Vectorized

Câu trả lời:


159

Java - GUI với chuyển đổi ngẫu nhiên lũy tiến

Tôi đã thử rất nhiều thứ, một số trong số chúng rất phức tạp, cuối cùng tôi đã quay lại với mã tương đối đơn giản này:

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

Tất cả các tham số có liên quan được định nghĩa là hằng số ở đầu lớp.

Trước tiên, chương trình sao chép hình ảnh bảng màu vào kích thước nguồn, sau đó liên tục chọn 2 pixel ngẫu nhiên và hoán đổi chúng nếu điều đó sẽ khiến chúng gần với hình ảnh nguồn hơn. "Closer" được xác định bằng cách sử dụng hàm khoảng cách màu để tính toán sự khác biệt giữa các thành phần r, g, b (trọng số luma) cùng với tổng chênh lệch độ sáng, với trọng số lớn hơn đối với độ sáng.

Chỉ mất vài giây để các hình dạng hình thành, nhưng một thời gian nữa để các màu kết hợp với nhau. Bạn có thể lưu hình ảnh hiện tại bất cứ lúc nào. Tôi thường đợi khoảng 1-3 phút trước khi tiết kiệm.

Các kết quả:

Không giống như một số câu trả lời khác, tất cả các hình ảnh này đều được tạo bằng các tham số chính xác giống nhau (trừ tên tệp).

Bảng màu gothic Mỹ

mona-gothic la hét-gothic

Bảng màu Mona Lisa

gothic-mona la hét hình cầu-mona

Bảng màu đêm đầy sao

đêm đơn đêm la hét quả cầu đêm

Bảng màu Scream

tiếng la hét mona-hét tiếng thét đêm hình cầu-la hét

Bảng màu hình cầu

Tôi nghĩ rằng đây là bài kiểm tra khó nhất và mọi người nên đăng kết quả của mình với bảng màu này:

quả cầu gothic hình cầu quả cầu

Xin lỗi, tôi không thấy hình ảnh dòng sông rất thú vị nên tôi đã không đưa nó vào.

Tôi cũng đã thêm một video tại https://www.youtube.com/watch?v=_-w3cKL5teM , nó cho thấy chương trình làm gì (không chính xác trong thời gian thực nhưng tương tự) sau đó nó hiển thị chuyển động pixel dần dần bằng cách sử dụng python của Calvin kịch bản. Thật không may, chất lượng video bị hỏng đáng kể do mã hóa / nén của youtube.


2
@Quincunx Và tôi cũng không gọi invokeLater, hãy bắn tôi: p Ngoài ra, cảm ơn :)
aditsu

16
Câu trả lời hay nhất cho đến nay ...
Yuval Filmus

8
Khi nghi ngờ, vũ phu nó? Có vẻ như là một giải pháp tuyệt vời, tôi rất thích xem một hình ảnh động cho điều này, thậm chí có thể là một video thay vì gif.
Lilienthal

3
Bạn có thể mở rộng thuật toán một chút thành ủ mô phỏng đầy đủ cho một cải tiến nhỏ. Những gì bạn đang làm đã rất gần (nhưng nó tham lam). Tìm hoán vị giảm thiểu khoảng cách có vẻ như là một vấn đề tối ưu hóa khó khăn, vì vậy loại heuristic này là phù hợp. @Lilienthal đây không phải là vũ phu, nó thực sự gần với các kỹ thuật tối ưu hóa thường được sử dụng.
Szabolcs

3
Thuật toán này có kết quả tốt nhất cho đến nay. Và nó rất đơn giản. Điều này làm cho nó một người chiến thắng rõ ràng cho tôi.
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;
    }
}

Cách tiếp cận của tôi hoạt động bằng cách tìm màu gần nhất với từng pixel (tốt, có thể là gần nhất), trong 3 không gian, vì màu là 3D.

Điều này hoạt động bằng cách tạo một danh sách tất cả các điểm chúng ta cần điền và một danh sách tất cả các màu có thể chúng ta có thể sử dụng. Chúng tôi chọn ngẫu nhiên danh sách các điểm (vì vậy hình ảnh sẽ bật ra tốt hơn), sau đó chúng tôi đi qua từng điểm và lấy màu của hình ảnh nguồn.

Cập nhật: Tôi đã từng chỉ đơn giản là tìm kiếm nhị phân, vì vậy màu đỏ phù hợp hơn màu xanh lá cây phù hợp hơn màu xanh lam. Bây giờ tôi đã thay đổi nó để thực hiện sáu tìm kiếm nhị phân (tất cả các hoán vị có thể), sau đó chọn màu gần nhất. Chỉ mất ~ 6 lần (tức là 1 phút). Trong khi các hình ảnh vẫn còn hạt, màu sắc phù hợp hơn.

Cập nhật 2: Tôi không còn ngẫu nhiên danh sách. Thay vào đó, tôi chọn 4 điểm theo quy tắc một phần ba, sau đó sắp xếp ngẫu nhiên các điểm, với ưu tiên điền vào trung tâm.

Lưu ý: Xem lịch sử sửa đổi cho các hình ảnh cũ.

Mona Lisa -> Sông:

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

Mona Lisa -> American Gothic:

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

Mona Lisa -> Quả cầu Raytraced:

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

Đêm đầy sao -> Mona Lisa:

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


Đây là một Gif hoạt hình cho thấy cách hình ảnh được xây dựng:

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

Và hiển thị các pixel được lấy từ Mona Lisa:

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


11
Điều đó thật tuyệt vời. Tôi sẽ không nghĩ rằng nó có thể.
AndoDaan

6
Tôi nghi ngờ rằng nó sẽ là tầm thường để làm, nhưng thật tuyệt vời khi có thể tạo ra một phiên bản hoạt hình cho thấy các pixel di chuyển từ hình ảnh gốc vào hình ảnh cuối cùng.
millinon

2
Tôi nghĩ rằng bạn đã hiểu sai vấn đề. Bạn phải sắp xếp lại các pixel trong bảng màu để tạo bản sao, không chỉ đơn giản là sử dụng màu từ bảng màu. Mỗi màu riêng biệt phải được sử dụng trong bản sao chính xác cùng số lần xuất hiện trong bảng màu. Hình ảnh của bạn không vượt qua kịch bản của tôi.
Sở thích của Calvin

7
@Quincunx Vì hóa ra kịch bản của tôi là chính xác (mặc dù tôi đã đơn giản hóa nó cho hậu thế) và chương trình của bạn cũng vậy. Vì lý do tôi không chắc chắn hình ảnh Mona Lisa đã thay đổi một chút khi nó được tải lên. Tôi nhận thấy rằng pixel tại (177, 377) có rgb (0, 0, 16) trực tuyến và (0, 0, 14) trên máy tính ở nhà của tôi. Tôi đã thay thế các tệp jpeg bằng png để hy vọng tránh các sự cố với loại tệp bị mất. Dữ liệu pixel trong ảnh không nên thay đổi nhưng vẫn có thể khôn ngoan khi tải lại ảnh.
Sở thích của Calvin

8
Đây không phải là câu trả lời phổ biến nhất. Thuật toán phức tạp không cần thiết và kết quả rất tệ, mặc dù chúng trông thú vị. So sánh sự biến đổi từ Mona Lisa thành các quả cầu raytraced với kết quả của arditsu
Leif

97

Perl, với không gian màu Lab và hoà sắc

Lưu ý: Bây giờ tôi có một giải pháp C quá.

Sử dụng một cách tiếp cận tương tự với aditsu, (chọn hai vị trí ngẫu nhiên và hoán đổi các pixel ở các vị trí đó nếu nó làm cho hình ảnh giống với hình ảnh mục tiêu hơn), với hai cải tiến chính:

  1. Sử dụng không gian màu CIE L a b * để so sánh màu sắc - chỉ số Euclide trên không gian này là một xấp xỉ rất tốt với sự khác biệt về nhận thức giữa hai màu, do đó ánh xạ màu phải chính xác hơn RGB hoặc thậm chí HSV / HSL.
  2. Sau khi vượt qua ban đầu, đặt các pixel ở vị trí đơn tốt nhất có thể, nó sẽ thực hiện thêm một lần nữa với một hoà sắc ngẫu nhiên. Thay vì so sánh các giá trị pixel ở hai vị trí hoán đổi, nó tính giá trị pixel trung bình của vùng lân cận 3x3 tập trung tại các vị trí hoán đổi. Nếu một hoán đổi cải thiện màu trung bình của các vùng lân cận thì nó được phép, ngay cả khi nó làm cho các pixel riêng lẻ kém chính xác hơn. Đối với một số cặp hình ảnh, điều này có ảnh hưởng đáng ngờ đến chất lượng (và làm cho hiệu ứng bảng màu bớt nổi bật), nhưng đối với một số (như hình cầu -> bất cứ điều gì) thì nó giúp ích khá nhiều. Yếu tố "chi tiết" nhấn mạnh pixel trung tâm ở một mức độ khác nhau. Việc tăng nó sẽ làm giảm tổng lượng hoà sắc, nhưng vẫn giữ được nhiều chi tiết tốt hơn từ hình ảnh mục tiêu. Tối ưu hóa hoà sắc chậm hơn,

Các giá trị trung bình của Lab, giống như hoà sắc, không thực sự hợp lý (chúng nên được chuyển đổi thành XYZ, lấy trung bình và chuyển đổi trở lại) nhưng nó hoạt động tốt cho các mục đích này.

Những hình ảnh này có giới hạn chấm dứt là 100 và 200 (kết thúc giai đoạn đầu tiên khi chấp nhận ít hơn 1 trong 5000 giao dịch hoán đổi và giai đoạn thứ hai ở 1 trong 2500) và hệ số chi tiết hoà sắc là 12 (hòa sắc hơn một chút so với tập trước ). Ở cài đặt chất lượng siêu cao này, hình ảnh mất nhiều thời gian để tạo, nhưng song song, toàn bộ công việc vẫn hoàn thành trong vòng một giờ trên hộp 6 lõi của tôi. Va chạm các giá trị lên đến 500 hoặc hơn hoàn thành hình ảnh trong vài phút, chúng chỉ trông kém bóng bẩy hơn một chút. Tôi muốn thể hiện thuật toán tốt nhất ở đây.

Mã không có nghĩa là đẹp:

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

Các kết quả

Bảng màu gothic Mỹ

Ít khác biệt ở đây với hoà sắc hay không.

Bảng màu Mona Lisa

Phối màu làm giảm dải trên các mặt cầu, nhưng không đặc biệt đẹp.

Bảng màu đêm đầy sao

Mona Lisa giữ lại một chút chi tiết với độ hoà sắc. Spheres là về tình huống tương tự như lần trước.

Bảng màu hét

Đêm đầy sao mà không phối màu là điều tuyệt vời nhất từng có. Phối màu làm cho nó chính xác hơn, nhưng ít thú vị hơn.

Bảng màu hình cầu

Như aditsu nói, thử nghiệm thực sự. Tôi nghĩ rằng tôi vượt qua.

Phối màu giúp vô cùng với American Gothic và Mona Lisa, trộn một số màu xám và các màu khác với các pixel cường độ cao hơn để tạo ra tông màu da bán chính xác thay vì các đốm màu khủng khiếp. The Scream bị ảnh hưởng ít hơn nhiều.

Camaro - Mustang

Nguồn hình ảnh từ bài viết của flawr.

Camaro:

Mustang:

Bảng màu camaro

Trông khá tốt mà không có hoà sắc.

Một hoà sắc "chặt chẽ" (cùng yếu tố chi tiết như trên) không thay đổi nhiều, chỉ thêm một chút chi tiết trong các điểm nổi bật trên mui xe và mái nhà.

Một hoà sắc "lỏng lẻo" (yếu tố chi tiết giảm xuống còn 6) thực sự làm giảm âm sắc, và nhiều chi tiết hơn có thể nhìn thấy qua kính chắn gió, nhưng các mẫu ditherng rõ ràng hơn ở mọi nơi.

Bảng màu Mustang

Các bộ phận của chiếc xe trông tuyệt vời, nhưng các pixel màu xám trông rối mắt. Tệ hơn nữa, tất cả các pixel màu vàng đậm hơn được phân phối trên thân Camaro màu đỏ và thuật toán không phối màu không thể tìm thấy bất cứ điều gì với những cái sáng hơn (di chuyển chúng vào trong xe sẽ khiến trận đấu trở nên tồi tệ hơn và chuyển chúng sang một cái khác vị trí trên nền không tạo ra sự khác biệt thuần túy), do đó, có một Mustang ma trong nền.

Phối màu có thể trải các pixel màu vàng thêm đó ra xung quanh để chúng không chạm vào, phân tán chúng nhiều hơn hoặc ít hơn trên nền trong quá trình. Các điểm nổi bật và bóng trên xe trông tốt hơn một chút.

Một lần nữa, hoà sắc lỏng lẻo có âm sắc đồng đều nhất, tiết lộ nhiều chi tiết hơn trên đèn pha và kính chắn gió. Chiếc xe gần như đỏ trở lại. nền tảng là clumpier cho một số lý do. Không chắc chắn nếu tôi thích nó.

Video

( Liên kết HQ )


3
Tôi thực sự như thế này, những hình ảnh nặng nề dithered có một tuyệt vời chấm màu cảm giác. Seurat có ai Mona Lisa không?
nhện của

2
Thuật toán của bạn chắc chắn làm rất tốt với pallet Spheres khủng khiếp, làm tốt lắm!
Người tuyết

1
@hobbs Sử dụng tuyệt vời của bảng màu cầu vồng, và những chiếc xe của bạn gần như hoàn hảo! Sẽ ổn chứ nếu tôi sử dụng một số hình ảnh của bạn trong video YouTube để giới thiệu kịch bản hoạt hình của mình?
Sở thích của Calvin

1
Tôi nghĩ lý do duy nhất để phối màu của bạn đưa ra mô hình đó là vì bạn đang sử dụng khối pixel 3x3 với trọng lượng chỉ thay đổi cho trung tâm. Nếu bạn tính trọng số pixel theo khoảng cách từ tâm (vì vậy các pixel góc đóng góp ít hơn 4 pixel liền kề) và có thể được mở rộng thành nhiều pixel hơn một chút thì độ hoà sắc sẽ ít được chú ý hơn. Đây đã là một cải tiến tuyệt vời cho bảng màu cầu vồng vì vậy có thể đáng để xem những gì nó có thể làm hơn ...
trichoplax

1
@githubphagocyte Tôi đã dành nửa ngày để thử những thứ như vậy, nhưng không ai trong số đó làm việc theo cách tôi muốn. Một biến thể tạo ra một hoà sắc trông rất ngẫu nhiên rất đẹp, nhưng cũng cho tôi một giai đoạn tối ưu hóa không bao giờ chấm dứt. Các biến thể khác có khả năng phối màu kém hơn hoặc phối màu quá mạnh. Mặc dù vậy, giải pháp C của tôi có độ hoà sắc tốt hơn nhờ phép nội suy spline của ImageMagick. Đó là một spline hình khối vì vậy tôi nghĩ rằng nó sử dụng một khu phố 5x5.
hobbs

79

Con trăn

Ý tưởng rất đơn giản: Mỗi pixel có một điểm trong không gian 3D RGB. Mục tiêu phù hợp với từng pixel của nguồn và một trong các hình ảnh đích, tốt nhất là chúng phải là 'đóng' (đại diện cho màu 'giống nhau'). Vì chúng có thể được phân phối theo những cách khá khác nhau, chúng tôi không thể chỉ phù hợp với người hàng xóm gần nhất.

Chiến lược

Đặt nlà một số nguyên (nhỏ, 3-255 hoặc hơn). Bây giờ pixelcloud trong không gian RGB được sắp xếp theo trục đầu tiên (R). Tập hợp pixel này hiện được phân vùng thành n phân vùng. Mỗi phân vùng hiện được sắp xếp dọc theo trục thứ hai (B), một lần nữa được sắp xếp theo cùng một cách được phân vùng. Chúng tôi làm điều này với cả hai hình ảnh, và bây giờ cho cả một loạt các điểm. Bây giờ chúng ta chỉ có thể khớp các pixel theo vị trí của chúng trong mảng, chọn một pixel ở cùng một vị trí trong mỗi mảng có vị trí tương tự với mỗi pixelcloud trong không gian RGB.

Nếu sự phân bố các pixel trong không gian RGB của cả hai hình ảnh tương tự nhau (có nghĩa là chỉ được dịch chuyển và / hoặc kéo dài dọc theo trục 3) thì kết quả sẽ rất dễ đoán. Nếu các bản phân phối trông hoàn toàn khác nhau, thuật toán này sẽ không tạo ra kết quả tốt (như đã thấy trong ví dụ trước) nhưng đây cũng là một trong những trường hợp khó hơn tôi nghĩ. Những gì nó không làm là sử dụng hiệu ứng tương tác của các pixel lân cận trong nhận thức.

Disclaimer: Tôi là một người mới tuyệt đối với 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")

Kết quả

Tôi nghĩ rằng hóa ra không tệ khi xem xét các giải pháp đơn giản. Tất nhiên bạn có thể nhận được kết quả tốt hơn khi loay hoay với tham số hoặc trước tiên chuyển đổi màu sắc sang không gian màu khác hoặc thậm chí tối ưu hóa phân vùng.

so sánh kết quả của tôi

Thư viện ảnh đầy đủ tại đây: https://imgur.com/a/hzaAm#6

Chi tiết cho sông

monalisa> sông

monalisa> sông

người> sông

người> sông

bóng> sông

bóng> sông

đêm đầy sao> sông

về đêm> sông

tiếng khóc> dòng sông

thecry> sông

bóng> MonaLisa, thay đổi n = 2,4,6, ..., 20

Đây là nhiệm vụ khó khăn nhất mà tôi nghĩ, khác xa với những bức ảnh đẹp, ở đây một gif (phải giảm xuống 256 màu) của các giá trị tham số khác nhau n = 2,4,6, ..., 20. Đối với tôi, điều đáng ngạc nhiên là các giá trị rất thấp tạo ra hình ảnh tốt hơn (khi nhìn vào khuôn mặt của Mme. Lisa): bóng> monalisa

Xin lỗi tôi không thể dừng lại

Bạn thích cái nào hơn? Camy Camaro hay Ford Mustang? Có lẽ kỹ thuật này có thể được cải thiện và sử dụng để tô màu hình ảnh bw. Bây giờ ở đây: đầu tiên tôi cắt những chiếc xe hơi ra khỏi nền bằng cách sơn nó màu trắng (bằng sơn, không chuyên nghiệp lắm ...) và sau đó sử dụng chương trình python theo từng hướng.

Bản gốc

nguyên nguyên

Thu hồi

Có một số đồ tạo tác, tôi nghĩ bởi vì diện tích của một chiếc xe hơi lớn hơn chiếc kia và vì kỹ năng nghệ thuật của tôi khá tệ =) thao túng nhập mô tả hình ảnh ở đây


5
Wow, tôi thực sự yêu dòng sông Starry Night, và cách The Scream làm cho nó trông giống như một dòng sông lửa.
Sở thích của Calvin

@ Calvin'sHob sở thích wow yeah! Chúng gần như bị thu hút, tôi thậm chí không nhìn kỹ chúng vì tôi đang bận tải lên những hình ảnh mới = P Nhưng cảm ơn bạn vì thử thách tuyệt vời này!
flawr

3
Tôi yêu những chiếc xe biến hình. Điều này có thể một lần trở thành một loại chuyển đổi chỉnh sửa hình ảnh, thực sự!
tomsmeding

@tomsmeding Cảm ơn bạn, tôi đã nghĩ về việc sử dụng kỹ thuật tô màu cho hình ảnh b / w, nhưng cho đến nay với sự thành công hạn chế. Nhưng có lẽ chúng ta cần thêm một số ý tưởng để thực hiện điều này =)
flawr

@flawr Sẽ ổn chứ nếu tôi sử dụng một số hình ảnh của bạn trong video YouTube để giới thiệu kịch bản hoạt hình của mình?
Sở thích của Calvin

48

Python - Một giải pháp tối ưu về mặt lý thuyết

Tôi nói về mặt lý thuyết là tối ưu vì giải pháp thực sự tối ưu không hoàn toàn khả thi để tính toán. Tôi bắt đầu bằng cách mô tả giải pháp lý thuyết, và sau đó giải thích cách tôi điều chỉnh nó để làm cho nó khả thi về mặt tính toán trong cả không gian và thời gian.

Tôi xem xét giải pháp tối ưu nhất là giải pháp cho tổng số lỗi thấp nhất trên tất cả các pixel giữa các hình ảnh cũ và mới. Lỗi giữa hai pixel được xác định là khoảng cách Euclidian giữa các điểm trong không gian 3D trong đó mỗi giá trị màu (R, G, B) là tọa độ. Trong thực tế, vì cách con người nhìn nhận mọi thứ, giải pháp tối ưu rất có thể không phải là cái nhìn tốt nhất giải pháp . Tuy nhiên, nó dường như làm khá tốt trong mọi trường hợp.

Để tính toán ánh xạ, tôi coi đây là một vấn đề khớp lưỡng cực trọng lượng tối thiểu . Nói cách khác, có hai bộ nút: pixel gốc và pixel pixel. Một cạnh được tạo giữa mỗi pixel trên hai bộ (nhưng không có cạnh nào được tạo trong một bộ). Chi phí hoặc trọng lượng của một cạnh là khoảng cách Euclidian giữa hai pixel, như được mô tả ở trên. Hai màu càng gần trực quan, chi phí giữa các pixel càng thấp.

Ví dụ kết hợp lưỡng cực

Điều này tạo ra một ma trận chi phí có kích thước N 2 . Đối với những hình ảnh có N = 123520, cần khoảng 40 GB bộ nhớ để thể hiện chi phí dưới dạng số nguyên và một nửa là số nguyên ngắn. Dù bằng cách nào, tôi không có đủ bộ nhớ trong máy để thử. Một vấn đề khác là thuật toán Hungary , hay thuật toán Jonker-Volgenant , có thể được sử dụng để giải quyết vấn đề này, chạy trong N 3 lần. Mặc dù chắc chắn có thể tính toán được, việc tạo ra một giải pháp cho mỗi hình ảnh có thể sẽ mất hàng giờ hoặc nhiều ngày.

Để giải quyết vấn đề này, tôi sắp xếp ngẫu nhiên cả hai danh sách pixel, chia danh sách thành các khối C, chạy triển khai C ++ của thuật toán Jonker-Volgenant trên mỗi cặp danh sách phụ, sau đó nối lại danh sách để tạo ánh xạ cuối cùng. Do đó, đoạn mã dưới đây sẽ cho phép một người tìm ra giải pháp thực sự tối ưu với điều kiện là họ đặt kích thước khối C thành 1 (không có đoạn mã) và có đủ bộ nhớ. Đối với những hình ảnh này, tôi đặt C là 16, để N trở thành 7720, chỉ mất vài phút cho mỗi hình ảnh.

Một cách đơn giản để nghĩ về lý do tại sao điều này hoạt động là việc sắp xếp ngẫu nhiên danh sách các pixel và sau đó lấy một tập hợp con giống như lấy mẫu hình ảnh. Vì vậy, bằng cách đặt C = 16, giống như lấy 16 mẫu ngẫu nhiên có kích thước N / C khác nhau từ cả bảng gốc và bảng màu. Cấp, có thể có nhiều cách tốt hơn để phân chia danh sách, nhưng một cách tiếp cận ngẫu nhiên cung cấp kết quả tốt.

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)

Các kết quả:

Giống như giải pháp của aditsu, những hình ảnh này đều được tạo bằng các tham số chính xác giống nhau. Tham số duy nhất ở đây là C, nên được đặt càng thấp càng tốt. Đối với tôi, C = 16 là sự cân bằng tốt giữa tốc độ và chất lượng.

Tất cả hình ảnh: http://imgur.com/a/RCZiX#0

Bảng màu gothic Mỹ

mona-gothic la hét-gothic

Bảng màu Mona Lisa

gothic-mona la hét

Bảng màu đêm đầy sao

đêm đơn đêm sông

Bảng màu hét

tiếng la hét mona-hét

Bảng màu sông

quả cầu gothic hình cầu

Bảng màu hình cầu

quả cầu gothic hình cầu


4
Tôi thực sự thích (Scream -> Starry night) và (Spheres -> Starry night). (Spheres -> Mona Lisa) cũng không tệ lắm, nhưng tôi muốn xem thêm độ hoà sắc.
John Dvorak

Lol, tôi đã suy nghĩ tương tự về việc khớp đồ thị lưỡng cực, nhưng đã từ bỏ ý tưởng này vì N ^ 3 ..
RobAu

Thuật toán "gần như xác định" này đánh bại tất cả các thuật toán xác định IMO và đứng lên với các thuật toán ngẫu nhiên tốt. Tôi thích nó.
hobbs

1
Tôi không đồng ý với quan điểm của bạn về một giải pháp tối ưu. Tại sao? Phối màu có thể cải thiện chất lượng cảm nhận (đối với con người) nhưng vẫn mang lại điểm thấp hơn bằng cách sử dụng định nghĩa của bạn. Ngoài ra, sử dụng RGB trên một cái gì đó như CIELUV là một sai lầm.
Thomas Eding

39

Con trăn

Chỉnh sửa: Chỉ cần nhận ra rằng bạn thực sự có thể làm sắc nét nguồn bằng ImageFilter để làm cho kết quả được xác định rõ hơn.

Cầu vồng -> Mona Lisa (nguồn Mona Lisa được làm sắc nét, chỉ độ chói)

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

Cầu vồng -> Mona Lisa (nguồn không được mài, có trọng số với Y = 10, I = 10, Q = 0)

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

Mona Lisa -> American Gothic (nguồn không được mài, chỉ độ chói)

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

Mona Lisa -> American Gothic (nguồn không được mài, có trọng số Y = 1, I = 10, Q = 1)

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

Sông -> Cầu vồng (nguồn không được làm sắc nét, chỉ độ chói)

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

Về cơ bản, nó nhận được tất cả các pixel từ hai hình ảnh thành hai danh sách.

Sắp xếp chúng với độ chói làm chìa khóa. Y trong YIQ đại diện cho độ chói.

Sau đó, đối với mỗi pixel trong nguồn (theo thứ tự độ chói tăng dần), hãy lấy giá trị RGB từ pixel của cùng một chỉ mục trong danh sách pallete.

import Image, ImageFilter, colorsys

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

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

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

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

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

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

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

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

Để theo kịp xu hướng của hình ảnh động ...

Điểm ảnh trong tiếng hét bị đẩy nhanh vào đêm đầy sao và ngược lại

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


2
Ý tưởng đơn giản đó hoạt động thực sự tốt. Tôi tự hỏi nếu nó có thể được mở rộng và sử dụng độ chói, độ bão hòa và màu sắc có trọng số. (Ví dụ 10 * L + S + H) để có được kết hợp màu cùng khu vực tốt hơn.
Moogie

1
@bitpwnr Hình ảnh của bạn không vượt qua được kịch bản của tôi nhưng điều đó gần như chắc chắn bởi vì bạn đang sử dụng các jpeg hơi khác nhau mà tôi có ban đầu, vì vậy không có vấn đề gì lớn. Tuy nhiên, tôi chỉ có thể chạy mã của bạn sau khi thay thế [6], [7] và [8] bằng [1], [2] và [1]. Tôi đang nhận được những hình ảnh giống nhau nhưng đó là một lỗi đánh máy rất độc đáo: P
Sở thích của Calvin

Hình ảnh của bạn rất rõ ràng nhưng không bão hòa: p
aditsu

@ Calvin'sHob sở thích Opps, đã sửa lỗi chính tả.
Vectorized

@bitpwner Sẽ ổn chứ nếu tôi sử dụng một số hình ảnh của bạn trong video YouTube để giới thiệu kịch bản hoạt hình của mình?
Sở thích của Calvin

39

Winform C # - Visual Studio 2010

Chỉnh sửa phối màu được thêm vào.

Đó là phiên bản thuật toán hoán đổi ngẫu nhiên của tôi - hương vị @hobbs. Tôi vẫn cảm thấy rằng một số loại phối màu không ngẫu nhiên có thể làm tốt hơn ...

Công phu màu trong không gian Y-Cb-Cr (như trong nén jpeg)

Công phu hai pha:

  1. Sao chép pixel từ nguồn theo thứ tự độ chói. Điều này đã cho một hình ảnh tốt, nhưng không bão hòa - gần như thang màu xám - trong gần 0 thời gian
  2. Lặp lại trao đổi ngẫu nhiên các pixel. Việc hoán đổi được thực hiện nếu điều này mang lại một delta tốt hơn (liên quan đến nguồn) trong ô 3x3 chứa pixel. Vì vậy, đó là một hiệu ứng hoà sắc. Đồng bằng được tính trên không gian Y-Cr-Cb không có trọng số của các thành phần khác nhau.

Đây thực chất là cùng một phương thức được sử dụng bởi @hobbs, không có sự hoán đổi ngẫu nhiên đầu tiên mà không phối màu. Chỉ là, thời gian của tôi ngắn hơn (ngôn ngữ được tính?) Và tôi nghĩ rằng hình ảnh của tôi tốt hơn (có lẽ không gian màu được sử dụng chính xác hơn).

Sử dụng chương trình: đặt hình ảnh .png vào thư mục c: \ temp của bạn, kiểm tra thành phần trong danh sách để chọn hình ảnh bảng màu, chọn thành phần trong danh sách để chọn hình ảnh nguồn (không thân thiện với người dùng). Nhấp vào nút bắt đầu để bắt đầu xây dựng, tiết kiệm là tự động (ngay cả khi bạn không thích - hãy cẩn thận).

Thời gian xây dựng dưới 90 giây.

Kết quả cập nhật

Bảng màu: American Gothic

Monna Lisa cầu vồng con sông Hét lên Đêm đầy sao

Bảng màu: Monna Lisa

Mỹ gothic cầu vồng con sông Hét lên Đêm đầy sao

Bảng màu: Cầu vồng

Mỹ gothic Monna Lisa con sông Hét lên Đêm đầy sao

Bảng màu: sông

Mỹ gothic Monna Lisa cầu vồng Hét lên Đêm đầy sao

Bảng màu: Gào

Mỹ gothic Monna Lisa cầu vồng con sông Đêm đầy sao

Bảng màu: Đêm đầy sao

Mỹ gothic Monna Lisa cầu vồng con sông Hét lên

Mẫu1.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;
    }
}

Chương trình.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());
        }
    }
}

Kiểm tra 'Mã không an toàn' trong thuộc tính dự án để biên dịch.


4
IMO cái này tạo ra kết quả tốt nhất
figgycity50

9
Điều đó hoàn toàn không thể tin được với bảng màu cầu vồng khủng khiếp đó.
Michael B

1
Thật tuyệt vời, người chiến thắng!
jjrv

25

JS

Chỉ cần chạy trên hai url hình ảnh.

Là một gói JS, bạn có thể tự chạy nó trong trình duyệt. Được cung cấp là các câu đố chơi xung quanh với các cài đặt khác nhau. Xin lưu ý rằng fiddle này: http://jsfiddle.net/eithe/J7jEk/ sẽ luôn được cập nhật (chứa tất cả các cài đặt). Khi điều này đang phát triển (các tùy chọn mới được thêm vào), tôi sẽ không cập nhật tất cả các câu đố trước đó.

Các cuộc gọi

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

Tùy chọn

  • Thuật toán: 'balanced','surrounding' , 'reverse', 'hsv', 'yiq','lab'
  • tốc độ: tốc độ hoạt hình
  • chuyển động: true- nếu hoạt hình hiển thị chuyển động từ vị trí bắt đầu đến kết thúc
  • xung quanh: nếu 'surrounding' thuật toán được chọn, đây là trọng số của xung quanh sẽ được tính đến khi tính trọng số của pixel đã cho
  • hsv: nếu 'hsv' thuật toán được chọn, các tham số này sẽ kiểm soát mức độ màu sắc, độ bão hòa và giá trị ảnh hưởng đến các trọng số
  • yiq: nếu 'qiv' thuật toán được chọn, các tham số này sẽ kiểm soát mức độ yiq ảnh hưởng đến các trọng số
  • phòng thí nghiệm: nếu 'lab' thuật toán được chọn, các tham số này sẽ kiểm soát mức độ ảnh hưởng của lab
  • tiếng ồn: bao nhiêu ngẫu nhiên sẽ được thêm vào trọng lượng
  • độc đáo: các pixel từ bảng màu chỉ nên được sử dụng một lần (xem: Photomosaics hoặc: Cần bao nhiêu lập trình viên để thay thế một bóng đèn? )
  • pixel_1 / pixel_2 {width, height}: kích thước của pixel (tính bằng pixel: D)

Thư viện ảnh (dành cho các phòng trưng bày Tôi luôn sử dụng Mona Lisa & American Gothic, trừ khi có quy định khác):


Các hình ảnh động trông thật tuyệt! nhưng hình ảnh của bạn ngắn hơn một pixel so với bình thường.
Sở thích của Calvin

@ Sở thích của Calvin - Phải cắt nó bằng sơn: P Có lẽ đó là nơi mà sự khác biệt đến từ. Đã cập nhật!

Tôi thích cái này: jsfiddle.net/q865W/4
Justin

@Quincunx Chúc mừng! Với phiên bản có trọng số, nó hoạt động thậm chí còn tốt hơn
kể từ

Ồ 0_0 Điều đó thực sự tốt. jsfiddle.net/q865W/6
Justin

24

C, với không gian màu Lab và phối màu được cải thiện

Tôi đã nói tôi đã làm xong? Tôi đã nói dối. Tôi nghĩ thuật toán trong giải pháp khác của tôi là tốt nhất ngoài kia, nhưng Perl không đủ nhanh để thực hiện các nhiệm vụ khủng hoảng số, vì vậy tôi đã thực hiện lại công việc của mình trong C. Bây giờ nó chạy tất cả các hình ảnh trong bài đăng này, với chất lượng cao hơn so với bản gốc ở khoảng 3 phút mỗi hình ảnh và chất lượng thấp hơn một chút (mức 0,5%) chạy trong 20-30 giây mỗi hình ảnh. Về cơ bản, tất cả các công việc đều được thực hiện với ImageMagick và việc phối màu được thực hiện bằng phép nội suy spline khối của ImageMagick, mang lại kết quả tốt hơn / ít hoa văn hơn.

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

Biên dịch với

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

Các kết quả

Chủ yếu giống như phiên bản Perl, chỉ tốt hơn một chút, nhưng có một vài ngoại lệ. Phối màu ít được chú ý nói chung. Scream -> Starry Night không có hiệu ứng "ngọn lửa rực rỡ" và Camaro trông bớt rối mắt hơn với các pixel màu xám. Tôi nghĩ mã không gian màu của phiên bản Perl có lỗi với các pixel bão hòa thấp.

Bảng màu gothic Mỹ

Bảng màu Mona Lisa

Bảng màu đêm đầy sao

Bảng màu hét

Bảng màu hình cầu

Mustang (bảng màu Camaro)

Camaro (bảng màu Mustang)


Vâng thưa ngài, quả thực là của bạn tốt nhất ngoài kia. Tại sao trong C nó tạo ra 0,5% tồi tệ hơn?
RMalke

@RMalke Nó chỉ tệ hơn khi anh ta chỉ để nó chạy trong 20-30 giây.
trlkly

Bạn có thể vui lòng gửi các giá trị bạn đã sử dụng như nodither_pct, dither_pctdetailtrong ví dụ này? Tôi đang chạy chương trình của bạn với các kết hợp khác nhau, nhưng đối với hình ảnh của tôi, chúng dường như không tối ưu, và các bảng màu gần với các kết hợp của bạn, vì vậy ... làm ơn?
Andreï Kostyrka

@ AndreïKostyrka 0.1 0.1 1.6là những giá trị tôi đã sử dụng để tạo ra những hình ảnh này.
hobbs

@ AndreïKostyrka 0.5 0.5 1.6sẽ cho chất lượng gần như tốt với tốc độ nhanh hơn nhiều.
hobbs

23

Giá trị gần nhất của HSL với sự lan truyền lỗi và phối màu

Tôi đã thực hiện các điều chỉnh nhỏ cho mã mà tôi đã sử dụng cho hình ảnh AllRGB của mình . Điều này được thiết kế để xử lý hình ảnh 16 megapixel với các hạn chế về thời gian và bộ nhớ hợp lý, do đó, nó sử dụng một số lớp cấu trúc dữ liệu không có trong thư viện chuẩn; tuy nhiên, tôi đã bỏ qua những thứ đó vì đã có rất nhiều mã ở đây và đây là mã thú vị.

Đối với AllRGB, tôi điều chỉnh thủ công các sóng con ưu tiên cho các khu vực nhất định của hình ảnh; đối với việc sử dụng không có điều kiện này, tôi chọn một wavelet giả định quy tắc bố trí thứ ba đặt lợi ích chính xuống một phần ba từ trên xuống.

American Gothic với bảng màu từ Mona Lisa Mona Lisa với bảng màu từ American Gothic

Yêu thích của tôi trong số 36:

Dòng sông với bảng màu từ Mona Lisa

Sản phẩm đầy đủ của Cartesian của (hình ảnh, bảng màu)

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

Con trăn

Không phải là tiền mã hóa đẹp, cũng không phải do kết quả.

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

Những cải tiến có thể có:

  • Chỉnh màu thông minh hơn?
  • chất lượng tốt hơn?
  • đẩy lỗi đến tất cả các pixel xung quanh chứ không phải một

Xấu xí (1-> 2): 1-> 2

Tốt hơn một chút (2-> 1): 2-> 1

Decent (2-> 3): 2-> 3

Giống như một raytracer xấu (3-> 4): 3-> 4

Gian lận - sử dụng tất cả các pixel tốt ở nửa trên và yêu cầu sơn hết: 1-> 2


3
Điều cuối cùng là ... một ý tưởng thú vị. Nhưng vẫn không nâng cao.
John Dvorak

20

Python (sử dụng cây kd và độ sáng)

Thử thách đẹp. Tôi quyết định đi theo cách tiếp cận cây kd. Vì vậy, ý tưởng cơ bản đằng sau việc sử dụng phương pháp cây kd là nó phân chia màu sắc và độ sáng theo sự hiện diện của chúng trong hình.

Vì vậy, đối với cây kd, loại đầu tiên được dựa trên màu đỏ. Nó phân chia tất cả các màu thành hai nhóm màu đỏ gần bằng nhau (đỏ nhạt và đỏ đậm). Tiếp theo, nó phân chia hai phân vùng dọc theo green. Tiếp theo màu xanh và sau đó là độ sáng và sau đó lại màu đỏ. Và như vậy cho đến khi cây đã được xây dựng. Trong phương pháp này, tôi đã xây dựng một cây kd cho hình ảnh nguồn và hình ảnh đích. Sau đó, tôi ánh xạ cây từ nguồn tới đích và ghi đè dữ liệu màu của tệp đích. Tất cả các kết quả được hiển thị ở đây .

Vài ví dụ:

Mona Lisa -> Mỹ gothic

nàng mô na Li Sa gothic Mỹ (phong cách mona_lisa)

Mỹ gothic -> Mona Lisa

mỹ gothic mona_lisa (phong cách gothic Mỹ)

Đêm đầy sao -> Tiếng thét

đêm đầy sao tiếng hét đầy sao

Tiếng thét -> Đêm đầy sao

hét lên sao la hét

Quả cầu cầu vồng

nhập mô tả hình ảnh ở đây bóng lisa bóng la hét

Đây là bộ phim ngắn sử dụng nhà sản xuất khung phim @ Calvin's Sở thích:

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

Và bây giờ cho mã :-)

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

Tôi đã không nhận thấy điều này một năm trước nhưng nó khá tốt!
hobbs 29/07/2015

16

Con trăn

Chỉ để giữ cho quả bóng lăn, đây là câu trả lời đơn giản và đau đớn của riêng tôi.

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

Đối với mỗi pixel trong nguồn, nó tìm kiếm pixel không sử dụng trong bảng màu gần nhất trong khối màu RGB. Về cơ bản, nó giống như thuật toán của Quincunx nhưng không có tính ngẫu nhiên và chức năng so sánh màu khác nhau.

Bạn có thể nói tôi di chuyển từ trái sang phải vì phía bên phải của hình ảnh có ít chi tiết hơn do sự cạn kiệt của các màu tương tự.

Dòng sông từ American Gothic

Dòng sông từ American Gothic

Mona Lisa từ Rainbow Spheres

Mona Lisa từ Rainbow Spheres


1
Mẹ ơi. Lisa có một chút màu vàng ...
tomsmeding

4
Tôi thực sự thích quá trình chuyển đổi trên sông từ American Gothic từ trái 'đẹp' sang phải 'trừu tượng' =)
flawr

12

Haskell

Tôi đã thử một vài cách tiếp cận khác nhau bằng cách sử dụng các tìm kiếm hàng xóm gần nhất trước khi giải quyết giải pháp này (đó thực sự là ý tưởng đầu tiên của tôi). Trước tiên tôi chuyển đổi định dạng pixel của hình ảnh thành YCbCr và tạo hai danh sách chứa dữ liệu pixel của chúng. Sau đó, các danh sách được sắp xếp ưu tiên cho giá trị độ chói. Sau đó, tôi chỉ cần thay thế danh sách pixel được sắp xếp của hình ảnh đầu vào bằng hình ảnh bảng màu, sau đó sử dụng lại vị trí ban đầu và sử dụng nó để tạo một hình ảnh mới.

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

Các kết quả

Các hình ảnh mà chương trình của tôi tạo ra có xu hướng kém sinh động hơn so với nhiều giải pháp khác và nó không xử lý tốt các khu vực rắn lớn hoặc độ dốc tốt.

Đây là một liên kết đến album đầy đủ.

Mỹ gothic -> Mona Lisa

Mona Lisa -> Mỹ gothic

Hình cầu -> Mona Lisa

Tiếng thét -> Đêm đầy sao

Tiếng thét -> Hình cầu


3
Tôi thích phối màu trên (Spheres -> Mona Lisa) nhưng những tạo tác xấu xí đó ở đâu (Scream -> Spheres) từ đâu?
John Dvorak

1
Các tạo phẩm là một tác dụng phụ của cách thuật toán của tôi sắp xếp các pixel. Ngay bây giờ, sự khác biệt màu đỏ của mỗi pixel đã được ưu tiên so với sự khác biệt màu xanh trong bước sắp xếp, điều đó có nghĩa là các màu tương tự trong hình ảnh đầu vào có thể được kết hợp với các màu rất khác nhau từ hình ảnh bảng màu. Tuy nhiên, tôi gần như chắc chắn rằng hiệu ứng tương tự này là nguyên nhân gây ra sự phối màu rõ ràng trong các hình ảnh như Spheres -> Mona Lisa, vì vậy tôi quyết định giữ nó.
ChaseC

9

Java

Lấy cảm hứng từ câu trả lời java trước đó từ 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 -> Nông dân

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

Nó sắp xếp những điểm cần thay thế bằng cường độ của chúng, thay vì ngẫu nhiên.


8

Hồng ngọc

Tổng quan:

Cách tiếp cận thực sự đơn giản, nhưng dường như nhận được kết quả khá tốt:

  1. Lấy bảng màu và nhắm mục tiêu, sắp xếp các pixel của chúng theo một số chức năng; gọi đây là các mảng "tham chiếu". Tôi đã chọn sắp xếp theo HSLA, nhưng thích Luminance to Saturation đến Hue (hay còn gọi là "LSHA")
  2. Tạo hình ảnh đầu ra bằng cách lặp qua từng pixel của hình ảnh mục tiêu, tìm nơi nó được sắp xếp trong mảng tham chiếu đích và lấy pixel từ bảng màu được sắp xếp theo cùng một chỉ mục trong mảng tham chiếu bảng màu.

Mã số:

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)

Các kết quả:

http://imgur.com/a/Iu7Ds

Điểm nổi bật:

Đêm đầy sao làm từ Scream American Gothic được làm từ Mona Lisa Mona Lisa làm từ ảnh sông Bức ảnh chụp từ đêm đầy sao


2
Bạn có thể thêm các bảng nguồn cho mỗi hình ảnh?
PlasmaHH

7

Perl

Đây là một cách tiếp cận khá đơn giản. Mất khoảng năm giây để tạo 100 khung hình cho mỗi cặp hình ảnh trên MacBook Pro của tôi với dung lượng bộ nhớ khoảng 120 MB.

Ý tưởng là sắp xếp các pixel trong cả hai và hình ảnh bảng màu theo RGB được đóng gói 24 bit và thay thế màu trong nguồn bằng các màu từ bảng màu theo tuần tự.

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

Đầu ra

Hét lên bằng bảng màu Starry Night

Hét lên bằng bảng màu Starry Night

American Gothic sử dụng màu sắc Mona Lisa

American Gothic sử dụng màu sắc Mona Lisa

Mona Lisa sử dụng màu Scream

Mona Lisa sử dụng màu Scream

Dòng sông sử dụng màu bi

Dòng sông sử dụng màu bi

Ảnh động

Tôi lười biếng, vì vậy tôi đã đưa hoạt hình lên YouTube: Mona Lisa sử dụng màu sắc từ Starry NightAmerican Gothic sử dụng màu sắc từ Mona Lisa .


7

Con trăn

Hình tôi sẽ tận dụng cơ hội nhỏ này để chơi golf mã và sử dụng nó như một cái cớ để làm việc trên sườn Python của tôi vì nó xuất hiện thường xuyên hơn trong công việc những ngày này. Tôi đã chạy qua một vài thuật toán, bao gồm một vài thuật toán có thời gian O (n ^ 2) và O (nlog (n)) để thử và tối ưu hóa màu sắc, nhưng rõ ràng là điều này rất tốn kém về mặt tính toán và thực sự có rất ít ảnh hưởng đến kết quả rõ ràng. Vì vậy, dưới đây là những gì tôi đã thực hiện đối với những thứ hoạt động trong thời gian O (n) (về cơ bản là tức thời trên hệ thống của tôi) có được yếu tố thị giác quan trọng nhất (độ chói) là hợp lý và cho phép sắc độ ở nơi có thể.

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

Kết quả cuối cùng có một số hậu quả gọn gàng. Tuy nhiên, nếu các hình ảnh có sự phân bố độ chói cực kỳ khác nhau, sẽ không thể làm được gì nhiều nếu không tiến bộ và phối màu, đây có thể là một điều thú vị để làm ở một số điểm, nhưng bị loại trừ ở đây là một vấn đề ngắn gọn.

Mọi thứ -> Mona Lisa

Mỹ gothic -> Mona Lisa Đêm đầy sao -> Mona Lisa Hét lên -> Mona Lisa Sông -> Mona Lisa Hình cầu -> Mona Lisa

Mona Lisa -> Hình cầu

Mona Lisa -> Hình cầu


6

Mathematica - hoán vị ngẫu nhiên

Ý tưởng

Chọn hai pixel trong ảnh nguồn và kiểm tra xem lỗi của ảnh đích có giảm không nếu hai pixel sẽ bị tráo đổi. Chúng tôi thêm một số ngẫu nhiên nhỏ (-d | + d) vào kết quả để tránh cực tiểu cục bộ. Nói lại. Đối với tốc độ làm điều này với 10000 pixel cùng một lúc.

Nó hơi giống một chuỗi ngẫu nhiên Markov. Có lẽ sẽ tốt khi giảm tính ngẫu nhiên trong quá trình tối ưu hóa tương tự như ủ mô phỏng.

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

Các kết quả

Gothic đến Mona Lisa. Trái: Sử dụng không gian màu LAB (delta = 0). Phải: Sử dụng không gian màu RBG (delta = 0) img7 img8

Gothic đến Mona Lisa. Trái: Không gian màu RGB, delta = 0,05. Phải: Không gian màu RGB, delta = 0,15. img9 img10

Các hình ảnh sau đây cho thấy hình ảnh động cho khoảng 3.500.000 giao dịch hoán đổi với không gian màu RGB và delta = 0.

img1 img2 img3 img4 img5 img6


Trông giống như cách của aditsu, nhưng tôi đang mong chờ kết quả của bạn.
Leif

5

Chế biến

Nguồn và bảng màu được hiển thị cạnh nhau và có một hình ảnh động của các pixel được lấy từ bảng màu;

Trong dòng int i = chooseIndexIncremental();, bạn có thể thay đổi các chooseIndex*chức năng để xem thứ tự lựa chọn của các pixel.

int scanRate = 20; // pixels per frame

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

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

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

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

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

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

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

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

  area = areaSrc;

  println("Area: " + area);

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

  randomSeed(1);
}

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

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

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

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

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

int chooseIndexIncremental() {
  return count;
}

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

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

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

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

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

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

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

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

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

American Gothic -> Mona Lisa, gia tăng

gia tăng

American Gothic -> Mona Lisa, ngẫu nhiên

ngẫu nhiên


2
Nó trông như thế nào nếu bạn sử dụng bảng màu cầu vồng?
phyzome

5

C-sắc nét

Không có ý tưởng mới / thú vị, nhưng tôi nghĩ tôi sẽ thử. Đơn giản chỉ cần sắp xếp các pixel, ưu tiên độ sáng hơn độ bão hòa trên màu sắc. Mã này là hợp lý ngắn mặc dù, cho những gì giá trị của nó.

EDIT: Đã thêm một phiên bản thậm chí ngắn hơn

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

Mẫu vật:

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

+

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

= =

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


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

Đây là một ý tưởng hoàn toàn khác. Tôi tạo một danh sách các màu của mỗi hình ảnh, sau đó tôi sắp xếp theo màu sắc, được tính theo công thức của wikipedia:

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

Không giống như câu trả lời khác của tôi, điều này là cực kỳ nhanh chóng. Mất khoảng 2 giây, bao gồm cả xác nhận.

Kết quả là một số nghệ thuật trừu tượng. Dưới đây là một số hình ảnh (di chuột để xem đến / từ):

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


5
Có vẻ như một cái gì đó Động vật ăn thịt sẽ nhìn thấy o_O
gọi là

Đây là khá đáng sợ, nhưng họ thực sự là chính xác!
Sở thích của Calvin

1
@ Calvin'sHob sở thích Điều này đáng sợ như thế nào? Tôi gọi nó là vẻ đẹp.
Justin

3
Khuôn mặt của họ trống rỗng và kỳ quái ... nhưng họ có một vẻ đẹp ám ảnh.
Sở thích của Calvin

1
Các hình cầu là tuyệt vời.
siledh

5

Con trăn

Vâng, tôi quyết định tôi cũng có thể đăng giải pháp của mình, vì tôi đã dành thời gian để làm điều đó. Về cơ bản, những gì tôi hình dung tôi sẽ làm là lấy dữ liệu pixel thô của hình ảnh, sắp xếp dữ liệu theo độ sáng và sau đó đặt các giá trị của cùng một chỉ mục vào một hình ảnh mới. Tôi đã thay đổi suy nghĩ về độ sáng và thay vào đó là sử dụng độ chói. Tôi đã nhận được một số kết quả khá tốt với điều này.

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

Các kết quả

Tôi khá hài lòng với kết quả. Nó dường như hoạt động nhất quán cho tất cả các hình ảnh tôi đưa qua nó.

Đêm đầy sao với Scream Pixels

Tiếng thét + Đêm đầy sao

Đêm đầy sao với Rainbow Pixels

Đêm cầu vồng + đầy sao

Cầu vồng với Starry Night Pixels

Đêm đầy sao + Cầu vồng

Mona Lisa với Scream Pixels

Hét lên + Mona Lisa

Dòng sông với Starry Night Pixels

Đêm đầy sao + Sông

Mona Lisa với Pixels American American

Gothic + Mona Lisa

Mustang với Chevy Pixels

Tôi có lẽ nên thu nhỏ hình ảnh xuống với những hạn chế phần cứng của mình, nhưng ồ.

Chevy + Mustang

Chevy với Mustang Pixels

Mustang + Chevy

Dòng sông với cầu vồng

Cầu vồng + sông

Mona Lisa với Rainbow Pixels

Cầu vồng + Mona Lisa

Mỹ gothic với Rainbow Pixels

Cầu vồng + Gô tích


Cập nhật Tôi đã thêm một vài hình ảnh nữa, và đây là một vài hình ảnh động. Cách thứ nhất cho thấy phương thức của tôi hoạt động như thế nào và cách thứ hai là sử dụng tập lệnh @ Calvin'sHob sở thích được đăng.

Phương pháp của tôi

@ Calvin'sHob sở thích kịch bản


Cập nhật 2 Tôi đã thêm một lệnh lưu trữ các chỉ số pixel theo màu của chúng. Điều này làm cho kịch bản cách hiệu quả hơn. Để xem bản gốc, kiểm tra lịch sử sửa đổi.


5

C ++ 11

Cuối cùng, tôi giải quyết một thuật toán tham lam xác định tương đối đơn giản. Đây là một luồng đơn, nhưng chạy trong một sợi tóc hơn 4 giây trên máy của tôi.

Thuật toán cơ bản hoạt động bằng cách sắp xếp tất cả các pixel trong cả bảng màu và hình ảnh mục tiêu bằng cách giảm độ chói (L của L a b * ). Sau đó, với mỗi pixel mục tiêu được sắp xếp, nó tìm kiếm kết quả gần nhất trong 75 mục đầu tiên của bảng màu, sử dụng bình phương của thước đo khoảng cách CIEDE2000 với độ chói của màu bảng được kẹp với màu của mục tiêu. (Để thực hiện và gỡ lỗi CIEDE2000, trang này rất hữu ích). Kết quả phù hợp nhất sau đó được xóa khỏi bảng màu, được gán cho kết quả và thuật toán chuyển sang pixel sáng nhất tiếp theo trong ảnh đích.

Bằng cách sử dụng độ chói được sắp xếp cho cả mục tiêu và bảng màu, chúng tôi đảm bảo rằng độ chói tổng thể (yếu tố nổi bật nhất) của kết quả khớp với mục tiêu càng sát càng tốt. Sử dụng một cửa sổ nhỏ gồm 75 mục sẽ giúp bạn tìm ra màu sắc phù hợp với độ sáng phù hợp, nếu có. Nếu không có màu thì màu sẽ tắt, nhưng ít nhất độ sáng phải nhất quán. Kết quả là, nó xuống cấp khá duyên dáng khi màu bảng màu không phù hợp.

Để biên dịch cái này, bạn sẽ cần các thư viện phát triển ImageMagick ++. Một tệp CMake nhỏ để biên dịch nó cũng được bao gồm bên dưới.

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 )

Kết quả

Album đầy đủ ở đây. Trong số các kết quả dưới đây, yêu thích của tôi có lẽ là American Gothic với bảng màu Mona Lisa và Starry Night với bảng Spheres.

Bảng màu gothic Mỹ

Bảng màu Mona Lisa

Bảng màu sông

Bảng màu

Bảng màu cầu

Bảng màu đêm đầy sao


Điều này có vẻ tuyệt vời! Bạn nghĩ gì về việc này có thể được tăng tốc bao nhiêu? Có một cơ hội cho thời gian thực, tức là 60fps trên phần cứng trung bình?
danijar

4

C ++

Không phải là mã ngắn nhất, nhưng tạo ra câu trả lời 'ngay lập tức' mặc dù được xử lý đơn luồng và không được tối ưu hóa. Tôi hài lòng với kết quả.

Tôi tạo hai danh sách pixel được sắp xếp, một cho mỗi hình ảnh và việc sắp xếp dựa trên giá trị trọng số của 'độ sáng'. Tôi sử dụng 100% màu xanh lá cây, 50% màu đỏ và 10% màu xanh để tính độ sáng, cân nó cho mắt người (nhiều hay ít). Sau đó, tôi trao đổi pixel trong hình ảnh nguồn cho cùng pixel được lập chỉ mục trong hình ảnh bảng màu và viết ra hình ảnh đích.

Tôi sử dụng thư viện FreeImage để đọc / ghi các tập tin hình ảnh.

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

Các kết quả

American Gothic sử dụng bảng màu Mona Lisa American Gothic sử dụng bảng màu Mona Lisa American Gothic sử dụng bảng màu Rainbow American Gothic sử dụng bảng màu Rainbow Cầu vồng Mona Lisa sử dụng bảng màu Scream Mona Lisa sử dụng bảng màu Scream Mona Lisa sử dụng bảng màu Rainbow Mona Lisa sử dụng bảng màu Rainbow Hét lên bằng bảng màu Starry Night Scream sử dụng bảng màu Starry Night


4

C #

Các điểm được sắp xếp đi bộ ngẫu nhiên, bắt đầu từ trung tâm. luôn luôn có được màu sắc gần nhất trong hình ảnh bảng màu. Vì vậy, các pixel cuối cùng có phần rất xấu.

Các kết quả

Bảng màu gothic

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

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

cặp vợ chồng người Mỹ đến từ wikipedia

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

Bảng màu Mona

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

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

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

Mã số:

Tôi không biết tại sao nhưng mã khá chậm ...

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
Đây là khá tuyệt vời. Chúng trông giống như những bức ảnh đã bị đốt cháy hoặc để lại ở đâu đó để mục nát.

Cảm ơn, A đã thực hiện một số thuật toán, nhưng các thuật toán khác giống như các câu trả lời khác. Vì vậy, tôi đã đăng bài đặc biệt hơn
RMalke

3

C #

So sánh màu sắc bằng cách xa chúng. Bắt đầu từ trung tâm.

EDIT: Đã cập nhật, bây giờ sẽ nhanh hơn khoảng 1,5 lần.

American Gothic
nhập mô tả hình ảnh ở đây
The Scream
nhập mô tả hình ảnh ở đây
Starry Night
nhập mô tả hình ảnh ở đây
Marbled
nhập mô tả hình ảnh ở đây
River
nhập mô tả hình ảnh ở đây
Ngoài ra, đây là chiếc Chevy màu vàng:
nhập mô tả hình ảnh ở đây

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

Tôi quyết định thử sử dụng một thuật toán rất giống như câu trả lời khác của tôi, nhưng chỉ hoán đổi các khối pixel 2x2 thay vì các pixel riêng lẻ. Thật không may, thuật toán này thêm một ràng buộc bổ sung là yêu cầu kích thước hình ảnh chia hết cho 2, điều này làm cho các quả cầu raytraced không thể sử dụng được trừ khi tôi thay đổi kích thước.

Tôi thực sự thích một số kết quả!

American Gothic với bảng màu sông:

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

Mona Lisa với bảng màu gothic Mỹ:

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

Mona Lisa với bảng màu sông:

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

Tôi cũng đã thử 4 x 4, và đây là mục yêu thích của tôi!

Đêm đầy sao với bảng màu Scream:

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

Mona Lisa với bảng màu gothic Mỹ:

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

The Scream với bảng màu Mona Lisa:

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

American Gothic với bảng màu Mona Lisa:

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


1
Đã suy nghĩ về việc làm điều tương tự + tính trọng lượng pixel dựa trên các khối vuông. Tôi rất thích kết quả của Mona Lisa - họ nhắc tôi về hình ảnh từ hình ảnh. Bạn có thể bằng bất kỳ cơ hội nào để làm khối 4 không?

1
@eithedog Mình đã dùng thử 4x4 và có vẻ khá tốt. Xem câu trả lời cập nhật của tôi!
LVBen

3

C #

Điều này thực sự thực sự rất chậm, nhưng nó làm rất tốt, đặc biệt là khi sử dụng bảng màu hình cầu raytraced.

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

Bảng màu Scream:

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

Bảng màu Mona Lisa:

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

Bảng màu gothic Mỹ:

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

Bảng màu sông:

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

Bảng màu Starry Night:

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

   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 - Cách tiếp cận ánh xạ khác

Chỉnh sửa 1: Sau khi được chia sẻ trong môi trường "toán học" trên G +, tất cả chúng ta dường như sử dụng các phương pháp phù hợp với nhiều cách khác nhau để tránh sự phức tạp.

Chỉnh sửa 2: Tôi đã làm hỏng các hình ảnh trong ổ đĩa google của mình và khởi động lại, vì vậy các liên kết cũ không còn hoạt động nữa. Xin lỗi, tôi vẫn đang làm việc với danh tiếng nhiều hơn cho nhiều liên kết hơn.

Chỉnh sửa 3: Đọc các bài viết khác tôi có một số cảm hứng. Tôi đã nhận được chương trình nhanh hơn bây giờ và tái đầu tư một số thời gian CPU, để thực hiện một số thay đổi tùy thuộc vào vị trí hình ảnh mục tiêu.

Chỉnh sửa 4: Phiên bản chương trình mới. Nhanh hơn! Điều trị đặc biệt cho cả hai khu vực có góc nhọn và thay đổi rất trơn tru (giúp ích rất nhiều cho việc dò tia, nhưng đôi khi lại có đôi mắt đỏ Mona Lisa)! Khả năng tạo khung trung gian từ hình ảnh động!

Tôi thực sự yêu thích ý tưởng và loại giải pháp Quincunx khiến tôi tò mò. Vì vậy, tôi nghĩ rằng tôi có thể có thể thêm 2 Euro của mình.

Ý tưởng là, rõ ràng là chúng ta cần một ánh xạ (bằng cách nào đó gần) giữa hai bảng màu.

Với ý tưởng này, tôi đã dành đêm đầu tiên cố gắng tìm ra thuật toán kết hôn ổn định để chạy nhanh và với bộ nhớ PC của tôi trên 123520 ứng cử viên. Trong khi tôi đi vào phạm vi bộ nhớ, tôi thấy vấn đề thời gian chạy không thể giải quyết được.

Đêm thứ hai, tôi quyết định chỉ đi sâu hơn và đi sâu vào Thuật toán Hungary , hứa sẽ cung cấp các thuộc tính gần đúng, tức là khoảng cách tối thiểu giữa các màu trong một trong hai hình ảnh. May mắn thay, tôi đã tìm thấy 3 sẵn sàng để triển khai các triển khai Java này (không tính nhiều bài tập học sinh bán thành phẩm bắt đầu làm cho google thực sự khó khăn cho các thuật toán cơ bản). Nhưng như người ta có thể mong đợi, Thuật toán Hungary thậm chí còn tồi tệ hơn về thời gian chạy và sử dụng bộ nhớ. Thậm chí tệ hơn, cả 3 lần thực hiện tôi đã thử nghiệm, đều trả về kết quả sai. Tôi rùng mình khi nghĩ về các chương trình khác, có thể dựa trên những chương trình này.

Cách tiếp cận thứ ba (kết thúc đêm thứ hai) rất dễ dàng, nhanh chóng, nhanh chóng và sau tất cả không tệ: Sắp xếp màu sắc trong cả hai hình ảnh theo độ sáng và bản đồ đơn giản theo xếp hạng, tức là tối nhất đến tối nhất, tối thứ hai đến tối thứ hai. Điều này ngay lập tức tạo ra sự tái tạo sắc nét đen và trắng với một số màu ngẫu nhiên được phun xung quanh.

* Cách tiếp cận 4 và cuối cùng cho đến nay (sáng của đêm thứ hai) bắt đầu với ánh xạ độ chói ở trên và thêm các hiệu chỉnh cục bộ cho nó bằng cách áp dụng thuật toán Hungary cho các chuỗi pixel chồng chéo khác nhau. Bằng cách này, tôi đã lập bản đồ tốt hơn và làm việc xung quanh cả sự phức tạp của vấn đề và các lỗi trong việc triển khai.

Vì vậy, đây là một số mã Java, một số phần có thể trông giống với mã Java khác được đăng ở đây. Phiên bản được sử dụng là một phiên bản vá của John Millers trong dự án ontologySimilariy. Đây là cách nhanh nhất tôi tìm thấy và chỉ ra ít lỗi nhất.

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

Thời gian chạy hiện tại là 20 đến 30 giây cho mỗi cặp ảnh trên, nhưng có rất nhiều điều chỉnh để làm cho nó nhanh hơn hoặc có thể có chất lượng hơn một chút từ nó.

Có vẻ như danh tiếng người mới của tôi không đủ cho nhiều liên kết / hình ảnh như vậy, vì vậy đây là một lối tắt văn bản đến thư mục ổ đĩa Google của tôi cho các mẫu hình ảnh: http://goo.gl/qZHTao

Các mẫu tôi muốn hiển thị đầu tiên:

Mọi người -> Mona Lisa http://goo.gl/mGvq9h

Chương trình theo dõi tất cả các tọa độ điểm, nhưng hiện tại tôi cảm thấy kiệt sức và không có kế hoạch thực hiện hoạt hình. Nếu tôi dành nhiều thời gian hơn, tôi có thể tự mình thực hiện một thuật toán Hungary hoặc gấp đôi lịch trình tối ưu hóa cục bộ của chương trình của mình.

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