フォトモザイクまたは:電球を交換するには何人のプログラマーが必要ですか?


33

Stack Overflowトップユーザーのアバターから2025枚のヘッドショットのモザイクを編集しました。
(画像をクリックすると、フルサイズで表示されます。)

StackOverflowヘッドショットモザイク

あなたの仕事は、この45×45グリッドの48×48ピクセルのアバターを使用して、別の画像の正確なフォトモザイクを作成するアルゴリズムを記述することです。

テスト画像

これがテスト画像です。最初はもちろん電球です!
(ここではフルサイズではありません。画像をクリックしてフルサイズで表示してください。ハーフサイズバージョンは、The KissA Sunday Afternoon ...Steve Jobs、およびspheresで利用できます。)

電球 キス ラグランデジャッテ島での日曜日の午後 スティーブ・ジョブズ 球体

レイトレースされた球体を除くすべてのウィキペディアに感謝します。

フルサイズでは、これらの画像のサイズはすべて48で割り切れます。大きい画像はJPEGである必要があり、アップロードに十分な圧縮が可能です。

得点

これは人気コンテストです。元の画像を最も正確に描写するモザイクを使用した投稿は、投票する必要があります。私は1週間か2週間で最高票を獲得します。

ルール

  • フォトモザイクは、グリッドから配置された、上記のモザイクから取られた変更されていない 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
これは本質的に前のものの複製ではありませんか?それぞれの色を計算し、ターゲットを2025ピクセルにダウンスケールし、既存のアルゴリズムを適用しますか?
ジョンドヴォルザーク14


2
@JanDvorakそれは似ていますが、複製するには十分ではないと思います。あなたが言及したアルゴリズムは、結果を得るための1つの方法です。しかし、もっと洗練されたソリューションがあります。
ハワード14

1
私の猫がアバターからなくなっています:
ジョーイ14

2
「電球を作る」を「電球を交換する」に変更することもできます。
DavidC 14

回答:


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タイル以内の同じタイルを抑制します。

ライトブルド 日曜日 radius = 0

ライトブルド
ライトブルド
radius = 3

スケーリング係数の影響

このscaling係数を使用して、一致するタイルの検索方法を決定できます。平均タイルの検索をscaling=1行いながら、ピクセルに完全に一致するものを検索することを意味しscaling=48ます。

スケーリング48
スケーリング= 48

スケーリング16
スケーリング= 16

スケーリング4
スケーリング= 4

スケーリング1
スケーリング= 1


1
ワオ。半径係数は結果を本当に改善します。同じアバターの斑点はよくありませんでした。
ジョン・ドヴォルザーク

1
わからない、それは私ですが、PictureshackはImgurに比べ残虐な帯域幅を持っていると思われる場合
ニック・T

@NickTおそらく、しかしImgurはすべてを最大1MBに圧縮します(imgur.com/faq#size)。:(
カルビンの趣味14

うーん、それは私だけですか、DavidによるMathematicaの答えは、このトップ投票の答えよりもはるかに優れていますか?
ちょうど半分14

残念なことに、これらの写真はすべてなくなっています。万が一imgurに再アップロードできますか?
MCMastery

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 4番目のパラメーター(PNGおよび一部のJPG)を削除し、R、G、Bのみを残します。

dims の寸法 targetPic

tiles 一緒にターゲット画像を構成する小さな正方形です。

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

tileReplacements 適切な順序で、各タイルに一致する写真です。

gallery 適切な次元(つまり、タイルに一致する行と列の数)を持つタイル交換(写真)のセットです。

ImageAssembly モザイクを連続出力画像に結合します。


これにより、日曜日の画像の各12 x 12の正方形が、平均的な色に最もよく一致する48 x 48ピクセルの写真に置き換えられます。

photoMosaic[sunday, 12]

日曜日2


日曜日(詳細)

トップハット


photoMosaic[lightbulb, 6]

電球6


photoMosaic[stevejobs, 24]

スティーブジョブ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}})(1つのピクセルを1回使用するようにパレットを処理しないでください。パレットのピクセルは48x48の見本、形状ピクセルは48x48の見本です)。

現在、選択されたアルゴリズムの重みで最も近い一致を見つけるためにアバターリストを検索しますが、色の均一性の一致は実行しません(私が調べておく必要があるものです)。

  • バランスのとれた
  • ラボ

残念ながら、RAMが使い果たされるため、大きな画像で再生することはできません。D可能であれば、小さな出力画像に感謝します。指定された画像サイズの1/2を使用する場合、日曜日の午後は次のとおりです。

  • バランスのとれた
  • ラボ

2
まだ48ピクセルで割り切れるハーフサイズの画像を追加しました。
カルバンの趣味14

5

GLSL

この課題とモナリザのパレットにあるアメリカンゴシックの課題の違いモザイクタイルは再利用できるのに、ピクセルは再利用できないため、興味のあるピクセル再配置します。これは、アルゴリズムを簡単に並列化できることを意味するため、超並列バージョンを試すことにしました。「大規模」とは、GLSLを介してデスクトップのGTX670で1344シェーダーコアをすべて同時に使用することを意味します。

方法

実際のタイルマッチングは簡単です。ターゲットエリアの各ピクセルとモザイクタイルエリアの間のRGB距離を計算し、差が最も小さい(輝度値で重み付けされた)タイルを選択します。タイルインデックスはフラグメントの赤と緑の色の属性で書き出され、すべてのフラグメントがレンダリングされた後、フレームバッファから値を読み取り、それらのインデックスから出力画像を構築します。実際の実装はかなりハックです。FBOを作成する代わりに、ウィンドウを開いてレンダリングするだけですが、GLFWは任意の小さな解像度でウィンドウを開くことができないため、必要以上に大きなウィンドウを作成し、正しいサイズの小さな長方形を描画してソースイメージにマップするタイルごとに1つのフラグメント。MSVC2013ソリューション全体は、次のWebサイトで入手できます。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;
}

結果

画像はほぼ瞬時にレンダリングされるため、並列化は成功しました。欠点は、個々のフラグメントを他のフラグメントの出力に依存させることができないため、特定の範囲内で同じタイルを2回選択しないことで得られる大幅な品質向上を得る方法がありません。そのため、高速の結果が得られますが、タイルが大量に繰り返されるため品質が制限されます。全体として、楽しかったです。 フルサイズバージョンの場合は、http://imgur.com/a/M0Db0ここに画像の説明を入力してください


4

Python

ここでは、平均的なアプローチを使用した最初の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

全景

sunday_rgb

日曜日-ラボ

全景

sunday_lab

キス-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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.