昨晚一朋友跟我說在網上看到了別人作的視頻轉字符動畫,以爲很厲害,我因而也打算玩玩。今天中午花時間實現了這樣一個小玩意。
順便把過程記錄在這裏。html
注:最新版使用了畫布方式實現,和本文相比改動很是大,若是對舊版本的實現沒啥興趣,能夠直接移步 video2chars,它的效果動畫見 極樂淨土。新版本的核心代碼不算註釋70行不到,功能更強大。python
先上效果,來點動力:git
這個程序須要用到這樣幾個模塊:github
準備階段,首先安裝依賴:算法
pip3 install numpy opencv-python
而後新建python代碼文檔,在開頭添加上下面的導入語句shell
#-*- coding:utf-8 -*- import numpy as np
材料就是須要轉換的視頻文件了,我這裏用的是BadApple.mp4,下載下來和代碼放到同一目錄下
你也能夠換成本身的,建議是學習時儘可能選個短一點的視頻,幾十秒就好了,否則調試起來很痛苦。(或者本身稍微修改一下函數,只轉換必定範圍、必定數量的幀。)
此外,要選擇對比度高的視頻。不然的話,就須要彩色字符纔能有足夠好的表現,有時間我試試。編程
如今繼續添加代碼,實現第一步:按幀讀取視頻。
下面這個函數,接受視頻路徑和字符視頻的尺寸信息,返回一個img列表,其中的img是尺寸都爲指定大小的灰度圖。windows
#導入 opencv import cv2 def video2imgs(video_name, size): """ :param video_name: 字符串, 視頻文件的路徑 :param size: 二元組,(寬, 高),用於指定生成的字符畫的尺寸 :return: 一個 img 對象的列表,img對象實際上就是 numpy.ndarray 數組 """ img_list = [] # 從指定文件建立一個VideoCapture對象 cap = cv2.VideoCapture(video_name) # 若是cap對象已經初始化完成了,就返回true,換句話說這是一個 while true 循環 while cap.isOpened(): # cap.read() 返回值介紹: # ret 表示是否讀取到圖像 # frame 爲圖像矩陣,類型爲 numpy.ndarry. ret, frame = cap.read() if ret: # 轉換成灰度圖,也可不作這一步,轉換成彩色字符視頻。 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # resize 圖片,保證圖片轉換成字符畫後,能完整地在命令行中顯示。 img = cv2.resize(gray, size, interpolation=cv2.INTER_AREA) # 分幀保存轉換結果 img_list.append(img) else: break # 結束時要釋放空間 cap.release() return img_list
寫完後能夠寫個main方法測試一下,像這樣:數組
if __name__ == "__main__": imgs = video2imgs("BadApple.mp4", (64, 48)) assert len(imgs) > 10
若是運行沒報錯,就沒問題
代碼裏的註釋應該寫得很清晰了,繼續下一步。緩存
視頻轉換成了圖像,這一步即是把圖像轉換成字符畫
下面這個函數,接受一個img對象爲參數,返回對應的字符畫。
# 用於生成字符畫的像素,越日後視覺上越明顯。。這是我本身按感受排的,你能夠隨意調整。 pixels = " .,-'`:!1+*abcdefghijklmnopqrstuvwxyz<>()\/{}[]?234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ%&@#$" def img2chars(img): """ :param img: numpy.ndarray, 圖像矩陣 :return: 字符串的列表:圖像對應的字符畫,其每一行對應圖像的一行像素 """ res = [] # 灰度是用8位表示的,最大值爲255。 # 這裏將灰度轉換到0-1之間 # 使用 numpy 的逐元素除法加速,這裏 numpy 會直接對 img 中的全部元素都除以 255 percents = img / 255 # 將灰度值進一步轉換到 0 到 (len(pixels) - 1) 之間,這樣就和 pixels 裏的字符對應起來了 # 一樣使用 numpy 的逐元素算法,而後使用 astype 將元素所有轉換成 int 值。 indexes = (percents * (len(pixels) - 1)).astype(np.int) # 要注意這裏的順序和 以前的 size 恰好相反(numpy 的 shape 返回 (行數、列數)) height, width = img.shape for row in range(height): line = "" for col in range(width): index = indexes[row][col] # 添加字符像素(最後面加一個空格,是由於命令行有行距卻沒幾乎有字符間距,用空格當間距) line += pixels[index] + " " res.append(line) return res
上面的函數只接受一幀爲參數,一次只轉換一幀,可咱們須要的是轉換全部的幀,因此就再把它包裝一下:
def imgs2chars(imgs): video_chars = [] for img in imgs: video_chars.append(img2chars(img)) return video_chars
好了,如今咱們能夠測試一下:
if __name__ == "__main__": imgs = video2imgs("BadApple.mp4", (64, 48)) video_chars = imgs2chars(imgs) assert len(video_chars) > 10
沒報錯的話,就能夠下一步了。(這一步比較慢,測試階段建議用短一點的視頻,或者稍微改一下,只處理前30秒之類的)
寫了這麼多代碼,如今終於要出成果了。如今就是最激動人心的一步:播放字符畫了。
一樣的,我把它封裝成了一個函數。下面這個函數接受一個字符畫的列表並播放。
# 導入須要的模塊 import time import subprocess def play_video(video_chars): """ 播放字符視頻 :param video_chars: 字符畫的列表,每一個元素爲一幀 :return: None """ # 獲取字符畫的尺寸 width, height = len(video_chars[0][0]), len(video_chars[0]) for pic_i in range(len(video_chars)): # 顯示 pic_i,即第i幀字符畫 for line_i in range(height): # 將pic_i的第i行寫入第i列。 print(video_chars[pic_i][line_i]) time.sleep(1 / 24) # 粗略地控制播放速度。 subprocess.call("clear") # 調用shell命令清屏,用 cmd 的話要把 "clear"改爲 "cls"
# 導入須要的模塊 import time import curses def play_video(video_chars): """ 播放字符視頻, :param video_chars: 字符畫的列表,每一個元素爲一幀 :return: None """ # 獲取字符畫的尺寸 width, height = len(video_chars[0][0]), len(video_chars[0]) # 初始化curses,這個是必須的,直接抄就行 stdscr = curses.initscr() curses.start_color() try: # 調整窗口大小,寬度最好略大於字符畫寬度。另外注意curses的height和width的順序 stdscr.resize(height, width * 2) for pic_i in range(len(video_chars)): # 顯示 pic_i,即第i幀字符畫 for line_i in range(height): # 將pic_i的第i行寫入第i列。(line_i, 0)表示從第i行的開頭開始寫入。最後一個參數設置字符爲白色 stdscr.addstr(line_i, 0, video_chars[pic_i][line_i], curses.COLOR_WHITE) stdscr.refresh() # 寫入後須要refresh纔會當即更新界面 time.sleep(1 / 24) # 粗略地控制播放速度。更精確的方式是使用遊戲編程裏,精靈的概念 finally: # curses 使用前要初始化,用完後不管有沒有異常,都要關閉 curses.endwin() return
好,接下來就是見證奇蹟的時刻
不過開始前要注意,字符畫的播放必須在shell窗口下運行,在pycharm裏運行會看到一堆無心義字符。另外播放前要先最大化shell窗口
if __name__ == "__main__": imgs = video2imgs("BadApple.mp4", (64, 48)) video_chars = imgs2chars(imgs) input("`轉換完成!按enter鍵開始播放") play_video(video_chars)
寫完後,開個shell,最大化窗口,而後鍵入(文件名換成你的)
python3 video2chars.py
可能要等好久。我使用示例視頻大概須要 12 秒左右。看到提示的時候,按回車,開始播放!
這樣就完成了視頻到字符動畫的轉換, 除去註釋, 大概七十行代碼的樣子. 稍微超出了點預期, 不過效果然是挺棒的.
到了這裏,核心功能基本都完成了。
不過仔細想一想,其實還有不少能夠作的:
這些東西,就不寫這裏了,再寫下去,大家確定要說我這標題是騙人了哈哈。
因此若是有興趣的,請移步這個系列的下一篇:Python 視頻轉字符畫 - 進階
完整代碼見 video2chars.py,要注意的是代碼庫的代碼,包含了第二篇文章的內容(音頻、緩存、幀率控制等),並且相對這篇文章也有一些小改動(目的是方便使用,可是稍微增長了點代碼量,因此改動沒有寫在這篇文章裏了)
想運行起來的話,仍是建議跟着文章作。。
容許轉載, 可是要求附上來源連接: 視頻轉字符動畫-Python-60行代碼