Photomosaics lub: Ilu programistów zajmuje wymiana żarówki?


33

Skompilowałem mozaikę 2025 strzałów w głowę z awatarów najlepszych użytkowników Stack Overflow .
(Kliknij obraz, aby wyświetlić go w pełnym rozmiarze).

Mozaika strzałów StackOverflow

Twoim zadaniem jest napisanie algorytmu, który utworzy dokładną fotomozaikę innego obrazu przy użyciu awatarów 48 × 48 pikseli z tej siatki 45 × 45.

Testuj obrazy

Oto zdjęcia testowe. Pierwszą jest oczywiście żarówka!
(Nie są tutaj pełnowymiarowe. Kliknij zdjęcie, aby wyświetlić je w pełnym rozmiarze. Połowy wielkości są dostępne dla Pocałunku , Niedzielnego popołudnia ... , Steve'a Jobsa i sfer .)

żarówka Pocałunek Niedzielne popołudnie na wyspie La Grande Jatte Steve Jobs kule

Dzięki Wikipedii za wszystkie sfery oprócz raytraced.

W pełnym rozmiarze wszystkie te obrazy mają wymiary podzielne przez 48. Większe musiały być plikami JPEG, aby można je było wystarczająco skompresować, aby je załadować.

Punktacja

To konkurs popularności. Zgłoszenie z mozaikami, które najdokładniej przedstawiają oryginalne obrazy, powinno zostać poddane pod głosowanie. Przyjmę najwyższą głosowaną odpowiedź za tydzień lub dwa.

Zasady

  • Twoja fotomozaika musi w całości składać się z niezmienionych awatarów 48 × 48 pikseli wziętych z powyższej mozaiki, ułożonych w siatkę.

  • Możesz ponownie użyć awatara w mozaice. (Rzeczywiście, w przypadku większych zdjęć testowych będziesz musiał.)

  • Pokaż swoje wyniki, ale pamiętaj, że obrazy testowe są bardzo duże, a StackExchange pozwala tylko na publikowanie obrazów do 2 MB . Więc skompresuj swoje zdjęcia lub umieść je gdzie indziej i umieść tutaj mniejsze wersje.

  • Aby zostać zwycięzcą, musisz dostarczyć wersje PNG mozaiki żarówki lub kulek. Dzięki temu mogę je zweryfikować (patrz poniżej), aby upewnić się, że nie dodajesz dodatkowych kolorów do awatarów, aby mozaiki wyglądały lepiej.

Walidator

Tego skryptu w Pythonie można użyć do sprawdzenia, czy ukończona mozaika rzeczywiście używa niezmienionych awatarów. Po prostu ustaw toValidatei allTiles. Jest mało prawdopodobne, aby działał w przypadku plików JPEG lub innych formatów stratnych, ponieważ porównuje dokładnie rzeczy, piksel po pikselu.

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

Powodzenia wszystkim! Nie mogę się doczekać, aby zobaczyć wyniki.

Uwaga: wiem, że algorytmy fotomozaiczne są łatwe do znalezienia w Internecie, ale jeszcze ich nie ma na tej stronie. Naprawdę mam nadzieję, że zobaczymy coś bardziej interesującego niż zwykły algorytm „uśredniania każdego kafelka i każdego pola siatki i dopasowywania ich” .


1
Czy nie jest to zasadniczo kopia poprzedniego? Oblicz kolor każdego z nich, przeskaluj cel do 2025px i zastosuj istniejący algorytm?
John Dvorak


2
@JanDvorak Jest podobnie, ale myślę, że nie wystarczy, aby być duplikatem. Wspomniany algorytm jest jednym ze sposobów uzyskania wyniku. Istnieją jednak znacznie bardziej wyrafinowane rozwiązania.
Howard

1
Mojego kota brakuje w awatarach :-(
Joey,

2
Może chcesz zmienić „aby zrobić żarówkę” do „ wymienić żarówkę”.
DavidC

Odpowiedzi:


15

Java, średni dystans

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

Algorytm przeszukuje wszystkie kafelki awatara dla każdego pola siatki osobno. Ze względu na małe rozmiary nie wdrożyłem żadnych skomplikowanych struktur danych ani algorytmów wyszukiwania, ale po prostu brutalnie wykorzystałem całą przestrzeń.

Ten kod nie wprowadza żadnych modyfikacji kafelków (np. Nie dostosowuje kolorów docelowych).

Wyniki

Kliknij, aby wyświetlić obraz w pełnym rozmiarze.

żarówka kule
niedziela

Wpływ promienia

Używając radiusmożesz zmniejszyć powtarzalność płytek w wyniku. Ustawienie radius=0nie ma wpływu. Np. radius=3Tłumi tę samą płytkę w promieniu 3 płytek.

żarówka niedziela promień = 0

żarówka
żarówka
promień = 3

Wpływ współczynnika skalowania

Za pomocą tego scalingwspółczynnika możemy ustalić, w jaki sposób zostanie wyszukany pasujący kafelek. scaling=1oznacza wyszukiwanie dopasowania w pikselach podczas scaling=48wyszukiwania średniego kafelka.

skalowanie 48
skalowanie = 48

skalowanie 16
skalowanie = 16

skalowanie 4
skalowanie = 4

skalowanie 1
skalowanie = 1


1
Łał. Współczynnik promienia naprawdę poprawia wyniki. Te plamy tego samego awatara nie były dobre.
John Dvorak

1
Nie jestem pewien, czy to ja, ale Pictureshack wydaje się mieć okropną przepustowość w porównaniu do Imgura
Nick T

@NickT Prawdopodobnie, ale Imgur kompresuje wszystko maksymalnie do 1 MB ( imgur.com/faq#size ). :(
Calvin's Hobbies

Hmm, czy to tylko ja, czy odpowiedź Mathematiki Davida jest znacznie lepsza niż ta najczęściej głosowana odpowiedź?
justhalf

Szkoda, że ​​wszystkie te zdjęcia zniknęły. Czy możesz przypadkiem ponownie załadować do imgur?
MCMastery

19

Matematyka, z kontrolą szczegółowości

W razie potrzeby wykorzystuje zdjęcia 48 x 48 pikseli. Domyślnie zamienia te piksele na odpowiadający im kwadrat 48 x 48 pikseli z obrazu, który ma zostać przybliżony.

Jednak rozmiar kwadratów docelowych można ustawić na mniejszy niż 48 x 48, co pozwala na większą wierność detali. (patrz przykłady poniżej).

Wstępne przetwarzanie palety

collage to obraz zawierający zdjęcia, które mają służyć jako paleta.

picsColorsto lista pojedynczych zdjęć w połączeniu z ich średnią wartością czerwoną, średnią zieloną i średnią niebieską.

targetColorToPhoto [] `pobiera średni kolor docelowego pokosu i znajduje zdjęcie z palety, która najlepiej do niego pasuje.

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

Przykład

Znajdźmy zdjęcie, które najlepiej pasuje do RGBColor [0.640, 0.134, 0.249]:

Przykład 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 przyjmuje jako surowe zdjęcie, z którego zrobimy mozaikę fotograficzną.

targetPic usunie czwarty parametr (PNG i niektórych JPG), pozostawiając tylko R, G, B.

dimssą wymiary targetPic.

tiles to małe kwadraty, które razem składają się na obraz docelowy.

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

tileReplacements są zdjęcia pasujące do każdego kafelka w odpowiedniej kolejności.

gallery to zestaw zamienników kafelków (zdjęć) o odpowiedniej wymiarowości (tj. liczbie wierszy i kolumn pasujących do kafelków).

ImageAssembly łączy mozaikę w ciągły obraz wyjściowy.


Przykłady

To zastępuje każdy kwadrat 12 x 12 z obrazu, w niedzielę, odpowiadającym mu zdjęciem 48 x 48 pikseli, który najlepiej pasuje do przeciętnego koloru.

photoMosaic[sunday, 12]

niedziela2


Niedziela (szczegóły)

cylinder


photoMosaic[lightbulb, 6]

żarówka 6


photoMosaic[stevejobs, 24]

Steve Jobs 24


Szczegóły, stevejobs.

szczegóły pracy


photoMosaic[kiss, 24]

pocałunek


Szczegół pocałunku:

pocałunek szczegółów


photoMosaic[spheres, 24]

kule


1
Podoba mi się pomysł granulacji. Daje większy realizm mniejszym obrazom.
Calvin's Hobbies

7

JS

Tak jak w poprzednim golfie: http://jsfiddle.net/eithe/J7jEk/ : D

(tym razem nazywane za pomocą unique: false, {pixel_2: {width: 48, height: 48}, pixel_1: {width: 48, height: 48}}) (nie traktuj palety, aby użyć jednego piksela raz, piksele palety to próbki 48 x 48, piksele kształtu to próbki 48 x 48).

Obecnie przeszukuje listę awatarów, aby znaleźć najbliższe dopasowanie według wagi wybranego algorytmu, jednak nie wykonuje żadnego dopasowania jednorodności kolorów (coś, na co muszę spojrzeć.

  • zrównoważony
  • laboratorium

Niestety nie jestem w stanie bawić się większymi obrazami, ponieważ kończy się pamięć RAM: D Jeśli to możliwe, doceniłbym mniejsze obrazy wyjściowe. Jeśli używasz 1/2 rozmiaru obrazu, oto niedzielne popołudnie:

  • zrównoważony
  • laboratorium

2
Właśnie dodałem zdjęcia o połówkowej wielkości, które wciąż można podzielić przez 48 pikseli.
Calvin's Hobbies

5

GLSL

Różnica między tym wyzwaniem a wyzwaniem w American Gothic w palecie Mona Lisa: Zmień rozmieszczenie pikseli , ponieważ mozaiki można ponownie użyć, a pikseli nie. Oznacza to, że można łatwo zrównoleglić algorytm, więc postanowiłem spróbować wersji masowo równoległej. Pod pojęciem „masowo” mam na myśli jednoczesne używanie rdzeni cieniujących 1344 na GTX670 mojego komputera stacjonarnego za pośrednictwem GLSL.

metoda

Rzeczywiste dopasowanie kafelków jest proste: obliczam odległość RGB między każdym pikselem w obszarze docelowym a obszarem mozaiki i wybieram kafelek o najniższej różnicy (ważony wartościami jasności). Indeks kafelków jest zapisywany w atrybutach koloru czerwonego i zielonego fragmentu, a następnie po wyrenderowaniu wszystkich fragmentów czytam wartości z bufora ramki i buduję obraz wyjściowy z tych indeksów. Rzeczywista implementacja jest dość hack; zamiast tworzenia FBO właśnie otworzyłem okno i renderowałem je, ale GLFW nie może otwierać okien w dowolnie małych rozdzielczościach, więc tworzę okno większe niż jest to wymagane, a następnie narysuję mały prostokąt o odpowiednim rozmiarze, aby miał jeden fragment na kafelek, który jest odwzorowany na obraz źródłowy. Całe rozwiązanie MSVC2013 jest dostępne pod adresemhttps://bitbucket.org/Gibgezr/mosaicmaker Wymaga GLFW / FreeImage / GLEW / GLM do kompilacji oraz OpenGL 3.3 lub lepszych sterowników / karty graficznej do uruchomienia.

Źródło Shader Fragment

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

Wyniki

Obrazy renderują się niemal natychmiast, więc równoległość zakończyła się sukcesem. Minusem jest to, że nie mogę sprawić, aby poszczególne fragmenty opierały się na wynikach innych fragmentów, więc nie ma sposobu, aby uzyskać znaczny wzrost jakości, jaki można uzyskać, nie wybierając tej samej płytki dwukrotnie w określonym zakresie. Szybkie wyniki, ale ograniczona jakość z powodu ogromnej liczby powtórzeń płytek. W sumie było fajnie. http://imgur.com/a/M0Db0 dla wersji pełnowymiarowych. wprowadź opis zdjęcia tutaj


4

Pyton

Oto pierwsze rozwiązanie w języku Python, oparte na podejściu. Możemy ewoluować stąd. Reszta zdjęć jest tutaj .

niedziela 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

Jeszcze inne rozwiązanie w języku Python - oparte na średniej (RGB vs L a b *)

Wyniki (istnieją pewne nieznaczne różnice)

Żarówka - RGB

Pełny widok

bulb_rgb

Żarówka - Lab

Pełny widok

bulb_lab

Steve - RGB

Pełny widok

steve_rgb

Steve - Lab

Pełny widok

steve_lab

Kule - RGB

Pełny widok

spheres_rgb

Kule - Lab

Pełny widok

spheres_lab

Niedziela - RGB

Pełny widok

niedziela_rgb

Niedziela - Lab

Pełny widok

niedziela_lab

Pocałunek - RGB

Pełny widok

kiss_rgb

Kiss - Lab

Pełny widok

kiss_lab

Kod

wymaga Python-colormath dla 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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.