大家好,我是小小明。

使用微信也有那么多年了,你有想过要给你的微信好友的头像生成图片墙吗?

例如:

image-20210818155936489

image-20210818195550313

注意:从效果看有不少重复头像,这只是因为我本地并没有那么多微信头像,只能重复选取。对于微信好友多的朋友,只需要把文章中np.random.choice(imgs, size=data.sum())这行代码加个参数replace=False就能实现非重复选取。

如果有跟我一样想法的朋友,咱们Let’s go⏩👊。


首先我们需要先获取微信头像的缓存列表:

🌀获取微信头像的缓存列表🌀

在我们自己点开过目标的头像查看大图时,微信电脑版会将其保存到HDHeadImage(高清大图)目录中。

这个目标可以直接通过python读取到:

wechat_id = "你的微信号"
path = os.path.expanduser(f"~/Documents/WeChat Files/{wechat_id}/FileStorage/General/HDHeadImage")
path
'C:\\Users\\ASUS/Documents/WeChat Files/你的微信号/FileStorage/General/HDHeadImage'

wechat_id是你的微信号。

~/Documents取决于你没有修改微信文件默认保存位置。如果已经修改的情况下需要改成你修改过的文件夹。

然后就可以通过该文件夹看到你在电脑上点开过头像的高清缓存:

image-20210818130306317

读取高清大图图像缓存,就可以再微信电脑版的这个位置读取到。

但如果我们想获取所有微信头像的小图缓存呢?这个时候无法通过微信电脑版直接获取需要的数据,但我们可以借助有root权限的手机拿到小图缓存(几乎能包含所有的小图缓存)。

我使用了夜神模拟器,通过夜神模拟器登录个人微信后,多游览一段时间后,本地就会产生头像缓存。

然后进入/data/data/com.tencent.mm/MicroMsg文件夹:

image-20210818133618011

再进入最近产生修改而且名称比较长的文件夹,其中的avatar文件夹就存放了所有的头像缓存:

image-20210818133743657

此时将其复制到夜神模拟器的安卓共享路径,就可以在PC端上读取到这些图片文件了:

image-20210818133922824

不过图片文件都在一个个的子文件夹中,这里我使用everything搜索目标文件夹,然后将里面的图片一次性全部剪切出来:

image-20210818134243158

提取出来后就是这样的效果:

image-20210818134615198

准备好了头像路径之后,咱们开始绘制文字:

📣绘制文字图案⏩

为了制作文字照片墙,需要在绘制出文字后,分析像素点决定哪些位置摆放照片。

这里依然是PIL库进行文字绘制,经过多次调试,设计出如下绘制方法:

from PIL import Image, ImageFont, ImageDraw, ImageChops


def create_text_img(text, size=30, fontname="msyhbd.ttc"):
    "作者:小小明"
    # 获取字体对象
    font = ImageFont.truetype(fontname, size)
    width = len(text) * size

    # 左上角对齐绘制文字
    im = Image.new(mode='RGBA', size=(width, size))
    draw = ImageDraw.Draw(im=im)
    w, h = draw.textsize(text, font)
    o1, o2 = font.getoffset(text)
    draw.text(xy=(-o1, -o2), text=text,
              fill="black", font=font)

    # 裁切文字多余空白
    bg = Image.new(mode='RGBA', size=im.size)
    bbox = ImageChops.difference(im, bg).getbbox()
    im = im.crop(bbox)
    text_img = Image.new(mode='L', size=im.size, color=255)
    text_img.paste(im, mask=im)
    return text_img


display(create_text_img("好友头像", fontname="STHUPO.TTF"))
display(create_text_img("好友头像"))

分别用华文琥珀和默认的微软雅黑粗体测试一下:

image-20210818145218161

⭐️绘制文字照片墙🔚

获取到文字图案的灰度图像对象,就可以很轻松的绘制文字照片墙了。

import numpy as np

im = create_text_img("照片墙", fontname="msyh.ttc")
data = np.array(im) != 255
h, w = data.shape
print(f"共需{data.sum()}张图片,宽{w}张,高{h}张")
共需1115张图片,宽90张,高29张

我使用了照片墙作为文字图片,显示共需1890张头像图片,但是我的缓存文件夹并没有这么多图片,只能允许头像能够被重复选取。

获取随机头像列表:

import os

img_path = r"C:\Users\ASUS\Nox_share\ImageShare\avatar"
imgs = os.listdir(img_path)
img_lists = np.random.choice(imgs, size=data.sum())

然后就可以生成头像照片墙了:

# 设置每个头像的大小
size = 50
new_img = Image.new('RGB', (size * w, size * h), "white")
random_imgs = iter(img_lists)

