Photomosaics hoặc: Cần bao nhiêu lập trình viên để thay thế một bóng đèn?


33

Tôi đã biên soạn một bức tranh khảm 2025 từ các avatar của người dùng Stack Overflow hàng đầu .
(Nhấp vào hình ảnh để xem kích thước đầy đủ.)

Khảm khảm StackOverflow

Nhiệm vụ của bạn là viết một thuật toán sẽ tạo ra một photomosaic chính xác của một hình ảnh khác bằng cách sử dụng các hình đại diện 48 × 48 pixel từ lưới 45 × 45 của chúng.

Hình ảnh thử nghiệm

Dưới đây là những hình ảnh thử nghiệm. Đầu tiên, tất nhiên, là một bóng đèn!
(Chúng không có kích thước đầy đủ ở đây. Nhấp vào hình ảnh để xem nó ở kích thước đầy đủ. Các phiên bản có kích thước bằng một nửa có sẵn cho The Kiss , Chiều chủ nhật ... , Steve Jobscác quả cầu .)

bóng đèn Nụ hôn Một chiều chủ nhật trên đảo La Grande Jatte Steve Jobs hình cầu

Cảm ơn Wikipedia cho tất cả trừ những quả cầu raytraced.

Ở kích thước đầy đủ, tất cả các hình ảnh này đều có kích thước chia hết cho 48. Những hình lớn hơn phải là JPEG để chúng có thể được nén đủ để tải lên.

Chấm điểm

Đây là một cuộc thi phổ biến. Việc đệ trình với các bức tranh khảm mô tả chính xác nhất các hình ảnh gốc nên được bỏ phiếu. Tôi sẽ chấp nhận câu trả lời được bình chọn cao nhất trong một hoặc hai tuần.

Quy tắc

  • Photomosaics của bạn phải hoàn toàn bao gồm các hình đại diện 48 × 48 pixel không thay đổi được lấy từ khảm ở trên, được sắp xếp trong một lưới.

  • Bạn có thể sử dụng lại một hình đại diện trong một bức tranh khảm. (Thật vậy, đối với những hình ảnh thử nghiệm lớn hơn bạn sẽ phải.)

  • Hiển thị đầu ra của bạn, nhưng hãy nhớ rằng hình ảnh thử nghiệm rất lớn và StackExchange chỉ cho phép đăng hình ảnh lên tới 2MB . Vì vậy, nén hình ảnh của bạn hoặc lưu trữ chúng ở một nơi khác và đặt các phiên bản nhỏ hơn ở đây.

  • Để được xác nhận người chiến thắng, bạn phải cung cấp các phiên bản PNG của bóng đèn hoặc hình cầu khảm. Điều này là để tôi có thể xác nhận chúng (xem bên dưới) để đảm bảo bạn không thêm màu bổ sung vào hình đại diện để làm cho bức tranh ghép trông đẹp hơn.

Trình xác nhận

Tập lệnh Python này có thể được sử dụng để kiểm tra xem một khảm hoàn thành có thực sự sử dụng các avatar không thay đổi hay không. Chỉ cần đặt toValidateallTiles. Nó không có khả năng hoạt động đối với JPEG hoặc các định dạng mất dữ liệu khác vì nó so sánh chính xác mọi thứ, pixel-for-pixel.

from PIL import Image, ImageChops

toValidate = 'test.png' #test.png is the mosaic to validate
allTiles = 'avatars.png' #avatars.png is the grid of 2025 48x48 avatars

def equal(img1, img2):
    return ImageChops.difference(img1, img2).getbbox() is None

def getTiles(mosaic, (w, h)):
    tiles = {}
    for i in range(mosaic.size[0] / w):
        for j in range(mosaic.size[1] / h):
            x, y = i * w, j * h
            tiles[(i, j)] = mosaic.crop((x, y, x + w, y + h))
    return tiles

def validateMosaic(mosaic, allTiles, tileSize):
    w, h = tileSize
    if mosaic.size[0] % w != 0 or mosaic.size[1] % h != 0:
        print 'Tiles do not fit mosaic.'
    elif allTiles.size[0] % w != 0 or allTiles.size[1] % h != 0:
        print 'Tiles do not fit allTiles.'
    else:
        pool = getTiles(allTiles, tileSize)
        tiles = getTiles(mosaic, tileSize)
        matches = lambda tile: equal(tiles[pos], tile)
        success = True
        for pos in tiles:
            if not any(map(matches, pool.values())):
                print 'Tile in row %s, column %s was not found in allTiles.' % (pos[1] + 1, pos[0] + 1)
                success = False
        if success:
            print 'Mosaic is valid.'
            return
    print 'MOSAIC IS INVALID!'

validateMosaic(Image.open(toValidate).convert('RGB'), Image.open(allTiles).convert('RGB'), (48, 48))

Chúc mọi người may mắn! Tôi không thể chờ đợi để xem kết quả.

Lưu ý: Tôi biết các thuật toán photomosaic rất dễ tìm thấy trên mạng, nhưng chúng chưa có trên trang web này. Tôi thực sự hy vọng chúng ta sẽ thấy một cái gì đó thú vị hơn thuật toán "trung bình mỗi ô và mỗi không gian lưới và kết hợp chúng" .


1
Đây không phải là bản sao của cái trước sao? Tính toán màu sắc của từng cái, hạ thấp mục tiêu xuống 2025px và áp dụng thuật toán hiện có?
John Dvorak


2
@JanDvorak Tương tự nhưng tôi nghĩ không đủ để trở thành một bản sao. Thuật toán bạn đề cập là một cách để có kết quả. Có nhiều giải pháp phức tạp hơn mặc dù.
Howard

1
Con mèo của tôi bị mất từ ​​avatar :-(
Joey

2
Bạn có thể muốn thay đổi "để làm một bóng đèn" thành "để thay thế một bóng đèn".
DavidC

Câu trả lời:


15

Java, khoảng cách trung bình

package photomosaic;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.imageio.ImageIO;

public class MainClass {

    static final String FILE_IMAGE = "45148_sunday.jpg";
    static final String FILE_AVATARS = "25745_avatars.png";
    static final String FILE_RESULT = "mosaic.png";

    static final int BLOCKSIZE = 48;

    static final int SCALING = 4;

    static final int RADIUS = 3;

    public static void main(String[] args) throws IOException {
        BufferedImage imageAvatars = ImageIO.read(new File(FILE_AVATARS));
        int[] avatars = deBlock(imageAvatars, BLOCKSIZE);

        BufferedImage image = ImageIO.read(new File(FILE_IMAGE));
        int[] data = deBlock(image, BLOCKSIZE);

        // perform the mosaic search on a downscaled version
        int[] avatarsScaled = scaleDown(avatars, BLOCKSIZE, SCALING);
        int[] dataScaled = scaleDown(data, BLOCKSIZE, SCALING);
        int[] bests = mosaicize(dataScaled, avatarsScaled, (BLOCKSIZE / SCALING) * (BLOCKSIZE / SCALING), image.getWidth() / BLOCKSIZE);

        // rebuild the image from the mosaic tiles
        reBuild(bests, data, avatars, BLOCKSIZE);

        reBlock(image, data, BLOCKSIZE);
        ImageIO.write(image, "png", new File(FILE_RESULT));
    }

    // a simple downscale function using simple averaging
    private static int[] scaleDown(int[] data, int size, int scale) {
        int newsize = size / scale;
        int newpixels = newsize * newsize;
        int[] result = new int[data.length / scale / scale];
        for (int r = 0; r < result.length; r += newpixels) {
            for (int y = 0; y < newsize; y++) {
                for (int x = 0; x < newsize; x++) {
                    int avgR = 0;
                    int avgG = 0;
                    int avgB = 0;
                    for (int sy = 0; sy < scale; sy++) {
                        for (int sx = 0; sx < scale; sx++) {
                            int dt = data[r * scale * scale + (y * scale + sy) * size + (x * scale + sx)];
                            avgR += (dt & 0xFF0000) >> 16;
                            avgG += (dt & 0xFF00) >> 8;
                            avgB += (dt & 0xFF) >> 0;
                        }
                    }
                    avgR /= scale * scale;
                    avgG /= scale * scale;
                    avgB /= scale * scale;
                    result[r + y * newsize + x] = 0xFF000000 + (avgR << 16) + (avgG << 8) + (avgB << 0);
                }
            }
        }
        return result;
    }

    // the mosaicize algorithm: take the avatar with least pixel-wise distance
    private static int[] mosaicize(int[] data, int[] avatars, int pixels, int tilesPerLine) {
        int tiles = data.length / pixels;

        // use random order for tile search
        List<Integer> ts = new ArrayList<Integer>();
        for (int t = 0; t < tiles; t++) {
            ts.add(t);
        }
        Collections.shuffle(ts);

        // result array
        int[] bests = new int[tiles];
        Arrays.fill(bests, -1);

        // make list of offsets to be ignored
        List<Integer> ignores = new ArrayList<Integer>();
        for (int sy = -RADIUS; sy <= RADIUS; sy++) {
            for (int sx = -RADIUS; sx <= RADIUS; sx++) {
                if (sx * sx + sy * sy <= RADIUS * RADIUS) {
                    ignores.add(sy * tilesPerLine + sx);
                }
            }
        }

        for (int t : ts) {
            int b = t * pixels;
            int bestsum = Integer.MAX_VALUE;
            for (int at = 0; at < avatars.length / pixels; at++) {
                int a = at * pixels;
                int sum = 0;
                for (int i = 0; i < pixels; i++) {
                    int r1 = (avatars[a + i] & 0xFF0000) >> 16;
                    int g1 = (avatars[a + i] & 0xFF00) >> 8;
                    int b1 = (avatars[a + i] & 0xFF) >> 0;

                    int r2 = (data[b + i] & 0xFF0000) >> 16;
                    int g2 = (data[b + i] & 0xFF00) >> 8;
                    int b2 = (data[b + i] & 0xFF) >> 0;

                    int dr = (r1 - r2) * 30;
                    int dg = (g1 - g2) * 59;
                    int db = (b1 - b2) * 11;

                    sum += Math.sqrt(dr * dr + dg * dg + db * db);
                }
                if (sum < bestsum) {
                    boolean ignore = false;
                    for (int io : ignores) {
                        if (t + io >= 0 && t + io < bests.length && bests[t + io] == at) {
                            ignore = true;
                            break;
                        }
                    }
                    if (!ignore) {
                        bestsum = sum;
                        bests[t] = at;
                    }
                }
            }
        }
        return bests;
    }

    // build image from avatar tiles
    private static void reBuild(int[] bests, int[] data, int[] avatars, int size) {
        for (int r = 0; r < bests.length; r++) {
            System.arraycopy(avatars, bests[r] * size * size, data, r * size * size, size * size);
        }
    }

    // splits the image into blocks and concatenates all the blocks
    private static int[] deBlock(BufferedImage image, int size) {
        int[] result = new int[image.getWidth() * image.getHeight()];
        int r = 0;
        for (int fy = 0; fy < image.getHeight(); fy += size) {
            for (int fx = 0; fx < image.getWidth(); fx += size) {
                for (int l = 0; l < size; l++) {
                    image.getRGB(fx, fy + l, size, 1, result, r * size * size + l * size, size);
                }
                r++;
            }
        }
        return result;
    }

    // unsplits the block version into the original image format
    private static void reBlock(BufferedImage image, int[] data, int size) {
        int r = 0;
        for (int fy = 0; fy < image.getHeight(); fy += size) {
            for (int fx = 0; fx < image.getWidth(); fx += size) {
                for (int l = 0; l < size; l++) {
                    image.setRGB(fx, fy + l, size, 1, data, r * size * size + l * size, size);
                }
                r++;
            }
        }
    }
}

Thuật toán thực hiện tìm kiếm thông qua tất cả các ô hình đại diện cho từng không gian lưới riêng biệt. Do kích thước nhỏ, tôi đã không thực hiện bất kỳ cấu trúc dữ liệu hoặc thuật toán tìm kiếm phức tạp nào mà chỉ đơn giản là vũ phu toàn bộ không gian.

Mã này không thực hiện bất kỳ sửa đổi nào đối với các ô xếp (ví dụ: không thích ứng với màu đích).

Các kết quả

Nhấn vào đây để xem hình ảnh kích thước đầy đủ.

ánh sáng hình cầu
chủ nhật

Ảnh hưởng của bán kính

Sử dụng radiusbạn có thể làm giảm sự lặp đi lặp lại của gạch trong kết quả. Thiết lập radius=0không có hiệu lực. Ví dụ, thay radius=3thế cùng một ô trong bán kính 3 ô.

ánh sáng chủ nhật bán kính = 0

ánh sáng
ánh sáng
bán kính = 3

Ảnh hưởng của hệ số tỷ lệ

Sử dụng scalingyếu tố chúng ta có thể xác định cách tìm kiếm gạch phù hợp. scaling=1có nghĩa là tìm kiếm một kết hợp pixel hoàn hảo trong khi scaling=48tìm kiếm gạch trung bình.

chia tỷ lệ 48
chia tỷ lệ = 48

chia tỷ lệ 16
chia tỷ lệ = 16

chia tỷ lệ 4
chia tỷ lệ = 4

chia tỷ lệ 1
chia tỷ lệ = 1


1
Ồ Yếu tố bán kính thực sự cải thiện kết quả. Những đốm hình avatar đó không tốt.
John Dvorak

1
Không chắc đó có phải là tôi không, nhưng Pictureshack dường như có băng thông khủng khiếp so với Imgur
Nick T

@NickT Có thể, nhưng Imgur nén mọi thứ tối đa 1MB ( imgur.com/faq#size ). :(
Sở thích của Calvin

Hmm, chỉ có tôi hoặc câu trả lời Mathicala của David là tốt hơn nhiều so với câu trả lời được bình chọn hàng đầu này?
vừa qua

Quá xấu tất cả những hình ảnh đã biến mất. Bạn có thể tải lại lên imgur bất kỳ cơ hội nào không?
MCM Abbey

19

Mathicala, với sự kiểm soát về độ chi tiết

Điều này sử dụng ảnh 48 x 48 pixel, theo yêu cầu. Theo mặc định, nó sẽ hoán đổi các pixel đó cho một hình vuông 48x48 pixel tương ứng từ hình ảnh để được xấp xỉ.

Tuy nhiên, kích thước của hình vuông đích có thể được đặt nhỏ hơn 48 x 48, cho phép độ chính xác cao hơn đến từng chi tiết. (xem các ví dụ dưới đây).

Tiền xử lý bảng màu

collage là hình ảnh chứa các bức ảnh để phục vụ như bảng màu.

picsColorslà danh sách các ảnh riêng lẻ được ghép nối với các giá trị trung bình màu đỏ, xanh lục và xanh lam trung bình của chúng.

targetColorToPhoto [] `lấy màu trung bình của swath đích và tìm ảnh từ bảng màu phù hợp nhất với nó.

parts=Flatten[ImagePartition[collage,48],1];
picsColors={#,c=Mean[Flatten[ImageData[#],1]]}&/@parts;
nf=Nearest[picsColors[[All,2]]];

targetColorToPhoto[p_]:=Cases[picsColors,{pic_,nf[p,1][[1]]}:>pic][[1]]

Thí dụ

Hãy tìm bức ảnh phù hợp nhất với RGBColor [0.640, 0.134, 0.249]:

ví dụ 1


ảnhMosaic

photoMosaic[rawPic_, targetSwathSize_: 48] :=
 Module[{targetPic, data, dims, tiles, tileReplacements, gallery},
  targetPic = Image[data = ImageData[rawPic] /. {r_, g_, b_, _} :> {r, g, b}];
  dims = Dimensions[data];
  tiles = ImagePartition[targetPic, targetSwathSize];
  tileReplacements = targetColorToPhoto /@ (Mean[Flatten[ImageData[#], 1]] & /@Flatten[tiles, 1]);
  gallery = ArrayReshape[tileReplacements, {dims[[1]]/targetSwathSize,dims[[2]]/targetSwathSize}];
  ImageAssemble[gallery]]

`photoMosaic lấy làm hình ảnh thô mà chúng ta sẽ tạo ra một bức ảnh khảm.

targetPic sẽ xóa tham số thứ tư (của PNG và một số JPG), chỉ còn lại R, G, B.

dimslà kích thước của targetPic.

tiles là những hình vuông nhỏ cùng nhau bao gồm hình ảnh mục tiêu.

targetSwathSize is the granularity parameter; it defaults at 48 (x48).

tileReplacements là những bức ảnh phù hợp với từng ô, theo đúng thứ tự.

gallery là tập hợp các thay thế gạch (ảnh) với kích thước phù hợp (nghĩa là số lượng hàng và cột khớp với các ô).

ImageAssembly tham gia khảm vào một hình ảnh đầu ra liên tục.


Ví dụ

Điều này thay thế mỗi hình vuông 12x12 từ hình ảnh, Chủ nhật, với một bức ảnh 48 x 48 pixel tương ứng phù hợp nhất với màu trung bình.

photoMosaic[sunday, 12]

chủ nhật2


Chủ nhật (chi tiết)

mũ hàng đầu


photoMosaic[lightbulb, 6]

bóng đèn 6


photoMosaic[stevejobs, 24]

công việc steve 24


Chi tiết, stevejobs.

chi tiết công việc


photoMosaic[kiss, 24]

hôn


Chi tiết về nụ hôn:

chi tiết nụ hôn


photoMosaic[spheres, 24]

hình cầu


1
Tôi thích ý tưởng về độ chi tiết. Nó mang lại tính chân thực hơn cho những hình ảnh nhỏ hơn.
Sở thích của Calvin

7

JS

Tương tự như trong golf trước: http://jsfiddle.net/eithe/J7jEk/ : D

(lần này được gọi với unique: false, {pixel_2: {width: 48, height: 48}, pixel_1: {width: 48, height: 48}} ) (không xử lý bảng màu để sử dụng một pixel một lần, pixel pixel là 48x48 swatches, pixel pixel là 48x48 swatches).

Hiện tại, nó tìm kiếm thông qua danh sách hình đại diện để tìm kết quả khớp gần nhất theo trọng số của thuật toán đã chọn, tuy nhiên nó không thực hiện bất kỳ kết hợp đồng nhất màu nào (điều mà tôi cần xem qua.

  • cân bằng
  • phòng thí nghiệm

Thật không may, tôi không thể chơi xung quanh với hình ảnh lớn hơn, vì RAM của tôi hết: D Nếu có thể tôi sẽ đánh giá cao hình ảnh đầu ra nhỏ hơn. Nếu sử dụng 1/2 kích thước hình ảnh được cung cấp, thì đây là Chiều Chủ nhật:

  • cân bằng
  • phòng thí nghiệm

2
Tôi vừa thêm hình ảnh nửa kích thước vẫn chia hết cho 48 pixel.
Sở thích của Calvin

5

GLSL

Sự khác biệt giữa thử thách này và thử thách tại American Gothic trong bảng màu của Mona Lisa: Sắp xếp lại các pixel khiến tôi quan tâm, bởi vì các ô khảm có thể được sử dụng lại, trong khi các pixel không thể. Điều này có nghĩa là có thể dễ dàng song song hóa thuật toán, vì vậy tôi quyết định thử một phiên bản song song ồ ạt. Bằng cách "ồ ạt", ý tôi là sử dụng đồng thời 1344 shader trên GTX670 trên máy tính để bàn của tôi, thông qua GLSL.

phương pháp

Kết hợp gạch thực tế rất đơn giản: Tôi tính khoảng cách RGB giữa mỗi pixel trong vùng mục tiêu và vùng gạch khảm và chọn lát có chênh lệch thấp nhất (được tính theo giá trị độ sáng). Chỉ số ô được viết ra trong các thuộc tính màu đỏ và xanh lục của đoạn, sau đó sau khi tất cả các mảnh được hiển thị, tôi đọc các giá trị ra khỏi bộ đệm khung và xây dựng hình ảnh đầu ra từ các chỉ số đó. Việc thực hiện thực tế là khá hack; thay vì tạo FBO Tôi chỉ mở một cửa sổ và hiển thị vào nó, nhưng GLFW không thể mở các cửa sổ ở độ phân giải nhỏ tùy ý, vì vậy tôi tạo cửa sổ lớn hơn yêu cầu, sau đó vẽ một hình chữ nhật nhỏ có kích thước chính xác để nó có một mảnh trên mỗi ô ánh xạ tới hình ảnh nguồn. Toàn bộ giải pháp MSVC2013 có sẵn tạihttps://bitbucket.org/Gibgezr/mosaicmaker Nó yêu cầu GLFW / FreeImage / GLEW / GLM để biên dịch và OpenGL 3.3 hoặc trình điều khiển / thẻ video tốt hơn để chạy.

Mảnh vỡ Shader Nguồn

#version 330 core

uniform sampler2D sourceTexture;
uniform sampler2D mosaicTexture;

in vec2 v_texcoord;

out vec4 out_Color;

void main(void)
{   
    ivec2 sourceSize = textureSize(sourceTexture, 0);
    ivec2 mosaicSize = textureSize(mosaicTexture, 0);

    float num_pixels = mosaicSize.x/45.f;
    vec4 myTexel;
    float difference = 0.f;

    //initialize smallest difference to a large value
    float smallest_difference = 255.0f*255.0f*255.0f;
    int closest_x = 0, closest_y = 0;

    vec2 pixel_size_src = vec2( 1.0f/sourceSize.x, 1.0f/sourceSize.y);
    vec2 pixel_size_mosaic = vec2( 1.0f/mosaicSize.x , 1.0f/mosaicSize.y);

    vec2 incoming_texcoord;
    //adjust incoming uv to bottom corner of the tile space
    incoming_texcoord.x =  v_texcoord.x - 1.0f/(sourceSize.x / num_pixels * 2.0f);
    incoming_texcoord.y =  v_texcoord.y - 1.0f/(sourceSize.y / num_pixels * 2.0f);

    vec2 texcoord_mosaic;
    vec2 pixelcoord_src, pixelcoord_mosaic;
    vec4 pixel_src, pixel_mosaic;

    //loop over all of the mosaic tiles
    for(int i = 0; i < 45; ++i)
    {
        for(int j = 0; j < 45; ++j)
        {
            difference = 0.f;
            texcoord_mosaic = vec2(j * pixel_size_mosaic.x * num_pixels, i * pixel_size_mosaic.y * num_pixels);

            //loop over all of the pixels in the images, adding to the difference
            for(int y = 0; y < num_pixels; ++y)
            {
                for(int x = 0; x < num_pixels; ++x)
                {
                    pixelcoord_src = vec2(incoming_texcoord.x + x * pixel_size_src.x, incoming_texcoord.y + y * pixel_size_src.y);                  
                    pixelcoord_mosaic = vec2(texcoord_mosaic.x + x * pixel_size_mosaic.x, texcoord_mosaic.y + y * pixel_size_mosaic.y); 
                    pixel_src = texture(sourceTexture, pixelcoord_src);
                    pixel_mosaic = texture(mosaicTexture, pixelcoord_mosaic);

                    pixel_src *= 255.0f;
                    pixel_mosaic *= 255.0f;

                    difference += (pixel_src.x - pixel_mosaic.x) * (pixel_src.x - pixel_mosaic.x) * 0.5f+
                        (pixel_src.y - pixel_mosaic.y) * (pixel_src.y - pixel_mosaic.y) +
                        (pixel_src.z - pixel_mosaic.z) * (pixel_src.z - pixel_mosaic.z) * 0.17f;
                }

            }

            if(difference < smallest_difference)
            {
                smallest_difference = difference;
                closest_x = j;
                closest_y = i;
            }               
        }
    }

    myTexel.x = float(closest_x)/255.f;
    myTexel.y = float(closest_y)/255.f;
    myTexel.z = 0.f;
    myTexel.w = 0.f;    

    out_Color = myTexel;
}

Các kết quả

Các hình ảnh hiển thị gần như ngay lập tức, vì vậy việc song song hóa là một thành công. Nhược điểm là tôi không thể tạo ra các mảnh riêng lẻ dựa vào đầu ra của bất kỳ mảnh nào khác, vì vậy không có cách nào để tăng chất lượng đáng kể mà bạn có thể nhận được bằng cách không chọn cùng một lát trong hai phạm vi nhất định. Vì vậy, kết quả nhanh, nhưng chất lượng hạn chế do sự lặp lại lớn của gạch. Tất cả trong tất cả, nó là niềm vui. http://imgur.com/a/M0Db0 cho các phiên bản kích thước đầy đủ. nhập mô tả hình ảnh ở đây


4

Con trăn

Ở đây có giải pháp Python đầu tiên, sử dụng cách tiếp cận trung bình. Chúng ta có thể phát triển từ đây. Phần còn lại của hình ảnh là ở đây .

chủ nhật steve

from PIL import Image
import numpy as np

def calcmean(b):
    meansum = 0
    for k in range(len(b)):
        meansum = meansum + (k+1)*b[k]
    return meansum/sum(b)    

def gettiles(imageh,w,h):
    k = 0 
    tiles = {}
    for x in range(0,imageh.size[0],w):
        for y in range(0,imageh.size[1],h):
            a=imageh.crop((x, y, x + w, y + h))
            b=a.resize([1,1], Image.ANTIALIAS)
            tiles[k] = [a,x,y,calcmean(b.histogram()[0:256]) \
                             ,calcmean(b.histogram()[256:256*2]) \
                             ,calcmean(b.histogram()[256*2:256*3])]
            k = k + 1
    return tiles

w = 48 
h = 48

srch = Image.open('25745_avatars.png').convert('RGB')
files = ['21104_spheres.png', '45148_sunday.jpg', '47824_steve.jpg', '61555_kiss.jpg', '77388_lightbulb.png']
for f in range(len(files)):
    desh = Image.open(files[f]).convert('RGB')

    srctiles = gettiles(srch,w,h)
    destiles = gettiles(desh,w,h)

    #build proximity matrix 
    pm = np.zeros((len(destiles),len(srctiles)))
    for d in range(len(destiles)):
        for s in range(len(srctiles)):
            pm[d,s] = (srctiles[s][3]-destiles[d][3])**2 + \
                      (srctiles[s][4]-destiles[d][4])**2 + \
                      (srctiles[s][5]-destiles[d][5])**2

    for k in range(len(destiles)):
        j = list(pm[k,:]).index(min(pm[k,:]))
        desh.paste(srctiles[j][0], (destiles[k][1], destiles[k][2]))

    desh.save(files[f].replace('.','_m'+'.'))

1

Một giải pháp Python khác - Dựa trên trung bình (RGB so với L a b *)

Kết quả (Có một số khác biệt nhỏ)

Bóng đèn - RGB

xem toàn bộ

bóng đèn

Bóng đèn - Phòng thí nghiệm

xem toàn bộ

củ_lab

Steve - RGB

xem toàn bộ

steve_rgb

Steve - Phòng thí nghiệm

xem toàn bộ

steve_lab

Hình cầu - RGB

xem toàn bộ

hình cầu_rgb

Lĩnh vực - Phòng thí nghiệm

xem toàn bộ

hình cầu_lab

Chủ nhật - RGB

xem toàn bộ

chủ nhật_rgb

Chủ nhật - Phòng thí nghiệm

xem toàn bộ

chủ nhật_lab

Nụ hôn - RGB

xem toàn bộ

hôn_rgb

Nụ hôn - Phòng thí nghiệm

xem toàn bộ

hôn_lab

yêu cầu python-colormath cho Lab

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image
from colormath.color_objects import LabColor,sRGBColor
from colormath.color_conversions import convert_color
from colormath.color_diff import delta_e_cie1976

def build_photomosaic_ils(mosaic_im,target_im,block_width,block_height,colordiff,new_filename):

    mosaic_width=mosaic_im.size[0]              #dimensions of the target image
    mosaic_height=mosaic_im.size[1]

    target_width=target_im.size[0]              #dimensions of the target image
    target_height=target_im.size[1]

    target_grid_width,target_grid_height=get_grid_dimensions(target_width,target_height,block_width,block_height)       #dimensions of the target grid
    mosaic_grid_width,mosaic_grid_height=get_grid_dimensions(mosaic_width,mosaic_height,block_width,block_height)       #dimensions of the mosaic grid

    target_nboxes=target_grid_width*target_grid_height
    mosaic_nboxes=mosaic_grid_width*mosaic_grid_height

    print "Computing the average color of each photo in the mosaic..."
    mosaic_color_averages=compute_block_avg(mosaic_im,block_width,block_height)
    print "Computing the average color of each block in the target photo ..."
    target_color_averages=compute_block_avg(target_im,block_width,block_height)

    print "Computing photomosaic ..."
    photomosaic=[0]*target_nboxes
    for n in xrange(target_nboxes):
        print "%.2f " % (n/float(target_nboxes)*100)+"%"
        for z in xrange(mosaic_nboxes):
            current_diff=colordiff(target_color_averages[n],mosaic_color_averages[photomosaic[n]])
            candidate_diff=colordiff(target_color_averages[n],mosaic_color_averages[z])

            if(candidate_diff<current_diff):
                photomosaic[n]=z

    print "Building final image ..."
    build_final_solution(photomosaic,mosaic_im,target_nboxes,target_im,target_grid_width,block_height,block_width,new_filename)

def build_initial_solution(target_nboxes,mosaic_nboxes):
    candidate=[0]*target_nboxes

    for n in xrange(target_nboxes):
        candidate[n]=random.randint(0,mosaic_nboxes-1)

    return candidate

def build_final_solution(best,mosaic_im,target_nboxes,target_im,target_grid_width,block_height,block_width,new_filename):

    for n in xrange(target_nboxes):

        i=(n%target_grid_width)*block_width             #i,j -> upper left point of the target image
        j=(n/target_grid_width)*block_height

        box = (i,j,i+block_width,j+block_height)        

        #get the best photo from the mosaic
        best_photo_im=get_block(mosaic_im,best[n],block_width,block_height)

        #paste the best photo found back into the image
        target_im.paste(best_photo_im,box)

    target_im.save(new_filename);


#get dimensions of the image grid
def get_grid_dimensions(im_width,im_height,block_width,block_height):
    grid_width=im_width/block_width     #dimensions of the target image grid
    grid_height=im_height/block_height
    return grid_width,grid_height

#compute the fitness of given candidate solution
def fitness(candidate,mosaic_color_averages,mosaic_nboxes,target_color_averages,target_nboxes):
    error=0.0
    for i in xrange(target_nboxes):
        error+=colordiff_rgb(mosaic_color_averages[candidate[i]],target_color_averages[i])
    return error

#get a list of color averages, i.e, the average color of each block in the given image
def compute_block_avg(im,block_height,block_width):

    width=im.size[0]
    height=im.size[1]

    grid_width_dim=width/block_width                    #dimension of the grid
    grid_height_dim=height/block_height

    nblocks=grid_width_dim*grid_height_dim              #number of blocks

    avg_colors=[]
    for i in xrange(nblocks):
        avg_colors+=[avg_color(get_block(im,i,block_width,block_height))]
    return avg_colors

#returns the average RGB color of a given image
def avg_color(im):
    avg_r=avg_g=avg_b=0.0
    pixels=im.getdata()
    size=len(pixels)
    for p in pixels:
        avg_r+=p[0]/float(size)
        avg_g+=p[1]/float(size)
        avg_b+=p[2]/float(size)

    return (avg_r,avg_g,avg_b)

#get the nth block of the image
def get_block(im,n,block_width,block_height):

    width=im.size[0]

    grid_width_dim=width/block_width                        #dimension of the grid

    i=(n%grid_width_dim)*block_width                        #i,j -> upper left point of the target block
    j=(n/grid_width_dim)*block_height

    box = (i,j,i+block_width,j+block_height)
    block_im = im.crop(box)
    return block_im


#calculate color difference of two pixels in the RGB space
#less is better
def colordiff_rgb(pixel1,pixel2):

    delta_red=pixel1[0]-pixel2[0]
    delta_green=pixel1[1]-pixel2[1]
    delta_blue=pixel1[2]-pixel2[2]

    fit=delta_red**2+delta_green**2+delta_blue**2
    return fit

#http://python-colormath.readthedocs.org/en/latest/index.html
#calculate color difference of two pixels in the L*ab space
#less is better
def colordiff_lab(pixel1,pixel2):

    #convert rgb values to L*ab values
    rgb_pixel_1=sRGBColor(pixel1[0],pixel1[1],pixel1[2],True)
    lab_1= convert_color(rgb_pixel_1, LabColor)

    rgb_pixel_2=sRGBColor(pixel2[0],pixel2[1],pixel2[2],True)
    lab_2= convert_color(rgb_pixel_2, LabColor)

    #calculate delta e
    delta_e = delta_e_cie1976(lab_1, lab_2)
    return delta_e


if __name__ == '__main__':
    mosaic="images/25745_avatars.png"
    targets=["images/lightbulb.png","images/steve.jpg","images/sunday.jpg","images/spheres.png","images/kiss.jpg"]
    target=targets[0]
    mosaic_im=Image.open(mosaic)
    target_im=Image.open(target)
    new_filename=target.split(".")[0]+"_photomosaic.png"
    colordiff=colordiff_rgb

    build_photomosaic_ils(mosaic_im,target_im,48,48,colordiff,new_filename)
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.