一、绘制字符画
主要内容是将图片转化成灰度图,然后简化成像素值,然后将像素值映射成字符。
步骤:(结尾附完整代码)
1. 加载图像首先,你需要加载要转换的图像。这通常通过使用图像处理库(如Pillow)来完成。、
首需要安装必要的第三方库:如
pip install pillow
然后加载图片:
image = Image.open(123.jpg") #加载图片
2. 调整图像大小为了使字符画在终端或文本编辑器中显示得更合适,通常需要调整图像的大小。由于字符的高度通常大于宽度,因此调整后的图像高度应该比宽度小一些。
def resize_image(image, new_width=100):
width, height = image.size
ratio = height / width
new_height = int(new_width * ratio * 0.5) # 0.5 是一个经验系数,用于调整字符的高度
resized_image = image.resize((new_width, new_height))
return resized_image
3. 转换为灰度图将彩色图像转换为灰度图,这样每个像素只有一个亮度值,便于后续的字符映射。
def grayscale_image(image):
return image.convert("L")
4. 将像素值转换为字符将灰度图中的每个像素值映射到一个字符。通常使用一个字符集(如 @%#*+=-:. ),其中 @ 表示最亮,. 表示最暗。
ASCII_CHARS = "@%#*+=-:. "
def pixels_to_ascii(image):
pixels = image.getdata()
ascii_str = "".join([
ASCII_CHARS[pixel // 32] # 32 是 256(灰度范围)除以字符集长度
for pixel in pixels
])
return ascii_str
5. 格式化字符画将字符字符串格式化为多行文本,以便在终端或文本编辑器中正确显示
def format_ascii_str(ascii_str, width):
return "\n".join([ascii_str[i:i+width] for i in range(0, len(ascii_str), width)])
6. 显示或保存字符画最后,你可以将字符画显示在终端中,或者将其保存到文件中。
def main():
image = Image.open("654.jpg") #你的图片地址
resized_image = resize_image(image, new_width=100)
gray_image = grayscale_image(resized_image)
ascii_str = pixels_to_ascii(gray_image)
formatted_ascii_str = format_ascii_str(ascii_str, resized_image.width)
print(formatted_ascii_str)
with open("ascii_image.txt", "w") as f:
f.write(formatted_ascii_str)
if __name__ == "__main__":
main()
完整代码:
from PIL import Image, ImageFont, ImageDraw
# 定义字符集
ASCII_CHARS = "@%#*+=-:. "
def resize_image(image, new_width=100):
"""调整图像大小"""
width, height = image.size
ratio = height / width
new_height = int(new_width * ratio)
resized_image = image.resize((new_width, new_height))
return resized_image
def grayscale_image(image):
"""将图像转换为灰度图"""
return image.convert("L")
def pixels_to_ascii(image):
"""使用线性插值将像素值转换为ASCII字符"""
pixels = image.getdata()
max_gray = 255
min_gray = 0
ascii_str = "".join([
ASCII_CHARS[int(((pixel - min_gray) / (max_gray - min_gray)) * (len(ASCII_CHARS) - 1))]
for pixel in pixels
])
return ascii_str
def frame_to_ascii_image(image, new_width=100, font_size=10):
"""将一帧图像转换为ASCII字符画,并返回一个新的PIL图像"""
resized_image = resize_image(image, new_width)
gray_image = grayscale_image(resized_image)
ascii_str = pixels_to_ascii(gray_image)
img_width = gray_image.width
ascii_str_len = len(ascii_str)
ascii_img = "\n".join([ascii_str[index:(index + img_width)] for index in range(0, ascii_str_len, img_width)])
# 创建一个新的空白图像,背景为白色
font = ImageFont.truetype("arial.ttf", font_size)
text_image = Image.new("RGB", (img_width * font_size // 2, gray_image.height * font_size // 2), color=(255, 255, 255))
draw = ImageDraw.Draw(text_image)
# 模拟抗锯齿效果
for y, line in enumerate(ascii_img.split('\n')):
for x, char in enumerate(line):
position = (x * font_size // 2, y * font_size // 2)
draw.text(position, char, fill=(0, 0, 0), font=font)
return text_image
def main():
# 打开图像文件
image_path = "123.jpg" # 替换为你的图像路径
image = Image.open(image_path)
# 将图像转换为ASCII字符画
new_width = 100 # 你可以根据需要调整宽度
font_size = 10 # 你可以根据需要调整字体大小
ascii_image = frame_to_ascii_image(image, new_width, font_size)
# 保存或显示ASCII字符画
ascii_image.save("456.jpg") # 替换为你要保存的路径
ascii_image.show()
if __name__ == "__main__":
main()
效果:
二、字符视频
流程是读取视频,逐帧处理,将每一帧转换为ASCII字符画,再将这些字符画帧组合成一个新的视频文件,最终输出的一个由ASCII字符构成的新视频。
需要导入第三方库:
cv2、Pillow、numpy
同时为了方便查看视频处理进度,加一个tqdm库
这里不过多解释,直接给出代码:
import cv2
from PIL import Image, ImageFont, ImageDraw
import numpy as np
from tqdm import tqdm #加进度条
# 定义字符集
ASCII_CHARS = "@%#*+=-:.{[;'.,&^),.]} "
def resize_image(image, new_width=200):
"""调整图像大小"""
width, height = image.size
ratio = height / width
new_height = int(new_width * ratio)
resized_image = image.resize((new_width, new_height))
return resized_image
def grayscale_image(image):
"""将图像转换为灰度图"""
return image.convert("L")
def pixels_to_ascii(image):
"""使用线性插值将像素值转换为ASCII字符"""
pixels = image.getdata()
max_gray = 255
min_gray = 0
ascii_str = "".join([
ASCII_CHARS[int(((pixel - min_gray) / (max_gray - min_gray)) * (len(ASCII_CHARS) - 1))]
for pixel in pixels
])
return ascii_str
def frame_to_ascii_image(image, new_width=200, font_size=12):
"""将一帧图像转换为ASCII字符画,并返回一个新的PIL图像"""
resized_image = resize_image(image, new_width)
gray_image = grayscale_image(resized_image)
ascii_str = pixels_to_ascii(gray_image)
img_width = gray_image.width
ascii_str_len = len(ascii_str)
ascii_img = "\n".join([ascii_str[index:(index + img_width)] for index in range(0, ascii_str_len, img_width)])
# 创建一个新的空白图像,背景为白色
font = ImageFont.truetype("arial.ttf", font_size)
text_image = Image.new("RGB", (img_width * font_size // 2, gray_image.height * font_size // 2), color=(255, 255, 255))
draw = ImageDraw.Draw(text_image)
# 模拟抗锯齿效果
for y, line in enumerate(ascii_img.split('\n')):
for x, char in enumerate(line):
position = (x * font_size // 2, y * font_size // 2)
draw.text(position, char, fill=(0, 0, 0), font=font)
return text_image
def video_to_ascii_video(input_path, output_path="345.mp4", new_width=200, font_size=12):
# 修改了默认宽度
"""将视频转换为字符画视频并保存"""
cap = cv2.VideoCapture(input_path)
if not cap.isOpened():
print("Error: Could not open video.")
return
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 计算新高度
new_height = int(new_width * frame_height / frame_width)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (new_width * font_size // 2, new_height * font_size // 2))
with tqdm(total=total_frames, desc="Processing frames", unit="frame") as pbar:
while True:
ret, frame = cap.read()
if not ret:
break
# 将OpenCV的BGR图像转换为PIL的RGB图像
pil_image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
ascii_image = frame_to_ascii_image(pil_image, new_width, font_size)
# 将PIL图像转换为OpenCV图像
ascii_frame = cv2.cvtColor(np.array(ascii_image), cv2.COLOR_RGB2BGR)
out.write(ascii_frame)
pbar.update(1)
cap.release()
out.release()
print(f"视频保存在:{output_path}")
if __name__ == "__main__":
input_path = r"123.mp4" # 替换为你的视频路径
video_to_ascii_video(input_path, new_width=200, font_size=12)