for y, x in zip(*np.where(data)):
    img_name = next(random_imgs)
    src_img = Image.open(f'{img_path}/{img_name}')
    src_img = src_img.resize((size, size), Image.ANTIALIAS)
    # 将图片复制到 new_image
    new_img.paste(src_img, (x * size, y * size))
new_img

生成结果:

image-20210818155936489

可以看到我们已经顺利的给图片列表生成了照片墙,以后的中秋节,国庆节,情人节,都可以直接用。我们不仅仅可能使用文字生成照片墙,也可能根据特殊的形状图片,为了方便以后使用,将上述逻辑封装一下:

def create_picture_wall(data, imgs, size=50):
    h, w = data.shape
    random_imgs = iter(np.random.choice(imgs, size=data.sum()))

    new_img = Image.new('RGB', (size * w, size * h), "white")
    for y, x in zip(*np.where(data)):
        img_name = next(random_imgs)
        src_img = Image.open(f'{img_path}/{img_name}')
        src_img = src_img.resize((size, size), Image.ANTIALIAS)
        # 将图片复制到 new_image
        new_img.paste(src_img, (x * size, y * size))
    return new_img

调用示例:

im = create_text_img("小小明")
img_path = r"C:\Users\ASUS\Nox_share\ImageShare\avatar"
create_picture_wall(np.array(im) != 255, os.listdir(img_path))

image-20210818161255918

im = create_text_img("小小明", fontname="msyh.ttc")
img_path = r"C:\Users\ASUS\Nox_share\ImageShare\avatar"
create_picture_wall(np.array(im) != 255, os.listdir(img_path))

img-FZTjYKb7-1629292153595

❤️Python绘制爱心图案💖

想画出爱心图案照片墙,首先得使用PIL画出爱心图案。绘制爱心的函数有很多种,下面我分别演示一下,并先用matplotlib实现爱心图像的绘制。

最流行的参数方程是:

x ( t ) = 16 sin ⁡ t 3 x(t)=16 {\sin t}^3 x(t)=16sint3

y ( t ) = 13 cos ⁡ t − 5 cos ⁡ 2 t − 2 cos ⁡ 3 t − cos ⁡ 4 t y(t)=13\cos t- 5\cos 2t - 2\cos 3t -\cos4t y(t)=13cost5cos2t2cos3tcos4t

这个参数方程用python表达就是:

import math
import numpy as np

t = np.arange(0, 2*math.pi, 0.1)
x = 16*np.sin(t)**3
y = 13*np.cos(t)-5*np.cos(2*t)-2*np.cos(3*t)-np.cos(4*t)

用matplotlib可以直接绘制:

from matplotlib import pyplot as plt

plt.plot(x, y, color="r");

image-20210818164118738

对x轴和y轴均加个偏移即使使其落在大于0的区间内。

绘制一个实心爱心:

plt.fill(x+15, y+15, color="r");

image-20210818164503221

不过这种参数方程的形式只是得到边界的点坐标,要转换到绘制到PIL图像中还比较困难。下面我使用另一个不流行的心形函数方程进行绘制,函数方程分别由上下两个方程组成。

上半部分方程为: f ( x ) = 2 ⋅ x 2 − x 2 f(x)=\sqrt{2 \cdot \sqrt{x^{2}}-x^{2}} f(x)=2x2 x2

下半部分方程为: g ( x ) = − 2.14 ⋅ 2 − ∣ x ∣ g(x)=-2.14 \cdot \sqrt{\sqrt{2}-\sqrt{|x|}} g(x)=2.142 x

import math
from matplotlib import pyplot as plt
import numpy as np

x = np.linspace(-2, 2, 1000)
fx = np.sqrt(2*np.abs(x)-x**2)
gx = -2.14*np.sqrt(np.sqrt(2)-np.sqrt(np.abs(x)))

plt.plot(x, fx, color="r", label="upper")
plt.plot(x, gx, color="b", label="down")
plt.legend();

image-20210818165350568

如何将其偏移到正数范围呢❓我采用下面的方式:

import math
from matplotlib import pyplot as plt
import numpy as np

x = np.linspace(-2, 2, 2000)
fx = np.sqrt(2*np.abs(x)-x**2)
gx = -2.14*np.sqrt(np.sqrt(2)-np.sqrt(np.abs(x)))

x = (x+2)*7.5
fx = (fx+2.5)*7.5
gx = (gx+2.5)*7.5
plt.plot(x, fx, color="r", label="upper")
plt.plot(x, gx, color="b", label="down")
plt.legend();

image-20210818174603978

同时有上下两个函数方程时,画实心爱心也很简单:

plt.fill_between(x, gx, fx, color="r");

image-20210818180851315

接下来我们去掉轴,并保存图片,就可以直接用PIL读取了。

