首頁 > 軟體

用Python字元畫出了一個谷愛凌

2022-02-16 13:00:54

之前經常在網上看到那種由一個個字元構成的視訊,非常炫酷。一直不懂是怎麼做的,這兩天研究了一下,發現並不難。

先來看一個最終效果(如果模糊的話,點選下方連結看高清版):

https://pan.baidu.com/s/1DvedXlDZ4dgHKLogdULogg 提取碼:1234

怎麼實現的?

簡單來說,要將一個彩色的視訊變成字元畫出來的黑白視訊,用下面幾步就能搞定:

  1. 對原視訊進行抽幀,對每一幀黑白化,並將畫素點用對應的字元表示。
  2. 將表示出來的字串再重新組合成字元影象。
  3. 將所有的字元影象再組合成字元視訊。
  4. 將原視訊的音訊匯入到新的字元視訊中。

執行方法

完整的程式碼我放在文章末尾了,直接執行python3 video2char.py即可。程式會要求你輸入視訊的本地路徑和轉變後的清晰度(0最模糊,1最清晰。當然越清晰,轉變越慢)。

執行程式碼的話需要用到tqdm、opencv_python、moviepy等幾個庫,首先得pip3 install確保它們都有了。

原理分析

這裡面最關鍵的步驟就是如何將一幀彩色影象轉變為黑白的字元影象,如下圖所示:

從青蛙公主視訊抽幀出來的

用字元畫出來的

而轉變的原理其實很簡單。首先因為一個字元畫在影象裡會佔據很大一個畫素塊,所以必須先對彩色影象進行壓縮,連續的一個畫素塊可以合併,這個壓縮過程就是opencv的resize操作。

然後將壓縮後的畫素點轉變為黑白畫素點,並轉變為對應的字元。字元的話我這裡採用的是下面的字串,從黑到白,經過我的實踐這一組是效果最好的:

"#8XOHLTI)i=+;:,. "

接著就需要將轉變後的字元畫到新的畫布上去,需要注意的點是排布得均勻緊湊了,畫布四周最好不要有太多多餘的空白。

最後把所有的字元影象合併成視訊就行了,但是合併後是沒有聲音的,需要用moviepy庫把原視訊的聲音匯入過來。

完整程式碼

import os
import re
import shutil
from tqdm import trange, tqdm
import cv2
from PIL import Image, ImageFont, ImageDraw
from moviepy.editor import VideoFileClip
 
 
class V2Char:
    font_path = "Arial.ttf"
    ascii_char = "#8XOHLTI)i=+;:,. "
 
    def __init__(self, video_path, clarity):
        self.video_path = video_path
        self.clarity = clarity
 
    def video2str(self):
        def convert(img):
            if img.shape[0] > self.text_size[1] or img.shape[1] > self.text_size[0]:
                img = cv2.resize(img, self.text_size, interpolation=cv2.INTER_NEAREST)
            ascii_frame = ""
            for i in range(img.shape[0]):
                for j in range(img.shape[1]):
                    ascii_frame += self.ascii_char[
                        int(img[i, j] / 256 * len(self.ascii_char))
                    ]
            return ascii_frame
 
        print("正在將原視訊轉為字元...")
        self.char_video = []
        cap = cv2.VideoCapture(self.video_path)
        self.fps = cap.get(cv2.CAP_PROP_FPS)
        self.nframe = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.raw_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.raw_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        font_size = int(25 - 20 * max(min(float(self.clarity), 1), 0))
        self.font = ImageFont.truetype(self.font_path, font_size)
        self.char_width, self.char_height = max(
            [self.font.getsize(c) for c in self.ascii_char]
        )
        self.text_size = (
            int(self.raw_width / self.char_width),
            int(self.raw_height / self.char_height),
        )
        for _ in trange(self.nframe):
            raw_frame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)
            frame = convert(raw_frame)
            self.char_video.append(frame)
        cap.release()
 
    def str2fig(self):
        print("正在生成字元影象...")
        col, row = self.text_size
        catalog = self.video_path.split(".")[0]
        if not os.path.exists(catalog):
            os.makedirs(catalog)
        blank_width = int((self.raw_width - self.text_size[0] * self.char_width) / 2)
        blank_height = int((self.raw_height - self.text_size[1] * self.char_height) / 2)
        for p_id in trange(len(self.char_video)):
            strs = [self.char_video[p_id][i * col : (i + 1) * col] for i in range(row)]
            im = Image.new("RGB", (self.raw_width, self.raw_height), (255, 255, 255))
            dr = ImageDraw.Draw(im)
            for i, str in enumerate(strs):
                for j in range(len(str)):
                    dr.text(
                        (
                            blank_width + j * self.char_width,
                            blank_height + i * self.char_height,
                        ),
                        str[j],
                        font=self.font,
                        fill="#000000",
                    )
            im.save(catalog + r"/pic_{}.jpg".format(p_id))
 
    def jpg2video(self):
        print("正在將字元影象合成字元視訊...")
        catalog = self.video_path.split(".")[0]
        images = os.listdir(catalog)
        images.sort(key=lambda x: int(re.findall(r"d+", x)[0]))
        im = Image.open(catalog + "/" + images[0])
        fourcc = cv2.VideoWriter_fourcc("m", "p", "4", "v")
        savedname = catalog.split("/")[-1]
        vw = cv2.VideoWriter(savedname + "_tmp.mp4", fourcc, self.fps, im.size)
        for image in tqdm(images):
            frame = cv2.imread(catalog + "/" + image)
            vw.write(frame)
        vw.release()
        shutil.rmtree(catalog)
 
    def merge_audio(self):
        print("正在將音訊合成到字元視訊中...")
        raw_video = VideoFileClip(self.video_path)
        char_video = VideoFileClip(self.video_path.split(".")[0] + "_tmp.mp4")
        audio = raw_video.audio
        video = char_video.set_audio(audio)
        video.write_videofile(
            self.video_path.split(".")[0] + f"_{self.clarity}.mp4",
            codec="libx264",
            audio_codec="aac",
        )
        os.remove(self.video_path.split(".")[0] + "_tmp.mp4")
 
    def gen_video(self):
        self.video2str()
        self.str2fig()
        self.jpg2video()
        self.merge_audio()
 
 
if __name__ == "__main__":
    video_path = input("輸入視訊檔路徑:n")
    clarity = input("輸入清晰度(0~1, 直接回車使用預設值0):n") or 0
    v2char = V2Char(video_path, clarity)
    v2char.gen_video()

以上就是用Python字元畫出了一個谷愛凌的詳細內容,更多關於Python字元畫的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com