Python: 生成带用户昵称的头像

2023-11-09

Python: 生成带用户昵称的头像

需求

新建用户后,根据用户输入的昵称生成图片。(例:注册"钉钉"用户后,头像根据输入的名字生成)

开发环境

  • Windows 10
  • Python 3.8
  • Pillow 8.1.2

实现

蛇皮皮蛋:Python创建文字图片(居中)/多图片合并(PIL),参考这篇文章,实现了新建一个图片,并把文字渲染到图片上。代码如下:

import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image


def new_image(size, color, name):
    img = Image.new('RGB', size, color)  # 生成图片
    img.show()  # 展示图片
    img.save(name)  # 保存图片


def create_font_img(value, file_name, path):
    img = cv2.imread(path)  # 打开底片
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    img_pil = Image.fromarray(img)
    draw = ImageDraw.Draw(img_pil)
    text_width, text_height = draw.textsize(value, font)  # 获取字体宽高
    # 绘制文字信息,img.shape 求图片的宽和高,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img.shape[1] - text_width) / 2, (img.shape[0] - text_height) / 2), value, fill=(255, 255, 255), font=font)
    bk_img = np.array(img_pil)
    # cv2.imshow("add_text", bk_img)
    # cv2.waitKey()
    cv2.imwrite(file_name + "_font.png", bk_img)

new_image((128, 128), (192, 202, 208), "new.jpg")
create_font_img("张", "name", "new.jpg")

代码执行后得到两张图片,如下:

底图

底图

成果图

成果图1

根据上文提到的文章,简化后得到了以上代码,第一个函数 new_image() 新建一个图片,没有异议。

第二个函数 create_font_img() 将文字渲染到图片上。首先是 cv2.imread() 读取图片,之后设置文字的字体与大小,draw.text() 将文字画到图片上。注意到此函数的最后三行代码,cv2.imshow() 的意思应该非常明显,即直接显示图片,cv2.imwrite() 保存图片,那么 cv2.waitKey() 是什么意思呢?

这不是此文章重点,可以看看下面这篇文章:山上有强强:cv2.waitKey的入门级理解

注意 cv2 需要安装 opencv-python 使用,命令是 pip install opencv-python

事情到这里并没有结束,我对以上代码并不满意。主要问题如下:

  • 需要新建一张图片为底片,再读取此图片,渲染文字。如果能不保存,直接使用此图片就更好了。
  • 需要读字体文件,一个字体文件非常大,至少此处用到的 simsun.ttc 字体文件就有 10 MB 之大。如果能从系统读取,或者不使用字体文件会更好。
  • 所用第三方库太多,达到三个。三个库看起来不多,但是如非必要勿增实体(包括上面的字体文件),特别是部分库版本不一,用在项目中,越少越好,以免出现什么问题。

基于这三个问题,必需简化代码。

进阶

经过搜索,发现了下面这篇文章:amigo1226:自动生成带昵称的头像(仿照钉钉头像)。此文章,实现了不用底片直接生成图片。不过这篇文章是用 Java 写的,为了验证是否成功,我特意用 IDEA 运行了代码,确实可以成功,得到的图与上文的成果图1一样。那么,Python 理论上也是可以的。

仔细阅读此文章中的代码,发现一句代码:BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);, BufferedImage 这意思似乎是说图片存在缓存中?那么就看看 PIL 库有没有类似的方法把。果然,Image 类中还真有一个方法,是 frombuffer(),不过在多次尝试后,此方法似乎并不行。仔细思考我们的代码,第一个函数中用了 img.save() 来保存图片。

那么我们不能立即保存图片,其次 create_font_img() 中使用了 cv2.imread() 读取图片,既然不用读取图片,这句代码也就可以删除了。此时 cv2 的用法只剩 cv2.imwrite() 保存图片了,既然打算不使用那么多库,PIL 也是有保存方法的(img.save()),如此一来便可以将 cv2 库的用法从代码中删除。

