Photomosaics atau: Berapa Banyak Programmer yang Dibutuhkan untuk Mengganti Bohlam?

33

Saya telah menyusun mosaik 2025 headshots dari avatar pengguna Stack Overflow teratas .
(Klik gambar untuk melihatnya dalam ukuran penuh.)

Mosaik head Head StackOverflow

Tugas Anda adalah menulis algoritma yang akan membuat fotomosaic akurat dari gambar lain menggunakan avatar 48 × 48 piksel dari kisi 45x45 ini.

Gambar Uji

Inilah gambar-gambar uji. Yang pertama, tentu saja, bola lampu!
(Mereka tidak berukuran penuh di sini. Klik gambar untuk melihatnya dalam ukuran penuh. Versi setengah ukuran tersedia untuk The Kiss , A Sunday Afternoon ... , Steve Jobs , dan the spheres .)

bolam Ciuman Sore Minggu di Pulau La Grande Jatte Steve Jobs bola

Terima kasih untuk Wikipedia untuk semua kecuali bidang raytraced.

Pada ukuran penuh gambar-gambar ini semua memiliki dimensi yang dapat dibagi oleh 48. Yang lebih besar harus JPEG sehingga mereka dapat dikompresi cukup untuk diunggah.

Mencetak gol

Ini adalah kontes popularitas. Kiriman dengan mosaik yang paling akurat menggambarkan gambar asli harus dipilih. Saya akan menerima jawaban dengan suara tertinggi dalam satu atau dua minggu.

Aturan

  • Fotomosis Anda harus seluruhnya terdiri dari avatar 48 × 48 piksel yang tidak diubah yang diambil dari mosaik di atas, disusun dalam kisi-kisi.

  • Anda dapat menggunakan kembali avatar di mosaik. (Memang untuk gambar uji yang lebih besar Anda harus melakukannya.)

  • Tunjukkan hasil Anda, tetapi perlu diingat bahwa gambar uji sangat besar, dan StackExchange hanya memungkinkan pengiriman gambar hingga 2MB . Jadi kompres gambar Anda atau tempatkan mereka di tempat lain dan letakkan versi yang lebih kecil di sini.

  • Untuk dikonfirmasi pemenang, Anda harus memberikan versi bola lampu atau mosaik bola PNG. Ini agar saya dapat memvalidasi mereka (lihat di bawah) untuk memastikan Anda tidak menambahkan warna ekstra ke avatar untuk membuat mosaik terlihat lebih baik.

Validator

Skrip Python ini dapat digunakan untuk memeriksa apakah mosaik yang lengkap benar-benar menggunakan avatar yang tidak diubah. Cukup atur toValidatedan allTiles. Ini tidak mungkin bekerja untuk JPEG atau format lossy lainnya karena ia membandingkan hal-hal dengan tepat, 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))

Semoga beruntung semuanya! Saya tidak sabar untuk melihat hasilnya.

Catatan: Saya tahu algoritma photomosaic mudah ditemukan secara online, tetapi belum ada di situs ini. Saya benar-benar berharap kita melihat sesuatu yang lebih menarik daripada algoritma "rata-rata setiap ubin dan setiap ruang kotak dan mencocokkannya" .

