原文链接:https://blog.csdn.net/weixin_45595560/article/details/103651606
本项目程序基于python3环境编写。
字符画是一系列字符的组合,每个字符由于构成的不同,可以视为颜色深浅不一的像素点,聚合在一起构成的图像。根据成像效应,大脑会将视野中相似的像素色块合并,得到一个更加易于理解的图案,这也是人眼分辨字符画内容的理论依据。
基本原理
灰度值是一个像素的固有属性,灰度值决定了该像素在去色条件下的黑白值,也决定该像素在非去色条件下的饱和度。本程序建立字符画的基础在于:构建一个字符与灰度值的映射关系,再由程序将图像中的像素点代替为不同的字符,完成字符画的构建。
灰度值的计算公式为:
gray = 0.2126 * r + 0.7152 * g +
0.0722 * b
通过PIL的函数计算出每个像素点的灰度值,再argparse建立一个灰度值与字符的灰度关系映射。通过映射关系将像素点转化为字符存于数组里。
设计思想
程序设计时需要考虑以下几个要点:
- argparse输出字符画的尺寸
- 构建字符灰度字典
- PIL对于像素灰度值的确定与灰度关系映射
- getpixel函数得到像素点数据后的传递与处理
- 双循环结构确立一个数组矩阵
所以在程序中,我将代码分为了三部分。
一、关于argparse
#命令行输入参数处理
parser = argparse.ArgumentParser()
parser.add_argument('file') #输入文件
parser.add_argument('-o', '--output') #输出文件
parser.add_argument('--width', type = int,
default = 80) #输出字符画宽
parser.add_argument('--height', type = int,
default = 80) #输出字符画高
#获取参数
args = parser.parse_args()
IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output
argparse模块使用时要先创立ArgumentParser对象,它包含了将命令行参数解析为python语句的全部参数。
需要先给ArgumentParser添加参数,在对其进行解析
通过Add_argument函数给ArgumentParser添加参数,再通过parse_args进行数据解析。
关于argparse.ArgumentParser():
class argparse.ArgumentParser(prog=None,
usage=None, description=None, epilog=None, parents=[],
formatter_class=argparse.HelpFormatter, prefix_chars='-',
fromfile_prefix_chars=None, argument_default=None, conflict_handler='error',
add_help=True, allow_abbrev=True)
prog - 程序的名称(默认:sys.argv[0])
usage - 描述程序用途的字符串(默认值:从添加到解析器的参数生成)
description - 在参数帮助文档之前显示的文本(默认值:无)
epilog - 在参数帮助文档之后显示的文本(默认值:无)
parents - 一个ArgumentParser 对象的列表,它们的参数也应包含在内
formatter_class - 用于自定义帮助文档输出格式的类
prefix_chars - 可选参数的前缀字符集合(默认值:’-’)
fromfile_prefix_chars - 当需要从文件中读取其他参数时,用于标识文件名的前缀字符集合(默认值:None)
argument_default - 参数的全局默认值(默认值: None)
conflict_handler - 解决冲突选项的策略(通常是不必要的)
add_help - 为解析器添加一个-h/–help 选项(默认值: True)
allow_abbrev - 如果缩写是无歧义的,则允许缩写长选项 (默认值:True)
对于ArgumentParser,我们仅需要控制字符画的长和宽,以及get文件以及output,所以我们仅需要添加所需要的参数进入ArgumentParser并对其进行解析。
灰度字典、灰度值的计算与映射关系
ascii_char =
list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'.
")
ascii_char是我们自定义的灰度字典。而list中,数据从左到右读出,所以我们仅需要在构建字典时,确保字典由左到右是灰度值从大到小即可。在灰度值解析完成后,会通过这个字典构建灰度映射关系。
def get_char(r,g,b,alpha = 256):
if alpha == 0:
return ' '
length = len(ascii_char)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0 + 1)/length
return ascii_char[int(gray/unit)]
get_char会对得到的像素进行数据分析,解析出RGB三色值及像素Alpha值,将三色值带入灰度值计算公式中,得到该像素点的灰度值。unit函数则计算了每个字符可以代表多大范围的灰度值,籍此构建灰度映射关系。最后借助灰度字典,确定此像素的gray应该用字典中第几个字符代替。
判断alpha值,若为零直接输出空格,提高运行效率。
Main函数
if __name__ == '__main__':
im = Image.open(IMG)
im = im.resize((WIDTH,HEIGHT), Image.NEAREST)
txt = ""
for i in range(HEIGHT):
for j in range(WIDTH):
txt += get_char(*im.getpixel((j,i)))
txt += '\n'
print(txt)
Image.open负责IMG方式打开文件,IMG在前面已经定义过,是借助argparse中的打开方式。resize重置了图片的尺寸,根据WIDTH与HEIGHT来重新构建图片,此二变量均为在第一部分通过argparse定义的长和宽,NEAREST将图片质量设为最低,合并相似的像素点。txt变量负责储存文本。
双循环负责构建一个字符矩阵,i变量负责控制行数,j变量负责控制列数。
假设,我们仅让双循环负责print一个Ascii字符〇,Height设为10,Weight设为10,则会print出如下图像:
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
〇〇〇〇〇〇〇〇〇〇
鉴于CSDN上字符间距与行宽不等,且不为等宽字体,所以上方举例尽可能采用等宽字体显示。
这段代码中,我们可以理解为,双循环已经构建完成了图片像素矩阵,getpexil仅需要直接从数组中调用像素数据进行分析。
j 变量循环,每一次都会调用get_char函数进行像素分析,并将结构输出到txt中,循环结束时进行换行,进行下一个 i 变量循环。
最后将txt print到终端状态栏,并且新建一个txt文档储存转换完成的 txt 变量。
附上源码
# -*- coding=utf-8 -*-
from PIL import Image
import argparse
#命令行输入参数处理
parser = argparse.ArgumentParser()
parser.add_argument('file') #输入文件
parser.add_argument('-o', '--output') #输出文件
parser.add_argument('--width', type = int,
default = 80) #输出字符画宽
parser.add_argument('--height', type = int,
default = 80) #输出字符画高
#获取参数
args = parser.parse_args()
IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output
ascii_char =
list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'.
")
# 将256灰度映射到70个字符上
def get_char(r,g,b,alpha = 256):
if alpha == 0:
return ' '
length = len(ascii_char)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0 + 1)/length
return ascii_char[int(gray/unit)]
if __name__ == '__main__':
im = Image.open(IMG)
im = im.resize((WIDTH,HEIGHT), Image.NEAREST)
txt = ""
for i in range(HEIGHT):
for j in range(WIDTH):
txt += get_char(*im.getpixel((j,i)))
txt += '\n'
print(txt)
#字符画输出到文件
if OUTPUT:
with open(OUTPUT,'w') as f:
f.write(txt)
else:
with open("output.txt",'w') as f:
f.write(txt)