def create_font_img(value, file_name, path):
    # img = cv2.imread(path)  # 打开底片
    img = Image.new('RGB', (128, 128), (192, 202, 208))  # 生成图片
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    img_pil = Image.fromarray(img)
    draw = ImageDraw.Draw(img_pil)
    text_width, text_height = draw.textsize(value, font)  # 获取字体宽高
    # 绘制文字信息,img.shape 求图片的宽和高,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img.shape[1] - text_width) / 2, (img.shape[0] - text_height) / 2), value, fill=(255, 255, 255), font=font)
    bk_img = np.array(img_pil)
    # cv2.imwrite(file_name + "_font.png", bk_img)
    img.save(file_name + "_font.png")  # 保存图片

初步简化代码如上,这样的代码并不能正确执行,问题很多,首先是,cv2.imread() 读取图片文件得到的是 <class 'numpy.ndarray'> 类型。这个类型下面 Image.fromarray() 创建图像,和 img.shape 要读取图片像素大小。而 Image.new() 创建的图片并不是此种类型,而是 <class 'PIL.Image.Image'>。本来打算看看是否可以将 <class 'PIL.Image.Image'> 转为 <class 'numpy.ndarray'>(其实就是 np.array() 这个方法),但转念一想,不打算用 numpy 库,也就没有转类型的必要,直接看能否只使用 PIL 库作图。

整理一下,那么整个作图流程如下:新建一张图,求出图的像素大小(其实大小在新建图时已给出),设置需要画上去的文字的字体与大小,求出文字的像素大小(设置字体时已给出),将文字画在图片上;保存图片。

那么由此得出的代码如下:

def create_font_img(name):
    img = Image.new('RGB', (128, 128), (192, 202, 208))  # 生成一张像素 128*128 图片
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    draw = ImageDraw.Draw(img)  # 绘图
    # 绘制文字信息,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((128 - 100) / 2, (128 - 100) / 2), name, fill=(255, 255, 255), font=font)
    # img.show()  # 展示图片
    img.save("name.png")  # 保存图片

执行以上的代码,得到的图与成果图1一样。

那么图片的像素大小与文字的像素大小是否有相应的方法获取呢?既然 cv2.imread()img.shape()) 和 ImageDraw.Draw()draw.textsize())可以读取图片和文字像素大小,理论上 Image.new()ImageFont.truetype() 应该有对应的方法。通过查找该对象所拥有的函数,发现了两个方法 size 和 getsize,分别使用 img.sizefont.getsize() 可以得到图片像素和文字像素。

修改后的代码如下:

def create_font_img(name):
    img = Image.new('RGB', (128, 128), (192, 202, 208))  # 生成图片
    img_width, img_height  = img.size
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    text_width, text_height = font.getsize(name)  # 获取字体宽高
    draw = ImageDraw.Draw(img)
    # 绘制文字信息,img.shape 求图片的宽和高,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img_width - text_width) / 2, (img_height - text_height) / 2), name, fill=(255, 255, 255), font=font)
    # img.show()
    img.save("name.png")  # 保存图片

用户昵称可能是中文也可能是英文,这样字体的大小为一个不变的值不太好,同时将需要更改的数值提取出来,优化代码后如下:

from PIL import Image, ImageFont, ImageDraw

def create_font_img(username, img_size, img_bg_color, file_name):
    img = Image.new('RGB', img_size, img_bg_color)  # 生成图片
    img_width, img_height  = img.size
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    if '\u4e00' <= username <= '\u9fff':
        font = ImageFont.truetype(font_path, size=100)  # 中文字符字体大小为 100
    elif 'a' <= username <= 'z' or 'A' <= username <= 'Z':
        username = username.upper()  # 将英文字母转为大写
        font = ImageFont.truetype(font_path, size=60)  # 英文字符字体大小为 60
    else:
        font = ImageFont.truetype(font_path, size=32)  # 其他字符字体大小为 32
    text_width, text_height = font.getsize(username)  # 获取字体宽高
    draw = ImageDraw.Draw(img)
    # 绘制文字信息,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img_width - text_width) / 2, (img_height - text_height) / 2), username, fill=(255, 255, 255), font=font)
    img.save(file_name + ".png")  # 保存图片

create_font_img('张', (128, 128), (192, 202, 208), 'zhang')

到此,解决了需要中间图片和第三方库多的问题,只剩下一个字体文件的问题?能否不使用字体文件呢?或者使用系统中的文件呢?

经过尝试,如果使用默认的字体文件,无法获取字体的像素大小,因为设置默认字体的方法是 ImageFont.load_default().font,并没有指定像素大小,将 draw.text() 中的 char_width_height 设置定值,但是 draw.text() 中的 font 出现了中文字符的编解码问题,英文倒是可以正确执行。不过没有设置字体大小,导致一张 128*128 像素的图片中,字符的大小非常小,几乎不可见,如图:

成果图2

成果图2

经过搜索,在这个问答中 如何使用Python ImageDraw库更改字体大小 发现了一句话,如下:

每个PIL’s docs,ImageDraw的默认字体是位图字体,因此无法缩放。要进行缩放,需要选择真正的字体类型。我希望不难找到一个不错的truetype字体“看起来有点像”默认字体在您想要的字体大小!

意思应该非常明显了,如果想要缩放字体的大小,那么就必需指定字体文件了。在搜索中了解到,也可以从系统中,加载字体文件,windows 系统中有许多字体文件,目录为 C:\Windows\Fonts,可以从这里获取字体文件。在项目中没有 simsun.ttc 时,需要先下载一个,或者先使用 Fonts 下的文件,Fonts 中的文件如下图:

Fonts 中的字体文件

Fonts 中的字体文件

将上文代码中 font_path = "simsun/simsun.ttc" 修改为 font_path = "C:\\Windows\\Fonts\\STXINGKA.TTF",字体为"华文行楷 常规",得到的图片如下:

成果图3

成果图3

总结

本文介绍了如何生成一张图片,并加以文字(用户昵称),探索了一些 PIL 的用法。

