Photomosaics หรือ: ต้องใช้โปรแกรมเมอร์กี่คนเพื่อเปลี่ยนหลอดไฟ?


33

เราได้รวบรวมกระเบื้องโมเสคของ 2025 headshots จากอวตารของผู้ใช้กองมากเกินด้านบน
(คลิกที่ภาพเพื่อดูขนาดเต็ม)

StackOverflow headshots mosaic

งานของคุณคือการเขียนอัลกอริทึมที่จะสร้าง photomosaic ที่ถูกต้องของรูปภาพอื่นโดยใช้อวตาร 48 × 48 พิกเซลจากกริด 45 × 45 ตัวนี้

ทดสอบภาพ

นี่คือภาพทดสอบ แรกคือแน่นอนหลอดไฟ!
(ภาพเหล่านี้ไม่เต็มขนาดคลิกที่ภาพเพื่อดูขนาดเต็มมีเวอร์ชั่นครึ่งขนาดให้บริการสำหรับThe Kiss , A Sunday Af บ่าย ... , Steve Jobs , และspheres )

หลอดไฟฟ้า จูบ บ่ายวันอาทิตย์บนเกาะ La Grande Jatte สตีฟจ็อบส์ ทรงกลม

ขอบคุณ Wikipedia สำหรับทุกคนยกเว้นทรงกลม raytraced

ในขนาดเต็มภาพเหล่านี้จะมีขนาดที่หารด้วย 48 ภาพที่มีขนาดใหญ่กว่าต้องเป็น JPEG ดังนั้นจึงสามารถบีบอัดได้เพียงพอที่จะอัปโหลด

เกณฑ์การให้คะแนน

นี่คือการประกวดความนิยม การส่งภาพโมเสกที่แสดงภาพต้นฉบับได้อย่างแม่นยำที่สุดควรได้รับการโหวต ฉันจะยอมรับคำตอบที่โหวตสูงสุดในหนึ่งหรือสองสัปดาห์

กฎระเบียบ

  • photomosaics ของคุณจะต้องประกอบด้วยอวตาร 48 × 48 พิกเซลที่ไม่เปลี่ยนแปลงทั้งหมดจากภาพโมเสคด้านบนซึ่งจัดเรียงเป็นตาราง

  • คุณสามารถใช้ Avatar ในโมเสกได้ (แน่นอนว่าสำหรับภาพทดสอบขนาดใหญ่ที่คุณต้องทำ)

  • แสดงผลลัพธ์ของคุณ แต่โปรดจำไว้ว่าภาพทดสอบมีขนาดใหญ่มากและStackExchange อนุญาตให้โพสต์ภาพได้สูงสุด 2MBเท่านั้น ดังนั้นบีบอัดรูปภาพของคุณหรือโฮสต์ไว้ที่อื่นแล้ววางรุ่นเล็กลงที่นี่

  • เพื่อให้ได้รับการยืนยันผู้ชนะคุณจะต้องระบุหลอดไฟ PNG หรือลูกกลมของคุณ นี่คือดังนั้นฉันสามารถตรวจสอบพวกเขา (ดูด้านล่าง) เพื่อให้แน่ใจว่าคุณไม่ได้เพิ่มสีพิเศษให้กับรูปประจำตัวเพื่อทำให้กระเบื้องโมเสคดูดีขึ้น

ตรวจสอบ

สามารถใช้สคริปต์ Python นี้เพื่อตรวจสอบว่า mosaic ที่เสร็จสมบูรณ์ใช้รูปประจำตัวที่ไม่เปลี่ยนแปลงหรือไม่ เพียงแค่ตั้งค่า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))

โชคดีนะทุกคน! ฉันไม่สามารถรอดูผลลัพธ์

หมายเหตุ:ฉันรู้ว่าอัลกอริทึม photomosaic นั้นหาได้ง่ายออนไลน์ แต่พวกเขายังไม่ได้อยู่ในไซต์นี้ ฉันหวังว่าเราจะเห็นสิ่งที่น่าสนใจกว่าอัลกอริทึม"เฉลี่ยแต่ละไทล์และแต่ละกริดและจับคู่กับอัลกอริทึม"


