祖国过生日,贴个国旗诉衷肠
马上十一了,今年还有有史以来最大的阅兵仪式,听着就让人激动!很多朋友纷纷 @微信官方,要求给自己头像加红旗,表达自己的喜悦之情。
然而没想到,大家的热情,把腾讯的服务器累趴了!
革命先辈告诉我们 “自力更生,自给自足”,今天我们也发挥一下老传统,自己用 Python 实现:给头像加国旗
完整代码请移步 github (https://github.com/JiangChuanGo/examples/tree/master/wx_head_icon_flag)
基本思路
微信头像,是一个正方形图片,我们需要做的是将任意图片,裁剪成正方形,然后在原始图片上增加国旗图像。我们可以想象,在原始图片的基础上,叠加一个国旗图像,但是这个图像是镂空的,从正上方看,就可以得到想要的图像。
用过 PS 的同学一定看出来了,这就是图层的概念:多个图片像透明卡片一样叠加在一起,根据不同层、不同位置透明度不同,得到最终的效果。
磨刀不误砍柴工
图片准备
我们需要准备:
一张头像素材,也就是拿来头像图片:head.jpg
一张国旗素材: flag.jpg
图中黑色部分是提前绘制好的,用来表示要挖空的地方,这个黑色区域后面有妙用。
Anyway,这就是一张普通的不能再普通的图。一会我们要把它,叠加在头像素材上。
我们要的最终效果是这样的:
工具准备
我们要用到一些 Python 第三方库,充分利用第三方库是用好 Python 重要奥义:
- Jupyter Lab 和 Python,强烈建议直接使用最新的 3.7 版本,官方很快(2020年1月1日)会结束对 Python2 的支持。
- Pillow,在 Python 2 中的名字是 PIL,二者的接口基本兼容。它一个图像处理库,本次的 VIP 选手。
- Numpy,一个快速的矩阵处理库,国旗素材镂空就靠他了,今天的二号人物就是它。
知识准备
图片的本质。 图片本质上是整数数组,数组的每个位置取值范围是 0~255(8 bit 位图),代表着一个像素的亮度。
现代彩色显示原理是用: 红(Red)、绿(Green)、蓝(Blue) 三种像素混合,形成千变万化的颜色。
三基色混色原理
液晶显示器放大以后三基色像素
所以一张彩色图片,其实是由 RBG 3张图片构成的。
除了 RGB 模式,还有其他的模式:CMYK、Lab、HSB、灰度(黑白图)等等,今天不做讨论。
PIL 库的 Image 类关键接口。
- img = Image.open("/file/path") 返回打开的图片对象
- img.convert("RGBA"),用于图片模式转换,“RGBA” 是模式字符串,今天还会用到 “L”,表示灰度图像。
- img.save("path"),保存为图像。
- img.crop
Alpha 通道的作用 Alpha 通道用来表示在图片混合中,当前图层的透明度, 0 ~ 255 表示 透明 ~ 不透明。
Numpy 的基本使用 Numpy 是矩阵计算库,今天主要用于计算 Alpha 通道,能不能把国旗素材里的黑色部分变成透明的,就靠它了。
开始
原理
切割正方形
头像素材是由用户提供的,可能不是正方形,为了尽量保留图片主体,我的策略是:
- 正方形变长最大是 min(素材的长、宽)
- 正方形应该截取图像的正中位置
为了实现这个功能,先找到素材(绿色区域)的中心,然后截取的正方形(红色区域)围绕这个中心居中剪裁即可。
计算中点
长方形的中点计算很简单, 图像的左上角为坐标原点,img.size 解包可以得到 x、y 轴的长度,也就是绿色矩形的宽和高。
按照下面的公式就可以计算出中心的坐标:
计算最大内接正方形的位置
在切割图片的时候,我们需要指定的是切割矩形区域的左上角和右下角坐标(x1,y1)和 (x2, y2) 来确定裁剪区域的位置。
根据位置关系,正方形的边长为 L,则容易有这样的计算公式:
切割图片
使用 img.crop()函数,接受 4 个参数,分别是左上角和右下角的 x、y 坐标。
代码实现
import 和 open
from PIL import Image
from IPython.display import display
import numpy as np
head = Image.open("./head.png").convert("RGBA")
flag = Image.open("./flag.jpg").convert("RGBA")
图片切割为正方形
def crop_square(img):
x, y = img.size
center = (int(x/2), int(y/2))
# 计算最大内切正方形的边长
length = int(min(x, y)/2)
left_top = (center[0] - length, center[1] - length)
right_bottom = (center[0] + length, center[1] + length)
# 创建剪切好的副本
new_img = img.crop(left_top + right_bottom)
return new_img
head = crop_square(head)
display(head)
此时头像看起来是这样的:
重置头像尺寸
为了头像可以与国旗素材完美匹配,我们还需要对头像素材进行尺寸调整,使其与国旗素材一样大。
resizedHead = head.resize(flag.size, Image.ANTIALIAS)
display(resizedHead)
调整之后
创建 Alpha 通道
先看一下国旗素材
我们希望创建一个 Alpha 层,将黑色部分全部透明,也就是像素值为 0,让下面的图层可以显示出来,其余部分 Alpha 层像素为 255,不透明,显示国旗素材上的边框。上面已经提到,彩色图像实际上是由 RGB 三张位图构成的,我们只需要一张。因为国旗是红色的,可以选取红色图层作为产生 Alpha 层的数据依据。为了方便,我直接将国旗图像转换为黑白,直接对亮度进行阈值处理:像素比阈值亮,就显示,Alpha 通道像素就设置为 255, 否则设置为 0,使其透明。
alphaLayer = flag.convert("L")
alphaLayer = np.array(alphaLayer)
THRESHOLD = 100
# 这里使用了 Numpy 的矩阵操作,
# 注意将 bool 矩阵转换为 uint 8 的时候,取值是 0 或 1,
# 再 * 255,结果就是 255 和 0 了,代表 Alpha 通道不透明和透明。
alphaLayer = (alphaLayer > THRESHOLD).astype(np.uint8) * 255
alphaLayer = Image.fromarray(alphaLayer)
display(alphaLayer)
黑色部分表示透明,可以看到红旗上有一小部分没有被正确处理,但是对效果影响不大,如果用 RGB 中的 R 通道作为数据源,就会好得多。
拼接图像
在 resizedHead 上粘贴 flag,resizedHead 与 flag 一样大,所以 flag 只要与 head 左上角对齐就可以了
resizedHead.paste(flag, (0, 0), mask= alphaLayer)
display(resizedHead)
总结
本次我们使用了 PIL 库,Python 经典的图像处理库,为微信头像加国旗,仰赖 PIL 的强大,步骤很简单。
我还将这里的代码部署到我的微信号里,你可以加入下面的群,发送图片体验功能,说不定你还没有换爱国头像,刚好用得上!
这是一个开放群,个人微信的群操作 API 也被和谐了,没法自动踢人。
所以呢,群里广告比较多,试完建议退群。