Photomosaics 또는 : 전구를 교체하는 데 몇 명의 프로그래머가 필요합니까?


33

나는 최고 스택 오버플로 사용자 의 아바타에서 2025 개의 헤드 샷 모자이크를 편집했습니다 .
이미지를 클릭하면 전체 크기로 볼 수 있습니다.

StackOverflow 헤드 샷 모자이크

당신의 임무는이 45 × 45 그리드에서 48 × 48 픽셀 아바타를 사용하여 다른 이미지의 정확한 포토 모자이크를 만드는 알고리즘을 작성하는 것입니다.

테스트 이미지

테스트 이미지는 다음과 같습니다. 첫 번째는 물론 전구입니다!
(여기서는 전체 크기가 아닙니다. 이미지를 클릭하면 전체 크기로 볼 수 있습니다. 절반 크기 버전은 The Kiss , Sunday Afternoon ... , Steve Jobsspheres에서 사용할 수 있습니다.)

전구 키스 라 그란데 자테 섬에서 일요일 오후 스티브 잡스 구체

광선 추적 구를 제외한 모든 Wikipedia에 감사합니다.

전체 크기에서이 이미지의 크기는 모두 48으로 나눌 수 있습니다. 더 큰 이미지는 JPEG 여야하므로 업로드 할 수 있도록 압축 될 수 있습니다.

채점

이것은 인기 콘테스트입니다. 원본 이미지를 가장 정확하게 묘사하는 모자이크가 포함 된 제출물에 투표를해야합니다. 나는 1 ~ 2 주 안에 가장 높은 투표 응답을받을 것입니다.

규칙

  • 포토 모자이크는 위의 모자이크에서 가져온 변경되지 않은 48 × 48 픽셀 아바타 로 완전히 그리드로 배열되어야합니다.

  • 모자이크에서 아바타를 재사용 할 수 있습니다. (실제로 더 큰 테스트 이미지가 필요합니다.)

  • 출력을 보여 주지만 테스트 이미지는 매우 커서 StackExchange는 최대 2MB의 이미지 만 게시 할 수 있습니다 . 따라서 이미지를 압축하거나 다른 곳에서 호스팅하고 더 작은 버전을 여기에 넣으십시오.

  • 우승자를 확인하려면 전구 또는 구 모자이크의 PNG 버전을 제공해야합니다. 이것은 모자이크를 더 잘 보이게하기 위해 아바타에 추가 색상을 추가하지 않도록하기 위해 유효성을 검사 할 수 있도록하기 위해 (아래 참조) 있습니다.

검사기

이 Python 스크립트를 사용하면 완성 된 모자이크가 변경되지 않은 아바타를 실제로 사용하는지 확인할 수 있습니다. 그냥 설정 toValidate하고 allTiles. JPEG를 정확하게 비교하기 때문에 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))

모두 행운을 빌어 요! 결과를보고 기다릴 수 없습니다.

참고 : Photomosaic 알고리즘은 온라인에서 쉽게 찾을 수 있지만 아직이 사이트에는 없습니다. 나는 우리가 일반적인 "각 타일과 각 그리드 공간을 평균화하고 일치시키는" 알고리즘 보다 더 흥미로운 것을 보게되기를 정말로 바라고있다 .


1
이것은 본질적으로 이전 것과 중복되지 않습니까? 각각의 색상을 계산하고 대상을 2025px로 축소하고 기존 알고리즘을 적용합니까?
John Dvorak


2
@JanDvorak 비슷하지만 복제본으로 충분하지 않다고 생각합니다. 언급 한 알고리즘은 결과를 얻는 한 가지 방법입니다. 훨씬 더 정교한 솔루션이 있습니다.
Howard

1
내 고양이가 아바타
Joey

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
와우. 반지름 계수는 실제로 결과를 향상시킵니다. 그 같은 아바타 얼룩은 좋지 않았다.
John Dvorak

1
그것이 나인지 확실하지 않지만 Pictureshack은 Imgur와 비교하여 끔찍한 대역폭을 가지고있는 것 같습니다
Nick T

@NickT 아마도, 그러나 Imgur는 모든 것을 최대 1MB까지 압축합니다 ( imgur.com/faq#size ). :(
Calvin 's Hobbies

흠, 데이빗의 나만 또는 매스 매 티카의 대답이이 최고 투표 한 대답보다 훨씬 낫습니까?
justhalf

모든 사진이 사라져서 너무 나쁘다. 우연히 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 R, G, B 만 남겨두고 PNG 및 일부 JPG의 네 번째 매개 변수를 제거합니다.

dims의 치수입니다 targetPic.

tiles 대상 그림을 함께 구성하는 작은 사각형입니다.

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

tileReplacements 올바른 순서로 각 타일과 일치하는 사진입니다.

gallery 적절한 치수 (예 : 타일과 일치하는 행과 열의 수)를 가진 타일 교체 (사진) 세트입니다.

ImageAssembly 모자이크를 연속 출력 이미지로 결합합니다.


이렇게하면 일요일 이미지의 12x12 정사각형이 평균 색상에 가장 적합한 해당 48 x 48 픽셀 사진으로 바뀝니다.

photoMosaic[sunday, 12]

일요일 2


일요일 (세부 사항)

모자


photoMosaic[lightbulb, 6]

전구 6


photoMosaic[stevejobs, 24]

스티브 잡스 24


디테일, 스티브 잡스

채용 정보


photoMosaic[kiss, 24]

키스


키스의 세부 사항 :

디테일 키스


photoMosaic[spheres, 24]

구체


1
세분성이라는 아이디어가 마음에 듭니다. 더 작은 이미지에 더 사실적입니다.
Calvin 's Hobbies

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 픽셀로 나눌 수있는 절반 크기 이미지를 추가했습니다.
Calvin 's Hobbies

5

GLSL

이 도전과 Mona Lisa 팔레트의 American Gothic 의 차이점 : 픽셀 재정렬 모자이크 타일은 재사용 할 수 있지만 픽셀은 사용할 수 없기 때문에 관심 다시 정렬합니다. 이것은 알고리즘을 쉽게 병렬화 할 수 있다는 것을 의미하므로 대규모 병렬 버전을 시도하기로 결정했습니다. "대규모"란 GLSL을 통해 데스크탑 GTX670의 1344 셰이더 코어를 모두 동시에 사용한다는 의미입니다.

방법

실제 타일 일치는 간단합니다. 대상 영역의 각 픽셀과 모자이크 타일 영역 사이의 RGB 거리를 계산하고 차이가 가장 작은 타일을 선택합니다 (밝기 값으로 가중치 적용). 타일 ​​인덱스는 프래그먼트의 빨강 및 녹색 색상 속성으로 작성 된 다음 모든 프래그먼트가 렌더링 된 후 프레임 버퍼에서 값을 다시 읽고 해당 인덱스의 출력 이미지를 작성합니다. 실제 구현은 상당히 해킹입니다. 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

또 다른 파이썬 솔루션-평균 기반 (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.