ascii 字符画
- categories
- >
- others
https://www.asciiart.eu/image-to-ascii
原理:
先将图像转为灰度图像,灰度图像通常使用 8 位表示,每个像素可以有 256 个不同的灰度值,范围从 0 到 255。其中:
- 0 表示黑色
- 255 表示白色
- 0 到 255 之间的值表示不同的灰度级别(如 128 为中灰色)。
将这些像素映射为对应字符,字符集长度越大,表示的层次越丰富(也要根据字符的稀疏对应,比如'
表示的颜色比$
浅)
ASCII_CHARS = ['`', '^', '"', ',', ':', ';', 'I', 'l', '!', 'i', '~', '+', '_', '-', '?', ']', '[', '}', '{', '1', ')', '(', '|', '\\', '/', '*', 'j', 't', 'f', 'r', 'x', 'n', 'v', 'c', 'z', 'X', 'Y', 'U', 'J', 'C', 'L', 'Q', '0', 'O', 'Z', 'm', 'w', 'q', 'p', 'd', 'b', 'k', 'h', ')', 'W', 'M', 'B', '8', '$', '%', '&', '@', '#']
当字符集有限的情况下,需要按比例划分灰度值,将一个范围的灰度映射为对应的字符
chars = ASCII_CHARS[pixel * len(ASCII_CHARS) // 256]
现有原图:
转换为 ascii 字符后:
emm…,好像不是特别美观
完整代码
from PIL import Image
# 定义要使用的字符集,字符集长度越大,表示层次越丰富
ASCII_CHARS = ['`', '^', '"', ',', ':', ';', 'I', 'l', '!', 'i', '~', '+', '_', '-', '?', ']', '[', '}', '{', '1', ')', '(', '|', '\\', '/', '*', 'j', 't', 'f', 'r', 'x', 'n', 'v', 'c', 'z', 'X', 'Y', 'U', 'J', 'C', 'L', 'Q', '0', 'O', 'Z', 'm', 'w', 'q', 'p', 'd', 'b', 'k', 'h', ')', 'W', 'M', 'B', '8', '$', '%', '&', '@', '#']
print(ASCII_CHARS)
print(len(ASCII_CHARS))
# 调整图像大小
def resize_image(image, new_width=100):
width, height = image.size
ratio = height / width
# 0.55 矫正宽高比
new_height = int(ratio * new_width * 0.55)
return image.resize((new_width, new_height))
# 将图像转为灰度
def grayify(image):
return image.convert("L")
# 将像素值映射到ASCII字符
def pixels_to_ascii(image):
pixels = image.getdata()
ascii_str = "".join([ASCII_CHARS[pixel * len(ASCII_CHARS) // 256] for pixel in pixels])
return ascii_str
# 主函数,执行图像到ASCII的转换
def image_to_ascii(image_path, new_width=100):
# 打开图像并处理
try:
image = Image.open(image_path)
except Exception as e:
print(f"无法打开图像文件: {e}")
return
# 调整图像大小和灰度
image = resize_image(image, new_width)
image = grayify(image)
# 转换为ASCII字符
ascii_str = pixels_to_ascii(image)
# 将ASCII字符串按宽度分行
img_width = image.width
ascii_str_len = len(ascii_str)
ascii_img = "\n".join([ascii_str[i:i + img_width] for i in range(0, ascii_str_len, img_width)])
# 输出或保存ASCII图像
print(ascii_img)
# 可以选择将ASCII字符输出到文件中
with open("ascii_image.txt", "w") as f:
f.write(ascii_img)
# 调整宽度参数来控制ASCII图像的分辨率
image_to_ascii("can.png", 600)
视频 ascii
对视频的处理就是对每一帧进行处理
reader = imageio.get_reader('bad_apple.mp4')
for frame in reader:
# 将帧转换为灰度 ASCII 并显示
image = Image.fromarray(frame)
ascii_img = image_to_ascii(image, new_width=80)
为了符合原始视频帧率,需要额外计算延时(粗略计算)
fps = reader.get_meta_data()['fps']
frame_delay = 1.0 / fps # 每帧显示的时间间隔(秒)
for frame in reader:
start = time.time()
end = time.time()
# 等待适当的时间来同步帧
time.sleep(frame_delay - (end - start))
由于使用curses
控制,需要额外注意宽度:
ascii_img = image_to_ascii(image, new_width=200)
完整代码
import curses
import imageio
import time
from PIL import Image
# 定义要使用的字符集
ASCII_CHARS = ['`', '^', '"', ',', ':', ';', 'I', 'l', '!', 'i', '~', '+', '_', '-', '?', ']', '[', '}', '{', '1', ')', '(', '|', '\\', '/', '*', 'j', 't', 'f', 'r', 'x', 'n', 'v', 'c', 'z', 'X', 'Y', 'U', 'J', 'C', 'L', 'Q', '0', 'O', 'Z', 'm', 'w', 'q', 'p', 'd', 'b', 'k', 'h', ')', 'W', 'M', 'B', '8', '$', '%', '&', '@', '#']
# 调整图像大小
def resize_image(image, new_width=100):
width, height = image.size
ratio = height / width
new_height = int(ratio * new_width * 0.55)
return image.resize((new_width, new_height))
# 将图像转为灰度
def grayify(image):
return image.convert("L")
# 将像素值映射到ASCII字符
def pixels_to_ascii(image):
pixels = image.getdata()
ascii_str = "".join([ASCII_CHARS[pixel * len(ASCII_CHARS) // 256] for pixel in pixels])
return ascii_str
# 主函数,执行图像到ASCII的转换
def image_to_ascii(image, new_width=100):
image = resize_image(image, new_width)
image = grayify(image)
ascii_str = pixels_to_ascii(image)
img_width = image.width
ascii_str_len = len(ascii_str)
ascii_img = "\n".join([ascii_str[i:i + img_width] for i in range(0, ascii_str_len, img_width)])
return ascii_img
def main(stdscr):
curses.curs_set(0) # 隐藏光标
stdscr.nodelay(True) # 使得 getch() 非阻塞
stdscr.clear()
# 打开视频文件
reader = imageio.get_reader('bad_apple.mp4')
fps = reader.get_meta_data()['fps']
frame_delay = 1.0 / fps # 每帧显示的时间间隔(秒)
for frame in reader:
start = time.time()
# 将帧转换为灰度 ASCII 并显示
image = Image.fromarray(frame)
ascii_img = image_to_ascii(image, new_width=80)
# 绘制 ASCII 图像
stdscr.clear()
for y, line in enumerate(ascii_img.split("\n")):
stdscr.addstr(y, 0, line)
stdscr.refresh()
end = time.time()
# 等待适当的时间来同步帧
time.sleep(frame_delay - (end - start))
# 按 'q' 键退出
if stdscr.getch() == ord('q'):
break
# 运行 curses 主程序
if __name__ == "__main__":
try:
curses.wrapper(main)
except Exception as e:
print(f"发生错误: {e}")
comment:
- Valine
- LiveRe
- ChangYan