前言
阅读时长 | 用脑度 | 前置知识 |
5min | 25% | Python |
最近看到一个 Up 主 Ele实验室 发布的一个视频:字符化视频是怎么做出来的,感觉很有意思。不如自己也实现一个来玩玩?
以前也没怎么写过 Python,只用来刷过 LeetCode。正好借这个机会再学一学 Python 吧。
效果
先来看看实现效果。
Emmm,有那味了。
思路
首先,我们都知道视频本质上是一张张图片快速展示的效果,所以第一步就是将视频进行 分帧。
当将视频分成一张张图片后,每张图片里的每个像素点都是由 红、绿、蓝 三原色混合而成的。而这样的混合机制就是通过数值值来表示的,比如 rgb(255, 255, 255) 就是白色,而 rgb(0, 0, 0) 则表示连个颜色都没有,也就是黑色。
拿到三种值后,可以通过一定计算将单像素变成一个值,一般来说这个过程可以是灰度化。
rgba(255, 255, 255) -> 0 复制代码
拿到灰度后的值,就可以将所有像素映射到 Hash 表上的一个字符,从而形成 字符画。
0 -> $ 复制代码
将这些字符画都以 txt 文件保存到一个目录,再按顺序打印出来就形成了 字符视频 了。
那我们现在开始实现吧。
分帧
分帧这里可以不用我们实现,直接使用 ffmpeg 就可以了。先用下面命令进行安装:
brew install ffmpeg 复制代码
然后使用这个命令来分帧:
ffmpeg -i res/cxk-video.mov res/image_frames/%d.jpg 复制代码
上面命令很容易理解:res/cxk-video.mov
是原视频,后面的 res/image_frames/%d.jpg
就是存放的路径,%d
表示数字.jpg。
生成字符画
这里要借用到 Pillow 这个库,可以直接获取图片的 rgb 值。
先安装一下这个库:
pip3 install Pillow 复制代码
如果你是 M1 的 Mac 电脑,需要用下面这两个命令来安装。
sudo python3 -m pip install --upgrade pip sudo python3 -m pip install --upgrade Pillow 复制代码
然后来实现将图片变成字符画:
from os import listdir from os.path import isfile, join image_frames_dir = 'res/image_frames' txt_frames_dir = 'res/txt_frames' def prepare(width, height): for file_name in listdir(image_frames_dir): print("正在处理 " + file_name) image_path = join(image_frames_dir, file_name) // 获取图片地址 txt_path = join(txt_frames_dir, file_name.split('.')[0] + '.txt') // 获取 txt 文件地址 if not isfile(image_path): continue image = Image.open(image_path) // 获取图片 image = image.resize((width, height), Image.NEAREST) # NEAREST 低质量图 txt = to_string(image, width, height) // 生成字符文本 with open(txt_path, 'w') as txt_file: // 保存字符文本 txt_file.write(txt) 复制代码
使用 getpiexel
获取 tuple 然后通过算法生成 gray
值,再映射到定义好的数组上。
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ") def get_char(r, g, b, alpha=256): if alpha == 0: return ' ' length = len(ascii_char) gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b) // 生成灰度值 unit = (256.0 + 1) / length return ascii_char[int(gray / unit)] // 映射到字符 def to_string(image, width, height): txt = "" for h in range(height): for w in range(width): txt += get_char(*image.getpixel((w, h))) # 获取 pixel 的颜色数值 txt += '\n' # 记得最后要换行 return txt 复制代码
然后就可以生成很多个 txt 文件了。
字符视频
好了,上面已经可以实现将所有图片转换成字符画了,下面将这些字符画顺序地打印出来就可以了。
import os from time import sleep def display(speed): def compare_file_name(file_name1, file_name2): index1 = int(file_name1.split('.')[0]) index2 = int(file_name2.split('.')[0]) return index1 - index2 # 以文件名来作对比 # 获取所有 txt 文件,并排好序 for file_name in sorted(listdir(txt_frames_dir), key=cmp_to_key(compare_file_name)): txt_path = join(txt_frames_dir, file_name) os.system('cat ' + txt_path) # cat 出来 sleep(speed) 复制代码
现在已经可以实现把坤坤打印出来了。
工具函数
虽然功能已经实现好了,但是如果要做出一个别人也能玩得嗨的产品还需要再打磨一下。
首先,可以添加 is_ready
函数来判断是否已经有生成好了的字符画。
from os import listdir def is_ready(): return len(listdir(txt_frames_dir)) != 0 复制代码
还可以添加 clear 函数来清楚缓存。
import glob import os def clear(): files = glob.glob(join(txt_frames_dir, '*')) for f in files: os.remove(f) 复制代码
最后一步,做一个入口文件,添加一些参数来自定义打出字符视频:
#!/usr/local/bin/python3 import argparse from procedure import prepare, display, is_ready, clear # 获取参数 def get_args(): parser = argparse.ArgumentParser() parser.add_argument('command', type=str, default='run') parser.add_argument('--width', type=int, default=240) parser.add_argument('--height', type=int, default=100) parser.add_argument('--speed', type=float, default=0.02) return parser.parse_args() if __name__ == '__main__': args = get_args() # 参数 command = args.command width = args.width height = args.height speed = args.speed if command == 'clear': clear() if command == 'compile': prepare(width, height) if command == 'run': if not is_ready(): print('运行 python3 main.py compile 来编译') else: display(speed) # 输出字符视频 复制代码
用户就可以有多种玩法了:
./main.py run --speed 0.02 # 控制速度,单位为 seconds,这里数值为默认值 ./main.py run --width 240 --height 100 # 控制宽高,这里数值为默认值 复制代码
最后
上面所有完整的代码都可以在 Github - cxk-dance 这个仓库 里获取,各位观众老爷自行 clone 来玩吧。
在 M1 的 Mac 上有可能会出现 Pillow 安装不成功的问题,在 README.md 也给出了相应的解决办法。