之前在b站看见一个大佬用岛村之刃画了一个安达,借鉴大佬的思路,用python写一个程序
效果图:

实现
众所周知,python中有一个强大的图象处理库叫做pillow,所以我们整体的编写主要围绕pillow库进行
实现原理
- 为了让图像有明显的轮廓,程序需要读到不同像素点就输出不同的文字来勾勒图像的轮廓,我们就可以使用 0101011001 这种形式,将像素点转换为 0/1 编码。当程序读到 0 时,就可以认为这是一个有效像素点,也就是图像中需要被描绘出来的部分,这个部分我们就可以输出文字;而当程序读到 1 时,就认为是背景或空白区域,可以用统一的字符(比如“一”,最好要使用中文‘一’,如果使用‘——’符号,会出现宽度不一致的情况,导致图像错位)来填充,形成对比,这样图像会有明显的轮廓,当放大之后,就会看见我们想要输出充当轮廓的那些文字。(遇见0输出字,遇见1输出‘一’)
- 那为了让像素可以转为一个一个01编码,就可以通过每一个像素点的灰度值来判断:
即:
已知一个像素的灰度值为 0–255 ,0 = 纯黑,255 = 纯白,我们可以取中间数128来做分界值,
当灰度值<128时,就认为这个像素点是黑色,认为是一个有效像素的,输出0,当灰度值>128,就认为是白色,输出1,于是会产生一个二维的 01 矩阵。 - 后续我们需要遇到0,就输出字,遇见1就输出‘一’,因此,我们需要一个文字库,将我们需要输出的文字全部存入这个文字库,当遇到0,我们就照着文字库的顺序一个一个往下取,取完了我们再回到开头重新往下取(每遇到一个0,就输出当前文字库指针指向的文字,然后文字库指针往下走一个单位),对于字库的创建,我们可以使用列表的形式存储文字,这样可以避免直接读取文件时产生的文件指针bug
主要是我不太会文件指针的使用 - 我们的目标是创建一张图片,所以需要准备一张空白画布,后续再往画布上输出不同的字符。那么这个画布的长宽要为多少?首先,不可能为原图长宽,因为一个文字不可能被压缩到1px,要是这样做文字根本看不清。一个文字可以被清晰看见的像素占用大小是12px左右,所以我们就取一个文字占12px,又因为我们要输出的文字数要与原图(或者被缩小后的图片)保持一致,那么我们所需要输出的长宽就是(原长12)(原长*12),后面我们用循环逐格写字就行了
以上就是大致思路(可能思路不是很清晰,但总的来说就是以0/1为单位来输出字符)
实现代码
- 那么理论存在,实践开始
- 导入所需库
关于这些库的作用可以网上查一下
from PIL import Image, ImageDraw, ImageFont
import re
import sys
3.设置字体等
font_size = 12 # 汉字所占像素量,可以根据实际更改
font_path = r'所用字体的路径' #填上自己的路径
out_image = r'结果图片的输出路径'
grayscale_line = 150 #灰度分界线,可以依据实际的图片明暗度调整
#grayscale_line越大,轮廓越明显4.打开原图,先整体缩小(控制图片体积)
# 打开原图,先整体缩小(控制图片体积)
img = Image.open(r"原图路径").convert('L')
MAX_BLOCKS =200 # 期望最长边不超过的方块数,越大->像素越多,图象越清晰,同时体积越大
scale = max(1, max(img.size) // MAX_BLOCKS) # 计算缩小倍数
small_Image = img.resize((img.width//scale, img.height//scale), Image.LANCZOS)#把原图 img 等比例缩小成原来的 1/scale
#Image.LANCZOS用于抗锯齿
w, h = small_Image.size
5.建立一个01二维矩阵,当然就是两个for循环一行一行扫描,我不会高级方法
mat = [] # 先建一个空列表,将来放每一行
for y in range(h): # 一行一行地扫
row = [] # 当前行
for x in range(w): # 一行里的每个像素
grayscale = small_Image.getpixel((x, y)) # 取灰度值
if grayscale < grayscale_line: # 比阈值小 → 黑 → '0'
row.append('0')
else: # 比阈值大 → 白 → '1'
row.append('1')
mat.append(row) # 把这一行放进总矩阵
6.建立文字库
为了防止将标点符号写入文字库,需要预编译一个只取汉字的正则表达式
Valid_words = re.compile(r'[\u4e00-\u9fff]')#需要预编译一个只取汉字的正则表达式
#\u4e00 到 \u9fff 这段 Unicode 区域是常用汉字,他表示取出这个范围内的汉字
with open(r'你的文字文件路径', encoding='utf-8') as f:#打开存储着你要写出的文字的文件(txt文件)
Font_Database = Valid_words.findall(f.read())
if not Font_Database:#字库为空
print('字库写入失败')
sys.exit()
word_index = 0#创建一个指针用于在文字库里取字
7.新建画布(总像素=whfont_size²)
out_w, out_h = w * font_size, h * font_size
out_img = Image.new('RGB', (out_w, out_h), 'white')#创建一个空白位图
draw = ImageDraw.Draw(out_img)#实例化一个 ImageDraw.Draw,用于绘图
font = ImageFont.truetype(font_path, font_size)#实例化 FreeTypeFont 对象,就是定一个字形用于绘图
8.写字
for y in range(h):
for x in range(w):
ch = '一' if mat[y][x] == '1' else Font_Database[word_index % len(Font_Database)]
word_index += 1
draw.text((x*font_size, y*font_size), ch, fill='black', font=font)
9.保存
out_img.save(out_image)
print(f'文字图生成功!:{out_image} 尺寸 {out_w}×{out_h}')于是整体代码为:
from PIL import Image, ImageDraw, ImageFont
import re
import sys
font_size = 12 # 汉字所占像素量,可以根据实际更改
font_path = r'所用字体的路径' #填上自己的路径
out_image = r'结果图片的输出路径'
grayscale_line = 150 #灰度分界线,可以依据实际的图片明暗度调整
#grayscale_line越大,轮廓越明显
# 打开原图,先整体缩小(控制图片体积)
img = Image.open(r"原图路径").convert('L')
MAX_BLOCKS =200 # 期望最长边不超过的方块数,越大->像素越多,图象越清晰,同时体积越大
scale = max(1, max(img.size) // MAX_BLOCKS) # 计算缩小倍数,写个1是防止scale为小数,要不然越放越大了就离谱
small_Image = img.resize((img.width//scale, img.height//scale), Image.LANCZOS)#把原图 img 等比例缩小成原来的 1/scale
#Image.LANCZOS用于抗锯齿
w, h = small_Image.size
# 二值化
mat = [] # 先建一个空列表,将来放每一行
for y in range(h): # 一行一行地扫
row = [] # 当前行
for x in range(w): # 一行里的每个像素
grayscale = small_Image.getpixel((x, y)) # 取灰度值
if grayscale < grayscale_line: # 比阈值小 → 黑 → '0'
row.append('0')
else: # 比阈值大 → 白 → '1'
row.append('1')
mat.append(row) # 把这一行放进总矩阵
Valid_words = re.compile(r'[\u4e00-\u9fff]')#需要预编译一个只取汉字的正则表达式
#\u4e00 到 \u9fff 这段 Unicode 区域是常用汉字,他表示取出这个范围内的汉字
with open(r'你的文字文件路径', encoding='utf-8') as f:#打开存储着你要写出的文字的文件(txt文件)
Font_Database = Valid_words.findall(f.read())
if not Font_Database:#字库为空
print('字库写入失败')
sys.exit()
word_index = 0#创建一个指针用于在文字库里取字
out_w, out_h = w * font_size, h * font_size
out_img = Image.new('RGB', (out_w, out_h), 'white')#创建一个空白位图
draw = ImageDraw.Draw(out_img)#实例化一个 ImageDraw.Draw,用于绘图
font = ImageFont.truetype(font_path, font_size)#实例化 FreeTypeFont 对象,就是定一个字形用于绘图
for y in range(h):
for x in range(w):
ch = '一' if mat[y][x] == '1' else Font_Database[word_index % len(Font_Database)]
word_index += 1
draw.text((x*font_size, y*font_size), ch, fill='black', font=font)
out_img.save(out_image)
print(f'文字图生成功!:{out_image} 尺寸 {out_w}×{out_h}')欢迎大家复制粘贴