用Python字符畫出了一個谷愛凌
之前經(jīng)常在網(wǎng)上看到那種由一個個字符構成的視頻,非常炫酷。一直不懂是怎么做的,這兩天研究了一下,發(fā)現(xiàn)并不難。
先來看一個最終效果(如果模糊的話,點擊下方鏈接看高清版):
https://pan.baidu.com/s/1DvedXlDZ4dgHKLogdULogg 提取碼:1234
怎么實現(xiàn)的?
簡單來說,要將一個彩色的視頻變成字符畫出來的黑白視頻,用下面幾步就能搞定:
- 對原視頻進行抽幀,對每一幀黑白化,并將像素點用對應的字符表示。
- 將表示出來的字符串再重新組合成字符圖像。
- 將所有的字符圖像再組合成字符視頻。
- 將原視頻的音頻導入到新的字符視頻中。
運行方法
完整的代碼我放在文章末尾了,直接運行python3 video2char.py即可。程序會要求你輸入視頻的本地路徑和轉(zhuǎn)變后的清晰度(0最模糊,1最清晰。當然越清晰,轉(zhuǎn)變越慢)。
運行代碼的話需要用到tqdm、opencv_python、moviepy等幾個庫,首先得pip3 install確保它們都有了。
原理分析
這里面最關鍵的步驟就是如何將一幀彩色圖像轉(zhuǎn)變?yōu)楹诎椎淖址麍D像,如下圖所示:

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

用字符畫出來的
而轉(zhuǎn)變的原理其實很簡單。首先因為一個字符畫在圖像里會占據(jù)很大一個像素塊,所以必須先對彩色圖像進行壓縮,連續(xù)的一個像素塊可以合并,這個壓縮過程就是opencv的resize操作。
然后將壓縮后的像素點轉(zhuǎn)變?yōu)楹诎紫袼攸c,并轉(zhuǎn)變?yōu)閷淖址?。字符的話我這里采用的是下面的字符串,從黑到白,經(jīng)過我的實踐這一組是效果最好的:
"#8XOHLTI)i=+;:,. "
接著就需要將轉(zhuǎn)變后的字符畫到新的畫布上去,需要注意的點是排布得均勻緊湊了,畫布四周最好不要有太多多余的空白。
最后把所有的字符圖像合并成視頻就行了,但是合并后是沒有聲音的,需要用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("正在將原視頻轉(zhuǎn)為字符...")
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字符畫出了一個谷愛凌的詳細內(nèi)容,更多關于Python字符畫的資料請關注腳本之家其它相關文章!
相關文章
對python 通過ssh訪問數(shù)據(jù)庫的實例詳解
今天小編就為大家分享一篇對python 通過ssh訪問數(shù)據(jù)庫的實例詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-02-02
Python3實現(xiàn)帶附件的定時發(fā)送郵件功能
這篇文章主要為大家詳細介紹了Python3實現(xiàn)帶附件的定時發(fā)送郵件功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02
python使用Pillow將照片轉(zhuǎn)換為1寸報名照片的教程分享
在現(xiàn)代科技時代,我們經(jīng)常需要調(diào)整和處理照片以適應特定的需求和用途,本文將介紹如何使用wxPython和Pillow庫,通過一個簡單的圖形界面程序,將選擇的照片轉(zhuǎn)換為指定尺寸的JPG格式,并保存在桌面上,需要的朋友可以參考下2023-09-09
python如何實現(xiàn)excel數(shù)據(jù)添加到mongodb
本文介紹了python是如何實現(xiàn)excel數(shù)據(jù)添加到mongodb,為了將數(shù)據(jù)導入mongodb,引入了pymongo,xlrd包,需要的朋友可以參考下2015-07-07

