Python 让蔡徐坤在我的命令行里打篮球!


作者 |?雇个城管打天下
责编 | 伍杏玲
本文经授权转载自01二进制(ID:gh_d1999add1857)
【程序人生 编者按】作者自称是一个经常逛 B 站的肥宅。最近 B 站上流行的视频素材除了“换脸”,其次就要属“蔡xx打球”视频了。有模仿的、对比的、手绘的,还有人在命令行输出了他的打球视频。不过,视频中的动画好像是用某个软件生成的 TXT 文件,作者就在想既然都可以用 TXT 输出了,能不能用 Python 在命令行中显示呢?
说到这作者便开始搜索资料,做后制作了下面一段视频“:
<iframe class="video_iframe rich_pages" data-vidtype="2" data-mpvid="wxv_755457897580167169" data-cover="http%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_jpg%2F1skeHK2Gybx4iaa4GrrPMo0ZnZL3ZmbOdY4Dial0zIg1Sdxd96zGpq8oRV9tMBKWG6I2S3vYJRyEvNUTQVoia1sOA%2F0%3Fwx_fmt%3Djpeg" allowfullscreen="" data-ratio="1.5769230769230769" data-w="574" scrolling="no" data-src="http://mp.weixin.qq.com/mp/readtemplate?t=pages/video_player_tmpl&auto=0&vid=wxv_755457897580167169" frameborder="0" width="352" height="198" data-vh="198" data-vw="352" style="display: none; width: 352px !important; height: 198px !important;"></iframe>
代码是自己在网上查询资料后自己修改的,本着学习和分享的精神,今天就来分享下上面这段视频的制作过程。

原理
既然要开始做东西,首要的问题就是想好要怎么做,大家都知道视频是由一系列图片一帧一帧组成的,因此视频转字符动画最基本的便是图片转字符画。
在这里简单的说一下图片转字符画的原理:首先将图片转为灰度图,每个像素都只有亮度信息(用 0~255 表示)。然后我们构建一个有限字符集合,其中的每一个字符都与一段亮度范围对应,我们便可以根据此对应关系以及像素的亮度信息把每一个像素用对应的字符表示,这样字符画就形成了。
Tips:如果对"灰度图像"这个概念不太理解的可以查阅百度百科
计算一张图片的灰度图像的方法如下(来自百度百科):

所以我们要做的就只是让字符画在命令行里面动起来就可以了。
Tips:图片转字符画可以参考:https://www.shiyanlou.com/courses/37
环境和工具:vscode、Mac OS、Python 3.7
这次实验使用到的核心的库是OpenCV-Python
Tips:这里分享一个我觉得还不错的OpenCV-Python的中文文档: https://www.kancloud.cn/aollo/aolloopencv/269602

实验
实验开始前我们需要安装?OpenCV-Python 的包:
pip?install?opencv
读取视频:
def?genCharVideo(self,?filepath):
self.charVideo?=[]
#?用opencv读取视频
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)):
#?转换颜色空间,第二个参数是转换类型,cv2.COLOR_BGR2GRAY表示从BGR?Gray
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()
这里的VideoCapture是用来读取视频的, cv2.cvtColor(input_image?ag)用于转换颜色空间,其中?ag就是转换类型。对于 BGR?Gray 的转换,我们使用的 ?ag 就是 cv2.COLORBGR2GRAY。对于 BGR?HSV 的转换我们用的 ?ag 就是 cv2.COLORBGR2HSV。
将帧转换成字符画
ascii_frame
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?!=-1and(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
这段代码其实就是将已经转变的灰度图的像素值映射到 ascii_char上,然后输出到控制台。
控制输出
#?创建线程
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('

