Photomosaics或:更换灯泡需要几个程序员?


33

我从Stack Overflow顶级用户的头像中整理了2025张头像的拼接图。
(单击图像可查看完整尺寸。)

StackOverflow爆头马赛克

您的任务是编写一种算法,该算法将使用来自45×45像素网格中的48×48像素化身来创建另一幅图像的精确马赛克。

测试影像

这是测试图像。首先,当然是灯泡!
(它们在这里不是完整尺寸。单击图像以完整尺寸查看。一半尺寸的版本可用于The KissSunday Afternoon ...Steve Jobsspheres。)

灯泡 这个吻 La Grande Jatte岛上的周日下午 史蒂夫·乔布斯 领域

感谢Wikipedia除了光线追踪领域以外的所有领域。

在全尺寸下,所有这些图片的尺寸都可以被48整除。较大的图片必须是JPEG,以便可以对其进行足够的压缩以进行上传。

计分

这是一次人气竞赛。带有马赛克的提交可以最准确地描绘原始图像,应予以表决。我将在一两周内接受投票最高的答案。

规则

  • 您的光马赛克必须完全由从上面的马赛克中选取并排列成网格的48×48像素不变的化身组成。

  • 您可以在镶嵌图中重复使用头像。(实际上是您必须拥有的较大测试图像。)

  • 显示输出,但请记住测试图像非常大,并且StackExchange仅允许发布最大2MB的图像。因此,压缩图像或将其托管在其他位置,然后在此处放置较小的版本。

  • 要确认获奖者,您必须提供灯泡或球形马赛克的PNG版本。这样一来,我就可以验证它们(请参阅下文),以确保您不会在头像上添加额外的颜色以使马赛克看起来更好。

验证器

该Python脚本可用于检查完成的镶嵌图是否确实使用了未更改的化身。只需设置toValidateallTiles。对于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
这实质上不是上一个的重复吗?计算每个颜色,将目标缩小至2025px,并应用现有算法?
约翰·德沃夏克


2
@JanDvorak相似,但我认为不足以重复。您提到的算法是获得结果的一种方法。但是,还有更多复杂的解决方案。
霍华德

1
我的猫从化身中丢失了:-(
Joey

2
您可能希望将“ 制作灯泡” 更改为“ 替换灯泡”。
DavidC 2014年

Answers:


15

Java,平均距离

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
不确定是否是我,但是Pictureshack与Imgur相比似乎拥有可怕的带宽
Nick T

@NickT可能是,但是Imgur会将所有内容压缩到最大1MB(imgur.com/faq#size)。:(
卡尔文的爱好2014年

嗯,是我还是David的Mathematica答案远胜于这个最受好评的答案?
Justhalf 2014年

可惜所有这些照片都不见了。您可以重新上传到imgur吗?
MCMastery '18

19

Mathematica,可控制粒度

这将根据需要使用48 x 48像素的照片。默认情况下,它将那些像素与要近似的图像交换为对应的48x48像素正方形。

但是,可以将目标正方形的大小设置为小于48 x 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 将镶嵌图合并成一个连续的输出图像。


例子

这会将周日图像中的每个12x12正方形替换为与48x 48像素对应的照片,该照片最适合平均颜色。

photoMosaic[sunday, 12]

星期日2


周日(详细)

高顶礼帽


photoMosaic[lightbulb, 6]

灯泡6


photoMosaic[stevejobs, 24]

史蒂夫·乔布斯24


细节,stevejobs。

工作细节


photoMosaic[kiss, 24]

吻


亲吻细节:

细节之吻


photoMosaic[spheres, 24]

领域


1
我喜欢粒度的想法。它使较小的图像更具真实感。
卡尔文的爱好2014年

7

JS

与之前的高尔夫相同:http : //jsfiddle.net/eithe/J7jEk/:D

(这次用调用unique: false, {pixel_2: {width: 48, height: 48}, pixel_1: {width: 48, height: 48}})(不要将调色板一次使用一个像素,调色板像素是48x48色板,形状像素是48x48色板)。

目前,它会在化身列表中进行搜索,以按所选算法的权重找到最接近的匹配项,但是它不执行任何颜色均匀性匹配(我需要看一下。

  • 均衡
  • 实验室

不幸的是,我无法处理较大的图像,因为我的RAM用尽了:D如果可能的话,我希望使用较小的输出图像。如果使用提供的图像尺寸的1/2,则为星期天下午:

  • 均衡
  • 实验室

2
我刚刚添加了仍可被48像素整除的半角图像。
卡尔文的爱好2014年

5

GLSL

这项挑战与蒙娜丽莎(Mona Lisa)调色板中的美国哥特式挑战之间的区别:重新排列像素我感兴趣,因为可以重复使用马赛克,而像素则不能。这意味着可以轻松地并行化算法,因此我决定尝试大规模并行版本。“大量”是指通过GLSL同时在台式机GTX670上使用1344个着色器内核。

方法

实际的图块匹配很简单:我计算目标区域中每个像素与马赛克图块区域之间的RGB距离,并选择差异最小(按亮度值加权)的图块。将图块索引写入片段的红色和绿色属性中,然后在渲染所有片段之后,我从帧缓冲区中读取值,并从这些索引构建输出图像。实际的实现是一个hack。我没有打开FBO,而是打开了一个窗口并渲染到其中,但是GLFW无法以任意小的分辨率打开窗口,因此我创建了比所需的窗口大的窗口,然后绘制了一个正确大小的小矩形,以便每个图块一个片段映射到源图像。完整的MSVC2013解决方案可从以下网站获得:https://bitbucket.org/Gibgezr/mosaicmaker 它需要GLFW / FreeImage / GLEW / GLM进行编译,并且需要OpenGL 3.3或更高版本的驱动程序/视频卡才能运行。

片段着色器源

#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解决方案。我们可以从这里发展。其余图像在这里

星期日 史蒂夫

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

全视角

spheres_rgb

领域-实验室

全视角

spheres_lab

星期日-RGB

全视角

星期日_rgb

星期日-实验

全视角

周日实验室

吻-RGB

全视角

kiss_rgb

吻-实验室

全视角

kiss_lab

需要实验室使用python-colormath

#!/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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.