📜  在Python中将图像转换为 ASCII 图像

📅  最后修改于: 2022-05-13 01:55:01.169000             🧑  作者: Mango

在Python中将图像转换为 ASCII 图像

ASCII艺术简介

ASCII 艺术是一种图形设计技术,它使用计算机进行演示,由 1963 年 ASCII 标准定义的 95 个可打印字符(总共 128 个)拼凑而成的图片和具有专有扩展字符的 ASCII 兼容字符集(超过 128标准 7 位 ASCII字符)。该术语也广泛用于泛指基于文本的视觉艺术。 ASCII 艺术可以用任何文本编辑器创建,并且经常与自由格式的语言一起使用。 ASCII 艺术的大多数示例都需要固定宽度的字体(非比例字体,如在传统打字机上),例如 Courier 用于演示。已知最古老的 ASCII 艺术示例是计算机艺术先驱肯尼斯·诺尔顿(Kenneth Knowlton)在 1966 年左右的创作,当时他在贝尔实验室工作。 Ken Knowlton 和 Leon Harmon 于 1966 年撰写的“Studies in Perception I”展示了他们早期 ASCII 艺术的一些例子。 ASCII 艺术的发明在很大程度上是因为早期的打印机通常缺乏图形能力,因此使用字符代替图形标记。此外,为了标记来自不同用户的不同打印作业之间的划分,批量打印机通常使用 ASCII 艺术来打印大横幅,使划分更容易被发现,以便计算机运算符或职员更容易区分结果。当图像无法嵌入时,ASCII 艺术也被用于早期的电子邮件中。你可以找到更多关于他们的信息。 [来源:维基。

这个怎么运作:

以下是程序生成 ASCII 的步骤
图片:

  • 将输入图像转换为灰度。
  • 将图像拆分为 M×N 个图块。
  • 更正 M(行数)以匹配图像和字体的纵横比。
  • 计算每个图像块的平均亮度,然后为每个块查找合适的 ASCII字符。
  • 组装多行 ASCII字符字符串它们打印到文件中以形成最终图像。

要求

您将在Python中执行此程序,我们将使用Python Imaging Library Pillow 来读取图像,访问它们的基础数据,并创建和修改它们以及科学模块 Numpy 来计算平均值。

编码
您将从定义用于生成 ASCII 艺术作品的灰度级别开始。然后,您将了解如何将图像拆分为图块,以及如何计算这些图块的平均亮度。接下来,您将使用 ASCII字符替换图块以生成最终输出。最后,您将为程序设置命令行解析,以允许用户指定输出大小、输出文件名等。

定义灰度级别和网格

作为创建程序的第一步,将用于将亮度值转换为 ASCII字符的两个灰度级别定义为全局值。

>>>gscale1 = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~i!lI;:,\"^`". "    #70 levels of gray
>>>gscale2 = "@%#*+=-:. "         #10 levels of gray

u 处的值 gscale1 是 70 级灰度渐变,v 处的 gscale2 是更简单的 10 级灰度渐变。这两个值都存储为字符串,字符范围从最暗到最亮。
现在您已经有了灰度渐变,您可以设置图像。以下代码打开图像并将其拆分为网格:

# open image and convert to grayscale
>>>    image = Image.open(fileName).convert('L')
    # store dimensions
>>>    W, H = image.size[0], image.size[1]
    # compute width of tile
>>>    w = W/cols
    # compute tile height based on aspect ratio and scale
>>>    h = w/scale
    # compute number of rows
>>>    rows = int(H/h)

计算平均亮度
接下来,您计算灰度图像中平铺的平均亮度。函数getAverageL() 完成了这项工作。

#Given PIL Image, return average value of grayscale value
>>>def getAverageL(image):
    # get image as numpy array
...    im = np.array(image)
    # get shape
...    w,h = im.shape
    # get average
...    return np.average(im.reshape(w*h))

首先,图像块作为 PIL Image 对象传入。在第二步将图像转换为 numpy 数组,此时“im”成为每个像素的二维亮度数组。在第三步,您存储图像的尺寸(宽度和高度)。第四步,numpy.average() 使用 numpy.reshape() 计算图像中亮度值的平均值,首先将维度宽度和高度 (w,h) 的二维数组转换为一个平面- 维数组,其长度是宽度乘以高度 (w*h) 的乘积。然后 numpy.average() 调用对这些数组值求和并计算平均值。

从图像生成 ASCII 内容

# ascii image is a list of character strings
>>>    aimg = []
    # generate list of dimensions
>>>    for j in range(rows):
...        y1 = int(j*h)
...        y2 = int((j+1)*h)
        # correct last tile
...        if j == rows-1:
...            y2 = H
        # append an empty string
...        aimg.append("")
...        for i in range(cols):
            # crop image to tile
...            x1 = int(i*w)
...            x2 = int((i+1)*w)
            # correct last tile
...            if i == cols-1:
...                x2 = W
            # crop image to extract tile
...            img = image.crop((x1, y1, x2, y2))
            # get average luminance
...            avg = int(getAverageL(img))
            # look up ascii char
...            if moreLevels:
...                gsval = gscale1[int((avg*69)/255)]
...            else:
...                gsval = gscale2[int((avg*9)/255)]
            # append ascii char to string
...            aimg[j] += gsval

在程序的这一部分中,ASCII 图像首先存储为字符串列表,在第一步初始化。接下来,遍历计算的图像块的行数,在第二步和下一行,计算每个图像块的开始和结束 y 坐标。尽管这些是浮点计算,但在将它们传递给图像裁剪方法之前将它们截断为整数。接下来,因为仅当图像宽度是列数的整数倍时,将图像分成块创建相同大小的边缘块,所以通过将 y 坐标设置为来更正最后一行中的块的 y 坐标图片的实际高度。通过这样做,您可以确保图像的顶部边缘不会被截断。在第三步中,您将一个空字符串添加到 ASCII 中作为表示当前图像行的紧凑方式。接下来您将填写此字符串。 (您将字符串视为字符列表。)在第四步和下一行,您计算每个图块的左右 x 坐标,在第五步,您更正最后一个图块的 x 坐标与您更正 y 坐标的原因相同。在第六步使用 image.crop() 提取图像块,然后将该块传递给上面定义的 getAverageL()函数,将平均亮度值从 [0, 255] 缩小到 [0, 9](范围默认 10 级灰度渐变的值)。然后,您使用 gscale2(存储的斜坡字符串)作为 ASCII Art 95 相关 ASCII 值的查找表。八步的行是类似的,只是它仅在命令行标志设置为使用 70 级的斜坡时使用。最后,在最后一步将查找到的 ASCII 值 gsval 附加到文本行,然后代码循环直到处理完所有行。
添加命令行界面并将 ASCII 艺术字符串写入文本文件
要添加命令行界面,请使用Python内置模块 argparse。
现在最后,获取生成的 ASCII字符列表并将这些字符串字符串文本文件。

# open a new text file
>>> f = open(outFile, 'w')
# write each string in the list to the new file
>>> for row in aimg:
...    f.write(row + '\n')
# clean up
>>> f.close()

然后添加这些部分以创建您的程序。完整的代码如下。

Python
# Python code to convert an image to ASCII image.
import sys, random, argparse
import numpy as np
import math
 
from PIL import Image
 
# gray scale level values from:
# http://paulbourke.net/dataformats/asciiart/
 
# 70 levels of gray
gscale1 = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
 
# 10 levels of gray
gscale2 = '@%#*+=-:. '
 
def getAverageL(image):
 
    """
    Given PIL Image, return average value of grayscale value
    """
    # get image as numpy array
    im = np.array(image)
 
    # get shape
    w,h = im.shape
 
    # get average
    return np.average(im.reshape(w*h))
 
def covertImageToAscii(fileName, cols, scale, moreLevels):
    """
    Given Image and dims (rows, cols) returns an m*n list of Images
    """
    # declare globals
    global gscale1, gscale2
 
    # open image and convert to grayscale
    image = Image.open(fileName).convert('L')
 
    # store dimensions
    W, H = image.size[0], image.size[1]
    print("input image dims: %d x %d" % (W, H))
 
    # compute width of tile
    w = W/cols
 
    # compute tile height based on aspect ratio and scale
    h = w/scale
 
    # compute number of rows
    rows = int(H/h)
     
    print("cols: %d, rows: %d" % (cols, rows))
    print("tile dims: %d x %d" % (w, h))
 
    # check if image size is too small
    if cols > W or rows > H:
        print("Image too small for specified cols!")
        exit(0)
 
    # ascii image is a list of character strings
    aimg = []
    # generate list of dimensions
    for j in range(rows):
        y1 = int(j*h)
        y2 = int((j+1)*h)
 
        # correct last tile
        if j == rows-1:
            y2 = H
 
        # append an empty string
        aimg.append("")
 
        for i in range(cols):
 
            # crop image to tile
            x1 = int(i*w)
            x2 = int((i+1)*w)
 
            # correct last tile
            if i == cols-1:
                x2 = W
 
            # crop image to extract tile
            img = image.crop((x1, y1, x2, y2))
 
            # get average luminance
            avg = int(getAverageL(img))
 
            # look up ascii char
            if moreLevels:
                gsval = gscale1[int((avg*69)/255)]
            else:
                gsval = gscale2[int((avg*9)/255)]
 
            # append ascii char to string
            aimg[j] += gsval
     
    # return txt image
    return aimg
 
# main() function
def main():
    # create parser
    descStr = "This program converts an image into ASCII art."
    parser = argparse.ArgumentParser(description=descStr)
    # add expected arguments
    parser.add_argument('--file', dest='imgFile', required=True)
    parser.add_argument('--scale', dest='scale', required=False)
    parser.add_argument('--out', dest='outFile', required=False)
    parser.add_argument('--cols', dest='cols', required=False)
    parser.add_argument('--morelevels',dest='moreLevels',action='store_true')
 
    # parse args
    args = parser.parse_args()
   
    imgFile = args.imgFile
 
    # set output file
    outFile = 'out.txt'
    if args.outFile:
        outFile = args.outFile
 
    # set scale default as 0.43 which suits
    # a Courier font
    scale = 0.43
    if args.scale:
        scale = float(args.scale)
 
    # set cols
    cols = 80
    if args.cols:
        cols = int(args.cols)
 
    print('generating ASCII art...')
    # convert image to ascii txt
    aimg = covertImageToAscii(imgFile, cols, scale, args.moreLevels)
 
    # open file
    f = open(outFile, 'w')
 
    # write to file
    for row in aimg:
        f.write(row + '\n')
 
    # cleanup
    f.close()
    print("ASCII art written to %s" % outFile)
 
# call main
if __name__ == '__main__':
    main()


输入:

$python "ASCII_IMAGE_GENERATOR.py" --file data/11.jpg --cols 120