昨晚一朋友跟我说在网上看到了别人作的视频转字符动画,以为很厉害,我因而也打算玩玩。今天中午花时间实现了这样一个小玩意。
顺便把过程记录在这里。html
注:最新版使用了画布方式实现,和本文相比改动很是大,若是对旧版本的实现没啥兴趣,能够直接移步 video2chars,它的效果动画见 极乐净土。新版本的核心代码不算注释70行不到,功能更强大。python
先上效果,来点动力:git
这个程序须要用到这样几个模块:github
准备阶段,首先安装依赖:算法
pip3 install numpy opencv-python
而后新建python代码文档,在开头添加上下面的导入语句shell
#-*- coding:utf-8 -*- import numpy as np
材料就是须要转换的视频文件了,我这里用的是BadApple.mp4,下载下来和代码放到同一目录下
你也能够换成本身的,建议是学习时尽可能选个短一点的视频,几十秒就好了,否则调试起来很痛苦。(或者本身稍微修改一下函数,只转换必定范围、必定数量的帧。)
此外,要选择对比度高的视频。不然的话,就须要彩色字符才能有足够好的表现,有时间我试试。编程
如今继续添加代码,实现第一步:按帧读取视频。
下面这个函数,接受视频路径和字符视频的尺寸信息,返回一个img列表,其中的img是尺寸都为指定大小的灰度图。windows
#导入 opencv import cv2 def video2imgs(video_name, size): """ :param video_name: 字符串, 视频文件的路径 :param size: 二元组,(宽, 高),用于指定生成的字符画的尺寸 :return: 一个 img 对象的列表,img对象实际上就是 numpy.ndarray 数组 """ img_list = [] # 从指定文件建立一个VideoCapture对象 cap = cv2.VideoCapture(video_name) # 若是cap对象已经初始化完成了,就返回true,换句话说这是一个 while true 循环 while cap.isOpened(): # cap.read() 返回值介绍: # ret 表示是否读取到图像 # frame 为图像矩阵,类型为 numpy.ndarry. ret, frame = cap.read() if ret: # 转换成灰度图,也可不作这一步,转换成彩色字符视频。 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # resize 图片,保证图片转换成字符画后,能完整地在命令行中显示。 img = cv2.resize(gray, size, interpolation=cv2.INTER_AREA) # 分帧保存转换结果 img_list.append(img) else: break # 结束时要释放空间 cap.release() return img_list
写完后能够写个main方法测试一下,像这样:数组
if __name__ == "__main__": imgs = video2imgs("BadApple.mp4", (64, 48)) assert len(imgs) > 10
若是运行没报错,就没问题
代码里的注释应该写得很清晰了,继续下一步。缓存
视频转换成了图像,这一步即是把图像转换成字符画
下面这个函数,接受一个img对象为参数,返回对应的字符画。
# 用于生成字符画的像素,越日后视觉上越明显。。这是我本身按感受排的,你能够随意调整。 pixels = " .,-'`:!1+*abcdefghijklmnopqrstuvwxyz<>()\/{}[]?234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ%&@#$" def img2chars(img): """ :param img: numpy.ndarray, 图像矩阵 :return: 字符串的列表:图像对应的字符画,其每一行对应图像的一行像素 """ res = [] # 灰度是用8位表示的,最大值为255。 # 这里将灰度转换到0-1之间 # 使用 numpy 的逐元素除法加速,这里 numpy 会直接对 img 中的全部元素都除以 255 percents = img / 255 # 将灰度值进一步转换到 0 到 (len(pixels) - 1) 之间,这样就和 pixels 里的字符对应起来了 # 一样使用 numpy 的逐元素算法,而后使用 astype 将元素所有转换成 int 值。 indexes = (percents * (len(pixels) - 1)).astype(np.int) # 要注意这里的顺序和 以前的 size 恰好相反(numpy 的 shape 返回 (行数、列数)) height, width = img.shape for row in range(height): line = "" for col in range(width): index = indexes[row][col] # 添加字符像素(最后面加一个空格,是由于命令行有行距却没几乎有字符间距,用空格当间距) line += pixels[index] + " " res.append(line) return res
上面的函数只接受一帧为参数,一次只转换一帧,可咱们须要的是转换全部的帧,因此就再把它包装一下:
def imgs2chars(imgs): video_chars = [] for img in imgs: video_chars.append(img2chars(img)) return video_chars
好了,如今咱们能够测试一下:
if __name__ == "__main__": imgs = video2imgs("BadApple.mp4", (64, 48)) video_chars = imgs2chars(imgs) assert len(video_chars) > 10
没报错的话,就能够下一步了。(这一步比较慢,测试阶段建议用短一点的视频,或者稍微改一下,只处理前30秒之类的)
写了这么多代码,如今终于要出成果了。如今就是最激动人心的一步:播放字符画了。
一样的,我把它封装成了一个函数。下面这个函数接受一个字符画的列表并播放。
# 导入须要的模块 import time import subprocess def play_video(video_chars): """ 播放字符视频 :param video_chars: 字符画的列表,每一个元素为一帧 :return: None """ # 获取字符画的尺寸 width, height = len(video_chars[0][0]), len(video_chars[0]) for pic_i in range(len(video_chars)): # 显示 pic_i,即第i帧字符画 for line_i in range(height): # 将pic_i的第i行写入第i列。 print(video_chars[pic_i][line_i]) time.sleep(1 / 24) # 粗略地控制播放速度。 subprocess.call("clear") # 调用shell命令清屏,用 cmd 的话要把 "clear"改为 "cls"
# 导入须要的模块 import time import curses def play_video(video_chars): """ 播放字符视频, :param video_chars: 字符画的列表,每一个元素为一帧 :return: None """ # 获取字符画的尺寸 width, height = len(video_chars[0][0]), len(video_chars[0]) # 初始化curses,这个是必须的,直接抄就行 stdscr = curses.initscr() curses.start_color() try: # 调整窗口大小,宽度最好略大于字符画宽度。另外注意curses的height和width的顺序 stdscr.resize(height, width * 2) for pic_i in range(len(video_chars)): # 显示 pic_i,即第i帧字符画 for line_i in range(height): # 将pic_i的第i行写入第i列。(line_i, 0)表示从第i行的开头开始写入。最后一个参数设置字符为白色 stdscr.addstr(line_i, 0, video_chars[pic_i][line_i], curses.COLOR_WHITE) stdscr.refresh() # 写入后须要refresh才会当即更新界面 time.sleep(1 / 24) # 粗略地控制播放速度。更精确的方式是使用游戏编程里,精灵的概念 finally: # curses 使用前要初始化,用完后不管有没有异常,都要关闭 curses.endwin() return
好,接下来就是见证奇迹的时刻
不过开始前要注意,字符画的播放必须在shell窗口下运行,在pycharm里运行会看到一堆无心义字符。另外播放前要先最大化shell窗口
if __name__ == "__main__": imgs = video2imgs("BadApple.mp4", (64, 48)) video_chars = imgs2chars(imgs) input("`转换完成!按enter键开始播放") play_video(video_chars)
写完后,开个shell,最大化窗口,而后键入(文件名换成你的)
python3 video2chars.py
可能要等好久。我使用示例视频大概须要 12 秒左右。看到提示的时候,按回车,开始播放!
这样就完成了视频到字符动画的转换, 除去注释, 大概七十行代码的样子. 稍微超出了点预期, 不过效果然是挺棒的.
到了这里,核心功能基本都完成了。
不过仔细想一想,其实还有不少能够作的:
这些东西,就不写这里了,再写下去,大家确定要说我这标题是骗人了哈哈。
因此若是有兴趣的,请移步这个系列的下一篇:Python 视频转字符画 - 进阶
完整代码见 video2chars.py,要注意的是代码库的代码,包含了第二篇文章的内容(音频、缓存、帧率控制等),并且相对这篇文章也有一些小改动(目的是方便使用,可是稍微增长了点代码量,因此改动没有写在这篇文章里了)
想运行起来的话,仍是建议跟着文章作。。
容许转载, 可是要求附上来源连接: 视频转字符动画-Python-60行代码