Python3 & OpenCV 视频转字符动画

一、实验简介

1.1. 知识点

  • OpenCV 编译
  • 使用 OpenCV 处理图片、视频
  • 图片转字符画原理
  • 守护线程
  • 光标定位转义编码

1.2. 效果展示

(由于是在线环境,流畅度是不及本地环境的)

播放停止后的效果,注意终端中并无残留的动画字符:

二、实验步骤

2.1. 编译安装 OpenCV

由于安装过程需要等待很长时间,实验楼已经在环境中为大家安装OpenCV,可直接使用,以下安装步骤仅作参考

本课程的实验中使用了 OpenCV 3.1,因此我们需要编译安装它。

首先我们需要处理一个问题:当前实验楼的环境中 python3 命令使用的 python 版本为 3.5,但源中没有 python3.5-dev 的包,这会导致编译 numpy、OpenCV 出错,以及可能会出现其它问题。总而言之,我们需要将 python3 命令使用的 python 版本切换为 3.4。

$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.4 70 --slave /usr/bin/python3m python3m /usr/bin/python3.4m

然后安装一些依赖的包:

$ sudo apt-get update
$ sudo apt-get install python3-dev
$ sudo pip3 install numpy
$ sudo apt-get install cmake libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev

现在可以开始编译 OpenCV 3.1 了,不过这个编译过程极其耗费时间(2个小时多),我相信各位同学们是没有耐心等待编译完成的,所以我在后面提供了编译后的二进制文件下载链接,大家直接使用就可以了。

下面是在实验楼环境中编译 OpenCV 3.1 所需的命令,其他环境中的编译请参考官网:http://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html

$ wget https://github.com/Itseez/opencv/archive/3.1.0.zip
$ unzip 3.1.0.zip && cd opencv-3.1.0/
$ mkdir build && cd build
$ cmake -D CMAKE_BUILD_TYPE=Release \-D CMAKE_INSTALL_PREFIX=/usr/local  \PYTHON3_EXECUTABLE=/usr/bin/python3 \PYTHON_INCLUDE_DIR=/usr/include/python3.4 \PYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.4m.so \PYTHON3_NUMPY_INCLUDE_DIRS=/usr/local/lib/python3.4/dist-packages/numpy/core/include ..
$ make -j4

不想自己编译的同学请下载编译好的二进制文件,然后解压并进入 opencv-3.1.0/build 目录:

$ wget http://labfile.oss.aliyuncs.com/courses/637/opencv-3.1.0.tar.gz
$ tar xzvf opencv-3.1.0.tar.gz
$ cd opencv-3.1.0/build

然后我们开始安装:

$ sudo make install

2.2. 程序原理

大家应该都明白视频其实可以看作一系列图片组成的,因此视频转字符动画最基本的便是图片转字符画,这一部分内容也在Python 图片转字符画 课程中有讲过。

在这里简单的说一下图片转字符画的原理:首先将图片转为灰度图,每个像素都只有亮度信息(用 0~255 表示)。然后我们构建一个有限字符集合,其中的每一个字符都与一段亮度范围对应,我们便可以根据此对应关系以及像素的亮度信息把每一个像素用对应的字符表示,这样字符画就形成了。

字符动画要能播放才有意义。最最简单粗暴的,用文本编辑器打开字符动画文本文件,然后狂按 PageDown 键就能播放。然而这真的太简单太粗暴了,一点都不优雅。

我们还是在终端里面播放字符动画,只需要一帧一帧输出就能达到动画的效果了,然而这却有一个很大的弊端:播放时,你会发现终端右边的滚动条会越来越小(如果有的话);播放完毕后,在终端中往上翻页,全是之前输出的字符画,播放前的命令历史全部被挤占掉了。在本实验后面提供了这个问题的解决办法。

2.3. 程序实现

为了避免忘记,先导入会使用到的包:

import sys
import os
import time
import threading
import termios
import tty
import cv2
import pyprind

最后一个 pyprind 模块提供显示用的进度条。因为从视频转为字符动画是一个耗时的过程,我们需要一个进度条来查看任务进度以及大致剩余时间,能更直观的了解程序状态。这样安装它:

$ sudo pip3 install pyprind

本实验将编写的程序除了能将视频文件转为字符动画并播放外,还顺便把图片文件转字符画的功能也添加上了,因此我们的程序设计有三个类:CharFrameI2CharV2Char,后两个类继承自第一个类。

