Python极客项目编程之ASCII文本图形


前言

  ASCII文本图形源头是19世纪后期出现的打字机文本图形。在20世纪60年代,计算机有了较弱的图形处理硬件,ASCII被用于表示图形。今天,ASCII文本图形继续作为因特网上的一种表现形式,你可以在网上找到各种创意的例子。


一、学习目标及步骤

  这个项目用Python创建一个程序,从图像生成ASCII文本图形。该程序让你指定输出文本序列的宽度,并设置垂直比例因子。它也支持两种灰度值到ASCII字符的映射:稀疏的10级映射和更精细校正的70级映射。
  要从图像生成ASCII文本图像,需要学习如何做到以下几点:

  • 用Pillow将彩色图像转换成灰度图像,它是Python的图像库(PIL)的一个分支;
  • 使用numpy计算灰度图像的平均亮度;
  • 用一个字符串作为灰度值的快速查找表。
    下面是程序生成ASCII文本图形的步骤:

    1. 将输入的图形装成灰度;
    2. 将图像分成M×N个小块;
    3. 修正M(行数),以匹配图像和字体的横纵比;
    4. 计算每一个小块图像的平均亮度,然后为每个小块查找合适的ASCII字符;
    5. 汇集各行的ASCII字符串,将他们打印到文件,形成最终图像。

二、代码

1.引入库

代码如下(示例):

import argparse
import numpy as np
from PIL import Image

2.定义灰度等级和网格

  先定义两种灰度等级作为全局值,分别是70级,10级,这两个值保存为字符串,从最黑暗变到最亮,用于将亮度值转换为ASCII字符。

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

# 10 leves of gray
gscale2 = "@%#*+=-:. "

  既然有了灰度梯度,就可以准备图像了。下面的代码打开图像,并分割成网格:

# 打开图像并转换为灰度
image = Image.open(filename).convert('L')
# 存储图像尺寸
W, H = image.size[0], image.size[1]
# 计算平铺宽度
w = W/cols
# 根据字体的纵横比和比例计算平铺高度
h = w/scale
# 计算要在最终网格中使用的行数
rows = int(H/h)

  比例系数确定每个小块的大小,以匹配用于显示文字的字体的横纵比,这样最终图像不会失真。scale的值可以作为参数传入,或者设置位默认值0.43,在用Courier显示结果时,效果最好。

3.计算平均亮度

  计算灰度图像中每一小块的平均亮度。函数getAverageL()完成。

def getAverageL(image):
    # 以numpy数组的形式获取图像
    im = np.array(image)
    # 得到尺寸
    w, h = im.shape
    # 获得平均值
    return np.average(im.reshape(w*h))

  图像小块作为PIL Image对象传入,将 image转换成一个numpy数组,此时 im成为一个二维数组,包含每个像素的亮度。保存该图像的尺寸(宽度和高度)。numpy.average()计算该图像中的亮度平均值,做法是用numpy.reshape()先将维度为宽和高(w,h)的二维数组转换成扁平的一维,其长度是宽度乘以高度(w*h)。然后numpy.average()调用对这些数组值求和并计算平均值。

4.从图像生成ASCII内容

# ASCII图像是字符串的列表
aimg = []
# 生成平铺尺寸列表
for j in range(rows):
    y1 = int(j * h)
    y2 = int((j + 1) * h)
    # 更正最后一个平铺方块
    if j == rows - 1:
        y2 = H
    # 追加一个空字符串
    aimg.append("")
    for i in range(cols):
    	# 裁剪图像以适合平铺
        x1 = int(i * w)
        x2 = int((i + 1) * w)
        # 更正最后一个平铺方块
        if i == cols - 1:
            x2 = W
        # 裁剪图像以将其平铺提取到另一个图像对象中
        img = image.crop((x1, y1, x2, y2))
        # 获得平均亮度
        avg = int(getAverageL(img))
        # 在ascii字符中查找平均灰度值
        if moreLevels:
            gsval = gscale1[int((avg * 69) / 255)]
        else:
            gsval = gscale2[int((avg * 9) / 255)]
        # 将ascii字符附加到字符串
        aimg[j] += gsval

  在程序的这一部分,ASCII图像先作为一个字符串列表保存并初始化。接下来,按计算好的图像小块行数迭代遍历,计算每个图像小块的起始和结束y坐标。虽然这些是浮点运算,但在传给图像裁剪方法之前,将它们截断为整数。接着,因为只有当图像的宽度是列数的整数倍时,图像分割成小块时,边缘的小块才有相同的大小,所以在最后一行校正小块的y坐标,将y坐标设置为图像的实际高度。这样做确保了图像顶部的边缘不被截断。然后为ASCII 图像添加一个空字符串,作为一种紧凑的方式来表示图像的当前行。接下来会填充这个字符串(将字符串作为字符的列表)。
  计算每个小块的左、右x坐标,为最后一小块校正x坐标,原因和校正y坐标时一样。然后用 image.crop()提取图像小块,然后将该小块传入getAverageL()函数,取得小块的平均亮度。将平均亮度值从[0,255]缩小至[0,9](默认10级灰度梯度值的范围)。然后,用 gscale2(保存的梯度字符串)作为查找表,找到对应的ASCII 值。然后类似,不同之处在于,只有命令行标志设置为使用70级梯度时,才会用它。最后,在文本行中添加找到的ASCII 值 gsval,代码循环,直到处理完所有行。

5.类初始化定义

