Фотомозаїка або: скільки програмістів потрібно для заміни лампочки?


33

Я склав мозаїку з 2025 головок з аватарів провідних користувачів Stack Overflow .
(Клацніть зображення, щоб переглянути його у повному розмірі.)

Мозаїка StackOverflow для головної стріли

Ваше завдання - написати алгоритм, який створить точну фотомозаїку іншого зображення, використовуючи аватари 48 × 48 пікселів із цієї сітки 45 × 45.

Тестові зображення

Ось тестові зображення. Перший - це, звичайно, лампочка!
(Вони тут не в повному розмірі. Клацніть на зображення, щоб переглянути його у повному розмірі. Напіврозмірні версії доступні для The Kiss , A Sunday Afternight ... , Steve Jobs і сфер .)

лампочка Поцілунок В неділю вдень на острові Ла-Гранде-Джатт Стів Джобс сфери

Дякуємо Вікіпедії для всіх, окрім сфер, що променілися.

У повнорозмірному розмірі ці зображення мають розміри, що діляться на 48. Більші розміри повинні бути JPEG, щоб їх можна було достатньо стиснути для завантаження.

Оцінка балів

Це конкурс на популярність. Повідомлення з мозаїкою, які найточніше зображують оригінальні зображення, слід проголосувати. Я прийму найвищу відповідь за тиждень-два.

Правила

  • Ваша фотомозаїка повинна повністю складатися з незмінних аватарів 48 × 48 пікселів, знятих з мозаїки вгорі, розташованих у сітці.

  • Ви можете повторно використовувати аватар у мозаїці. (Дійсно, для великих тестових зображень вам доведеться.)

  • Покажіть свій вихід, але майте на увазі, що тестові зображення дуже великі, а StackExchange дозволяє розміщувати зображення лише до 2 Мб . Тому стискайте свої зображення або розміщуйте їх де-небудь ще та розміщуйте тут менші версії.

  • Щоб підтвердити переможця, ви повинні надати PNG версії мозаїки лампочки чи сфери. Це так, що я можу їх перевірити (див. Нижче), щоб переконатися, що ви не додаєте зайвих кольорів аватарам, щоб мозаїки виглядали краще.

Валідатор

Цей сценарій Python може бути використаний для перевірки, чи справді виконана мозаїка використовує незмінені аватари. Просто встановіть toValidateі allTiles. Це навряд чи буде працювати для JPEG або інших форматів з втратою, оскільки він порівнює речі точно, піксель за пікселем.

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

Успіхів усім! Я не можу чекати, щоб побачити результати.

Примітка. Я знаю, що алгоритми фотомозаїки легко знайти в Інтернеті, але їх ще немає на цьому сайті. Я дуже сподіваюся, що ми побачимо щось цікавіше, ніж звичайний алгоритм "середня кожна плитка та кожен простір сітки та їх відповідність" .


1
Це по суті не є дублікатом попереднього? Обчислити колір кожного, зменшити масштаб цілі до 2025 пікселів і застосувати існуючий алгоритм?
Джон Дворак


2
@JanDvorak Це схоже, але я думаю, що недостатньо, щоб бути дублікатом. Алгоритм, який ви згадали, - це один із способів отримати результат. Однак є набагато більш складні рішення.
Говард

