简单实现视频转化为字符视频

最简单的从零实现正常视频转字符视频

把大象关进冰箱需要三步

  1. 将视频按帧提取为图片序列
  2. 将图片依次转化为字符图片
  3. 将字符图片按帧重新合称为视频

环境配置

本项目主要利用视频处理库 moviepy 来读取处理视频,利用图片处理库 PIL 来处理图片(视频的每一帧)

pip install moivepy
# 也可利用万能安装模板来加速
pip --default-timeout=100 install moviepy -i https://pypi.tuna.tsinghua.edu.cn/simple

关于库的使用,可以参考这篇文章:moviepy 中文文档 和 PIL 图像基本处理,本文也会将涉及到的用法讲解出来。

  • 关于第一步,直接使用 moviepy 库的相关函数调用即可
  • 关于图片转字符,只需要按照像素遍历该图片,然后再将其按灰度值映射为字符,主要利用 PIL 库函数对 numpy 矩阵进行操作
  • 关于图片按帧重组视频,其实第二、三步是同时的,利用 moviepy 的特效设置方法,对视频的每一帧进行“图片转文字”特效即可。

代码细节

  • 利用 moviepy 的 VideoFileClip 方法对视频进行加载;
  • 利用 frame2char 函数作为特效函数,对目标帧的图片进行处理
    • ImageDraw 负责字符重画
    • ImageFont 负责绘画字符和字体控制

具体实现

利用类对该方法进行包装

# -*- coding: utf-8 -*-

import os
import os.path as osp

import argparse

import numpy as np
from moviepy.editor import *
from PIL import Image, ImageFont, ImageDraw


class Converter:
    def __init__(self, video_input_path, video_output_path, chars_width, fps, isGray, time_start=0, time_end=None):
        self.video_path = video_input_path
        self.video_name = osp.basename(video_input_path).split('.')[0]
        self.output_path = video_output_path if video_output_path else osp.dirname(video_input_path)
        self.output_path = osp.join(self.output_path, self.video_name + "_char2.mp4")
        self.isGray = isGray
        self.vfc = VideoFileClip(self.video_path).subclip(time_start, time_end)
        self.fps = fps
        self.chars_width = chars_width
        self.chars_height = int(self.chars_width / self.vfc.aspect_ratio)
        self.vfc = self.vfc.resize((self.chars_width, self.chars_height))
        font_fp = "DroidSansMono.ttf"
        self.font = ImageFont.truetype(font_fp, size=14)
        self.font_width = sum(self.font.getsize('a')) // 2

        self.video_size = int(self.chars_width * self.font_width), int(self.chars_height * self.font_width)

        # 值越大,映射的字母越靠后,表示像素更"白"
        self.pixels = \
            "$#@&%ZYXWVUTSRQPONMLKJIHGFEDCBA098765432?][}{/)(>i!lI;:,\"^`'. "

    def gray2char(self, gray):
        """通过灰度值获取当前像素的字符"""
        percent = float(gray) / 255
        index = int(percent * (len(self.pixels) - 1))
        return self.pixels[index]

    def frame2char(self, t):
        """
        将每一帧的图片转化为字符
        本函数作为 moviepy.editor.VideoClip 特效功能的入口参数
        相当于对时间 t 内的视频做转为字符的特效处理
        :return: numpy.array 类型字符列表
        """
        img_np = self.vfc.get_frame(t)
        img = Image.fromarray(img_np, 'RGB')
        img_gary = img.convert(mode='L')

        img_char = Image.new('RGB', self.video_size, color='white')
        brush = ImageDraw.Draw(img_char)

        white = (255, 255, 255)
        black = (0, 0, 0)

        for y in range(self.chars_height):
            for x in range(self.chars_width):
                r, g, b = img_np[y][x] if not self.isGray else black

                gray = img_gary.getpixel((x, y))
                char = self.gray2char(gray)

                position = x * self.font_width, y * self.font_width
                brush.text(position, char, fill=(r, g, b), font=self.font)

        # img_char = img_char if not self.isGray else img_char.convert(mode='L')
        return np.array(img_char)

    def generate(self):
        """生成字符对象"""
        clip = VideoClip(self.frame2char, duration=self.vfc.duration)
        clip = clip.set_fps(self.vfc.fps).set_audio(self.vfc.audio)
        clip.write_videofile(self.output_path)

def main():
    parser = argparse.ArgumentParser(description='Convert video to char-based video.')
    parser.add_argument('-i', type=str, help='the input path of source video, such as xx/xx/xx.mp4')
    parser.add_argument('-o', type=str, default=None, help='the output path of char-based video, default is input path')
    parser.add_argument('-fps', type=int, default=10, help='the frame per second of output video, default is 8')
    parser.add_argument('-cw', type=int, default=200, help='the chars width of chars in video, default is 80')
    parser.add_argument('-g', action='store_true', default=False, help='output the gray video, default is False')
    args = parser.parse_args()
    # args.g = True
    converter = Converter(args.i, args.o, args.cw, args.fps, args.g)
    converter.generate()


if __name__ == '__main__':
    main()

你可能感兴趣的:(python,TOOLS)