产生爱心图像并保存到文件中:

import math
from PIL import Image
from matplotlib import pyplot as plt
import numpy as np

x = np.linspace(-2, 2, 100)
fx = np.sqrt(2*np.abs(x)-x**2)
gx = -2.14*np.sqrt(np.sqrt(2)-np.sqrt(np.abs(x)))

fx = (fx+2.5)*7
gx = (gx+2.5)*7
fig = plt.figure()
plt.axis("off")
plt.fill_between(x, gx, fx, color="black")
fig.savefig("t.jpg");

读取图片并转换为黑白图像:

im = Image.open("t.jpg").convert("1")
im

image-20210818190746465

现在需要去除多余的空白,对于这种黑白图像,我直接使用numpy去除边界上的空白。

首先转换为numpy数组:

data = ~np.array(im)
print("去除前:")
display(Image.fromarray(data))
ys, xs = np.where(data)
data = data[min(ys):max(ys)+1, min(xs):max(xs)+1]
print("去除后:")
display(Image.fromarray(data))

image-20210818191038035

💕绘制爱心照片墙💘

有了上的爱心蒙版就可以很简单的绘制出照片墙了。

为了减少最后的渲染量,将爱心图片缩放一下:

im = Image.fromarray(data).resize((60, 40), Image.ANTIALIAS)
data = np.array(im)
h, w = data.shape
print(f"共需{data.sum()}张图片,宽{w}张,高{h}张")
共需1504张图片,宽60张,高40张

然后开始绘制:

import os

def create_picture_wall(data, imgs, size=50):
    h, w = data.shape
    random_imgs = iter(np.random.choice(imgs, size=data.sum()))

    new_img = Image.new('RGB', (size * w, size * h), "white")
    for y, x in zip(*np.where(data)):
        img_name = next(random_imgs)
        src_img = Image.open(f'{img_path}/{img_name}')
        src_img = src_img.resize((size, size), Image.ANTIALIAS)
        # 将图片复制到 new_image
        new_img.paste(src_img, (x * size, y * size))
    return new_img


img_path = r"C:\Users\ASUS\Nox_share\ImageShare\avatar"
create_picture_wall(data, os.listdir(img_path))

image-20210818195550313

当然一些系统相关的缓存也被加入到了图片列表,可以再人工删除这些系统图标后再进行生成。

🚩绘制任意图形照片墙🚀

其实我并不是一定要自己画一个爱心图形之后,才能画爱心照片墙。只要我们事先准备好图形的蒙版图片,用PIL读取后转换一下即可马上画成相应的照片墙。

这里有个微词云的网站:https://www.weiciyun.com/edit

可以下载各种各样的蒙版形状:

image-20210818201514677

我们以大拇指为例进行演示,首先下载目标图片:

import requests
from io import BytesIO

url = "https://staticc.ywordle.com/static/2020-11-01/6d2e4f9d31d1b7201e23198869de2f9a_preview.png"
r = requests.get(url)
im = Image.open(BytesIO(r.content))
im.size
(190, 200)

图片过大,转换为bool数组,并缩放一下:

data = np.array(im) > 0
data = np.array(Image.fromarray(data).resize((48, 50), Image.ANTIALIAS))
display(Image.fromarray(data))

image-20210818203440667

然后就可以生成大拇指的照片墙了:

image-20210818203549329

将以上代码封装一下:

from PIL import Image
import requests
from io import BytesIO
import os


def get_mask_data(im, size=50):
    width, height = im.size
    if width > height:
        height = height*size//width
        width = size
    else:
        width = width*size//height
        height = size
    im = im.resize((width, height), Image.ANTIALIAS)
    return np.array(im) > 0


def create_picture_wall(data, img_path, size=50):
    h, w = data.shape
    imgs = os.listdir(img_path)
    random_imgs = iter(np.random.choice(imgs, size=data.sum()))
    new_img = Image.new('RGB', (size * w, size * h), "white")
    for y, x in zip(*np.where(data)):
        img_name = next(random_imgs)
        src_img = Image.open(f'{img_path}/{img_name}')
        src_img = src_img.resize((size, size), Image.ANTIALIAS)
        new_img.paste(src_img, (x * size, y * size))
    return new_img


def download_img(url):
    r = requests.get(url)
    return Image.open(BytesIO(r.content))

测试一下:

url = "https://staticc.ywordle.com/static/2020-11-03/f18f814d52768eb29111c0be52b14ca2_preview.png"
im = download_img(url)
data = get_mask_data(im)
create_picture_wall(data, r"C:\Users\ASUS\Nox_share\ImageShare\avatar")

image-20210818210023804

💎看完本文,相信你已经任何形式的照片墙都会画了吧?☀️


本文转载:CSDN博客