文章目录
一、课程介绍
本门课程将通过借助 Pillow 库把图像转换成相应样式的彩色字符,并在终端打印出来。
另外通过本门课程我们还将接触到 docopt 库,利用该库我们可以方便快捷地构建起强大的命令行解析器。
本次课程我们将分成两次实验,这是本门课程的第二个实验,在本次实验中我们将开始实现图像转彩色字符的程序。
备注:本次实验的项目来自于 https://github.com/mavidser/i2a 。我在这个项目的基础上做了微小改动。主要改动是精简代码和改变显示效果。
1.1 知识点
学习本门课程的过程中将接触到以下知识:
-
使用 Pillow 库操作图像
-
使用 docopt 库构建命令行解析器
-
使用转义字符改变 Bash 界面中输出的字符样式
1.2 主要流程
本次实验流程如下:
-
安装依赖模块
-
编写程序
1.3 效果截图
先放几张我们的图像转换彩色字符程序的效果图。
原图:
转换之后的效果图:
二、安装依赖模块
本项目的依赖模块如下:
-
Pillow
-
docopt
备注:如果之前已经装好依赖模块的可以跳过此处操作。
由于 Pillow 并不为 Linux 系统提供二进制版本文件,所以我们得通过下载源码自行编译。因此 Pillow 在安装过程中要稍微麻烦些,安装方法如下。
首先,由于实验环境默认的 Python 3 为 Python 3.5 版本。但是实验环境中的源却没有 3.5 版本的开发库,所以首先要将 Python 3 的版本从 3.5
切换至 3.4
。
$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.4 70 --slave /usr/bin/python3m python3m /usr/bin/python3.4m
安装 Python 的开发库 python3-dev
和 python3-setuptools
。
$ sudo apt-get update $ sudo apt-get install python3-dev python3-setuptools
安装 Pillow 依赖包。
$ sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk
最后安装 Pillow 。
$ sudo pip3 install Pillow
补充:Pillow 是 Python 的一个强大的图像处理库,它为 Python 提供了额外的文件格式支持和高效的图像表示方法。
参考:http://pillow-zh-cn.readthedocs.io/zh_CN/latest/installation.html
安装 docopt 。
$ sudo pip3 install docopt
补充:docopt 是 Python 的一个第三方参数解析库,可以根据使用者提供的文档描述自动生成解析器。因此使用者可以用它来定义交互参数与解析参数。
参考:http://docopt.org/
三、编程实现
在开始编程实现之前我们不妨先考虑一下程序的逻辑。我们的程序将实现以下几点:
-
能获取命令行参数并解析
-
加载并处理指定图像
-
将图像像素转换为相应的字符
-
在终端界面按指定格式输出彩色的字符图像
第一二点可以利用第一次试验介绍的 docopt 与 Pillow 库来实现。第三点我们可以这样考虑,构成一张图像的基本元素是像素,而每个像素都拥有不同的值,正是因为这些像素值不同而导致图像中不同区域显示着不同的颜色,而这些色块之间的差别才致使图像中有了轮廓。所以,在不考虑色彩的情况下要将灰阶图像转换为字符画,我们可以通过建立起 ASCII 与像素值的映射关系来实现。
当然,我们的课程还不止于此,因为最终我们要转换为彩色字符画。要在终端上实现这个,就需要借助 Unix 系统的转义字符了,这里先卖个关子,想知道的同学还请继续往下看。
3.1 项目结构
在开始编程之前,按照惯例还得再简单介绍一下项目的结构。
进入 Code
目录,创建文件夹 Image_2_ASCII
作为项目文件夹,之后本实验所有的文件均位于该文件夹之下。
最终项目文件树如下:
/home/shiyanlou/Code/Image_2_ASCII . |-- colors.py |-- curry.jpg |-- i2a.py `-- nyan.png
因为只是个小程序,目录结构很简单,各文件作用如下:
-
i2a.py
:主程序入口 -
colors.py
:自定义模块,负责输出打印 -
nyan.png, curry.jpg
:图像资源
这两张图像可以通过以下命令下载:
$ wget http://labfile.oss.aliyuncs.com/courses/673/images.zip $ unzip images.zip #解压文件
文件 i2a.py, colors.py
可以使用 touch
命令创建。
3.2 构建命令行解析器
有了 docopt 这样的神器之后,构建命令行解析器简直如鱼得水。而我们唯一需要做的就是好好考虑程序需要接受哪些参数,然后编写帮助文档。
在本次的这个程序里,我需要以下参数:
-
FIEL
:图像文件路径名,这是必须项 -
options
:可选项 -
help
:获取帮助文档 -
version
:获取程序版本号 -
colors
:程序默认是转换成黑白字符画,通过该选项指定转换为彩色字符画 -
constract
:调整图像对比度,默认值为 1.5 ,将影响最终转换效果 -
bold
:指定使用粗体显示字符 -
alt-chars
:指定使用另一套字符集进行映射
既然需求已经很明确了,我们就可以开始编写程序,使用 vim 或者 gedit 打开 i2a.py
文件编写代码。
""" ### #### ###### ### ### # ### ### ### ###### ### #### ### ## ### #### ### ## ### ########## ##### ### i2a creates ASCII art from images right on your terminal. Usage: i2a [options] [FILE] Options: -h --help Show this on screen. -v --version Show version. -c --colors Show colored output. -b --bold Output bold characters --contrast=<factor> Manually set contrast [default: 1.5]. --alt-chars Use an alternate set of characters. """import subprocessfrom colors import *from PIL import Image, ImageEnhancefrom docopt import docopt __version__ = '1.0'_ASCII = "@80GCLft1i;:,. "_ASCII_2 = "Q0RMNWBDHK@$U8&AOkYbZGPXgE4dVhgSqm6pF523yfwCJ#TnuLjz7oeat1[]!?I}*{srlcxvi)><\\)|\"/+=^;,:'_-`. "# 图像转彩色字符函数def display_output(arguments): passdef main(): # 获取命令行参数解析之后的字典 arguments = docopt(__doc__, version=__version__) # 若没有 FILE 参数,则打印帮助信息 # 若有则进行转换工作 if arguments['FILE']: display_output(arguments) else: print(__doc__)if __name__ == '__main__': main()
_ASCII
和 _ASCII_2
是我们定义的两个字符常量集,要使用哪些 ASCII 字符以及这些字符的顺序可以随意定义安排。
3.3 display_out 实现
display_output
函数是整个程序的核心,它要实现读取图像,调整图像格式,将图像转换为字符并输出的功能。
它的定义如下:
... ...def display_output(arguments): global _ASCII if arguments['--alt-chars']: _ASCII = _ASCII_2 try: # 加载图像 im = Image.open(arguments['FILE']) except: raise IOError('Unable to open the file.') # 将颜色模式转换为 RGBA im = im.convert("RGBA") # 以字符的个数为单位,获取当前终端的行数和列数 try: _HEIGHT, _WIDTH = map(int, subprocess.check_output(['stty', 'size']).split()) except: _HEIGHT, _WIDTH = 50, 50 # 按比例缩放图像 aspect_ratio = im.size[0] / im.size[1] scaled_height = _WIDTH / aspect_ratio scaled_width = _HEIGHT * aspect_ratio * 2 # 计算调整之后的图像的宽高 width = scaled_width height = scaled_height if scaled_width > _WIDTH: width = int(_WIDTH) height = int(scaled_height / 2) elif scaled_height > _HEIGHT: width = int(scaled_width) height = int(_HEIGHT) # 将图像长宽转换为指定值 # resample 参数可选,指定了在变换图像大小过程中的采样方式,为了保证转变之后的图像质量,我们采用 PIL.Image.ANTIALIAS 选项指定高质量的采样滤波器。 im = im.resize((width,height), resample=Image.ANTIALIAS) # 创建 PIL.ImageEnhance.Contrast 对象,用于调整对比度 enhancer = ImageEnhance.Contrast(im) im = enhancer.enhance(float(arguments['--contrast'])) # 获取 im 的图像数据 # 返回值为 list 对象 img = im.getdata() # 将图像转换为灰阶图 im = im.convert('L') # 定义前景色与背景色 bg = rgb(0, 0, 0) fg = rgb(5, 5, 5) # 是否加粗显示字符 bold = None if arguments['--bold']: bold = True else: bold = False # 用于计数当前在第几列打印 row_len = 0 # 遍历每个像素点 for (count, i) in enumerate(im.getdata()): # 将像素值映射到相应的字符 ascii_char = _ASCII[int((i / 255.0) * (len(_ASCII) - 1))] # 若要求转成彩色字符 if arguments['--colors']: # 颜色映射 color = rgb(int((img[count][0] / 255.0) * 5), int((img[count][1] / 255.0) * 5),int((img[count][2] / 255.0) * 5)) # 背景色设置为该颜色 bg = color # 前景色置位黑色 fg = rgb(0, 0, 0) # 打印字符 print_color(ascii_char, end='', fg=fg, bg=bg, bold=bold) row_len += 1 # 当列数等于终端宽的时候进行换行,并将 row_len 重新置 1 if row_len == width: row_len = 0 print('') ... ...
在获取终端长宽(字符为单位)的时候用到了 subprocess.check_output()
函数,该函数的作用就是就是将传入的命令交给系统执行,并以字符串格式返回执行结果。当传入的命令有一个或者多个参数的时候,可以通过构建成 list 传入,比如,subprocess.check_output(['stty', 'size'])
的返回结果与直接在终端运行命令 $ stty size
的结果是一样的。
另外,在这里为了提升图像的转换效果,还用到了 Pillow 的图像增强模块 PIL.ImageEnhance
。该模块包含有许多类,比如 Color
, Contrast
, Brightness
和 Sharpness
。程序中我们用到了 Contrast
类来提高图像的对比度。
参考:http://pillow-cn.readthedocs.io/zh_CN/latest/reference/ImageEnhance.html#module-PIL.ImageEnhance
3.4 colors.py 实现
接下来 colors.py
文件,开始编写 colors
模块。
# 颜色映射def rgb(red, green, blue): return 16 + (red * 36) + (green * 6) + blue# 设置输出样式def set_style(fg=None, bg=None, bold=None): # 将参数设置为空 end='' 消除自动换行 print(_set_style(fg, bg, bold), end='')# 实现设置输出样式def _set_style(fg=None, bg=None, bold=''): result = '' if fg: result += '\x1b[38;5;%dm' % fg if bg: result += '\x1b[48;5;%dm' % bg if bold: result += '\x1b[1m' return result# 重置颜色def reset_color(): print(_reset_color(), end='')# 实现重置颜色def _reset_color(): return '\x1b[0m'# 打印字符def print_color(*args, **kwargs): fg = kwargs.pop('fg', None) bg = kwargs.pop('bg', None) bold = kwargs.pop('bold', None) set_style(fg, bg, bold) print(*args, **kwargs) reset_color()
为了在终端实现颜色高亮,我们使用了 Unix 系统中的转义字符。例如 \x1b[38;5;1m hello world \x1b[0m
这样的转义序列实现的效果如下。
转义序列以控制字符 'ESC'
和左括号 '['
开头,该起始序列称为控制序列引导符,通常由 \033
或 \x1b
代替。
而通过转义序列设置终端显示属性的格式为:
\x1b[Param {;Param;...}m
其中转义序列以 \x1b[
为开头,m
为设置属性结束,中间部分的 Param
为属性值,{}
表示可以设置多个属性,属性值之间通过分号 ;
隔离。
几个常用的属性设置如下:
序列 | 描述 |
---|---|
\x1b[38;5;n m | 设置前景色(文字颜色),由 n 指定颜色 |
\x1b[48;5;n m | 设置背景色,由 n 指定颜色 |
\x1b[0m | 恢复默认设置 |
示例:
四、运行
编写好程序之后,执行以下命令进行运行。
$ python3 i2a.py nyan.png --colors
五、总结
通过本门课程的学习,我们学习了命令行解析神器 docopt 和 Pillow 库的使用方式。并且还借助转义字符在终端上实现了彩色输出,这是否一改你对终端呆板无味的传统印象了呢。
另外本次实验的源码可以通过以下方式下载:
$ wget http://labfile.oss.aliyuncs.com/courses/673/Image_2_ASCII.zip
六、课后作业
在实验结束之后同学们还可以尝试修改代码以实现以下效果: