首页 > 编程语言 >用 Python3 & OpenCV 将视频转成字符动画

用 Python3 & OpenCV 将视频转成字符动画

时间:2023-06-05 16:03:29浏览次数:49  
标签:动画 img stream self def sys OpenCV streamOut Python3


在介绍如何用 Python3 & OpenCV 将视频转成字符动画之前,先简单的介绍一下 OpenCV 吧,毕竟可能很多小伙伴不太了解:

百度百科:

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。

接着来看看最终的效果图吧,很可爱又很酷炫;

效果展示

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


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



说明:

这个项目是FrostSigh发表在实验楼上的项目课程;

知识点

通过开发这个项目可以学习到如下的知识点:

  • OpenCV 编译;
  • 使用 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 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

$ 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

$ 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

$ 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

$ sudo make install

程序原理

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

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

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

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

完整代码

import sys
import os
import time
import threading
import cv2
import pyprind


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 += blank
        return ascii_frame


class I2Char(CharFrame):

    result = None

    def __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:
            return
        self.result = self.convert(img, limitSize, fill, wrap)

    def show(self, stream = 2):
        if self.result is None:
            return
        if stream == 1 and os.isatty(sys.stdout.fileno()):
            self.streamOut = sys.stdout.write
            self.streamFlush = sys.stdout.flush
        elif stream == 2 and os.isatty(sys.stderr.fileno()):
            self.streamOut = sys.stderr.write
            self.streamFlush = sys.stderr.flush
        elif hasattr(stream, 'write'):
            self.streamOut = stream.write
            self.streamFlush = stream.flush
        self.streamOut(self.result)
        self.streamFlush()
        self.streamOut('\n')


class V2Char(CharFrame):

    charVideo = []
    timeInterval = 0.033

    def __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:
            return
        with 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:
        # 光标定位转义编码不兼容 Windows
        if not self.charVideo:
            return
        if stream == 1 and os.isatty(sys.stdout.fileno()):
            self.streamOut = sys.stdout.write
            self.streamFlush = sys.stdout.flush
        elif stream == 2 and os.isatty(sys.stderr.fileno()):
            self.streamOut = sys.stderr.write
            self.streamFlush = sys.stderr.flush
        elif hasattr(stream, 'write'):
            self.streamOut = stream.write
            self.streamFlush = stream.flush
        breakflag = False

        def getChar():
            nonlocal breakflag
            try:
                # 若系统为 windows 则直接调用 msvcrt.getch()
                import msvcrt
            except ImportError:
                import termios, tty
                # 获得标准输入的文件描述符
                fd = sys.stdin.fileno()
                # 保存标准输入的属性
                old_settings = termios.tcgetattr(fd)
                try:
                    # 设置标准输入为原始模式
                    tty.setraw(sys.stdin.fileno())
                    # 读取一个字符
                    ch = sys.stdin.read(1)
                finally:
                    # 恢复标准输入为原来的属性
                    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
                if ch:
                    breakflag = True
            else:
                if msvcrt.getch():
                    breakflag = True

        # 创建线程
        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:
                break
            self.streamOut(frame)
            self.streamFlush()
            time.sleep(self.timeInterval)
            # 共 rows 行,光标上移 rows-1 行回到开始处
            self.streamOut('\033[{}A\r'.format(rows-1))
        # 光标下移 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')
        if breakflag:
            self.streamOut('User interrupt!\n')
        else:
            self.streamOut('Finished!\n')

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()

import sys
import os
import time
import threading
import cv2
import pyprind


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 += blank
        return ascii_frame


class I2Char(CharFrame):

    result = None

    def __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:
            return
        self.result = self.convert(img, limitSize, fill, wrap)

    def show(self, stream = 2):
        if self.result is None:
            return
        if stream == 1 and os.isatty(sys.stdout.fileno()):
            self.streamOut = sys.stdout.write
            self.streamFlush = sys.stdout.flush
        elif stream == 2 and os.isatty(sys.stderr.fileno()):
            self.streamOut = sys.stderr.write
            self.streamFlush = sys.stderr.flush
        elif hasattr(stream, 'write'):
            self.streamOut = stream.write
            self.streamFlush = stream.flush
        self.streamOut(self.result)
        self.streamFlush()
        self.streamOut('\n')


