前言
最近在网上看到一个非常有好玩技术,可以把彩色照片转成字符图片,先看看效果。
原理
把图片中每个像素点转成灰度图(黑白电视的展现方式),然后根据每个像素的灰度值用不同的字符去替换。其中最大的问题是转字符后如何和原始图片尺寸保持一致,翻阅了很多资料才勉强看上去一致。
关键步骤
- 从视频中提取图片
- 图片转字符文本
- 字符文本转字符图片
- 字符图片合成gif动态图
#!/usr/bin/python3
# coding: utf-8
import os
import imageio.v2 as imageio
import cv2
from PIL import Image, ImageDraw, ImageFont
# ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
ascii_char = list('MNHQ$OC?7>!:-;. ')
def get_char(r, g, b, alpha=256):
if alpha == 0:
return ' '
# 这是一个优化,将浮点运算转成整型运算
gray = (2126 * r + 7152 * g + 722 * b) / 10000
# 为什么是1.0 因为alpha、gray是整型运算的时候按整型算如果小于1的话按0算
char_idx = int((gray / (alpha + 1.0)) * len(ascii_char))
return ascii_char[char_idx]
def text2image(content, width, height, filename):
'''
字符转图片
:param content: 字符数组
:param width: 输出的图片宽度
:param height: 输出的图片高度
:param filename: 输出的图片名称
:return: ''' image = Image.new("RGB", (width, height), (255, 255, 255))
canvas = ImageDraw.Draw(image)
font = ImageFont.truetype('JetBrainsMono-Thin.ttf')
fillcolor = "#000000"
x, y = 0, 0
font_w, font_h = 6, 12 # 设置单个字符长款
for i in range(len(content)):
if content[i] == '\n':
x = -font_w
y += font_h
continue
canvas.text((x, y), content[i], fill=fillcolor, font=font)
x += font_w
image.save(filename)
def image2textimage(file_name="test.jpg", multiple=1.0, out_file_name='output.txt'):
'''
将图片转成字符图片
:param file_name: :param multiple: 缩放比例
:param out_file_name: :return: ''' text = ''
im = Image.open(file_name)
width, height = int(im.size[0] * multiple), int(im.size[1] * multiple)
width_text, height_text = int(width / 6), int(height / 12) # 原始图片长款等比例缩放
im = im.resize((width_text, height_text), Image.NEAREST)
for i in range(height_text):
for j in range(width_text):
text += get_char(*im.getpixel((j, i)))
text += '\n'
# with open(out_file_name, 'w') as f:
# f.write(text) text2image(text, width, height, file_name + '_txt.png')
return file_name + '_txt.png'
def video2images(source_video, start_time, end_time, interval=1.0):
'''
将视频转保存为图片s
:param source_video: 视频文件
:param start_time: 开始时间/s
:param end_time: 结束时间/s
:param interval: 时间间隔/s
:return: ''' # 创建零时目录
tmppath = os.path.join(os.getcwd(), 'tmp')
if not os.path.exists(tmppath):
os.mkdir(tmppath)
cap = cv2.VideoCapture(source_video)
if not cap.isOpened():
print("Error: Could not open video.")
return []
else:
fps = cap.get(5) # 帧速率
frame_number = cap.get(7) # 视频文件的帧数
duration = frame_number / fps # 视频总帧数/帧速率 是时间/秒
print("总时长[%.2f]秒,开始时间[%.2f]秒结束时间[%.2f]秒" % (duration, start_time, end_time))
if start_time > duration or end_time > duration:
print("Error: 截取时间断异常.总时长[%d]秒,开始时间[%d]秒结束时间[%d]秒" % (duration, start_time, end_time))
return []
start_frame = int(fps * float(start_time))
end_frame = int(fps * float(end_time))
interval_frame = int(fps * float(interval))
print("总帧数[%d],帧率[%d],开始帧数[%d],结束帧数[%d], 间隔[%d]" % (frame_number, fps, start_frame, end_frame, interval_frame))
num = 0
images = []
while True:
success, frame = cap.read()
if success:
if int(start_frame) <= int(num) <= int(end_frame):
if success and int(num) % int(interval_frame) == 0:
image_name = os.path.join(tmppath, f"frame_{num}.png")
cv2.imwrite(image_name, frame)
images.append(image_name)
num += 1
if num > frame_number:
break
cap.release()
return images
return texts
def image2gif(textimages, output, fps=2):
images=[]
for pic in textimages:
images.append(imageio.imread(pic))
imageio.mimsave(output, images, format='GIF', fps=fps, loop=0)
def fontText(path=''):
'''
查看文本字符宽度和高度
:param path: :return: ''' font = ImageFont.truetype(path)
for t in ascii_char:
fbox = font.getbbox(t)
print(t, fbox[2]-fbox[0], fbox[3]-fbox[1])
if __name__ == '__main__':
images = video2images('庆余年2.mov', start_time=3, end_time=21)
textImages = []
for im in images:
textImages.append(image2textimage(im))
image2gif(textImages, 'output.gif')
完成项目内容见附件:源码附件
参考
使用python中的imageio生成gif动态图
Python写实用小工具-实现图片转字符画
使用Python完成曾火爆全网的图片转符号图片、GIF图像转字符GIF动画操作