CharFrame 类:

class CharFrame:ascii_char = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "# 像素映射到字符def pixelToChar(self, luminance):return self.ascii_char[int(luminance/256*len(self.ascii_char))]# 将普通帧转为 ASCII 字符帧def convert(self, img, limitSize=-1, fill=False, wrap=False):if limitSize != -1 and (img.shape[0] > limitSize[1] or img.shape[1] > limitSize[0]):img = cv2.resize(img, limitSize, interpolation=cv2.INTER_AREA)ascii_frame = ''blank = ''if fill:blank += ' '*(limitSize[0]-img.shape[1])if wrap:blank += '\n'for i in range(img.shape[0]):for j in range(img.shape[1]):ascii_frame += self.pixelToChar(img[i,j])ascii_frame += blankreturn ascii_frame

属性 ascii_char 可以根据你要转换的视频进行针对性调整。

pixelToChar() 方法只有一个参数,其接收像素的亮度信息。需要注意的是,方法的 return 语句中的表达式使用了一个数值 256,虽然像素的亮度范围是 0~255,但若是把 256 更改为 255,那么这个表达式将有可能引发 IndexError 异常。

convert() 方法有一个位置参数三个可选参数,参数 img 接收一个对象,这个对象类型是 numpy.ndarray,也就是 OpenCV 打开图片返回的对象,同样,之后用 OpenCV 得到的视频的帧也是这个对象。limitSize 参数接受一个元组,表示图片的限宽限高。fill 表示是否用空格填充图片宽度至限宽,wrap 表示是否在行末添加换行符。

img.shape 返回一个元组,含有图片的行数(高),列数(宽),以及颜色通道数。如果图片为灰度图则不包含颜色通道数。

cv2.resize() 函数用于缩放图片大小,第一个参数为 numpy.ndarray 对象,第二个是将缩放的宽高,interpolation 参数为插值方法。有几个插值方法可用,引用 OpenCV 官网的说明:

Preferable interpolation methods are cv2.INTER_AREA for shrinking and cv2.INTER_CUBIC (slow) &cv2.INTER_LINEAR for zooming. By default, interpolation method used is cv2.INTER_LINEAR for all resizing purposes.

img[i,j] 返回图片第 i 行第 j 列像素 BGR 值构成的列表,灰度图片则为直接返回对应像素的亮度值。

下面是继承自 CharFrame 的 I2Char 类:

class I2Char(CharFrame):result = Nonedef __init__(self, path, limitSize=-1, fill=False, wrap=False):self.genCharImage(path, limitSize, fill, wrap)def genCharImage(self, path, limitSize=-1, fill=False, wrap=False):img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)if img is None:returnself.result = self.convert(img, limitSize, fill, wrap)def show(self, stream = 2):if self.result is None:returnif stream == 1 and os.isatty(sys.stdout.fileno()):self.streamOut = sys.stdout.writeself.streamFlush = sys.stdout.flushelif stream == 2 and os.isatty(sys.stderr.fileno()):self.streamOut = sys.stderr.writeself.streamFlush = sys.stderr.flushelif hasattr(stream, 'write'):self.streamOut = stream.writeself.streamFlush = stream.flushself.streamOut(self.result)self.streamFlush()self.streamOut('\n')

cv2.imread() 读取一个图片,返回一个 numpy.ndarray 对象。第一个参数为要打开的图片路径,第二个参数指定图片的打开方式,可以具有下面三个值:

  • cv2.IMREAD_COLOR : 加载彩色图片,忽略透明通道。
  • cv2.IMREAD_GRAYSCALE : 以灰度模式加载 图片。
  • cv2.IMREAD_UNCHANGED : 加载包含透明通道的图片。

show() 方法接受一个参数,表示使用哪种输出流。1 代表输出流使用标准输出 sys.stdout2 代表输出流使用标准错误输出 sys.stderr。其中 sys.stdout.fileno() 和 sys.stderr.fileno() 分别返回标准输出和标准错误输出的文件描述符。os.isatty(fd) 返回一个布尔值,当文件描述符 fd 是打开并连接到 tty 设备时返回真。

然后便是我们的重点了,继承自 CharFrame 类的 V2Char 类。

先设想一下我们的类,类的其中一个属性为 charVideo ,这是一个列表,用来存放字符动画全部数据。

