拼图还能这么玩?

这两天将我所有微信好友的头像弄出来了,一共5000多张。然后想着可以用它们来做些啥,最后用它们拼图玩。

Mac微信的头像保存在:

~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat/2.0b4.0.9/c1a30fcf75eedba12764b4d4170b977e/Avatar

其中倒数第二个那一长串字符串每个人会不同,根据自己情况进行修改。

用到的代码在文章最后,github上也开源了:
https://github.com/godweiyang/FunnyMedia

最终效果

直接上最终效果图,首先是5000多张头像拼接成一张图片:

如果看不清的话可以放大看看细节:

然后是5000多张头像拼成的杨超越:

如果看不清的话可以放大看看细节:

杨超越的原图是这样的:

实现方法

实现方法很简单,拼接的话就是把头像依次粘贴在一块大画布上的不同区域,就跟贴瓷砖一样。

为了实现用不同头像拼接出杨超越,就需要先将杨超越图片进行分割,每一块小区域寻找一个颜色最相近的头像粘贴上去。我直接将图像区域的RBG值求了加权平均,然后在头像中寻找RGB均值相等的粘贴上去。

代码放到文章最后了,运行命令如下:

python3 image_stitch.py -d [图像合集目录] -i [待拼凑的图片] -s [小图最终的边长] -r

其中-d表示图像目录,你也可以放头像或者其他的图像;-i是你想拼成的大图路径,如果不设置的话就是直接把图像拼在一起;-s表示小图像最终的边长,实测设置为30效果最佳,图片大小和质量都比较好;-r表示是否随机排列图片。

此外之前还写过字符画视频的生成方法,代码也开源在同一个github上了:
https://zhuanlan.zhihu.com/p/466993864
https://github.com/godweiyang/FunnyMedia

代码

import argparse
import os
import random
import math
from collections import defaultdict

import numpy as np
import PIL.Image as Image
from tqdm import tqdm, trange


def generate1(dir, size, rand):
    print(f"正在拼接尺寸:{size}...")
    nums = len(os.listdir(dir))
    nums_width = int(math.sqrt(nums))
    nums_height = int((nums + nums_width - 1) / nums_width)
    img_width = nums_width * size
    img_height = nums_height * size

    image = Image.new("RGB", (img_width, img_height), "white")
    x = 0
    y = 0

    files = os.listdir(dir)
    if rand:
        random.shuffle(files)

    for i in tqdm(files):
        try:
            img = Image.open(os.path.join(dir, i))
        except IOError:
            print(i)
            print("图像打开失败")
        else:
            img = img.resize((size, size), Image.ANTIALIAS)
            image.paste(img, (x * size, y * size))
            x += 1
            if x == nums_width:
                x = 0
                y += 1
            img.close()

    image.save(f"avatar_{size}.jpg")
    image.close()


def mean_pixel(colors):
    colors = [0.3 * r + 0.59 * g + 0.11 * b for r, g, b in colors]
    return int(np.mean(colors))


def generate2(dir, source, size, rand):
    print(f"正在拼接尺寸:{size}...")

    files = os.listdir(dir)
    if rand:
        random.shuffle(files)

    image = Image.open(source)
    image = image.convert("RGB")
    img_width, img_height = image.size
    img_width = ((img_width + size - 1) // size) * size * ((size + 9) // 10)
    img_height = ((img_height + size - 1) // size) * size * ((size + 9) // 10)
    image = image.resize((img_width, img_height), Image.ANTIALIAS)

    colors = defaultdict(list)
    for i in tqdm(files):
        try:
            img = Image.open(os.path.join(dir, i))
        except IOError:
            print(i)
            print("图像打开失败")
        else:
            img = img.convert("RGB")
            img = img.resize((size, size), Image.ANTIALIAS)
            colors[mean_pixel(img.getdata())].append(i)
            img.close()
    for i in range(256):
        if len(colors[i]) == 0:
            for n in range(1, 256):
                if len(colors[i - n]) != 0:
                    colors[i] = colors[i - n]
                    break
                if len(colors[i + n]) != 0:
                    colors[i] = colors[i + n]
                    break

    index = defaultdict(int)
    for i in trange(0, img_width, size):
        for j in range(0, img_height, size):
            now_colors = []
            for ii in range(i, i + size):
                for jj in range(j, j + size):
                    now_colors.append(image.getpixel((ii, jj)))
            mean_color = mean_pixel(now_colors)
            img = Image.open(
                os.path.join(
                    dir, colors[mean_color][index[mean_color] % len(colors[mean_color])]
                )
            )
            img = img.convert("RGB")
            img = img.resize((size, size), Image.ANTIALIAS)
            image.paste(img, (i, j))
            img.close()
            index[mean_color] += 1

    source_name = ".".join(source.split(".")[:-1])
    image.save(f"{source_name}_{size}.jpg")
    image.close()


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--dir", "-d", type=str, default="avatar", help="directory of the avatars"
    )
    parser.add_argument(
        "--img", "-i", type=str, default="", help="source image to be coverd"
    )
    parser.add_argument(
        "--size",
        "-s",
        type=str,
        default="30",
        help="size of each avatar (size1,size2,...)",
    )
    parser.add_argument(
        "--rand",
        "-r",
        action="store_true",
        help="whether to shuffle the avatars",
    )
    args = parser.parse_args()
    sizes = [int(s) for s in args.size.split(",")]
    for size in sizes:
        if len(args.img) == 0:
            generate1(args.dir, size, args.rand)
        else:
            generate2(args.dir, args.img, size, args.rand)

   转载规则


《拼图还能这么玩?》 韦阳 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
“交叉熵”反向传播推导 “交叉熵”反向传播推导
交叉熵(CrossEntropy)是常见的损失函数,本文详细推导一下它的梯度,面试大厂或者工程实践中都可能会用到。 前向传播假设分类任务类别数是$V$,隐层输出是$V$维向量$\mathbf{h}$,标准的one-hot向量是$\mathb
2022-05-21
下一篇 
封城第20日,起飞了(文末有字节校招惊喜) 封城第20日,起飞了(文末有字节校招惊喜)
封城第20天了,最近上海日新增又降到2w以下了,京东快递和外卖也部分恢复了,部分小区甚至都转成防范区了。这两天小区里核酸检测率都在95%以下,有300左右的人都没去做核酸,据说达不到要求的话没法申报解封。而我也已经4天没去做过核酸了,因为在
2022-04-20
  目录