【Python 项目】ASCLL 文本图形 - 2

ASCLL 文本图形

步骤:

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

所需模块

这个项目将使用 Pillow(Python 图像库的友好分支)来读取图像,访问它们的底层数据,创建并修改它们。还将使用 numpy 库来计算平均值。

代码

开始先定义灰度等级,用于生成 ASCII 文本图形。然后,考虑如何将图像分割成小块,以及如何计算这些小块的平均亮度。接下来,用 ASCII 字符替换小块,生成最终的输出。最后,为程序设置命令行解析,允许用户指定输出尺寸、输出文件名,等等。

定义灰度等级和网格

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

#70 级的灰度梯度
gscale1 = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^ `". "
#简单的 10 级灰度梯度
gscale2 = "@%#*+=-:. "

这两个值保存为字符串,包含一组字符串,从最黑暗变到最亮。
既然有了灰度梯度,就可以准备图像。下面的代码打开图像,并分割成网格:

#Image.open()打开输入图像文件,Image.convert()将该图像转换为灰度图像。“L”代表 luminance,是图像亮度的单位。
image = Image.open(fileName).convert("L")
#保存输入图像的宽度和高度
W, H = image.size[0], image.size[1]
#根据用户指定的列数(cols),计算小块的宽度(如果用户没有在命令行中设置其他值,程序默认使用 80 列)
w = W/cols
#利用垂直比例系数(作为 scale 传入),计算它的高度
h = w/scale
#用这个网格高度来计算行数。
rows = int(H/h)

计算平均亮度

接下来,计算灰度图像中每一小块的平均亮度。函数 getAverageL()完成这项工作:

#图像小块作为 PIL Image 对象传入
def getAveragel(image):
	#将 image 转换成一个 numpy数组,此时 im 成为一个二维数组,包含每个像素的亮度
	im = np.array(image)
	#保存该图像的尺寸(宽度和高度)
	w,h = im.shape
	#计算该图像中的亮度平均值
	return np.average(im.reshape(w*h))

用 numpy.reshape()先将维度为宽和高(w,h)的二维数组转换成扁平的一维,其长度
是宽度乘以高度(w*h)。然后 numpy.average()调用对这些数组值求和并计算平均值。

从图像生成 ASCII 内容

程序的主要部分负责从图像生成 ASCII 内容:

#初始化
aimg = []

for j in range(rows):
	#计算每个图像小块的起始和结束 y 坐标
	y1 = int(j*h)
	y2 = int((j+1)*h)

	if j == rows-1:
		y2 = H

	#为 ASCII 图像添加一个空字符串,作为一种紧凑的方式来表示图像的当前行
	aimg.append("")
	for i in range(cols):
		#计算每个小块的左、右 x 坐标
		x1 = int(i*w)
		x2 = int((i+1)*w)
		#为最后一小块校正 x 坐标
		if i == cols-1:
			x2 = W
		#提取图像小块,然后将该小块传入 getAverageL()函数
		img = image.crop((x1, y1, x2, y2))
		avg = int(getAveragel(img))

		if moreLevels:
			gsval = gscale1[int((avg*69)/255)]
		else:
			gsval = gscale2[int((avg*9)/255)]

		#在文本行中添加找到的 ASCII 值 gsval,代码循环,直到处理完所有行
		aimg[j] += gsval

命令行选项

接下来,为程序定义一些命令行选项。这段代码使用内置的 argparse 类:

parser = argparse.ArgumentParser(description="descStr")
#包含指定图像文件输入的选项(唯一必须的参数)
parser.add_argument('--file', dest='imgFile', required=True)
#设置垂直比例因子
parser.add_argument('--scale', dest='scale', required=False)
#设置输出文件名
parser.add_argument('--out', dest='outFile', required=False)
#设置 ASCII 输出中的文本列数
parser.add_argument('--cols', dest='cols', required=False)
#添加--morelevels 选项,让用户选择更多层次的灰度梯度
parser.add_argument('--morelevels', dest='moreLevels', action='store_true')

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

最后,将生成的 ASCII 字符串列表,写入一个文本文件:

#使用内置的 open()方法,打开一个新的文本文件用于写入
f = open(outFile, 'w')
#迭代遍历列表中的每个字符串,将它写入文件
for row in aimg:
	f.write(row + '\n')
#关闭文件对象,释放系统资源。
f.close()

完整代码

下面是完整的 ASCII 文本图形程序,也可以下载

import sys, random, argparse
import numpy as np
import math
from PIL import Image

#70 级的灰度梯度
gscale1 = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
#简单的 10 级灰度梯度
gscale2 = "@%#*+=-:. "


#Image.open()打开输入图像文件,Image.convert()将该图像转换为灰度图像。“L”代表 luminance,是图像亮度的单位。
#image = Image.open(fileName).convert("L")
#保存输入图像的宽度和高度
#W, H = image.size[0], image.size[1]
#根据用户指定的列数(cols),计算小块的宽度(如果用户没有在命令行中设置其他值,程序默认使用 80 列)
#w = W/cols
#利用垂直比例系数(作为 scale 传入),计算它的高度
#h = w/scale
#用这个网格高度来计算行数。
#rows = int(H/h)


#图像小块作为 PIL Image 对象传入
def getAveragel(image):
	#将 image 转换成一个 numpy数组,此时 im 成为一个二维数组,包含每个像素的亮度
	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 x %d" % (W, H))
	w = W/cols
	h = w/scale
	rows = int(H/h)

	print("cols: %d, rows: %d" % (cols, rows))
	print("tile dims: %d x %d" % (w, h))

	if cols > W or rows > H:
		print("Image too small for specified cols!")
		exit(0)


	#初始化
	aimg = []

	for j in range(rows):
		#计算每个图像小块的起始和结束 y 坐标
		y1 = int(j*h)
		y2 = int((j+1)*h)

		if j == rows-1:
			y2 = H

		#为 ASCII 图像添加一个空字符串,作为一种紧凑的方式来表示图像的当前行
		aimg.append("")
		for i in range(cols):
			#计算每个小块的左、右 x 坐标
			x1 = int(i*w)
			x2 = int((i+1)*w)
			#为最后一小块校正 x 坐标
			if i == cols-1:
				x2 = W
			#提取图像小块,然后将该小块传入 getAverageL()函数
			img = image.crop((x1, y1, x2, y2))
			avg = int(getAveragel(img))

			if moreLevels:
				gsval = gscale1[int((avg*69)/255)]
			else:
				gsval = gscale2[int((avg*9)/255)]

			#在文本行中添加找到的 ASCII 值 gsval,代码循环,直到处理完所有行
			aimg[j] += gsval

	return aimg


def main():
	descStr = "This program converts an image into ASCII art."
	parser = argparse.ArgumentParser(description="descStr")
	#包含指定图像文件输入的选项(唯一必须的参数)
	parser.add_argument('--file', dest='imgFile', required=True)
	#设置垂直比例因子
	parser.add_argument('--scale', dest='scale', required=False)
	#设置输出文件名
	parser.add_argument('--out', dest='outFile', required=False)
	#设置 ASCII 输出中的文本列数
	parser.add_argument('--cols', dest='cols', required=False)
	#添加--morelevels 选项,让用户选择更多层次的灰度梯度
	parser.add_argument('--morelevels', dest='moreLevels', action='store_true')

	args = parser.parse_args()
	imgFile = args.imgFile
	outFile = 'out.txt'
	if args.outFile:
		outFile = args.outFile
	scale = 0.43
	if args.scale:
		scale = float(args.scale)
	cols = 80
	if args.cols:
		cols = int(args.cols)
	print('generating ASCII art...')
	aimg = covertImageToAscii(imgFile, cols, scale, args.moreLevels)


	#使用内置的 open()方法,打开一个新的文本文件用于写入
	f = open(outFile, 'w')
	#迭代遍历列表中的每个字符串,将它写入文件
	for row in aimg:
		f.write(row + '\n')
	#关闭文件对象,释放系统资源。
	f.close()
	print("ASCII art written to %s" % outFile)


if __name__ == '__main__':
	main()

运行 ASCII 文本图形生成程序

要运行编写好的程序,输入类似下面这样的命令,将 data/robot.jpg 替换为你想使用的图像文件的相对路径:

python ascii.py --file data/robot.jpg --cols 100

运行示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QuantumStack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值