然后是两个主要方法:一个是从视频文件转为字符动画的方法 genCharVideo(),一个是播放字符动画的方法 play()

另外由于从视频转到字符动画是一个耗时耗力的过程,所以我们可以把 charVideo 中的字符动画数据导出来,方便下次读取播放,这就意味着要有导出和读取方法,即 export() 方法和 load() 方法。

类还要有一个初始化方法,参数为要读取的文件路径,文件若是导出的 txt,则调用 load() 方法把数据加载到属性 charVideo 里。否则视为视频文件,调用 genCharVideo() 方法将视频转化为字符动画存放到属性 charVideo 里:

def __init__(self, path):if path.endswith('txt'):self.load(path)else:self.genCharVideo(path)

接下来是 genCharVideo() 方法:

def genCharVideo(self, filepath):self.charVideo = []cap = cv2.VideoCapture(filepath)self.timeInterval = round(1/cap.get(5), 3)nf = int(cap.get(7))print('Generate char video, please wait...')for i in pyprind.prog_bar(range(nf)):rawFrame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)frame = self.convert(rawFrame, os.get_terminal_size(), fill=True)self.charVideo.append(frame)cap.release()

用 cv2.VideoCapture() 方法读取视频文件,生成的对象我们赋值给 cap

通过 cap.get() 方法我们可以获得视频的属性信息,比如 cap.get(3) 和 cap.get(4) 分别返回视频的宽高信息,cap.get(5) 则返回视频的帧率,cap.get(7) 返回视频的总帧数。

timeInterval 存放播放时间间隔,用来让之后播放字符动画的帧率与原视频相同。

pyprind.prog_bar() 是一个生成器,使用这个生成器进行迭代会自然在终端中输出进度条。

cap.read() 读取视频的下一帧,其返回一个两元素的元组,第一个元素为 bool 值,指示帧是否被正确读取,第二个元素为numpy.ndarray ,其存放的便是帧的数据。

cv2.cvtColor() 用来转换图像的颜色空间,第一个参数为图像对象,第二个参数指示转换类型,OpenCV 中有超过 150 个颜色空间转换,这里我们使用的是彩色转灰度 cv2.COLOR_BGR2GRAY

os.get_terminal_size() 方法返回当前终端的列数(宽),行数(高)。这里我们将 fill 参数设置为 True,未设置 wrap 参数,其值为默认的 False。在终端里如果打印的字符超过一行的宽度,终端会自动进行显示上的换行。

最后别忘了用 cap.release() 释放资源。

然后是 play() 方法,这就要接着之前说了,要想终端不在播放字符动画后充满没用的字符,可以使用光标定位转义编码。

我们可以这样:每输出一帧,就将光标移动到播放开始处,下一帧会从这个位置输出,并自动覆盖掉之前的内容,如此往复循环,播放完毕时,清除输出的最后一帧,这样终端就不会被字符画挤满了。

这里是一系列用来定位光标的转义编码(某些终端不支持一些转义编码),引自 The Linux Command Line:

