視頻轉字符動畫-Python-60行代碼

昨晚一朋友跟我說在網上看到了別人作的視頻轉字符動畫,以爲很厲害,我因而也打算玩玩。今天中午花時間實現了這樣一個小玩意。
順便把過程記錄在這裏。html

注:最新版使用了畫布方式實現,和本文相比改動很是大,若是對舊版本的實現沒啥興趣,能夠直接移步 video2chars,它的效果動畫見 極樂淨土。新版本的核心代碼不算註釋70行不到,功能更強大。python

效果

先上效果,來點動力:git

  1. 源視頻: BadApple.mp4
  2. 轉換後:

步驟

  1. 將視頻轉化爲一幀一幀的圖片
  2. 把圖片轉化爲字符畫
  3. 按順序播放字符畫

1、準備

1. 模塊

這個程序須要用到這樣幾個模塊:github

  1. opencv-python # 用來讀取視頻和圖片
  2. numpy # opencv-python 依賴於它

準備階段,首先安裝依賴:算法

pip3 install numpy opencv-python

而後新建python代碼文檔,在開頭添加上下面的導入語句shell

#-*- coding:utf-8 -*-

import numpy as np

2. 材料

材料就是須要轉換的視頻文件了,我這裏用的是BadApple.mp4,下載下來和代碼放到同一目錄下
你也能夠換成本身的,建議是學習時儘可能選個短一點的視頻,幾十秒就好了,否則調試起來很痛苦。(或者本身稍微修改一下函數,只轉換必定範圍、必定數量的幀。)
此外,要選擇對比度高的視頻。不然的話,就須要彩色字符纔能有足夠好的表現,有時間我試試。編程

2、按幀讀取視頻

如今繼續添加代碼,實現第一步:按幀讀取視頻。
下面這個函數,接受視頻路徑和字符視頻的尺寸信息,返回一個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

若是運行沒報錯,就沒問題
代碼裏的註釋應該寫得很清晰了,繼續下一步。緩存

3、圖像轉化爲字符畫

視頻轉換成了圖像,這一步即是把圖像轉換成字符畫
下面這個函數,接受一個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秒之類的)

4、播放字符視頻

寫了這麼多代碼,如今終於要出成果了。如今就是最激動人心的一步:播放字符畫了。
一樣的,我把它封裝成了一個函數。下面這個函數接受一個字符畫的列表並播放。

  1. 通用版(使用 shell 的 clear 命令清屏,可是由於效率不高,可能會有一閃一閃的問題
    這個版本適用於 Unix 系 和 windows 的 powershell
# 導入須要的模塊
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"
  1. Unix系版本(使用了只支援 unix 系 的 curses 庫,比 clear 更流暢
# 導入須要的模塊
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 秒左右。看到提示的時候,按回車,開始播放!

這樣就完成了視頻到字符動畫的轉換, 除去註釋, 大概七十行代碼的樣子. 稍微超出了點預期, 不過效果然是挺棒的.

5、進一步優化

到了這裏,核心功能基本都完成了。
不過仔細想一想,其實還有不少能夠作的:

  • 能不能手動指定要轉換的區間、幀率?
  • 每次轉換都要好久的時間,能不能邊轉換邊播放?或者轉換後把數據保存起來,下次播放時,就直接讀緩存。
  • 爲啥個人字符動畫沒有聲音,看無聲電影麼?
  • 視頻的播放速度能不能精確控制?
  • 能不能用彩色字符?

這些東西,就不寫這裏了,再寫下去,大家確定要說我這標題是騙人了哈哈。
因此若是有興趣的,請移步這個系列的下一篇:Python 視頻轉字符畫 - 進階

6、總結

完整代碼見 video2chars.py,要注意的是代碼庫的代碼,包含了第二篇文章的內容(音頻、緩存、幀率控制等),並且相對這篇文章也有一些小改動(目的是方便使用,可是稍微增長了點代碼量,因此改動沒有寫在這篇文章裏了)
想運行起來的話,仍是建議跟着文章作。。

7、參考

容許轉載, 可是要求附上來源連接: 視頻轉字符動畫-Python-60行代碼

相關文章
相關標籤/搜索