descStr = "This program converts an image into ASCII art."
# 添加预期参数
parser = argparse.ArgumentParser(description=descStr)
parser.add_argument('--file', dest='imgFile', required=False)
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')

  指定图像文件输入的选项(唯一必须的参数),设置垂直比例因子,设置输出文件名,设置ASCII输出中的文本列数。在行,添加–morelevels选项,让用户选择更多层次的灰度梯度。

6.将ASCII文本图形字符串写入文本文件

# 打开一个新文件
f = open(outFile, 'w')
# 将列表中的每个字符串写入新文件
for row in aimg:
    f.write(row + '\n')
# 关闭文件
f.close()

  使用内置的 open()方法,打开一个新的文本文件用于写入。然后迭代遍历列表中的每个字符串,将它写入文件,关闭文件对象,释放系统资源。

三、完整代码

import argparse
from PIL import Image
import numpy as np
# 70 leves of gray
gscale1 = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^.y' "
# 10 leves of gray
gscale2 = "@%#*+=-:. "
def getAverageL(image):
    # 以numpy数组的形式获取图像
    im = np.array(image)
    # 得到尺寸
    w, h = im.shape
    # 获得平均值
    return np.average(im.reshape(w*h))
def covertImageToAscii(filename, cols, scale, moreLevels):
    global gscale1, gscale2
    # 打开图像并转换为灰度
    image = Image.open(filename).convert('L')
    # 存储图像尺寸
    W, H = image.size[0], image.size[1]
    # print("input image dims: %d * %d" % (W, H))
    # 计算平铺宽度
    w = W/cols
    # 根据字体的纵横比和比例计算平铺高度
    h = w/scale
    # 计算要在最终网格中使用的行数
    rows = int(H/h)
    # print("col: %d, row: %d" % (cols, rows))
    # print("tile dims: %d * %d" % (w, h))
    if cols > W or rows > H:
        # print("Image too small for specified cols!")
        exit(0)
    # ASCII图像是字符串的列表
    aimg = []
    # 生成平铺尺寸列表
    for j in range(rows):
        y1 = int(j * h)
        y2 = int((j + 1) * h)
        # 更正最后一个平铺方块
        if j == rows - 1:
            y2 = H
        # 追加一个空字符串
        aimg.append("")
        for i in range(cols):
            # 裁剪图像以适合平铺
            x1 = int(i * w)
            x2 = int((i + 1) * w)
            # 更正最后一个平铺方块
            if i == cols - 1:
                x2 = W
            # 裁剪图像以将其平铺提取到另一个图像对象中
            img = image.crop((x1, y1, x2, y2))
            # 获得平均亮度
            avg = int(getAverageL(img))
            # 在ascii字符中查找平均灰度值
            if moreLevels:
                gsval = gscale1[int((avg * 69) / 255)]
            else:
                gsval = gscale2[int((avg * 9) / 255)]
            # 将ascii字符附加到字符串
            aimg[j] += gsval
    return aimg
def main(imgFile, scale=0.52, cols=65, outFile='out.txt'):
    descStr = "This program converts an image into ASCII art."
    # 添加预期参数
    parser = argparse.ArgumentParser(description=descStr)
    parser.add_argument('--file', dest='imgFile', required=False)
    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')
    args = parser.parse_args()
    if args.outFile:
        outFile = args.outFile
    if args.scale:
        scale = float(args.scale)
    if args.cols:
        cols = int(args.cols)
    # print('generating ASCII art ...')
    aimg = covertImageToAscii(imgFile, cols, scale, args.moreLevels)
    # 打开一个新文件
    f = open(outFile, 'w')
    # 将列表中的每个字符串写入新文件
    for row in aimg:
        print(row)
        f.write(row + '\n')
    # 关闭文件
    f.close()
    # print("ASCII art written to %s" % outFile)
if __name__ == '__main__':
    main('fxp.jpg')

四、运行结果

在这里插入图片描述

在这里插入图片描述

  在这个项目中,我们学习了如何从任意的输入图像生成ASCII文本图形,还学习了计算平均亮度值,将图片转换成灰度,以及如何基于灰度值用字符替换一小块图像,对比原图和运行结果图,代码基本运行成功。(其实代码也是闲着没事给某个小姑娘写的,顺便学习一下python语法)

参考代码:https://github.com/electronut/pp/blob/master/ascii/ascii.py

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python极客项目编程是一本针对Python编程语言的项目开发指南。这本书主要面向具有一定编程基础的Python爱好者或开发者,可以帮助读者提升他们在Python项目开发方面的技能和水平。 编写Python项目需要一定的知识和经验,这本书在内容上涵盖了许多有趣的主题,如游戏开发、数据可视化、网络编程、人工智能等,读者可以根据自己的兴趣和需求,选择适合的章节进行学习和实践。 这本书的编程风格注重实战,每个章节都包含详细的代码示例和解释,读者可以通过阅读这些示例代码和相关的解释,逐步理解和掌握Python项目开发的要点和技巧。不仅如此,书中还提供了一些实践任务,读者可以通过完成这些任务来巩固所学知识,并提升自己的实际编程能力。 另外,这本书还特别强调了项目管理和团队合作的重要性。项目开发不仅仅关乎个人的编程能力,还需要具备良好的团队协作和项目管理能力。因此,这本书在一些章节中介绍了项目管理工具和团队协作技巧,帮助读者学会如何合理规划和组织自己的Python项目,以及如何与他人进行有效的合作。 总而言之,Python极客项目编程是一本对于Python项目开发感兴趣的读者来说非常实用的书籍。通过学习这本书,读者可以提升自己的编程水平,学会如何开发有趣和实用的Python项目,并掌握项目管理和团队合作的技巧。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值