生成的图片可能并不完美,比如色彩的搭配,图片的圆角设置,以及文字字符过多如何割舍等问题,留待读者自行探索。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python: 生成带用户昵称的头像 的相关文章

  • 中断 Select 以添加另一个要在 Python 中监视的套接字

    我正在 Windows XP 应用程序中使用 TCP 实现点对点 IPC 我正在使用select and socketPython 2 6 6 中的模块 我有三个 TCP 线程 一个读取线程通常会阻塞select 一个通常等待事件的写入线程
  • 使用特定的类/函数预加载 Jupyter Notebook

    我想预加载一个笔记本 其中包含我在另一个文件中定义的特定类 函数 更具体地说 我想用 python 来做到这一点 比如加载一个配置文件 包含所有相关的类 函数 目前 我正在使用 python 生成笔记本并在服务器上自动启动它们 因为不同的
  • 使用 Python 从文本中删除非英语单词

    我正在 python 上进行数据清理练习 我正在清理的文本包含我想删除的意大利语单词 我一直在网上搜索是否可以使用像 nltk 这样的工具包在 Python 上执行此操作 例如给出一些文本 Io andiamo to the beach w
  • 将 python2.7 与 Emacs 24.3 和 python-mode.el 一起使用

    我是 Emacs 新手 我正在尝试设置我的 python 环境 到目前为止 我已经了解到在 python 缓冲区中使用 python mode el C c C c将当前缓冲区的内容加载到交互式 python shell 中 显然使用了什么
  • 如何使用 Pandas、Numpy 加速 Python 中的嵌套 for 循环逻辑?

    我想检查一下表的字段是否TestProject包含了Client端传入的参数 嵌套for循环很丑陋 有什么高效简单的方法来实现吗 非常感谢您的任何建议 def test parameter a list parameter b list g
  • Pandas Merge (pd.merge) 如何设置索引和连接

    我有两个 pandas 数据框 dfLeft 和 dfRight 以日期作为索引 dfLeft cusip factorL date 2012 01 03 XXXX 4 5 2012 01 03 YYYY 6 2 2012 01 04 XX
  • 如何将张量流模型部署到azure ml工作台

    我在用Azure ML Workbench执行二元分类 到目前为止 一切正常 我有很好的准确性 我想将模型部署为用于推理的 Web 服务 我真的不知道从哪里开始 azure 提供了这个doc https learn microsoft co
  • 如何使用 Mysql Python 连接器检索二进制数据?

    如果我在 MySQL 中创建一个包含二进制数据的简单表 CREATE TABLE foo bar binary 4 INSERT INTO foo bar VALUES UNHEX de12 然后尝试使用 MySQL Connector P
  • 加快网络抓取速度

    我正在使用一个非常简单的网络抓取工具抓取 23770 个网页scrapy 我对 scrapy 甚至 python 都很陌生 但设法编写了一个可以完成这项工作的蜘蛛 然而 它确实很慢 爬行 23770 个页面大约需要 28 小时 我看过scr
  • javascript 是否有等效的 __repr__ ?

    我最接近Python的东西repr这是 function User name password this name name this password password User prototype toString function r
  • 从 NumPy ndarray 中选择行

    我只想从 a 中选择某些行NumPy http en wikipedia org wiki NumPy基于第二列中的值的数组 例如 此测试数组的第二列包含从 1 到 10 的整数 gt gt gt test numpy array nump
  • 如何使用原始 SQL 查询实现搜索功能

    我正在创建一个由 CS50 的网络系列指导的应用程序 这要求我仅使用原始 SQL 查询而不是 ORM 我正在尝试创建一个搜索功能 用户可以在其中查找存储在数据库中的书籍列表 我希望他们能够查询 书籍 表中的 ISBN 标题 作者列 目前 它
  • 如何断言 Unittest 上的可迭代对象不为空?

    向服务提交查询后 我会收到一本字典或一个列表 我想确保它不为空 我使用Python 2 7 我很惊讶没有任何assertEmpty方法为unittest TestCase类实例 现有的替代方案看起来并不正确 self assertTrue
  • 如何解决 PDFBox 没有 unicode 映射错误?

    我有一个现有的 PDF 文件 我想使用 python 脚本将其转换为 Excel 文件 目前正在使用PDFBox 但是存在多个类似以下错误 org apache pdfbox pdmodel font PDType0Font toUnico
  • 实现 XGboost 自定义目标函数

    我正在尝试使用 XGboost 实现自定义目标函数 在 R 中 但我也使用 python 所以有关 python 的任何反馈也很好 我创建了一个返回梯度和粗麻布的函数 它工作正常 但是当我尝试运行 xgb train 时它不起作用 然后 我
  • 使用for循环时如何获取前一个元素? [复制]

    这个问题在这里已经有答案了 可能的重复 Python 循环内的上一个和下一个值 https stackoverflow com questions 1011938 python previous and next values inside
  • 模拟pytest中的异常终止

    我的多线程应用程序遇到了一个错误 主线程的任何异常终止 例如 未捕获的异常或某些信号 都会导致其他线程之一死锁 并阻止进程干净退出 我解决了这个问题 但我想添加一个测试来防止回归 但是 我不知道如何在 pytest 中模拟异常终止 如果我只
  • Scipy Sparse:SciPy/NumPy 更新后出现奇异矩阵警告

    我的问题是由大型电阻器系统的节点分析产生的 我基本上是在设置一个大的稀疏矩阵A 我的解向量b 我正在尝试求解线性方程A x b 为了做到这一点 我正在使用scipy sparse linalg spsolve method 直到最近 一切都
  • Django-tables2 列总计

    我正在尝试使用此总结列中的所有值文档 https github com bradleyayers django tables2 blob master docs pages column headers and footers rst 但页
  • 更改 Tk 标签小部件中单个单词的颜色

    我想更改 Tkinter 标签小部件中单个单词的字体颜色 我知道可以使用文本小部件来实现与我想要完成的类似的事情 例如使单词 YELLOW 显示为黄色 self text tag config tag yel fg clr yellow s

随机推荐