簡體   English   中英

Pipe 視頻幀從 ffmpeg 到 numpy 數組,無需將整個電影加載到 ZCD69B4957F06CDE8218D7BF3D61980

[英]Pipe video frames from ffmpeg to numpy array without loading whole movie into memory

我不確定我要問的內容是否可行或實用,但我正在嘗試嘗試以有序但“按需”的方式從視頻中加載幀。

基本上我現在所擁有的是通過管道通過stdout將整個未壓縮視頻讀入緩沖區,例如:

H, W = 1080, 1920 # video dimensions
video = '/path/to/video.mp4' # path to video

# ffmpeg command
command = [ "ffmpeg",
            '-i', video,
            '-pix_fmt', 'rgb24',
            '-f', 'rawvideo',
            'pipe:1' ]

# run ffmpeg and load all frames into numpy array (num_frames, H, W, 3)
pipe = subprocess.run(command, stdout=subprocess.PIPE, bufsize=10**8)
video = np.frombuffer(pipe.stdout, dtype=np.uint8).reshape(-1, H, W, 3)

# or alternatively load individual frames in a loop
nb_img = H*W*3 # H * W * 3 channels * 1-byte/channel
for i in range(0, len(pipe.stdout), nb_img):
    img = np.frombuffer(pipe.stdout, dtype=np.uint8, count=nb_img, offset=i).reshape(H, W, 3)

我想知道是否可以在 Python 中執行相同的過程,但無需先將整個視頻加載到 memory 中。 在我的腦海中,我正在想象這樣的事情:

  1. 打開一個緩沖區
  2. 按需尋找memory位置
  3. 將幀加載到 numpy arrays

我知道還有其他庫,例如 OpenCV 可以實現同樣的行為,但我想知道:

  • 是否可以使用這種 ffmpeg-pipe-to-numpy-array 操作有效地執行此操作?
  • 這是否會直接破壞 ffmpeg 的加速優勢,而不是通過 OpenCV 搜索/加載或首先提取幀然后加載單個文件?

在不將整部電影加載到 memory 的情況下尋找和提取幀是可能的,並且相對簡單。

當請求的幀不是關鍵幀時,會有一些加速損失。
當FFmpeg被請求尋找非關鍵幀時,它會尋找到請求幀之前最近的關鍵幀,並將從關鍵幀到請求幀的所有幀解碼。

演示代碼示例執行以下操作:

  • 使用運行幀計數器構建合成 1fps 視頻 - 非常適合測試。
  • 執行 FFmpeg 作為子進程,stdout 作為 output PIPE。
    代碼示例尋找到第 11 秒,並將持續時間設置為 5 秒。
  • 從 PIPE 讀取(並顯示)解碼的視頻幀,直到沒有更多幀要讀取。

這是代碼示例:

import numpy as np
import cv2
import subprocess as sp
import shlex

# Build synthetic 1fps video (with a frame counter):
# Set GOP size to 20 frames (place key frame every 20 frames - for testing).
#########################################################################
W, H = 320, 240 # video dimensions
video_path = 'video.mp4'  # path to video
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size={W}x{H}:rate=1 -vcodec libx264 -g 20 -crf 17 -pix_fmt yuv420p -t 60 {video_path}'))
#########################################################################


# ffmpeg command
command = [ 'ffmpeg',
            '-ss', '00:00:11',    # Seek to 11'th second.
            '-i', video_path,
            '-pix_fmt', 'bgr24',  # brg24 for matching OpenCV
            '-f', 'rawvideo',
            '-t', '5',            # Play 5 seconds long
            'pipe:' ]

# Execute FFmpeg as sub-process with stdout as a pipe
process = sp.Popen(command, stdout=sp.PIPE, bufsize=10**8)

# Load individual frames in a loop
nb_img = H*W*3  # H * W * 3 channels * 1-byte/channel

# Read decoded video frames from the PIPE until no more frames to read
while True:
    # Read decoded video frame (in raw video format) from stdout process.
    buffer = process.stdout.read(W*H*3)

    # Break the loop if buffer length is not W*H*3 (when FFmpeg streaming ends).
    if len(buffer) != W*H*3:
        break

    img = np.frombuffer(buffer, np.uint8).reshape(H, W, 3)

    cv2.imshow('img', img)  # Show the image for testing
    cv2.waitKey(1000)

process.stdout.close()
process.wait()
cv2.destroyAllWindows()

筆記:
當預先知道播放持續時間時,參數-t 5是相關的。
如果事先不知道播放持續時間,您可以刪除-t並在需要時中斷循環。


時間測量:

  1. 測量一次讀取所有幀。
  2. 在循環中逐幀測量閱讀量。
# 6000 frames:
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size={W}x{H}:rate=1 -vcodec libx264 -g 20 -crf 17 -pix_fmt yuv420p -t 6000 {video_path}'))

# ffmpeg command
command = [ 'ffmpeg',
            '-ss', '00:00:11',    # Seek to 11'th second.
            '-i', video_path,
            '-pix_fmt', 'bgr24',  # brg24 for matching OpenCV
            '-f', 'rawvideo',
            '-t', '5000',         # Play 5000 seconds long (5000 frames).
            'pipe:' ]



# Load all frames into numpy array
################################################################################
t = time.time()

# run ffmpeg and load all frames into numpy array (num_frames, H, W, 3)
process = sp.run(command, stdout=sp.PIPE, bufsize=10**8)
video = np.frombuffer(process.stdout, dtype=np.uint8).reshape(-1, H, W, 3)

elapsed1 = time.time() - t
################################################################################


# Load load individual frames in a loop
################################################################################
t = time.time()

# Execute FFmpeg as sub-process with stdout as a pipe
process = sp.Popen(command, stdout=sp.PIPE, bufsize=10**8)

# Read decoded video frames from the PIPE until no more frames to read
while True:
    # Read decoded video frame (in raw video format) from stdout process.
    buffer = process.stdout.read(W*H*3)

    # Break the loop if buffer length is not W*H*3 (when FFmpeg streaming ends).
    if len(buffer) != W*H*3:
        break

    img = np.frombuffer(buffer, np.uint8).reshape(H, W, 3)

elapsed2 = time.time() - t

process.wait()


################################################################################

print(f'Read all frames at once elapsed time: {elapsed1}')
print(f'Read frame by frame elapsed time: {elapsed2}')

結果:

Read all frames at once elapsed time: 7.371837854385376

Read frame by frame elapsed time: 10.089557886123657

結果表明,逐幀閱讀存在一定的開銷。

  • 開銷相對較小。
    開銷有可能與 Python 相關,而與 FFmpeg 無關。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM