小姐姐学Python-class4-1

Python字符画

不知道你是否在抖音上看到过这样的图片?

《小姐姐学Python-class4-1》

这样滴?

《小姐姐学Python-class4-1》

还有这样滴?

《小姐姐学Python-class4-1》

今天我们就来学习一下如何实现这样功能的代码

要求:
1. pillow库的使用
2. argparse库的使用

我们知道,彩色图片都是有RGB三种颜色组成的,在图片中即是一个像素点包含了三个数(r,g,b) ,当然有时候还会有透明度,即(r,g,b,a)。现在我们要求的将一个彩色图像,变成一个单色图像,也就是灰度图像。

要讲灰度图像,先介绍一下灰度值。

灰度值:指黑白图像中点的颜色深度,范围一般从0到255,白色为255,黑色为0,故黑白图片也称灰度图像。

我们可以通过灰度公式将每个像素的RGB值映射到灰度值(注意这个公式并不是一个真实的算法,而是简化的 sRGB IEC61966-2.1 公式,真实的公式更复杂一些,不过在我们的这个应用场景下并没有必要)

gray = 0.2126 * r + 0.7152 * g + 0.0722 * b

这样就好办了,我们可以创建一个不重复的字符列表,灰度值小(暗)的用列表开头的符号,灰度值大(亮)的用列表末尾的符号。

安装必要的库

安装pillow库

pip install pillow

首先导入必要的库,argparse 库是用来管理命令行参数输入的

from PIL import Image
import argparse

我们首先使用 argparse 处理命令行参数,目标是获取输入的图片路径、输出字符画的宽和高以及输出文件的路径:

# 首先,构建命令行输入参数处理 ArgumentParser 实例
parser = argparse.ArgumentParser()

# 定义输入文件、输出文件、输出字符画的宽和高
parser.add_argument('file')     #输入文件
parser.add_argument('-o', '--output')   #输出文件
parser.add_argument('--width', type = int, default = 80) #输出字符画宽
parser.add_argument('--height', type = int, default = 80) #输出字符画高

# 解析并获取参数
args = parser.parse_args()

# 输入的图片文件路径
IMG = args.file

# 输出字符画的宽度
WIDTH = args.width

# 输出字符画的高度
HEIGHT = args.height

# 输出字符画的路径
OUTPUT = args.output

接着我们写一个函数get_char, 用来接收rgb参数,映射到对应的灰度值,再映射到指定的字符上。

我们映射的字符如下,共70个

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")

get_char函数

def get_char(r,g,b,alpha = 256):

    # 判断 alpha 值
    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
    # 需要进行如下处理才能将灰度值映射到指定的字符上
    unit = (256.0 + 1)/length

    # 返回灰度值对应的字符
    return ascii_char[int(gray/unit)]

图片的处理

这一个步骤我们放入到 if name == ‘main‘: 代码块中(表示如果 ascii.py 被当作 python 模块 import 的时候,这部分代码不会被执行)。
图片的处理步骤如下:

  1. 首先使用 PIL 的 Image.open 打开图片文件,获得对象 im
  2. 使用 PIL 库的 im.resize() 调整图片大小对应到输出的字符画的宽度和高度,注意这个函数第二个参数使用 Image.NEAREST,表示输出低质量的图片。
  3. 遍历提取图片中每行的像素的 RGB 值,调用 getchar 转成对应的字符
  4. 将所有的像素对应的字符拼接在一起成为一个字符串 txt
  5. 打印输出字符串 txt
  6. 如果执行时配置了输出文件,将打开文件将 txt 输出到文件,如果没有,则默认输出到 output.txt 文件

这个过程中需要注意的是调用 getchar 时候的参数是通过 PIL 库的 getpixel 获取的,见如下代码:

char = get_char(*im.getpixel((j,i)))

其中 im.getpixel((j,i)) 获取得到坐标 (j,i) 位置的 RGB 像素值(有的时候会包含 alpha 值),返回的结果是一个元组,例如 (1,2,3) 或者 (1,2,3,0)。我们使用 * 可以将元组作为参数传递给 get_char,同时元组中的每个元素都对应到 get_char 函数的每个参数。

该部分的代码实现如下(注意 name 和 main 前后都是两个下划线):

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):
            # 将 (j,i) 坐标的 RGB 像素转为字符后添加到 txt 字符串
            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)

完整代码


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 = 80) #输出字符画宽 parser.add_argument('--height', type = int, default = 80) #输出字符画高 #获取参数 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)

测试

在代码所在目录下,在控制台中使用wget下载测试图片

wget http://labfile.oss.aliyuncs.com/courses/370/ascii_dora.png

最后,使用刚刚编写的 ascii.py 来将下载的 ascii_dora.png 转换成字符画,此时执行过程没有指定其他的参数,比如输出文件、输出文件的宽和高,这些参数都将使用默认的参数值:

python ascii.py ascii_dora.png

指定参数的写法(指定宽高,以及输出文件为tmp.txt)

python ascii.py ascii_dora.png --width=100 --height=100 -o=tmp.txt

这时候用记事本打开tmp.txt就能看到效果。

《小姐姐学Python-class4-1》

注意,不同的环境中显示的效果可能不尽相同

终端显示的字体是不是等宽字体,终端显示的行高和行宽,输入输出的图像宽高等等,这些都会影响显示效果

参考

https://www.shiyanlou.com/courses/370

点赞
  1. 图图说道:

    :biggrin: :biggrin: :biggrin: :biggrin: 好文章,顶一下