Python 图像转彩色字符画 —— 实验二

文章目录

一、课程介绍

本门课程将通过借助 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/

三、编程实现

在开始编程实现之前我们不妨先考虑一下程序的逻辑。我们的程序将实现以下几点:

  1. 能获取命令行参数并解析

  2. 加载并处理指定图像

  3. 将图像像素转换为相应的字符

  4. 在终端界面按指定格式输出彩色的字符图像

第一二点可以利用第一次试验介绍的 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

六、课后作业

在实验结束之后同学们还可以尝试修改代码以实现以下效果:

此处输入图片的描述

七、传送门

原文链接:,发表于 源代码(CodeBeta),转发请注明来源!

发表评论