在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