這兩天,我在製作教學短視頻時,想將一些關鍵幀(Key Frame)快速製作成影片格式,好讓我快速就著關鍵資訊進行錄音,然後就可以快速後製。
我搜尋了在 Python 如何將圖片轉變成影片,找到 OpenCV 庫可以達成,找到幾個網站都差不多,代碼大致如下:
import cv2
import glob
frameSize = (2266, 1488)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('output_video.mp4',fourcc, 0.25, frameSize)
for filename in glob.glob('./test-img-to-videos/*.jpg'):
img = cv2.imread(filename)
print(img.shape)
out.write(img)
out.release()
來源:
當中有兩點注意:
- 生成影片用的每幀圖片尺寸必需一致。
- 我找到幾個網上代碼都是生成 AVI。我改為使用
*'mp4v'
格式以生成 MP4 格式。
再經過調整,我作了以下更改:
- 取得第一張圖片的尺寸作為影片尺寸。
- 判斷其餘圖片自動符合影片尺寸,自動縮放置中。(使用了 Pillow 庫)
- 對 glob 的檔案列表進行排序,按名稱順序排。(https://stackoverflow.com/a/6774404)
新改善後代碼:
import cv2
import glob
from PIL import Image
import numpy as np
def image_to_frame(filename, frame_size=(1920,1080), fit=True, background=(0,0,0)):
im = Image.new('RGB', frame_size, background)
img = Image.open(filename)
if not fit:
im.paste(img)
else:
width, height = img.size
frame_width, frame_height = frame_size
ratio = min(frame_width/width, frame_height/height)
img = img.resize((round(width*ratio), round(height*ratio)))
pos_x = round((frame_width - width*ratio)/2)
pos_y = round((frame_height - height*ratio)/2)
im.paste(img, (pos_x, pos_y))
return im
def cv2_image_from_pillow(pillow_image):
"""Converting from Pillow image to cv2 image"""
# https://medium.com/analytics-vidhya/the-ultimate-handbook-for-opencv-pillow-72b7eff77cd7
cv2_img = np.array(pillow_image)
return cv2.cvtColor(cv2_img, cv2.COLOR_RGB2BGR)
frame_rate = 0.25
frame_size = None
# frame_size = (1920,1080) # if you want a pre-defined frame size.
frames = []
files = glob.glob('./test-img-to-videos/*.jpg')
files.sort()
for filename in files:
img = cv2.imread(filename)
height, width, _ = img.shape
if frame_size == None:
frame_size = [width, height]
elif (width, height) != frame_size:
img = image_to_frame(filename, frame_size=frame_size)
img = cv2_image_from_pillow(img)
frames.append(img)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('output_video.mp4',fourcc, frame_rate, frame_size)
for frame in frames:
out.write(frame)
out.release()
當中,輸入資料夾及輸出檔案名仍為 hardcoded 固定名稱,若進一步要變成指令(Command Line Interface)來執行,可以將輸入輸出名稱改以 args 來存取。
例如可以通過 python img_to_mp4.py source_images output_keyframes.mp4
來生成。
最終代碼:
import cv2
import glob
from PIL import Image
import numpy as np
def image_to_frame(filename, frame_size=(1920,1080), fit=True, background=(0,0,0)):
im = Image.new('RGB', frame_size, background)
img = Image.open(filename)
if not fit:
im.paste(img)
else:
width, height = img.size
frame_width, frame_height = frame_size
ratio = min(frame_width/width, frame_height/height)
img = img.resize((round(width*ratio), round(height*ratio)))
pos_x = round((frame_width - width*ratio)/2)
pos_y = round((frame_height - height*ratio)/2)
im.paste(img, (pos_x, pos_y))
return im
def cv2_image_from_pillow(pillow_image):
"""Converting from Pillow image to cv2 image"""
# https://medium.com/analytics-vidhya/the-ultimate-handbook-for-opencv-pillow-72b7eff77cd7
cv2_img = np.array(pillow_image)
return cv2.cvtColor(cv2_img, cv2.COLOR_RGB2BGR)
def main():
import sys
if len(sys.argv) != 3:
print("Please provide two arguments: source directory (*.jpg) and output_video (.mp4) name.")
return
source_dir = sys.argv[1]
output_video = sys.argv[2]
frame_rate = 0.25
frame_size = None
# frame_size = (1920,1080) # if you want a pre-defined frame size.
frames = []
files = glob.glob(f"./{source_dir}/*.jpg")
files.sort()
for filename in files:
img = cv2.imread(filename)
height, width, _ = img.shape
if frame_size == None:
frame_size = [width, height]
elif (width, height) != frame_size:
img = image_to_frame(filename, frame_size=frame_size)
img = cv2_image_from_pillow(img)
frames.append(img)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video,fourcc, frame_rate, frame_size)
for frame in frames:
out.write(frame)
out.release()
print(f"Done creating video {output_video} from folder {source_dir}.")
if __name__ == "__main__":
main()