該圖片由F. Muhammad在Pixabay上釋出
完整程式碼可在公眾號:「01二進位制」後臺回覆:「蔡xx籃球」獲取
前言
承接上文,作為一個經常逛b站的肥宅,近期b站上除了流行"品如”素材的視訊,更多的莫過於蔡xx打球視訊的了,有模仿的,有對比的,有手繪的,更過分的是竟然有人在命令列輸出了他的打球視訊,地址在:www.bilibili.com/video/av473…,不過視訊中的動畫好像是用某個軟體生成的txt檔案,看到這我就在想既然都可以用txt輸出了,為啥不能用python在命令列中輸出呢?說到這我便開始搜尋資料,然後製作了下面一段視訊:
程式碼是我在網上查詢資料後自己修改的,本著學習和分享的精神,今天我來分享下上面這段視訊的製作過程。
原理
既然要開始做東西,首要的問題就是想好要怎麼做,大家都知道視訊是由一系列圖片一幀一幀組成的,因此視訊轉字元動畫最基本的便是圖片轉字元畫。
在這裡簡單的說一下圖片轉字元畫的原理:首先將圖片轉為灰度圖,每個畫素都只有亮度資訊(用 0~255 表示)。然後我們構建一個有限字符集合,其中的每一個字元都與一段亮度範圍對應,我們便可以根據此對應關係以及畫素的亮度資訊把每一個畫素用對應的字元表示,這樣字元畫就形成了。
Tips:如果對"灰度影象"這個概念不太理解的可以查閱百度百科
計算一張圖片的灰度影象的方法如下(來自百度百科):
所以我們要做的就只是讓字元畫在命令列裡面動起來就可以了。
Tips:圖片轉字元畫可以參考:www.shiyanlou.com/courses/370
準備
環境和工具:
- vscode
- Mac OS
- python 3.7
這次實驗使用到的核心的庫是opencv-python,關於opencv上篇文章已經簡單介紹過了,這裡不多闡述了,只要知道這是一個和計算機視覺有關的庫就可以了。
Tips:這裡分享一個我覺得還不錯的opencv-python的中文文件:www.kancloud.cn/aollo/aollo…
實驗
實驗開始前我們需要安裝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 = "$@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
複製程式碼
這段程式碼其實就是將已經轉變的灰度圖的畫素值對映到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[{}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')
複製程式碼
執行
最後在main函式中設定下要讀取的檔名,再play一下就可以了
if __name__ == '__main__':
v2char = V2Char('vedio.mp4')
v2char.play()
複製程式碼
完整程式碼可在公眾號:「01二進位制」後臺回覆:「蔡xx籃球」獲取
寫在最後
方法教給大家了,視訊素材可以優化,大家可以自己收集好的視訊素材,發到朋友圈,讓程式碼騷動起來!
喜歡的小夥伴可以長按下方二維碼關注哦~。?