如何通过python将视频转换为字符视频

发布于:2025-03-29 ⋅ 阅读:(28) ⋅ 点赞:(0)

请欣赏另类的老鼠舞

字符老鼠舞

与原版对比

对比
对比

实现过程

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)

结尾

成功了,我们可以将任意视频转换为字符视频了,如果你喜欢这篇文章,不要吝啬你的赞,那将是我源源不断创作的动力。