引言

本篇博文写作初衷是看到中秋活动,正好之前有总结过两篇关于图片与视频转字符动画的博文,以下大部分代码与小部分说明引用自之前写过的笔记,一篇是来自2019年过年的时候写得Python利用argparse模块图片转字符文件 ,以及2020年中旬总结的Python3 & OpenCV 视频转字符动画笔记,不曾想现在都时隔2年多了,那时候还是初学python,因为我是转行的,大学学的是电气,从自学python到现在,一路还是比较唏嘘的。那么话不多说,在此进入正题。

环境准备

在绘制字符前,我们首先得准备好python的相关环境:

pip install pyprind opencv-python pillow argparse
"""
Requirement already satisfied: pyprind in /home/anaconda3/envs/py37_kt/lib/python3.6/site-packages (2.11.3)
Requirement already satisfied: opencv-python in /home/anaconda3/envs/py37_kt/lib/python3.6/site-packages (4.5.2.54)
Requirement already satisfied: numpy>=1.13.3 in /home/anaconda3/envs/py37_kt/lib/python3.6/site-packages (from opencv-python) (1.18.5)
"""

确认没有问题后,就能先导入环境所需要的包,这边建议是在linux或者Mac下进行操作,因为我之前测试代码都是在linux上,并且下面有些包在win和linux下还是有区别的,具体如下:

import sys
import os
import time
import threading
"""
termios和tty库只存在于Linux服务器中的python库,win下相同功能的库为msvcrt库。
"""
import termios
import tty
import cv2
import pyprind
from PIL import Image, ImageFont, ImageDraw
import argparse

这里需要说明的是,termios该模块提供了一个用于tty I / O控制的POSIX调用的接口,该库有检测键盘输入,等待按键超时检测等功能。这里主要方便于后续写txt,修改终端的属性为non-canonical模式,而win下其实更简单一些,python默认编译了msvcrtmsvcrt库里有一个getch函数直接就可以搞定。理论上不需要这两个库也行,但我没有具体实践了。

pyprind库与argparse库可以看引言中引出的两篇笔记,前者我做了动图演示效果,后者我画了函数包的思维导图解析,至于opencv的功能,本篇并不对其深入,但会在使用过程中尽心解释,我也有做一个opencv专题,写了11篇开发笔记,可以参考一下。那么到此,就进入主题。

图片转换成字符

字符画是一系列字符的组合,我们可以把字符看作是比较大块的像素,一个字符能表现一种颜色(为了简化可以这么理解),字符的种类越多,可以表现的颜色也越多,图片也会更有层次感。而我们也一般用256的灰度值来表示上述关系,这里抛开精确不讲,大致用一个简单的式子表示为:

gray=0.2126∗r+0.7152∗g+0.0722∗bgray = 0.2126 * r + 0.7152 * g + 0.0722 * b gray=0.2126∗r+0.7152∗g+0.0722∗b

以此,我们可以创建一个不重复的字符列表ascii_char,灰度值小(暗)的用列表开头的符号,灰度值大(亮)的用列表末尾的符号。这就相当于一种映射,将灰度的区间转换成字符的区间,用字符替代灰度值,那么设当前字符为x,公式为:

gray256.0=xascii−char⇒x=gray256∗ascii−char\frac{gray}{256.0} = \frac{x}{ascii-char} \Rightarrow x = \frac{gray}{256}*ascii-char256.0gray​=ascii−charx​⇒x=256gray​∗ascii−char

如果是256个字符,与256的灰度值就是一一对应的关系,所得出来的结果也越精确,字符画也越清晰,但要想出256个不重复的字符,不容易。那么可以写出第一版代码。

我们首先使用嫦娥奔月图作为本篇的第一张图片:

而我们根据上述理论写出的转字符代码为:

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")# 将256灰度值映射到70个字符上
def get_char(r,g,b,alpha = 256):""":param r: red:param g: green:param b: blue:param alpha: 图像的灰度值或者说像素值 :return: 返回经过公式处理后的70个字符值中对应的区间"""if alpha == 0:  # 可能会出现0的区间,这部分如果不处理会产生报错return " "gray = 0.2126 * r + 0.7152 * g + 0.0722 * b  # RGB三原色的计算公式# print(gray,alpha)  发现gray会超过alpha边界,所以要加1.0,浮点数x = int((gray / (alpha + 1.0)) * len(ascii_char))  # 映射转换return ascii_char[x]    # 返回我们上面取到的信道区间值# 写入函数
def write_file(content):with open("out_file_name.png","w") as f:f.write(content)
# 主函数
def main(file_name="test.jpg",width=80,height=80):""":param file_name: 指定输入图片路径:param width: 宽度,默认80:param height: 高度,默认80:return: 无返回值"""text = ""im = Image.open(file_name)im = im.resize((width,height),Image.NEAREST)  # resize对图片进行压缩,NEAREST为KNN插值尺寸for i in range(height):for j in range(width):content = im.getpixel((j,i))  # 宽度代表横坐标,高度代表纵坐标,得到一个元组text += get_char(*content)  # 对上面的元组进行解包text += "\n"  # 行加上换行符# print(text)    # 在终端输出字符write_file(text)if __name__ == '__main__':main(file_name="1.jpg")

然后运行代码,结果为:

完全看不出是什么东西,因为我们只是对于颜色编码空间进行了ASCII转换,但其实很多图片的颜色通道并不是这样的,所以基于此进行改进为:

#-*- coding:utf-8 -*-
import os
from PIL import Image, ImageFont, ImageDraw
import argparse
#命令行输入参数处理
parser = argparse.ArgumentParser()
parser.add_argument('file')
parser.add_argument('-o','--output')#获取参数
args = parser.parse_args()
File = args.file
OUTPUT = args.outputascii_char = list("MNHQ$OC67)oa+>!:+. ")#将像素转换为ascii码
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)/lengthreturn ascii_char[int(gray/unit)]if __name__=='__main__':im = Image.open(File)WIDTH = int(im.width/6) #高度比例为原图的1/6较好,由于字体宽度HEIGHT = int(im.height/15)  #高度比例为原图的1/15较好,由于字体高度im_txt = Image.new("RGB",(im.width,im.height),(255,255,255))im = im.resize((WIDTH,HEIGHT),Image.NEAREST)txt = ""colors = []for i in range(HEIGHT):for j in range(WIDTH):pixel = im.getpixel((j,i))colors.append((pixel[0],pixel[1],pixel[2]))#记录像素颜色信息if(len(pixel) == 4):txt += get_char(pixel[0],pixel[1],pixel[2],pixel[3])else:txt += get_char(pixel[0],pixel[1],pixel[2])        txt += '\n' colors.append((255,255,255))dr = ImageDraw.Draw(im_txt)font=ImageFont.load_default().font#获取字体x=y=0#获取字体的宽高font_w,font_h=font.getsize(txt[1])font_h *= 1.37 #调整后更佳#ImageDraw为每个ascii码进行上色for i in range(len(txt)):if(txt[i]=='\n'):x+=font_hy=-font_wdr.text([y,x],txt[i],colors[i])y+=font_w#输出im_txt.save("output.png")

因为有用到argparse模块,这是一个命令行输入模块,具体的操作方法在上面博客链接中有画出它的思维导图实例,而这里将上面的代码封装进了color_ascii.py文件中,选取了一张彩色图片,调用并保存为:

这里就能清晰看到,情满中秋字符图清晰显现,是不是有內味了,emmm

视频转字符动画

首先,这里跟上一节的图片逻辑一样,先将转换字符的功能定义成类:

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

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

  • cv2.resize( src, dst, dsize, fx=0, fy=0, interpolation=INTER_LINEAR )

    fx,fy表示的是放缩的比例,interpolation代表差值的类型,其具体的形式主要有以下:INTER_NEAREST(邻近元素插值法)INTER_LINEAR(缺省值,双线性插值)INTER_AREA(使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 CV_INTER_NN 方法)INTER_CUBIC(立方插值)

然后定义我们的主要转义加输出类,继承字符类:

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)

这个类有两个主要方法,一个是从视频文件转为字符动画的方法 genCharVideo(),一个是播放字符动画的方法 play()。调用 genCharVideo() 方法将视频转化为字符动画存放到属性 charVideo。

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

下面是类方法中的函数介绍:

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

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

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

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

  • ·play() 方法:接受一个参数,表示使用哪种输出流。

    1.代表输出流使用标准输出 sys.stdout
    2. 代表输出流使用标准错误输出 sys.stderr

    其中 sys.stdout.fileno() 和 sys.stderr.fileno() 分别返回标准输出和标准错误输出的文件描述符。os.isatty(fd) 返回一个布尔值,当文件描述符 fd 是打开并连接到 tty 设备时返回真。

这里的play函数有两个地方需要注意:

  1. 不要使用 input() 方法接收字符输入

  2. 不能使用普通线程

还有play函数里的转义字符对应的功能,具体说明参考引言中引用的参考文献,这里不再多说,所以将这两个类合起来,代码为:


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

这里发现之前笔记中使用的竟然是Hold feat. Daniela Andrade,很久没听了,又找到一首可以循环的歌。特别是B站up主剪得MAD,两年了还能找到链接,并且效果依然炸裂,绝了,感动ing。。。链接为:

https://www.bilibili.com/video/BV1zb41167Ak?spm_id_from=333.999.0.0

之前的笔记用该视频作为运行程序的素材后,启动程序,会发现等待时间也比较长:

待等待时间过后,我们可以看见终端呈现的效果,因为csdn最大为5M,部分效果:

可以看到,效果还是很好的,但不是每个都能如此优秀,比如我现在找了一个就中秋快乐四个字的写字视频,效果就很差了:

我想主要原因,是B站大佬制作的MAD很大,并且色域非常全,而中秋快乐艺术字,总共视频就11秒,原视频只有几百k,并且可以看到这四个字全映射成了$符号,色彩太统一,才出现了问题。

这里还有一个改进点是,没有声音录入,以及彩色字符,之前笔记里还有一篇引用文献是Python将视频转换为全字符视频(含音频),我去年做内部总结的时候,有进行过小修,代码为:

# -*- coding:utf-8 -*-
# coding:utf-8
import argparse
import os
import cv2
import subprocess
from cv2 import VideoWriter, VideoWriter_fourcc, imread, resize
from PIL import Image, ImageFont, ImageDraw# 命令行输入参数处理
# aparser = argparse.ArgumentParser()
# aparser.add_argument('file')
# aparser.add_argument('-o','--output')
# aparser.add_argument('-f','--fps',type = float, default = 24)#帧
# aparser.add_argument('-s','--save',type = bool, nargs='?', default = False, const = True)
# 是否保留Cache文件,默认不保存# 获取参数
# args = parser.parse_args()
# INPUT = args.file
# OUTPUT = args.output
# SAVE = args.save
# FPS = args.fps
# 像素对应ascii码ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:oa+>!:+. ")# ascii_char = list("MNHQ$OC67+>!:-. ")
# ascii_char = list("MNHQ$OC67)oa+>!:+. ")# 将像素转换为ascii码
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) / lengthreturn ascii_char[int(gray / unit)]# 将txt转换为图片
def txt2image(file_name):im = Image.open(file_name).convert('RGB')# gif拆分后的图像,需要转换,否则报错,由于gif分割后保存的是索引颜色raw_width = im.widthraw_height = im.heightwidth = int(raw_width / 6)height = int(raw_height / 15)im = im.resize((width, height), Image.NEAREST)txt = ""colors = []for i in range(height):for j in range(width):pixel = im.getpixel((j, i))colors.append((pixel[0], pixel[1], pixel[2]))if (len(pixel) == 4):txt += get_char(pixel[0], pixel[1], pixel[2], pixel[3])else:txt += get_char(pixel[0], pixel[1], pixel[2])txt += '\n'colors.append((255, 255, 255))im_txt = Image.new("RGB", (raw_width, raw_height), (255, 255, 255))dr = ImageDraw.Draw(im_txt)# font = ImageFont.truetype(os.path.join("fonts","汉仪楷体简.ttf"),18)font = ImageFont.load_default().fontx = y = 0# 获取字体的宽高font_w, font_h = font.getsize(txt[1])font_h *= 1.37  # 调整后更佳# ImageDraw为每个ascii码进行上色for i in range(len(txt)):if (txt[i] == '\n'):x += font_hy = -font_w# self, xy, text, fill = None, font = None, anchor = None,# *args, ** kwargsdr.text((y, x), txt[i], fill=colors[i])# dr.text((y, x), txt[i], font=font, fill=colors[i])y += font_wname = file_name# print(name + ' changed')im_txt.save(name)# 将视频拆分成图片
def video2txt_jpg(file_name):vc = cv2.VideoCapture(file_name)c = 1if vc.isOpened():r, frame = vc.read()if not os.path.exists('Cache'):os.mkdir('Cache')os.chdir('Cache')else:r = Falsewhile r:cv2.imwrite(str(c) + '.jpg', frame)txt2image(str(c) + '.jpg')  # 同时转换为ascii图r, frame = vc.read()c += 1os.chdir('..')return vc# 将图片合成视频
def jpg2video(outfile_name, fps):fourcc = VideoWriter_fourcc(*"MJPG")images = os.listdir('Cache')im = Image.open('Cache/' + images[0])vw = cv2.VideoWriter(outfile_name + '.avi', fourcc, fps, im.size)os.chdir('Cache')for image in range(len(images)):# Image.open(str(image)+'.jpg').convert("RGB").save(str(image)+'.jpg')frame = cv2.imread(str(image + 1) + '.jpg')vw.write(frame)# print(str(image + 1) + '.jpg' + ' finished')os.chdir('..')vw.release()# 递归删除目录
def remove_dir(path):if os.path.exists(path):if os.path.isdir(path):dirs = os.listdir(path)for d in dirs:if os.path.isdir(path + '/' + d):remove_dir(path + '/' + d)elif os.path.isfile(path + '/' + d):os.remove(path + '/' + d)os.rmdir(path)returnelif os.path.isfile(path):os.remove(path)return# 调用ffmpeg获取mp3音频文件
def video2mp3(file_name):outfile_name = file_name.split('.')[0] + '.mp3'subprocess.call('ffmpeg -i ' + file_name + ' -f mp3 ' + outfile_name, shell=True)# 合成音频和视频文件
def video_add_mp3(file_name, mp3_file):outfile_name = file_name.split('.')[0] + '-txt.mp4'subprocess.call('ffmpeg -i ' + file_name + ' -i ' + mp3_file + ' -strict -2 -f mp4 ' + outfile_name, shell=True)if __name__ == '__main__':INPUT = "1.mp4"OUTPUT = "1.mp4"SAVE = "."FPS = "25"vc = video2txt_jpg(INPUT)FPS = vc.get(cv2.CAP_PROP_FPS)  # 获取帧率print(FPS)vc.release()jpg2video(INPUT.split('.')[0], FPS)print(INPUT, INPUT.split('.')[0] + '.mp3')video2mp3(INPUT)video_add_mp3(INPUT.split('.')[0] + '.avi', INPUT.split('.')[0] + '.mp3')if (not SAVE):remove_dir("Cache")os.remove(INPUT.split('.')[0] + '.mp3')os.remove(INPUT.split('.')[0] + '.avi')

然后启动:

最后,当然得以贺岁新春作为结束,所以,我打算用欣小萌于2019年9月12号中秋节前夕的视频作为结束,以此来见证中秋节的美。

视频链接为:https://www.bilibili.com/video/BV1sJ411P7CF?spm_id_from=333.337.search-card.all.click
(PS:不愧为镇站之作)


加上声音,如果按原视频1分45秒来算,字符输出后,大小直奔1个G,所以我裁剪为了1分钟,但还是很大,上述是压缩在5M以内的图像,另外,我将本篇中用到的基本上所有文件都打了个包,除了之前笔记里提到的视频外,其余部分大致为:

分享链接为:

链接:https://pan.baidu.com/s/1722im0N2Ka0eh_GXtMSWmQ?pwd=1kmj
提取码:1kmj

