Fotomosaicos ou: Quantos programadores são necessários para substituir uma lâmpada?

33

Compilei um mosaico de 2025 fotos na cabeça dos avatares dos principais usuários do Stack Overflow .
(Clique na imagem para vê-la em tamanho real.)

Mosaico de headshots StackOverflow

Sua tarefa é escrever um algoritmo que crie um fotomosaico preciso de outra imagem usando os avatares de 48 × 48 pixels dessa grade de 45 × 45 deles.

Imagens de teste

Aqui estão as imagens de teste. O primeiro é, obviamente, uma lâmpada!
(Eles não são em tamanho normal aqui. Clique em uma imagem para vê-la em tamanho normal. As versões em tamanho real estão disponíveis para The Kiss , A Sunday Afternoon ... , Steve Jobs e as esferas .)

lâmpada O beijo Tarde de domingo na ilha de La Grande Jatte Steve Jobs esferas

Obrigado à Wikipedia por todas, exceto as esferas traçadas por raios.

Em tamanho real, essas imagens têm todas as dimensões divisíveis por 48. As maiores tinham que ser JPEGs para que pudessem ser compactadas o suficiente para serem carregadas.

Pontuação

Este é um concurso de popularidade. A submissão com mosaicos que descrevam com mais precisão as imagens originais deve ser votada. Aceitarei a resposta mais votada em uma semana ou duas.

Regras

  • Seu fotomosaico deve ser inteiramente composto por avatares inalterados de 48 × 48 pixels, retirados do mosaico acima, dispostos em uma grade.

  • Você pode reutilizar um avatar em um mosaico. (Na verdade, para as imagens de teste maiores, você precisará.)

  • Mostre sua saída, mas lembre-se de que as imagens de teste são muito grandes e o StackExchange permite apenas a publicação de imagens de até 2 MB . Comprima suas imagens ou hospede-as em outro lugar e coloque versões menores aqui.

  • Para ser confirmado o vencedor, você deve fornecer versões PNG dos mosaicos de lâmpadas ou esferas. Isso é para que eu possa validá-los (veja abaixo) para garantir que você não esteja adicionando cores extras aos avatares para melhorar a aparência dos mosaicos.

Validador

Esse script Python pode ser usado para verificar se um mosaico completo realmente usa avatares inalterados. Basta definir toValidatee allTiles. É improvável que funcione para JPEGs ou outros formatos com perdas, pois compara as coisas exatamente, pixel por 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))

Boa sorte a todos! Mal posso esperar para ver os resultados.

Nota: Eu sei que é fácil encontrar algoritmos fotomosaicos on-line, mas ainda não estão neste site. Eu realmente espero que vejamos algo mais interessante do que o algoritmo usual "média de cada bloco e cada espaço da grade e combiná-los" .

