请欣赏另类的老鼠舞
字符老鼠舞
与原版对比

实现过程
1. 安装库
pip install numpy
pip install Pillow
pip install opencv-python
pip install moviepy
2. 读取视频帧并转换为灰度图
import cv2
def make_video(input_video_path, output_video_path):
video_cap = cv2.VideoCapture(input_video_path)
if not video_cap.isOpened():
print("无法打开视频文件")
return
while video_cap.isOpened():
ret, frame = video_cap.read()
if not ret:
break
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
video_cap.release()
通过 cv2 我们可以获取到视频的每一帧,并对每一帧进行处理。
为了能够将视频变为字符,我们需要获取视频每一帧的灰度图,这样就可以通过计算判断每一个像素点对应的字符了。
通常视频每一个像素是三通道RGB色彩,当变为灰度图的时候将变为0~255的单通道色彩。
3. 缩放帧
video_width = video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)
video_height = video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
rate = video_height / video_width
font_size = 10
letter_count = 50
scale_size = (int(letter_count), int(letter_count * rate))
原来视频每一帧画面比较大,我们后续需要使用字符来展示视频,因此需要将每一帧的像素要与字符相当。
while video_cap.isOpened():
ret, frame = video_cap.read()
if not ret:
break
frame = cv2.resize(frame, scale_size)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
4. 制作字符帧
import numpy as np
from PIL import Image, ImageDraw, ImageFont
ASCII_CHARS = '@%#*+=-:. '
def get_ascii_text(gray):
'''
将 0~1 的数值与字符相匹配
:param gray: 0~1
:return: 字符
'''
index = int(gray * len(ASCII_CHARS))
index = index if index <= (len(ASCII_CHARS) - 1) else index
return ASCII_CHARS[index]
def make_ascii_frame(frame, size, font_size):
'''
将帧进行归一化操作,并将其变为字符帧
:param frame: 帧
:param size: 帧的画面大小
:param font_size: 字体大小
:return: 字符帧
'''
image = Image.new("RGB", size, color="black")
draw = ImageDraw.Draw(image)
font = ImageFont.load_default(font_size)
frame = frame / 255
for row_index, row_value in enumerate(frame):
for col_index, col_value in enumerate(row_value):
draw.text(
(col_index * font_size, row_index * font_size),
get_ascii_text(col_value),
font=font,
fill="white"
)
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
通过 Pillow 我们可以自定义我们的画面,然后返回一个 cv2 可用的数据。
ASCII_CHARS 是我们画面的构成元素,可以自定义,只要满足类似颜色渐变的效果即可。
这里我们还需要把之前缩放的大小转换为画面帧的大小:
real_size = (scale_size[0] * font_size, scale_size[1] * font_size)
在获取帧的地方调用:
while video_cap.isOpened():
ret, frame = video_cap.read()
if not ret:
break
frame = cv2.resize(frame, scale_size)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = make_ascii_frame(frame, real_size, font_size)
5. 输出视频
def make_video(input_video_path, output_video_path):
video_cap = cv2.VideoCapture(input_video_path)
if not video_cap.isOpened():
print("无法打开视频文件")
return
fps = video_cap.get(cv2.CAP_PROP_FPS)
video_width = video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)
video_height = video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
rate = video_height / video_width
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
font_size = 10
letter_count = 50
scale_size = (int(letter_count), int(letter_count * rate))
real_size = (scale_size[0] * font_size, scale_size[1] * font_size)
out_writer = cv2.VideoWriter('temp_ascii_video.mp4', fourcc, fps, real_size)
while video_cap.isOpened():
ret, frame = video_cap.read()
if not ret:
break
frame = cv2.resize(frame, scale_size)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = make_ascii_frame(frame, real_size, font_size)
out_writer.write(frame)
video_cap.release()
out_writer.release()
我们使用 cv2.VideoWriter 来输出我们的字符视频,不过需要注意的是这样的视频是没有声音的,因此继续优化,添加声音。
6. 为视频添加声音
from moviepy import VideoFileClip, AudioFileClip
original_audio = AudioFileClip(input_video_path)
new_video = VideoFileClip('temp_ascii_video.mp4')
final_video = new_video.with_audio(original_audio)
final_video.write_videofile(output_video_path)
new_video.close()
original_audio.close()
7. 完整代码
import os
import time
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from moviepy import VideoFileClip, AudioFileClip
# ASCII字符集,用于表示不同的灰度级别
ASCII_CHARS = '@%#*+=-:. '
def get_ascii_text(gray):
'''
将 0~1 的数值与字符相匹配
:param gray: 0~1
:return: 字符
'''
index = int(gray * len(ASCII_CHARS))
index = index if index <= (len(ASCII_CHARS) - 1) else index
return ASCII_CHARS[index]
def make_ascii_frame(frame, size, font_size):
'''
将帧进行归一化操作,并将其变为字符帧
:param frame: 帧
:param size: 帧的画面大小
:param font_size: 字体大小
:return: 字符帧
'''
image = Image.new("RGB", size, color="black")
draw = ImageDraw.Draw(image)
font = ImageFont.load_default(font_size)
frame = frame / 255
for row_index, row_value in enumerate(frame):
for col_index, col_value in enumerate(row_value):
draw.text(
(col_index * font_size, row_index * font_size),
get_ascii_text(col_value),
font=font,
fill="white"
)
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
def make_video(input_video_path, output_video_path):
video_cap = cv2.VideoCapture(input_video_path)
if not video_cap.isOpened():
print("无法打开视频文件")
return
fps = video_cap.get(cv2.CAP_PROP_FPS)
video_width = video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)
video_height = video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
rate = video_height / video_width
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
font_size = 10
letter_count = 50
scale_size = (int(letter_count), int(letter_count * rate))
real_size = (scale_size[0] * font_size, scale_size[1] * font_size)
out_writer = cv2.VideoWriter('temp_ascii_video.mp4', fourcc, fps, real_size)
while video_cap.isOpened():
ret, frame = video_cap.read()
if not ret:
break
frame = cv2.resize(frame, scale_size)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = make_ascii_frame(frame, real_size, font_size)
out_writer.write(frame)
video_cap.release()
out_writer.release()
original_audio = AudioFileClip(input_video_path)
new_video = VideoFileClip('temp_ascii_video.mp4')
final_video = new_video.with_audio(original_audio)
final_video.write_videofile(output_video_path)
new_video.close()
original_audio.close()
if __name__ == "__main__":
video_path = "MouseVideo.mp4"# 输入视频路径
audio_output_video_path = "output_mouse_ascii_video.mp4"
make_video(video_path, audio_output_video_path)
结尾
成功了,我们可以将任意视频转换为字符视频了,如果你喜欢这篇文章,不要吝啬你的赞,那将是我源源不断创作的动力。