class V2Char(CharFrame):

    charVideo = []
    timeInterval = 0.033

    def __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:
            return
        with 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:
        # 光标定位转义编码不兼容 Windows
        if not self.charVideo:
            return
        if stream == 1 and os.isatty(sys.stdout.fileno()):
            self.streamOut = sys.stdout.write
            self.streamFlush = sys.stdout.flush
        elif stream == 2 and os.isatty(sys.stderr.fileno()):
            self.streamOut = sys.stderr.write
            self.streamFlush = sys.stderr.flush
        elif hasattr(stream, 'write'):
            self.streamOut = stream.write
            self.streamFlush = stream.flush
        breakflag = False

        def getChar():
            nonlocal breakflag
            try:
                # 若系统为 windows 则直接调用 msvcrt.getch()
                import msvcrt
            except ImportError:
                import termios, tty
                # 获得标准输入的文件描述符
                fd = sys.stdin.fileno()
                # 保存标准输入的属性
                old_settings = termios.tcgetattr(fd)
                try:
                    # 设置标准输入为原始模式
                    tty.setraw(sys.stdin.fileno())
                    # 读取一个字符
                    ch = sys.stdin.read(1)
                finally:
                    # 恢复标准输入为原来的属性
                    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
                if ch:
                    breakflag = True
            else:
                if msvcrt.getch():
                    breakflag = True

        # 创建线程
        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:
                break
            self.streamOut(frame)
            self.streamFlush()
            time.sleep(self.timeInterval)
            # 共 rows 行,光标上移 rows-1 行回到开始处
            self.streamOut('\033[{}A\r'.format(rows-1))
        # 光标下移 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')
        if breakflag:
            self.streamOut('User interrupt!\n')
        else:
            self.streamOut('Finished!\n')

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()

最后

  • 关于实现这个项目的详细代码讲解以及步骤,可以上实验楼进行查看;
  • 实验楼上还有更多Python项目教程;


作者:实验楼



标签:动画,img,stream,self,def,sys,OpenCV,streamOut,Python3
From: https://blog.51cto.com/u_14523369/6416833

相关文章

  • css实现点击按钮进行右移的动画
    html页面:<divv-if="hideTip"id="tipsButton">   <el-tooltipclass="item"effect="dark"content="打印表格"placement="top">    <el-buttonicon="el-icon-printer"style=......
  • python3类型标记
    1、介绍python是解释型数据类型,变量名称所指向的数据的类型由赋予的数据决定。一般情况下,ide是能够结合上下文环境分析确定变量类型的,进而提供其类型对应的属性和方法。但是,对于另外一些场景,则无法进行直接分析。类型标记机制,允许开发者对变量名称进行类型标记,以帮助ide提示。......
  • 【计算机视觉】---OpenCV实现物体追踪
    简介OpenCV中的物体追踪算法基于视觉目标跟踪的原理。物体追踪的目标是在连续的图像序列中定位和跟踪特定物体的位置。目标表示在物体追踪中,我们需要对目标对象进行表示。通常使用边界框(boundingbox)来表示目标的位置和大小。边界框是一个矩形区域,由左上角的坐标(x,y)和宽度(w)以及高度(h......
  • OpenCV 学习之旅
    OpenCV学习之旅​ 话说已经有一周没见面了,勇士们每天都聚集在我的门前敲门,请求我出山,可是我仍然忙于我的AI大作业,操作系统实验和信号与系统实验,简称三座大山。看在你们的情真意切,周末抽出一点时间帮助你们解决公主布置的问题。​ 上回说到IU公主想要比OpenCV招胥,到底谁能成为......
  • CSS3动画简介
    http://www.ruanyifeng.com/blog/2014/02/css_transition_and_animation.html[/url]参考:[color=red]CSS3transform旋转属性[/color][url]http://www.w3school.com.cn/cssref/pr_transform.asp[/url][color=red]CSS3transition渐变属性[/color][url]h......
  • jQuery动画插件: Velocity.js
    官方:[url]http://julian.com/research/velocity/[/url]介绍:[url]http://www.w3ctech.com/topic/1403[/url]使用Velocity.js改善用户体验[url]http://www.w3ctrain.com/2015/11/15/faster-ui-animations-with-velocity-js/[/url]使用VELOCITY.JS来改善和......
  • 为teamcity的代码语法检查工具pyflakes增加支持python2和python3
    TeamCity和pyflakesTeamCity是一款由JetBrains公司开发的持续集成和部署工具,它提供了丰富的功能来帮助团队协作进行软件开发。其中包括代码检查、自动化构建、测试运行、版本控制等多个方面。在我们团队中使用TeamCity进行配合pyflakes代码检查,我们需要升级pyflakes到支持python......
  • 打字动画 Typed.js
    概述Typed.js是一个JavaScript库,用于在网页上创建打字动画效果。它使您能够模拟打字机或逐字显示文本的效果,从而为用户提供逐渐展示文本的视觉体验。Typed.js提供了丰富的选项和配置,可以自定义打字动画的速度、暂停时间、删除效果等。您可以使用HTML元素或JavaScript调......
  • opencv之图像匹配
    输入一张原图和一张你从原图上扣下来的矩形图,用opencv寻找到这个区域。代码如下:#include<opencv2/opencv.hpp>#include<iostream>intmain(){//读取源图像和模板cv::MatsrcImg=cv::imread("targe_graph.jpg");cv::MattmplImg=cv::imread("template.......
  • opencv 读取图片 图片数据处理 笔记
    1.python中各类图片读取工具的比较参考Python各类图像库的图片读写方式总结可知,opencv的性能表现最好。opencvyyds相关文章收藏:Python-OpenCV:cv2.imread(),cv2.imshow(),cv2.imwrite()Python各类图像库的图片读写方式总结2.opencv常用函数2.1读取图片cv2.imread(filename,fla......