Hobi Calvin
sumber
1
Bukankah ini pada dasarnya duplikat dari yang sebelumnya? Hitung warna masing-masing, turunkan target menjadi 2025px dan terapkan algoritma yang ada?
John Dvorak
1
kemungkinan duplikat Gotik Amerika di palet Mona Lisa: Susun ulang piksel
John Dvorak
2
@ JanDvorak Ini mirip tapi saya pikir tidak cukup untuk menjadi duplikat. Algoritma yang Anda sebutkan adalah salah satu cara untuk mendapatkan hasil. Ada banyak solusi yang lebih canggih.
Howard
1
Kucing saya hilang dari avatar :-(
Joey
2
Anda mungkin ingin mengubah "untuk membuat bola lampu" menjadi "untuk mengganti bola lampu".
DavidC

Jawaban:

15

Jawa, jarak rata-rata

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

Algoritma melakukan pencarian melalui semua ubin avatar untuk setiap ruang kotak secara terpisah. Karena ukurannya yang kecil saya tidak mengimplementasikan struktur data yang canggih atau algoritma pencarian tetapi hanya memaksa seluruh ruang.

Kode ini tidak melakukan modifikasi apa pun pada ubin (mis. Tidak ada penyesuaian dengan warna tujuan).

Hasil

Klik untuk gambar ukuran penuh.

bola lampu bola
Minggu

Efek jari-jari

Menggunakan radiusAnda dapat mengurangi pengulangan ubin di hasilnya. Pengaturan radius=0tidak ada efek. Misalnya radius=3menekan ubin yang sama dalam radius 3 ubin.

bola lampu Minggu radius = 0

bola lampu
bola lampu
radius = 3

Pengaruh faktor penskalaan

Menggunakan scalingfaktor kita dapat menentukan bagaimana ubin yang cocok dicari. scaling=1berarti mencari pasangan yang sempurna piksel sembari scaling=48melakukan pencarian ubin rata-rata.

skala 48
skala = 48

penskalaan 16
skala = 16

penskalaan 4
penskalaan = 4

penskalaan 1
penskalaan = 1

Howard
sumber
1
Wow. Faktor radius benar-benar meningkatkan hasil. Bercak-bercak avatar yang sama itu tidak baik.
John Dvorak
1
Tidak yakin apakah ini saya, tetapi Pictureshack tampaknya memiliki bandwidth yang mengerikan dibandingkan dengan Imgur
Nick T
@NickT Mungkin, tetapi Imgur memampatkan semuanya hingga maksimal 1MB ( imgur.com/faq#size ). :(
Hobbies Calvin
Hmm, apakah hanya aku atau jawaban Mathematica dari David yang jauh lebih baik daripada jawaban terpilih ini?
justhalf
Sayang sekali semua foto itu hilang. Bisakah Anda mengupload ulang imgur secara kebetulan?
MCMastery
19

Mathematica, dengan kontrol untuk granularity

Ini menggunakan foto 48 x 48 piksel, sesuai kebutuhan. Secara default, ini akan menukar piksel tersebut dengan 48x48 piksel persegi yang sesuai dari gambar untuk diperkirakan.

Namun, ukuran kotak tujuan dapat diatur agar lebih kecil dari 48 x 48, memungkinkan untuk kesetiaan yang lebih besar untuk detail. (lihat contoh di bawah).

Memproses ulang palet

collage adalah gambar yang berisi foto untuk dijadikan sebagai palet.

picsColorsadalah daftar foto individu yang dipasangkan dengan nilai rata-rata merah, rata-rata hijau, dan biru.

targetColorToPhoto [] `mengambil warna rata-rata petak target dan menemukan foto dari palet yang paling cocok dengannya.

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

Contoh

Mari kita cari foto yang paling cocok dengan RGBColor [0,640, 0,134, 0,249]:

Contoh 1


photoMosaic

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 mengambil sebagai masukan gambar mentah kita akan membuat mosaik foto.

targetPic akan menghapus parameter keempat (PNG dan beberapa JPG), hanya menyisakan R, G, B.

dims adalah dimensi targetPic .

tiles adalah kotak kecil yang bersama-sama membentuk gambar target.

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

tileReplacements adalah foto yang cocok dengan setiap ubin, dalam urutan yang tepat.

gallery adalah himpunan penggantian ubin (foto) dengan dimensi yang tepat (yaitu jumlah baris dan kolom yang cocok dengan ubin).

ImageAssembly bergabung dengan mosaik menjadi gambar output yang berkelanjutan.


Contohnya

Ini menggantikan setiap 12x12 persegi dari gambar, Minggu, dengan foto 48 x 48 piksel yang sesuai yang paling cocok dengan warna rata-rata.

photoMosaic[sunday, 12]

minggu2


Minggu (detail)

pucuk topi


photoMosaic[lightbulb, 6]

bola lampu 6


photoMosaic[stevejobs, 24]

steve jobs 24


Detail, stevejobs.

detail pekerjaan


photoMosaic[kiss, 24]

ciuman


Detail ciuman:

ciuman detail


photoMosaic[spheres, 24]

bola

DavidC
sumber
1
Saya suka ide granularity. Ini memberi lebih banyak realisme ke gambar yang lebih kecil.
Hobi Calvin
7

JS

Sama seperti pada golf sebelumnya: http://jsfiddle.net/eithe/J7jEk/ : D

(kali ini disebut dengan unique: false, {pixel_2: {width: 48, height: 48}, pixel_1: {width: 48, height: 48}}) (jangan perlakukan palet menggunakan satu piksel sekali, piksel palet adalah swatch 48x48, bentuk pixel adalah swatch 48x48).

Saat ini ia mencari melalui daftar avatar untuk menemukan pencocokan terdekat berdasarkan berat dari algoritma yang dipilih, namun itu tidak melakukan pencocokan keseragaman warna (sesuatu yang perlu saya lihat.

  • seimbang
  • laboratorium

Sayangnya saya tidak dapat bermain-main dengan gambar yang lebih besar, karena RAM saya habis: D Jika memungkinkan saya akan menghargai gambar yang lebih kecil. Jika menggunakan 1/2 dari ukuran gambar yang disediakan, ini Minggu Sore:

  • seimbang
  • laboratorium
eithed
sumber
2
Saya baru saja menambahkan gambar setengah ukuran yang masih dapat dibagi dengan 48 piksel.
Hobi Calvin
5

GLSL

Perbedaan antara tantangan ini dan tantangan di American Gothic dalam palet Mona Lisa: Atur ulang piksel membuat saya tertarik, karena ubin mosaik dapat digunakan kembali, sedangkan piksel tidak bisa. Ini berarti bahwa adalah mungkin untuk dengan mudah memparalelkan algoritma, jadi saya memutuskan untuk mencoba versi paralel besar-besaran. Dengan "secara besar-besaran" yang saya maksud menggunakan core shader 1344 di GTX670 desktop saya secara bersamaan, melalui GLSL.

metode

Pencocokan ubin sebenarnya sederhana: Saya menghitung jarak RGB antara setiap piksel di area target dan area ubin mosaik, dan memilih ubin dengan perbedaan terendah (dibobot oleh nilai kecerahan). Indeks ubin ditulis dalam atribut warna merah dan hijau dari fragmen, kemudian setelah semua fragmen telah diberikan saya membaca nilai-nilai kembali dari framebuffer dan membangun gambar output dari indeks tersebut. Implementasi yang sebenarnya cukup hack; alih-alih membuat FBO, saya hanya membuka jendela dan merendernya, tetapi GLFW tidak dapat membuka jendela dengan resolusi kecil yang sewenang-wenang, jadi saya membuat jendela lebih besar dari yang dibutuhkan, kemudian menggambar persegi panjang kecil yang merupakan ukuran yang benar sehingga memiliki satu fragmen per ubin yang memetakan ke gambar sumber. Seluruh solusi MSVC2013 tersedia dihttps://bitbucket.org/Gibgezr/mosaicmaker Membutuhkan GLFW / FreeImage / GLEW / GLM untuk dikompilasi, dan OpenGL 3.3 atau driver / kartu video yang lebih baik untuk dijalankan.

Sumber Fragmen Shader

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

Hasil

Gambar-gambar itu merender hampir seketika, sehingga paralelisasi itu sukses. Kelemahannya adalah saya tidak bisa membuat fragmen individu bergantung pada output dari fragmen lain, jadi tidak ada cara untuk mendapatkan peningkatan kualitas yang signifikan yang bisa Anda dapatkan dengan tidak memilih ubin yang sama dua kali dalam rentang tertentu. Jadi, hasil yang cepat, tetapi kualitas terbatas karena pengulangan ubin besar-besaran. Secara keseluruhan, itu menyenangkan. http://imgur.com/a/M0Db0 untuk versi ukuran penuh. masukkan deskripsi gambar di sini

Darren
sumber
4

Python

Ini dia solusi Python pertama, menggunakan pendekatan yang berarti. Kita dapat berevolusi dari sini. Sisa gambar ada di sini .

Minggu 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'+'.'))
Willem
sumber
1

Yet Another Python Solution - Rata-Rata Berbasis (RGB vs L a b *)

Hasil (Ada beberapa perbedaan)

Bulb - RGB

tampilan penuh

bulb_rgb

Bulb - Lab

tampilan penuh

bulb_lab

Steve - RGB

tampilan penuh

steve_rgb

Steve - Lab

tampilan penuh

steve_lab

Spheres - RGB

tampilan penuh

spheres_rgb

Spheres - Lab

tampilan penuh

spheres_lab

Minggu - RGB

tampilan penuh

sunday_rgb

Minggu - Lab

tampilan penuh

sunday_lab

Ciuman - RGB

tampilan penuh

kiss_rgb

Ciuman - Lab

tampilan penuh

kiss_lab

Kode

membutuhkan python-colormath untuk 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)
AlexPnt
sumber