1
นี่ไม่ใช่สิ่งที่ซ้ำกันจากอันก่อนหรือไม่? คำนวณสีของแต่ละคนลดระดับเป้าหมายเป็น 2025px และใช้อัลกอริทึมที่มีอยู่หรือไม่
John Dvorak


2
@JanDvorak มันคล้ายกัน แต่ฉันคิดว่าไม่เพียงพอที่จะซ้ำซ้อน อัลกอริทึมที่คุณกล่าวถึงเป็นวิธีหนึ่งในการรับผลลัพธ์ มีโซลูชั่นที่ซับซ้อนมากขึ้นว่า
Howard

1
แมวของฉันหายไปจากอวตาร :-(
Joey

2
คุณอาจต้องการที่จะเปลี่ยน "เพื่อให้หลอดไฟ" กับ "การเปลี่ยนหลอดไฟ"
DavidC

คำตอบ:


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

อัลกอริทึมทำการค้นหาผ่านไทล์ Avatar ทั้งหมดสำหรับแต่ละพื้นที่กริดแยกจากกัน ด้วยขนาดที่เล็กฉันไม่ได้ใช้โครงสร้างข้อมูลหรืออัลกอริธึมการค้นหาที่ซับซ้อน แต่เพียงแค่เดียรัจฉานบังคับพื้นที่ทั้งหมด

รหัสนี้ไม่ได้ทำการแก้ไขใด ๆ กับไทล์ (เช่นไม่มีการปรับให้เข้ากับสีปลายทาง)

ผล

คลิกเพื่อดูภาพขนาดเต็ม

แสง buld ทรงกลม
วันอาทิตย์

ผลของรัศมี

การใช้radiusคุณสามารถลดความซ้ำซ้อนของกระเบื้องในผลลัพธ์ได้ การตั้งค่าradius=0ไม่มีผลกระทบ เช่นradius=3แทนที่กระเบื้องเดียวกันภายในรัศมี 3 แผ่น

แสง buld วันอาทิตย์ รัศมี = 0

แสง buld
แสง buld
รัศมี = 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

อืมมันเป็นแค่ฉันหรือ Mathematica คำตอบของ David นั้นดีกว่าคำตอบที่ได้รับคะแนนสูงสุดนี้หรือไม่?
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]:

example1


photomosaic

