之前經常在網上看到那種由一個個字符構成的視頻,非常炫酷,一直不懂是怎么做的,這兩天研究了一下,發現并不難,
先來看一個最終效果(如果模糊的話,點擊下方鏈接看高清版):
https://bytedance.feishu.cn/docx/doxcnCvvY051xFCBAmSqYZgiP7b
怎么實作的?
簡單來說,要將一個彩色的視頻變成字符畫出來的黑白視頻,用下面幾步就能搞定:
- 對原視頻進行抽幀,對每一幀黑白化,并將像素點用對應的字符表示,
- 將表示出來的字串再重新組合成字符影像,
- 將所有的字符影像再組合成字符視頻,
- 將原視頻的音頻匯入到新的字符視頻中,
運行方法
完整的代碼我放在文章末尾了,直接運行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()
參考鏈接
https://blog.csdn.net/weixin_41982136/article/details/89668958
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/423955.html
標籤:AI
