該圖檔由F. Muhammad在Pixabay上釋出
完整代碼可在公衆号:「01二進制」背景回複:「蔡xx籃球」擷取
前言
承接上文,作為一個經常逛b站的肥宅,近期b站上除了流行"品如”素材的視訊,更多的莫過于蔡xx打球視訊的了,有模仿的,有對比的,有手繪的,更過分的是竟然有人在指令行輸出了他的打球視訊,位址在:https://www.bilibili.com/video/av47306085?from=search&seid=11282891256876390136,不過視訊中的動畫好像是用某個軟體生成的txt檔案,看到這我就在想既然都可以用txt輸出了,為啥不能用python在指令行中輸出呢?說到這我便開始搜尋資料,然後制作了下面一段視訊:
代碼是我在網上查詢資料後自己修改的,本着學習和分享的精神,今天我來分享下上面這段視訊的制作過程。
原理
既然要開始做東西,首要的問題就是想好要怎麼做,大家都知道視訊是由一系列圖檔一幀一幀組成的,是以視訊轉字元動畫最基本的便是圖檔轉字元畫。
在這裡簡單的說一下圖檔轉字元畫的原理:首先将圖檔轉為灰階圖,每個像素都隻有亮度資訊(用 0~255 表示)。然後我們建構一個有限字元集合,其中的每一個字元都與一段亮度範圍對應,我們便可以根據此對應關系以及像素的亮度資訊把每一個像素用對應的字元表示,這樣字元畫就形成了。
Tips:如果對"灰階圖像"這個概念不太了解的可以查閱百度百科
計算一張圖檔的灰階圖像的方法如下(來自百度百科):
是以我們要做的就隻是讓字元畫在指令行裡面動起來就可以了。
Tips:圖檔轉字元畫可以參考: https://www. shiyanlou.com/courses/3 70
準備
環境和工具:
- vscode
- Mac OS
- python 3.7
這次實驗使用到的核心的庫是opencv-python,關于opencv上篇文章已經簡單介紹過了,這裡不多闡述了,隻要知道這是一個和計算機視覺有關的庫就可以了。
Tips:這裡分享一個我覺得還不錯的opencv-python的中文文檔: https://www. kancloud.cn/aollo/aollo opencv/269602
實驗
實驗開始前我們需要安裝opencv-python的包:
pip install opencv-python
讀取視訊
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_imageflag)
用于轉換顔色空間,其中flag就是轉換類型。對于BGR↔Gray的轉換,我們使用的flag就是cv2.COLOR_BGR2GRAY。對于BGR↔HSV的轉換我們用的flag就是cv2.COLOR_BGR2HSV。
将幀轉換成字元畫
ascii_char = "[email protected]%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
這段代碼其實就是将已經轉變的灰階圖的像素值映射到
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('033[{}Ar'.format(rows - 1))
# 光标下移 rows-1 行到最後一行,清空最後一行
self.streamOut('033[{}B033[K'.format(rows - 1))
# 清空最後一幀的所有行(從倒數第二行起)
for i in range(rows - 1):
# 光标上移一行
self.streamOut('033[1A')
# 清空光标所在行
self.streamOut('r033[K')
if breakflag:
self.streamOut('User interrupt!n')
else:
self.streamOut('Finished!n')
執行
最後在main函數中設定下要讀取的檔案名,再play一下就可以了
if __name__ == '__main__':
v2char = V2Char('vedio.mp4')
v2char.play()
完整代碼可在公衆号:「01二進制」背景回複:「蔡xx籃球」擷取
寫在最後
方法教給大家了,視訊素材可以優化,大家可以自己收集好的視訊素材,發到朋友圈,讓代碼騷動起來!