photoMosaic[rawPic_, targetSwathSize_: 48] :=
 Module[{targetPic, data, dims, tiles, tileReplacements, gallery},
  targetPic = Image[data = ImageData[rawPic] /. {r_, g_, b_, _} :> {r, g, b}];
  dims = Dimensions[data];
  tiles = ImagePartition[targetPic, targetSwathSize];
  tileReplacements = targetColorToPhoto /@ (Mean[Flatten[ImageData[#], 1]] & /@Flatten[tiles, 1]);
  gallery = ArrayReshape[tileReplacements, {dims[[1]]/targetSwathSize,dims[[2]]/targetSwathSize}];
  ImageAssemble[gallery]]

`photoMosaic ใช้เป็นภาพดิบเราจะทำภาพโมเสคของ

targetPic จะลบพารามิเตอร์ตัวที่สี่ (จาก PNG และ JPG บางส่วน) เหลือเพียง R, G, B

dims เป็นมิติของ targetPicมีขนาดของ

tiles เป็นสี่เหลี่ยมเล็ก ๆ ที่รวมกันเป็นรูปภาพเป้าหมาย

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

tileReplacements คือรูปภาพที่ตรงกับแต่ละไทล์ตามลำดับที่ถูกต้อง

gallery คือชุดของการเปลี่ยนไทล์ (ภาพถ่าย) ที่มีมิติข้อมูลที่เหมาะสม (เช่นจำนวนแถวและคอลัมน์ที่ตรงกับไทล์)

ImageAssembly เชื่อมต่อโมเสคเข้ากับอิมเมจเอาต์พุตต่อเนื่อง


ตัวอย่าง

สิ่งนี้จะแทนที่สี่เหลี่ยมจัตุรัสขนาด 12x12 จากภาพวันอาทิตย์ด้วยภาพถ่าย 48 x 48 พิกเซลที่สอดคล้องกันซึ่งเหมาะที่สุดสำหรับสีเฉลี่ย

photoMosaic[sunday, 12]

sunday2


วันอาทิตย์ (รายละเอียด)

หมวกทรงสูง


photoMosaic[lightbulb, 6]

หลอดไฟ 6


photoMosaic[stevejobs, 24]

สตีฟจ๊อบส์ 24


รายละเอียด stevejobs

รายละเอียดงาน


photoMosaic[kiss, 24]

จูบ


รายละเอียดของ Kiss:

จูบรายละเอียด


photoMosaic[spheres, 24]

ทรงกลม


1
ฉันชอบความคิดที่ละเอียดมาก มันให้ความสมจริงมากกว่าสำหรับภาพขนาดเล็ก
งานอดิเรกของ Calvin

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

5

GLSL

ความแตกต่างระหว่างความท้าทายนี้และความท้าทายที่American Gothic ในจานสีของ Mona Lisa: จัดเรียงพิกเซลใหม่น่าสนใจเพราะกระเบื้องโมเสกสามารถนำกลับมาใช้ใหม่ได้ในขณะที่พิกเซลไม่สามารถทำได้ ซึ่งหมายความว่าเป็นไปได้ที่จะอัลกอริธึมขนานได้อย่างง่ายดายดังนั้นฉันจึงตัดสินใจที่จะลองใช้เวอร์ชั่นที่ขนานกันอย่างหนาแน่น โดย "หนาแน่น" ฉันหมายถึงการใช้แกน shader 1344 บน GTX670 ของเดสก์ท็อปพร้อมกันผ่าน GLSL

วิธี

การจับคู่กระเบื้องที่แท้จริงนั้นง่ายมาก: ฉันคำนวณระยะทาง RGB ระหว่างแต่ละพิกเซลในพื้นที่เป้าหมายและพื้นที่ของกระเบื้องโมเสกและเลือกกระเบื้องที่มีความแตกต่างต่ำที่สุด (ถ่วงน้ำหนักตามค่าความสว่าง) ดัชนีไทล์ถูกเขียนในคุณลักษณะสีแดงและสีเขียวของแฟรกเมนต์จากนั้นหลังจากแฟรกเมนต์ทั้งหมดถูกเรนเดอร์ฉันอ่านค่ากลับออกมาจาก framebuffer และสร้างภาพเอาต์พุตจากดัชนีเหล่านั้น การใช้งานจริงค่อนข้างแฮ็ค แทนที่จะสร้าง FBO ฉันเพิ่งเปิดหน้าต่างและแสดงผลลงในนั้น แต่ GLFW ไม่สามารถเปิดหน้าต่างที่ความละเอียดขนาดเล็กโดยพลการดังนั้นฉันสร้างหน้าต่างที่ใหญ่กว่าที่ต้องการแล้ววาดสี่เหลี่ยมเล็ก ๆ ที่มีขนาดที่ถูกต้องเพื่อให้มันมี หนึ่งชิ้นต่อกระเบื้องที่แมปกับอิมเมจต้นทาง โซลูชัน MSVC2013 ทั้งหมดมีให้ที่https://bitbucket.org/Gibgezr/mosaicmaker มันต้องมีการรวบรวม GLFW / FreeImage / GLEW / GLM และ OpenGL 3.3 หรือไดรเวอร์ / การ์ดแสดงผลที่ดีกว่า

แหล่งที่มาของชิ้นส่วน 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 แรกโดยใช้วิธีการเฉลี่ย เราสามารถวิวัฒนาการได้จากที่นี่ ส่วนที่เหลือของภาพที่มีที่นี่

วันอาทิตย์ สตีฟ

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 กับ 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สำหรับ 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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.