Passatempos de Calvin
fonte
1
Isso não é essencialmente uma duplicata da anterior? Calcular a cor de cada uma, reduzir o alvo para 2025px e aplicar o algoritmo existente?
John Dvorak
2
@JanDvorak É semelhante, mas acho que não o suficiente para ser uma duplicata. O algoritmo que você mencionou é uma maneira de obter um resultado. Existem soluções muito mais sofisticadas.
139 Howard
1
Meu gato está faltando os avatares :-(
Joey
2
Você pode alterar "para fazer uma lâmpada" para "para substituir uma lâmpada".
18134 DavidC

Respostas:

15

Java, distância média

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

O algoritmo realiza uma pesquisa em todos os blocos de avatar para cada espaço da grade separadamente. Devido aos pequenos tamanhos, não implementei nenhuma estrutura sofisticada de dados ou algoritmos de pesquisa, mas simplesmente forço brutal de todo o espaço.

Este código não faz nenhuma modificação nos ladrilhos (por exemplo, nenhuma adaptação às cores de destino).

Resultados

Clique para ampliar a imagem.

lâmpada esferas
domingo

Efeito do raio

Usando radiusvocê pode reduzir a repetitividade dos blocos no resultado. Definir radius=0não há efeito. Por exemplo, radius=3suprime o mesmo bloco em um raio de 3 blocos.

lâmpada domingo raio = 0

lâmpada
lâmpada
raio = 3

Efeito do fator de escala

Usando o scalingfator, podemos determinar como o bloco correspondente é pesquisado. scaling=1significa procurar uma correspondência perfeita em pixels, enquanto scaling=48faz uma pesquisa de blocos médios.

escala 48
escala = 48

escala 16
escala = 16

escala 4
escala = 4

escala 1
escala = 1

Howard
fonte
1
Uau. O fator raio realmente melhora os resultados. Aquelas manchas de mesmo avatar não eram boas.
John Dvorak
1
Não tenho certeza se sou eu, mas Pictureshack parece ter largura de banda atroz comparação com Imgur
Nick T
@NickT Provavelmente, mas o Imgur comprime tudo no máximo 1 MB ( imgur.com/faq#size ). :(
Hobbies de Calvin
Hmm, sou apenas eu ou a resposta do Mathematica de David é muito melhor do que esta resposta mais votada?
justhalf 18/08/14
Pena que todas essas fotos se foram. Você pode reenviar para imgur por acaso?
MCMastery
19

Mathematica, com controle de granularidade

Isso usa as fotos de 48 x 48 pixels, conforme necessário. Por padrão, ele trocará esses pixels por um quadrado de 48x48 pixels correspondente da imagem a ser aproximada.

No entanto, o tamanho dos quadrados de destino pode ser menor que 48 x 48, permitindo maior fidelidade aos detalhes. (veja os exemplos abaixo).

Pré-processando a Paleta

collage é a imagem que contém as fotos para servir como paleta.

picsColorsé uma lista de fotos individuais combinadas com os valores médios de vermelho, verde e azul.

targetColorToPhoto [] `pega a cor média da faixa de destino e encontra a foto da paleta que melhor corresponde a ela.

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

Exemplo

Vamos encontrar a foto que melhor combina com RGBColor [0.640, 0.134, 0.249]:

Exemplo 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 toma como entrada a imagem crua da qual criaremos um mosaico de fotos.

targetPic removerá um quarto parâmetro (de PNGs e alguns JPGs), deixando apenas R, G, B.

dimssão as dimensões de targetPic.

tiles são os pequenos quadrados que juntos compõem a imagem alvo.

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

tileReplacements são as fotos que correspondem a cada bloco, na ordem correta.

gallery é o conjunto de substituições de blocos (fotos) com a dimensionalidade adequada (ou seja, o número de linhas e colunas que correspondem aos blocos).

ImageAssembly une o mosaico em uma imagem de saída contínua.


Exemplos

Isso substitui cada quadrado de 12x12 da imagem, domingo, por uma fotografia correspondente de 48 x 48 pixels que melhor corresponde à cor média.

photoMosaic[sunday, 12]

domingo2


Domingo (detalhe)

cartola


photoMosaic[lightbulb, 6]

lâmpada 6


photoMosaic[stevejobs, 24]

steve jobs 24


Detalhe, stevejobs.

detalhe dos trabalhos


photoMosaic[kiss, 24]

beijo


Detalhe do beijo:

beijo detalhe


photoMosaic[spheres, 24]

esferas

DavidC
fonte
1
Eu gosto da ideia de granularidade. Dá mais realismo às imagens menores.
22814 Calvin's Hobbies
7

JS

Igual ao golfe anterior: http://jsfiddle.net/eithe/J7jEk/ : D

(desta vez chamado com unique: false, {pixel_2: {width: 48, height: 48}, pixel_1: {width: 48, height: 48}}) (não trate a paleta para usar um pixel uma vez, os pixels da paleta são amostras de 48x48, os pixels de forma são amostras de 48x48).

Atualmente, ele pesquisa na lista de avatares para encontrar a correspondência mais próxima em peso do algoritmo selecionado, no entanto, não realiza nenhuma correspondência de uniformidade de cores (algo que eu preciso dar uma olhada.

  • equilibrado
  • laboratório

Infelizmente, não consigo brincar com imagens maiores, porque minha RAM acaba: D Se possível, eu apreciaria imagens de saída menores. Se você usar 1/2 do tamanho da imagem fornecido, aqui está a tarde de domingo:

  • equilibrado
  • laboratório
eithed
fonte
2
Acabei de adicionar imagens de tamanho médio que ainda são divisíveis por 48 pixels.
9789 Calvin's Hobbies
5

GLSL

A diferença entre esse desafio e o do American Gothic na paleta da Mona Lisa: reorganize os pixels que me interessam, porque os mosaicos podem ser reutilizados, enquanto os pixels não. Isso significa que é possível paralelizar facilmente o algoritmo, então decidi tentar uma versão massivamente paralela. Por "massivamente", quero dizer usando os 1344 núcleos de shader no GTX670 da minha área de trabalho simultaneamente, via GLSL.

Método

A correspondência real de blocos é simples: calculo a distância RGB entre cada pixel em uma área de destino e a área de mosaicos e escolho o bloco com a menor diferença (ponderada pelos valores de brilho). O índice do bloco é gravado nos atributos de vermelho e verde do fragmento, depois que todos os fragmentos foram renderizados, li os valores novamente no buffer de estrutura e construí a imagem de saída desses índices. A implementação real é um hack; em vez de criar um FBO, apenas abri uma janela e renderizei nela, mas o GLFW não pode abrir janelas em resoluções arbitrariamente pequenas; portanto, crio a janela maior que o necessário e, em seguida, desenhe um pequeno retângulo com o tamanho correto para que um fragmento por bloco que é mapeado para a imagem de origem. Toda a solução MSVC2013 está disponível emhttps://bitbucket.org/Gibgezr/mosaicmaker É necessário que o GLFW / FreeImage / GLEW / GLM seja compilado, e que o OpenGL 3.3 ou melhor driver / placa de vídeo seja executado.

Fonte do Shader do Fragmento

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

Resultados

As imagens são renderizadas quase instantaneamente, então a paralelização foi um sucesso. A desvantagem é que eu não posso fazer com que os fragmentos individuais dependam da saída de outros fragmentos, então não há como obter o aumento significativo da qualidade que você pode obter ao não escolher o mesmo bloco duas vezes dentro de um determinado intervalo. Resultados rápidos, mas com qualidade limitada devido a repetições massivas de peças. Ao todo, foi divertido. http://imgur.com/a/M0Db0 para versões em tamanho real. insira a descrição da imagem aqui

Darren
fonte
4

Python

Aqui vai a primeira solução Python, usando uma abordagem média. Nós podemos evoluir a partir daqui. O resto das imagens está aqui .

domingo 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
fonte
1

Outra solução Python - com base na média (RGB vs L a b *)

Resultados (existem algumas diferenças)

Bulb - RGB

Vista completa

bulb_rgb

Bulb - Lab

Vista completa

bulb_lab

Steve - RGB

Vista completa

steve_rgb

Steve - Laboratório

Vista completa

steve_lab

Esferas - RGB

Vista completa

spheres_rgb

Esferas - Laboratório

Vista completa

spheres_lab

Domingo - RGB

Vista completa

sunday_rgb

Domingo - Laboratório

Vista completa

sunday_lab

Kiss - RGB

Vista completa

kiss_rgb

Kiss - Lab

Vista completa

kiss_lab

Código

requer python-colormath para 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
fonte