趣味中秋,用动画字符来贺岁佳节相关推荐

  1. 趣味程序之打印字符图案系列

    打印字符图案系列程序一览如下,点击进入博客文章: I00005 打印直角三角形字符图案 I00006 打印等腰三角形字符图案(底边在下) I00007 打印菱形字符图案 I00015 打印等腰三角形字 ...

  2. 复古儿童卡通温馨手绘风格趣味MOGRT图形动画标题pr模板

    这些多彩的标题将给你的视频带来新的气氛!轻松自定义颜色,只需单击几下即可将文本更改为任何字体,并享受结果!使用这些动画来装饰您的博客.音乐视频.旅游视频.简介等等! 项目特点: 手工绘制的复古标题 全 ...

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

    在介绍如何用 Python3 & OpenCV 将视频转成字符动画之前,先简单的介绍一下 OpenCV 吧,毕竟可能很多小伙伴不太了解: 百度百科: OpenCV是一个基于BSD许可(开源)发 ...

  4. android 字体 动画,android 对绘制的文本添加动画

    场景: 存在较多绘制内容的区域需要某些动画效果, 需要尽量少修改视图的绘制方法,做到动画与绘制分离. 看个简单例子: image 我在一个视图上绘制了一行文字,先看一下绘制部分的代码: public ...

  5. 2021营销案例盘点,这些品牌的中秋营销创意,绝了!

    一年一度的中秋佳节如期而至.被赋予了"团圆"寓意的中秋,是中国人十分重视的节日,也是各大品牌势必要抓住的营销节点. 查看与中秋有关的热搜条数可以看到,9月18日与9月19日两天的热 ...

  6. mg动画制作软件分享,让你惊喜满满! | 万彩动画大师

    个人用户或企业设计师.市场专员想要找到一款合心意的软件制作精美的MG动画并分享发布,从而获得广泛的传播和宣传,是不大容易的.想要简单好用,又想要功能强大,更想要成本不高,嚯,确实有点难度.但是!别慌, ...

  7. 中秋节2007中秋节中秋祝福

    1.月圆年年相似,你我岁岁相盼.那满天的清辉,遍地水银,便是我们互倾的思念. 2.网缘!情缘!月圆!中秋夜语寄相思,花好月圆情难圆.带去问候和思恋,心想事成愿缘圆. 3.在这特别的日子里,送一份淡淡的 ...

  8. 2020年国内 IoT物联网平台横向对比报告

    金秋十月,丹桂飘香,中秋巧遇国庆! 值此佳节,我们为 IoT 物联网领域的广大开发者们带来了<2020年国内 IoT物联网云平台横向对比报告>. 此<报告>与Gartner和I ...

  9. 编程列入高考-青少儿编程学习-Python那些事

    一.人工智能时代,呼唤计算思维培养 随着2017年谷歌开发的计算机程序阿尔法狗打败了人类围棋高手李世石与柯洁 ,人工智能迅猛发展.2019是5G元年,而2018是人工智能"大爆发" ...

最新文章

  1. 低代码、RPA 和 AI,有什么区别
  2. rsync同步(2010年写作)
  3. 现代的缓存设计方案:Window-TinyLFU
  4. linux 环境下安装和配置mysql数据库以及远程登录
  5. redis-配置说明-重要的几个配置
  6. powercfg -h off_万代 S.H.Figuarts「假面骑士01 金属腾蝗形态」
  7. 高收益债券与杠杆收购:中国机会
  8. 【经验】win11上安装visio
  9. Java中的注释方法
  10. 5.ESL笔记:线性模型与高斯-马尔科夫定理
  11. 保姆教程 | YOLOv5在建筑工地中安全帽佩戴检测的应用
  12. springboot集成knife4j2.0.8实现自定义md文档及权限控制
  13. js将数字的金额转换成中文大写金额
  14. AutoHotKey实现百度云批量离线下载工具
  15. 这7个关键点,是每个产品用户体验设计的重中之重
  16. c#实现爬虫获取小说(.NET)
  17. 抛弃Visio,遇上效率作图工具Edraw亿图图示
  18. Origin数据设置为日期/时间的方法
  19. 解决linux网速慢问题
  20. java操作题35套

热门文章

  1. Cocos2D来制作横版过关游戏1
  2. 行人重识别综述学习笔记
  3. 5.分别画出下列二次曲面
  4. SVN权限的简单配置
  5. httpd服务及配置文件详解
  6. Caché SQL 高性能优化
  7. X86 android r7 z3735,安卓工业平板电脑android系统下各大主流CPU性能大对比分析
  8. PMP学习笔记 第3章 项目经理的角色
  9. 什么是无线HDMI,您应该使用它吗?
  10. java中finally语句是否一定会被执行