今天和大家分享一个简单又好玩的Python项目–“图片转字符画”。废话不多说,先上一个效果图迷惑一下众生。
没错的,图片转字符画就是将我们平常所看到的的图片根据像素RGB值和灰度值传化成一个个字符串的过程。
嗯…听起来有些费脑子喔。没关系,且听小五为小伙伴一一分解。
像素RGB值
什么是像素RGB值?
其实就像我们平常所看到的所有图片,无论是彩色照还是黑白照,其实它们都是有色彩的,更加神奇的是,我们肉眼所看到的所有色彩几乎都是由红(Red)、绿(Green)、蓝(Blue)经过不同深度的调色后得到的。
而RGB色彩模式就是是工业界的一种颜色标准,是通过对红®、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的。
所以RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。
所以也可以说,我们所看到的所有具有色彩的图片都是由这三种颜色调出来的。
而对于字符画,顾名思义是一系列字符的组合,我们可以把字符看作是比较大块的像素,一个字符能表现一种颜色。
就像我们刚才所看到的那张图片转成字符画之后,画中包含着各种不同层次的字符,所以字符的种类越多,可以表现的颜色也越多,图片也会更有层次感。
那么小伙伴们可能就会问了,我们是要转换一张彩色的图片,这么多的颜色,要怎么对应到只有一种颜色而且还包括那么多字符的的字符画上去?
哈哈,不知道了吧?这里就要介绍灰度值的概念了。来来来,科普一下!
灰度值计算
灰度值:指黑白图像中点的颜色深度,范围一般从0到255,白色为255,黑色为0,故黑白图片也称灰度图像。
所以我们可以使用灰度值公式将像素的 RGB 值映射到灰度值,也就是下面这个公式:(注意这个公式是简化过的,真正的计算公式会复杂些)
灰度值计算公式
gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
这样就好办了,我们可以创建一个不重复的字符列表,然后用每一个字符表示一种颜色,灰度值小(暗)的用列表开头的符号表示,灰度值大(亮)的用列表末尾的符号表示。
注意!重点来了!敲黑板!!!
我们知道。单从黑色到白色,颜色深度的范围就是0-255,也就是说这一共有256个不同深浅的颜色,那我们如果想要将每一种颜色以不同的字符表示出来,那岂不是需要256个不同的字符嘛?
哈哈,我觉得也是,去找256个不同的字符写的你的程序里面组成一个字符串,想想都刺激,但是你会去这么做嘛?我想不会吧?
所以我们只需要想一种办法,让颜色深度相近的灰度值用不同的符号表示即可,所以我们可以定义一个存放不同字符的字符串,用来表示不同的色彩的字符。在这里定义的这个字符串的长度是70。
定义代表灰度的字符
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^`'. ") #设置显示的字符集
灰度值映射字符函数
之后我们只需要定义一个函数,将我们需要转化成字符画的图片的每一个像素找到与其相对应的字符并返回即可。
该函数如下,传入的参数是图片上某一点处的像素值,同时还有一个透明度参数:
将256灰度映射到70个字符上
def get_char(r,g,b,alpha = 256):
#alpha为透明度
# 判断 alpha 值,为0表示全透明
if alpha == 0:
return ' '
# 获取字符集的长度,这里为 70
length = len(ascii_char)
# 将 RGB 值转为灰度值 gray,灰度值范围为 0-255
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
# 灰度值范围为 0-255,而字符集只有 70
# 需要进行如下处理才能将灰度值映射到指定的字符上
#防止当灰度值为255时,输出的第70个字符超出列表索引,所以需要将(255+1)
unit = (255.0 + 1)/length
# 返回灰度值对应的字符
return ascii_char[int(gray/unit)]
当我们写好像素映射字符串的函数方法之后,我们就可以对不同的像素获取到不同的字符表示了。
图片导入及尺寸设置
那么接下来就是我们应该如何获取图片上某一点处的像素了。这个时候我们需要调用pillow库,这个库的作用就是对图片进行基本的处理。
在这里我们需要使用的是pillow库下的Image类,使用Image下的resize方法,我们可以对图片进行缩放,并设置输出的图片质量。
以下面这张图片为例:
进行图片缩放和输出质量设置
IMG = 't01b2a945701805d7f1.jpg' #设置图片文件
WIDTH = 150 #设置字符画的宽
HEIGHT = 80 #设置字符画的高
im = Image.open(IMG)
im = im.resize((WIDTH,HEIGHT), Image.NEAREST)
遍历图片获取字符
之后可以调用Image.getpixel()方法,给其传入坐标参数就可以返回该坐标处的像素值,值得注意的地方是,如果这个坐标处的像素是具有透明属性的。
那么还会返回alpha透明度参数,这就是为什么我们在映射字符串函数方法的参数中传入透明度参数并进行判断的原因。
将 (j,i) 坐标的 RGB 像素转为字符后添加到 txt 字符串
txt += get_char(*im.getpixel((j,i)))
现在我们要做的就是对该尺寸的图片进行横向和纵向的坐标遍历,获取到每一个坐标点处映射的字符,并将其在存储在我们设定的存放图片字符串的列表中即可。
遍历图片中的每一行
for i in range(HEIGHT):
# 遍历该行中的每一列
for j in range(WIDTH):
# 将 (j,i) 坐标的 RGB 像素转为字符后添加到 txt 字符串
txt += get_char(*im.getpixel((j,i)))
# 遍历完一行后需要增加换行符
txt += 'n'
字符画输出和导入文件
最后将该字符列表在屏幕输出或者存放到文本文件中,为了可以更好的看到字符画的效果,在这里大灰狼建议将该字符列表存放到一个文本文件中。
导入文件
OUTPUT = 'output5.txt' #设置存放字符画的文本文件
#保存到文本文件
with open(OUTPUT,'w') as f:
f.write(txt)
将图片转字符画处理后保存在文本文件中,我们可以在文本文件中找到被字符化后的照片了。
好了,到这里图片转字符画的过程就基本结束了。
除去注释最后不到25行,使用的小伙伴只需要将程序最上面的图片路径修改成自己的图片即可!