转义编码 行动
\033[l;cH 把光标移到第 l 行,第 c 列。
\033[nA 把光标向上移动 n 行。
\033[nB 把光标向下移动 n 行。
\033[nC 把光标向前移动 n 个字符。
\033[nD 把光标向后移动 n 个字符。
\033[2J 清空屏幕,把光标移到左上角(第零行,第零列)。
\033[K 清空从光标位置到当前行末的内容。
\033[s 存储当前光标位置。
\033[u 唤醒之前存储的光标位置。

还有一个问题,如何在中途停止播放?当然你可以按 Ctrl + C,但这样程序是没办法做扫尾工作的,终端会留下一堆没用的乱七八糟的字符。

我们这样设计,字符动画开始播放时启动一个守护线程,进程启动时便开始等待用户输入,一旦接收到输入便停止播放字符动画。

这里有两个地方需要注意:

  1. 不要使用 input() 方法接收字符输入
  2. 不能使用普通线程

对于第一点,如果是想按任意字符终止播放的话,那么就不应该使用 input(),否则要么你按回车停止播放,要么按其他字符再敲回车停止播放,总而言之,使用 input() 并不舒服。最好的办法是,使用类似于 C 语言中的 getchar() 方法,然而 python 并没有提供类似方法,这在后面的代码中会给出替代方案。

对于第二点,我们要明白如果任何一个派生线程仍在运行中,主线程是不会退出的,除非派生线程被设定为守护线程。所以使用普通线程的话,若用户并没有在中途停止播放,等到播放完毕后,这个线程会永远的运行下去,直到用户输入任意字符。如果我们可以在播放完毕时手动 kill 掉这个派生线程,这也不是问题,然而 python 并没有提供 kill 掉线程的方法。所以,只能把这个派生线程设为守护线程。等主线程退出后,程序只剩下一个守护线程在运行,守护线程会自动被 kill 掉,程序退出。

下面类 play() 方法的代码:

def play(self, stream = 1):# Bug:# 光标定位转义编码不兼容 Windowsif not self.charVideo:returnif stream == 1 and os.isatty(sys.stdout.fileno()):self.streamOut = sys.stdout.writeself.streamFlush = sys.stdout.flushelif stream == 2 and os.isatty(sys.stderr.fileno()):self.streamOut = sys.stderr.writeself.streamFlush = sys.stderr.flushelif hasattr(stream, 'write'):self.streamOut = stream.writeself.streamFlush = stream.flushold_settings = Nonebreakflag = None# 获得标准输入的文件描述符fd = sys.stdin.fileno()def getChar():nonlocal breakflagnonlocal old_settings# 保存标准输入的属性old_settings = termios.tcgetattr(fd)# 设置标准输入为原始模式tty.setraw(sys.stdin.fileno())# 读取一个字符ch = sys.stdin.read(1)breakflag = True if ch else False# 创建线程getchar = threading.Thread(target=getChar)# 设置为守护线程getchar.daemon = True# 启动守护线程getchar.start()# 输出的字符画行数rows = len(self.charVideo[0])//os.get_terminal_size()[0]for frame in self.charVideo:# 接收到输入则退出循环if breakflag is True:breakself.streamOut(frame)self.streamFlush()time.sleep(self.timeInterval)# 共 rows 行,光标上移 rows-1 行回到开始处self.streamOut('\033[{}A\r'.format(rows-1))# 恢复标准输入为原来的属性termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)# 光标下移 rows-1 行到最后一行,清空最后一行self.streamOut('\033[{}B\033[K'.format(rows-1))# 清空最后一帧的所有行(从倒数第二行起)for i in range(rows-1):# 光标上移一行self.streamOut('\033[1A')# 清空光标所在行self.streamOut('\r\033[K')info = 'User interrupt!\n' if breakflag else 'Finished!\n'self.streamOut(info)

一些函数的文档链接:

termios.tcgetattr(fd)termios.tcsetattr(fd, when, attributes):链接

tty.setraw(fd, when=termios.TCSAFLUSH):链接

V2Char 类的完整代码如下:

class V2Char(CharFrame):charVideo = []timeInterval = 0.033def __init__(self, path):if path.endswith('txt'):self.load(path)else:self.genCharVideo(path)def genCharVideo(self, filepath):self.charVideo = []cap = cv2.VideoCapture(filepath)self.timeInterval = round(1/cap.get(5), 3)nf = int(cap.get(7))print('Generate char video, please wait...')for i in pyprind.prog_bar(range(nf)):rawFrame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)frame = self.convert(rawFrame, os.get_terminal_size(), fill=True)self.charVideo.append(frame)cap.release()def export(self, filepath):if not self.charVideo:returnwith open(filepath,'w') as f:for frame in self.charVideo:# 加一个换行符用以分隔每一帧f.write(frame + '\n')def load(self, filepath):self.charVideo = []# 一行即为一帧for i in  open(filepath):self.charVideo.append(i[:-1])def play(self, stream = 1):# Bug:# 光标定位转义编码不兼容 Windowsif not self.charVideo:returnif stream == 1 and os.isatty(sys.stdout.fileno()):self.streamOut = sys.stdout.writeself.streamFlush = sys.stdout.flushelif stream == 2 and os.isatty(sys.stderr.fileno()):self.streamOut = sys.stderr.writeself.streamFlush = sys.stderr.flushelif hasattr(stream, 'write'):self.streamOut = stream.writeself.streamFlush = stream.flushold_settings = Nonebreakflag = None# 获得标准输入的文件描述符fd = sys.stdin.fileno()def getChar():nonlocal breakflagnonlocal old_settings# 保存标准输入的属性old_settings = termios.tcgetattr(fd)# 设置标准输入为原始模式tty.setraw(sys.stdin.fileno())# 读取一个字符ch = sys.stdin.read(1)breakflag = True if ch else False# 创建线程getchar = threading.Thread(target=getChar)# 设置为守护线程getchar.daemon = True# 启动守护线程getchar.start()# 输出的字符画行数rows = len(self.charVideo[0])//os.get_terminal_size()[0]for frame in self.charVideo:# 接收到输入则退出循环if breakflag is True:breakself.streamOut(frame)self.streamFlush()time.sleep(self.timeInterval)# 共 rows 行,光标上移 rows-1 行回到开始处self.streamOut('\033[{}A\r'.format(rows-1))# 恢复标准输入为原来的属性termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)# 光标下移 rows-1 行到最后一行,清空最后一行self.streamOut('\033[{}B\033[K'.format(rows-1))# 清空最后一帧的所有行(从倒数第二行起)for i in range(rows-1):# 光标上移一行self.streamOut('\033[1A')# 清空光标所在行self.streamOut('\r\033[K')info = 'User interrupt!\n' if breakflag else 'Finished!\n'self.streamOut(info)

2.4. 测试效果

把下面的代码键入到之前的代码后面,这份代码就可以用做视频转字符画的脚本文件了。

if __name__ == '__main__':import argparse# 设置命令行参数parser = argparse.ArgumentParser()parser.add_argument('file',help='Video file or charvideo file')parser.add_argument('-e', '--export', nargs = '?', const = 'charvideo.txt',help='Export charvideo file')# 获取参数args = parser.parse_args()v2char = V2Char(args.file)if args.export:v2char.export(args.export)v2char.play()

用如下命令把测试用视频下载下来:

$ wget http://labfile.oss.aliyuncs.com/courses/637/BadApple.mp4

假设你的代码文件为 CLIPlayVideo.py,输入如下命令将 BadApple.mp4 文件转为字符动画并导出为文件,同时播放转换后的字符动画。

$ python3 CLIPlayVideo.py BadApple.mp4 -e

之后再次播放不必重新转换一次,可以直接读取导出的字符画,假设这个文件是 charvideo.txt:

$ python3 CLIPlayVideo.py charvideo.txt

三、实验总结

本实验使用了 OpenCV 处理图片及视频,虽然编译 OpenCV 的过程很恼人,但这也是一个必要的过程。这里的 OpenCV 的操作对于大家来说应该只是一个开始,如果想深入学习应该多看看官方文档。使用了守护线程后,相信大家能够明白守护线程是什么以及与普通线程有什么区别。最后还了解了光标定位转义编码,虽然这个用处可能并不大,但也是一个有趣的东西,它还可以做很多事情,大家可以多玩玩。

四、完整代码

可以直接把代码下载下来:

$ wget http://labfile.oss.aliyuncs.com/courses/637/CLIPlayVideo.py

下面贴出了完整的代码:

import sys
import os
import time
import threading
import termios
import tty
import cv2
import pyprindclass CharFrame:ascii_char = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "# 像素映射到字符def pixelToChar(self, luminance):return self.ascii_char[int(luminance/256*len(self.ascii_char))]# 将普通帧转为 ASCII 字符帧def convert(self, img, limitSize=-1, fill=False, wrap=False):if limitSize != -1 and (img.shape[0] > limitSize[1] or img.shape[1] > limitSize[0]):img = cv2.resize(img, limitSize, interpolation=cv2.INTER_AREA)ascii_frame = ''blank = ''if fill:blank += ' '*(limitSize[0]-img.shape[1])if wrap:blank += '\n'for i in range(img.shape[0]):for j in range(img.shape[1]):ascii_frame += self.pixelToChar(img[i,j])ascii_frame += blankreturn ascii_frameclass I2Char(CharFrame):result = Nonedef __init__(self, path, limitSize=-1, fill=False, wrap=False):self.genCharImage(path, limitSize, fill, wrap)def genCharImage(self, path, limitSize=-1, fill=False, wrap=False):img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)if img is None:returnself.result = self.convert(img, limitSize, fill, wrap)def show(self, stream = 2):if self.result is None:returnif stream == 1 and os.isatty(sys.stdout.fileno()):self.streamOut = sys.stdout.writeself.streamFlush = sys.stdout.flushelif stream == 2 and os.isatty(sys.stderr.fileno()):self.streamOut = sys.stderr.writeself.streamFlush = sys.stderr.flushelif hasattr(stream, 'write'):self.streamOut = stream.writeself.streamFlush = stream.flushself.streamOut(self.result)self.streamFlush()self.streamOut('\n')class V2Char(CharFrame):charVideo = []timeInterval = 0.033def __init__(self, path):if path.endswith('txt'):self.load(path)else:self.genCharVideo(path)def genCharVideo(self, filepath):self.charVideo = []cap = cv2.VideoCapture(filepath)self.timeInterval = round(1/cap.get(5), 3)nf = int(cap.get(7))print('Generate char video, please wait...')for i in pyprind.prog_bar(range(nf)):rawFrame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)frame = self.convert(rawFrame, os.get_terminal_size(), fill=True)self.charVideo.append(frame)cap.release()def export(self, filepath):if not self.charVideo:returnwith open(filepath,'w') as f:for frame in self.charVideo:# 加一个换行符用以分隔每一帧f.write(frame + '\n')def load(self, filepath):self.charVideo = []# 一行即为一帧for i in  open(filepath):self.charVideo.append(i[:-1])def play(self, stream = 1):# Bug:# 光标定位转义编码不兼容 Windowsif not self.charVideo:returnif stream == 1 and os.isatty(sys.stdout.fileno()):self.streamOut = sys.stdout.writeself.streamFlush = sys.stdout.flushelif stream == 2 and os.isatty(sys.stderr.fileno()):self.streamOut = sys.stderr.writeself.streamFlush = sys.stderr.flushelif hasattr(stream, 'write'):self.streamOut = stream.writeself.streamFlush = stream.flushold_settings = Nonebreakflag = None# 获得标准输入的文件描述符fd = sys.stdin.fileno()def getChar():nonlocal breakflagnonlocal old_settings# 保存标准输入的属性old_settings = termios.tcgetattr(fd)# 设置标准输入为原始模式tty.setraw(sys.stdin.fileno())# 读取一个字符ch = sys.stdin.read(1)breakflag = True if ch else False# 创建线程getchar = threading.Thread(target=getChar)# 设置为守护线程getchar.daemon = True# 启动守护线程getchar.start()# 输出的字符画行数rows = len(self.charVideo[0])//os.get_terminal_size()[0]for frame in self.charVideo:# 接收到输入则退出循环if breakflag is True:breakself.streamOut(frame)self.streamFlush()time.sleep(self.timeInterval)# 共 rows 行,光标上移 rows-1 行回到开始处self.streamOut('\033[{}A\r'.format(rows-1))# 恢复标准输入为原来的属性termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)# 光标下移 rows-1 行到最后一行,清空最后一行self.streamOut('\033[{}B\033[K'.format(rows-1))# 清空最后一帧的所有行(从倒数第二行起)for i in range(rows-1):# 光标上移一行self.streamOut('\033[1A')# 清空光标所在行self.streamOut('\r\033[K')info = 'User interrupt!\n' if breakflag else 'Finished!\n'self.streamOut(info)if __name__ == '__main__':import argparse# 设置命令行参数parser = argparse.ArgumentParser()parser.add_argument('file',help='Video file or charvideo file')parser.add_argument('-e', '--export', nargs = '?', const = 'charvideo.txt',help='Export charvideo file')# 获取参数args = parser.parse_args()v2char = V2Char(args.file)if args.export:v2char.export(args.export)v2char.play()

Python3 OpenCV 视频转字符动画相关推荐

  1. (Python3)视频转字符动画初学者超详细实战,亲测有效!

    之前是为了完成老师布置的一个作业,觉得视频转字符动画乍一看比较容易完成,于是就选了这个项目.但是实际做了之后才觉得对于初学者来讲,确实轻敌了. 在网上找了好几篇相关的文章,但是总觉得解释得不够详细,新 ...

  2. OpenCV3+python3实现视频转字符动画

    1.实验环境 Anaconda python 3.5        Opencv 3        pyprind 2 Ubuntu 16.04 2.环境安装 2.1下载 Anaconda pytho ...

  3. python视频转字符动画_python字符动画

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! python3 & opencv 视频转字符动画本课程使用 opencv ...

  4. 视频转字符动画(Python3)

    今天刷抖音,看到了一个<极乐净土>的字符动画,舞蹈看起来妖娆啊,满屏幕的字符勾勒出美女的身影,在配上极乐净土的音乐,简直是程序员们的精神食粮啊!!!! 以前也遇到过这种字符动画,当时简单想 ...

  5. python视频转换字符动画_视频转字符动画-Python-60行代码

    昨晚我朋友三十六咲跟我说在网上看到了别人做的视频转字符动画,觉得很厉害,我于是也打算玩玩.今天中午花时间实现了这样一个小玩意. 顺便把过程记录在这里. 注:最新版使用了画布方式实现,和本文相比改动非常 ...

  6. python视频转换字符动画_视频转字符动画

    导 语 原本今天想发用DQN玩T-Rex Rush,结果出现了一点小意外,昨天睡前开始RUN的模型,早上起来发现C盘内存溢出模型根本没被训练,万万没想到5万张图片竟然没法一次性保存.好的,说了一些废话 ...

  7. python视频转字符动画_Python 视频转字符动画 - 进阶

    这篇文章是 视频转字符动画-Python-60行代码 的后续,如果感兴趣,请先看看它. 0. 话说在前头 最新版使用了画布方式实现,和本文相比改动非常大,如果对旧版本的实现没啥兴趣,可以直接移步 vi ...

  8. python3 opencv 视频格式转换

    python3 opencv 视频格式转换: import cv2 #获得视频的格式 videoCapture = cv2.VideoCapture('ad3.avi') #获得码率及尺寸 fps = ...

  9. python视频转字符动画_Python实现视频转字符动画

    导语 又到了愉快地周末,应某好友的要求,本周来补一期视频转字符动画相关的内容,核心主题是将蔡徐坤打球的视频转成字符动画.我也不知道为什么,真的不知道,真的真的不知道为什么.嗯,重要的话说三遍.说干就干 ...

  10. 利用Python将视频转为字符动画

    这几天某音上很多高校都在拍高校手势舞,非常火,尤其是河南工业大学拍的小姐姐手势舞,一度上榜到热搜. 为什么会上榜?先来看原视频. 河南工业大学手势舞 嗯嗯,这回知道原因了. 那么接下来用Python将 ...

最新文章

  1. Java8的集合:HashSet的实现原理
  2. 【华为出品】智能体白皮书2020(附全文下载)
  3. wifi信息修改插件ios_这21款Android Studio插件,你不得不装
  4. 学会这几个Redis技巧,让你的程序快如闪电
  5. 前端还是后端?这些公号教你一锅端!
  6. ueditor百度富文本编辑器linux下报错: class path resource [config.json] cannot be resolved to absolute file path
  7. 循环序列模型 —— 1.9 GRU单元(门控循环单元)
  8. java批量导入和批量删除_MyBatis 实现批量插入和删除中双层循环的写法案例
  9. mysql 联合索引匹配原则
  10. 关于信道利用率的总结与一道习题的最终解释
  11. php如何架构设计,PHP – 架构设计帮助 – OOP固体原则
  12. 如何把win7官方补丁集成到win7iso镜像中
  13. 【聚客通scrm】-微信个人号sdk实现的微信云控方案
  14. vue实现PDF文件导出
  15. 从多个Word文档中批量取值,整理到Excel表中。
  16. 深度揭密SSD中的原片/白片/黑片:莫贪小便宜
  17. 查询至少具有两份工作员工的姓名和其公司名
  18. 期货模拟盘有效果吗?
  19. 手工雕刻图纸_手工玉石雕刻图样大全
  20. 页面打印、打印预览、页面设置

热门文章

  1. 苹果手机如何深度清理_手机应用 | 推荐5款深度清理手机内存的软件
  2. 软件开发基本流程【一】
  3. 删除excel中复制网页带过来的下拉框、复选框、单选框
  4. 怎么批量修改图片尺寸大小?
  5. html match函数,match函数的使用方法 match函数怎么使用
  6. 解决 Invalid MEX-file ‘xxx.mexw64‘: 找不到指定的模块 的问题
  7. excel如何设置单元格的下拉选项框并着色
  8. compass的安装及使用,以及常见命令行指令与注意事项、Compass核心模块
  9. CText更新至V1.1.0
  10. Js解决微信浏览器刷新的问题