1
Мій кіт відсутній у аватарів :-(
Джої,

2
Ви можете змінити " зробити лампочку" на " замінити лампочку".
DavidC

Відповіді:


15

Ява, середня відстань

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

Алгоритм здійснює пошук по всіх аватарних плитках для кожного простору сітки окремо. Через малі розміри я не реалізував жодних складних структур даних або алгоритмів пошуку, а просто жорстоко застосовував весь простір.

Цей код не вносить жодних змін у плитки (наприклад, не адаптується до кольорів призначення).

Результати

Клацніть для повного розміру зображення.

легка кришка сфери
неділя

Вплив радіусу

Використовуючи radiusви можете зменшити повторюваність плиток в результаті. Встановлення radius=0ефекту немає. Наприклад, radius=3пригнічує ту саму плитку в радіусі 3 плиток.

легка кришка неділя радіус = 0

легка кришка
легка кришка
радіус = 3

Вплив коефіцієнта масштабування

За допомогою scalingкоефіцієнта ми можемо визначити, як шукається відповідна плитка. scaling=1означає пошук ідеального для пікселя відповідності, тоді як scaling=48пошук середнього рівня в плитці.

масштабування 48
масштабування = 48

масштабування 16
масштабування = 16

масштабування 4
масштабування = 4

масштабування 1
масштабування = 1


1
Ого. Коефіцієнт радіусу дійсно покращує результати. Ці ж плями-аватари були непогані.
Джон Дворак

1
Не впевнений, чи це я, але, схоже, у Photoshack є жорстка пропускна здатність порівняно з Імгур
Нік Т

@NickT Напевно, але Imgur стискає все щонайменше до 1 Мб ( imgur.com/faq#size ). :(
Захоплення Кальвіна

Хм, це лише я чи відповідь Математики Девіда набагато краща, ніж ця відповідь, що голосує вище?
justhalf

Шкода, що всіх цих фотографій немає. Чи можете ви перезавантажити програму imgur випадково?
MCMastery

19

Mathematica, з контролем на деталізацію

Для цього використовуються фотографії розміром 48 x 48 пікселів. За замовчуванням він замінить ці пікселі на відповідний квадрат 48x48 пікселів із зображення, яке буде приблизним.

Однак розмір квадратів призначення може бути встановлений меншим, ніж 48 х 48, що забезпечує більшу точність деталізації. (див. приклади нижче).

Попередня обробка палітри

collage це зображення, що містить фотографії, які служать палітрою.

picsColors- це список окремих фотографій, сполучених із середнім значенням червоного, середнього зеленого та середнього значення синього.

targetColorToPhoto [] `бере середній колір цільового контуру та знаходить фотографії з палітри, яка найкраще відповідає йому.

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

Приклад

Давайте знайдемо фотографію, яка найкраще відповідає RGBColor [0.640, 0.134, 0.249]:

приклад1


фотомозаїка

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 приймає в якості сирого зображення, з якого ми зробимо фотомозаїку.

targetPic видалить четвертий параметр (PNG та деякі JPG), залишивши лише R, G, B.

dims- розміри targetPic.

tiles - це маленькі квадрати, які разом складають цільовий малюнок.

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

tileReplacements - фотографії, які відповідають належній кожній плитці.

gallery - це набір плиткозамінників (фотографій) з відповідною розмірністю (тобто кількість рядків і стовпців, які відповідають плитці).

ImageAssembly приєднує мозаїку до безперервного вихідного зображення.


Приклади

Це замінює кожен квадрат 12х12 від зображення в неділю на відповідну фотографію розміром 48 х 48 пікселів, яка найкраще відповідає середньому кольорові.

photoMosaic[sunday, 12]

неділя2


Неділя (детально)

верхній капелюх


photoMosaic[lightbulb, 6]

лампочка 6


photoMosaic[stevejobs, 24]

робочі місця steve 24


Детально, stevejobs.

деталі робочих місць


photoMosaic[kiss, 24]

поцілунок


Деталь Поцілунку:

детально поцілунок


photoMosaic[spheres, 24]

сфери


1
Мені подобається ідея деталізації. Це надає більше реалізму меншим образам.
Захоплення Кальвіна

7

JS

Те саме, що і в попередньому гольфі: http://jsfiddle.net/eithe/J7jEk/ : D

(цього разу називається за допомогою unique: false, {pixel_2: {width: 48, height: 48}, pixel_1: {width: 48, height: 48}}) (не слід розглядати палітру, щоб використовувати один піксель один раз, пікселі палітри - це розміри 48x48, пікселі форми - 48х48).

В даний час він здійснює пошук за списком аватарів, щоб знайти найближчу відповідність за вагою вибраного алгоритму, проте він не виконує жодної відповідності кольорів (те, що мені потрібно розглянути.

  • врівноважений
  • лабораторія

На жаль, я не в змозі розігруватись з більшими зображеннями, тому що мій оперативної пам’яті закінчується: D Якщо можливо, я би оцінив менші вихідні зображення. Якщо ви використовуєте 1/2 наданого розміру зображення, ось неділя вдень:

  • врівноважений
  • лабораторія

2
Я щойно додав зображення половинного розміру, які досі поділяються на 48 пікселів.
Захоплення Кальвіна

5

GLSL

Різниця між цим викликом та американським готичним у палітрі Mona Lisa: Перестановіть пікселі, які мене зацікавили, оскільки мозаїчні плитки можна повторно використовувати, тоді як пікселі не змогли. Це означає, що алгоритм можна легко паралелізувати, тому я вирішив спробувати масово паралельну версію. Під масовим розумінням я використовую шейдерні ядра 1344 на GTX670 мого робочого столу одночасно через GLSL.

Метод

Фактична відповідність плитки проста: я обчислюю RGB відстань між кожним пікселем у цільовій області та мозаїчною плиткою, і вибираю плитку з найменшою різницею (зважена значеннями яскравості). Індекс плитки записується в атрибути червоного та зеленого кольору фрагмента, після того, як всі фрагменти будуть виведені, я читаю значення з рамки буфера і будую вихідне зображення з цих індексів. Фактична реалізація є досить зломною; замість створення ФБО я просто відкрив вікно і візуалізував у нього, але GLFW не може відкрити вікна при довільно малих роздільних здатностях, тому я створю вікно більше, ніж потрібно, а потім намалюю невеликий прямокутник правильного розміру, щоб він мав по одному фрагменту на плитку, який відображає вихідне зображення. Все рішення MSVC2013 доступне за адресоюhttps://bitbucket.org/Gibgezr/mosaicmaker Для компіляції потрібен GLFW / FreeImage / GLEW / GLM та OpenGL 3.3 або кращі драйвери / відеокарта.

Джерело Shader 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;
}

Результати

Образи відображаються майже миттєво, тому паралелізація мала успіх. Мінус полягає в тому, що я не можу змусити окремі фрагменти розраховувати на вихід будь-яких інших фрагментів, тому немає можливості досягти значного підвищення якості, який можна отримати, не вибираючи одну і ту ж плитку двічі в межах певного діапазону. Отже, швидкі результати, але обмежена якість через масивні повтори плитки. Загалом, було весело. http://imgur.com/a/M0Db0 для повнорозмірних версій. введіть тут опис зображення


4

Пітон

Тут йдеться про перше рішення Python, використовуючи середній підхід. Ми можемо розвиватися звідси. Решта зображень тут .

неділя 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

Ще одне рішення Python - середнє значення (RGB vs L a b *)

Результати (Є кілька незначних відмінностей)

Лампа - RGB

повний перегляд

bulb_rgb

Лампочка - лабораторія

повний перегляд

bulb_lab

Стів - RGB

повний перегляд

steve_rgb

Стів - лабораторія

повний перегляд

steve_lab

Сфери - RGB

повний перегляд

сфери_ргб

Сфери - лабораторія

повний перегляд

сфери_лаб

Неділя - RGB

повний перегляд

неділя_ргб

Неділя - Лабораторія

повний перегляд

неділя_лаб

Поцілунок - RGB

повний перегляд

kiss_rgb

Поцілунок - лабораторія

повний перегляд

kiss_lab

Код

вимагає python-colormath для 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.