上次在实验楼看到一个挺有意思的小练习:通过 Python 完成图片转字符画,于是打算记录一下。
环境要求
- Python 3.6
- pillow
- argparse
- Windows / Linux
原理介绍
字符画是一系列字符的组合,可以把每个字符看作具有一种颜色的较大块像素。字符的种类越多,可表现的颜色相应越多,图片也随之更具层次感。
然而,常见的彩色图片颜色丰富,该如何与单色的字符画一一对应呢?这里就要介绍灰度值的概念了。
灰度值:指黑白图像中点的颜色深度,范围 0~255,白色为255,黑色为0,故黑白图片也称灰度图像。
另外一个概念是 RGB 色彩:
RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。 —— 百度百科
可以使用灰度值公式将像素的 RGB 值映射到灰度值(注意该公式并非真实算法,而是简化的 sRGB IEC61966-2.1 公式,真实公式更复杂些,不过在本应用场景下并无必要):
gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
从而,可以创建一个不重复的字符列表,灰度值小 (暗) 的用列表开头的符号,灰度值大 (亮) 的用列表末尾的符号。
实验
Windows 下 IDE 运行版本:
# -*- coding=utf-8 -*-
from PIL import Image
## 自定义基本设置
# 输入图片路径
IMG_PATH = 'F:\shiyanlou\dlam.png'
# 输出字符画宽度
WIDTH = 75
# 输出字符画高度
HEIGHT = 40
# 输出文件名
OUTPUT = 'output.txt'
# 字符集
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
# 字符集长度
length = len(ascii_char)
# 将256个灰度值映射到70个字符上
def get_char(r, g, b, alpha = 256):
if alpha == 0: # 透明度
return ' '
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b) # RGB → 灰度
unit = (256.0 + 1)/length # 颜色单元区间
char = ascii_char[int(gray/unit)] # 根据颜色区间选中字符
return char
# if __name__ == '__main__': 表示若 character_painting_win.py 被当作
# Python 模块 import 时, 以下代码不会被执行
if __name__ == '__main__':
im = Image.open(IMG_PATH) # 读取输入图像
im = im.resize((WIDTH,HEIGHT), Image.NEAREST) # 使用最近邻插值缩放原图
txt = "" # 初始化输出字符画
for i in range(HEIGHT):
for j in range(WIDTH):
txt += get_char(*im.getpixel((j,i))) # 遍历缩放图像素添加字符
txt += '\n'
print(txt) # 打印字符画
# 字符画输出到文件保存
if OUTPUT:
with open(OUTPUT,'w') as f:
f.write(txt)
else:
with open("output.txt",'w') as f:
f.write(txt)
输入图像 (dlam.png):
输出字符画 (output.txt):
Linux 下命令行运行版本 (文件名 character_painting_linux.py):
# -*- coding=utf-8 -*-
from PIL import Image
import argparse
# 命令行输入参数处理
parser = argparse.ArgumentParser()
parser.add_argument('file') #输入文件
parser.add_argument('-o', '--output') #输出文件
parser.add_argument('--width', type = int, default = 70) #输出字符画宽
parser.add_argument('--height', type = int, default = 35) #输出字符画高
# 获取参数
args = parser.parse_args()
IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
# 将256灰度映射到70个字符上
def get_char(r,g,b,alpha = 256):
if alpha == 0:
return ' '
length = len(ascii_char)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0 + 1)/length
return ascii_char[int(gray/unit)]
if __name__ == '__main__':
im = Image.open(IMG)
im = im.resize((WIDTH,HEIGHT), Image.NEAREST)
txt = ""
for i in range(HEIGHT):
for j in range(WIDTH):
txt += get_char(*im.getpixel((j,i)))
txt += '\n'
print(txt)
if OUTPUT:
with open(OUTPUT,'w') as f:
f.write(txt)
else:
with open("output.txt",'w') as f:
f.write(txt)
## 命令行运行方式:
## $ python3 character_painting_linux.py dlam.png
参考资料: