根据this的帖子,您不能使用cv.VideoCapture在内存流中进行解码。
您可以通过“管道”将流解码到FFmpeg。
解决方案有点复杂,写入磁盘要简单得多,而且可能是更清洁的解决方案。
我正在发布一个使用 FFmpeg(和 FFprobe)的解决方案。
FFmpeg 有 Python 绑定,但解决方案是使用 subprocess 模块将 FFmpeg 作为外部应用程序执行。
(Python 绑定在 FFmpeg 上运行良好,但到 FFprobe 的管道却不行)。
我使用的是Windows 10,我将ffmpeg.exe和ffprobe.exe放在了执行文件夹中(你也可以设置执行路径)。
对于 Windows,请下载最新的(静态喜欢的)稳定版本。
我创建了一个执行以下操作的独立示例:
- 生成合成视频,并将其保存到 WebM 文件(用作测试的输入)。
- 将文件作为二进制数据读入内存(将其替换为来自服务器的 blob)。
- 将二进制流通过管道传输到 FFprobe,以查找视频分辨率。
如果事先知道分辨率,您可以跳过此部分。
通过管道连接到 FFprobe 使解决方案变得比应有的复杂。
- 将二进制流通过管道传输到 FFmpeg
stdin 进行解码,并从 stdout 管道中读取解码后的原始帧。
使用 Python 线程分块写入 stdin。
(使用stdin 和stdout 而不是命名管道的原因是为了与Windows 兼容。
管道架构:
-------------------- Encoded --------- Decoded ------------
| Input WebM encoded | data | ffmpeg | raw frames | reshape to |
| stream (VP9 codec) | ----------> | process | ----------> | NumPy array|
-------------------- stdin PIPE --------- stdout PIPE -------------
代码如下:
import numpy as np
import cv2
import io
import subprocess as sp
import threading
import json
from functools import partial
import shlex
# Build synthetic video and read binary data into memory (for testing):
#########################################################################
width, height = 640, 480
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size={}x{}:rate=1 -vcodec vp9 -crf 23 -t 50 test.webm'.format(width, height)))
with open('test.webm', 'rb') as binary_file:
in_bytes = binary_file.read()
#########################################################################
# https://stackoverflow.com/questions/5911362/pipe-large-amount-of-data-to-stdin-while-using-subprocess-popen/14026178
# https://stackoverflow.com/questions/15599639/what-is-the-perfect-counterpart-in-python-for-while-not-eof
# Write to stdin in chunks of 1024 bytes.
def writer():
for chunk in iter(partial(stream.read, 1024), b''):
process.stdin.write(chunk)
try:
process.stdin.close()
except (BrokenPipeError):
pass # For unknown reason there is a Broken Pipe Error when executing FFprobe.
# Get resolution of video frames using FFprobe
# (in case resolution is know, skip this part):
################################################################################
# Open In-memory binary streams
stream = io.BytesIO(in_bytes)
process = sp.Popen(shlex.split('ffprobe -v error -i pipe: -select_streams v -print_format json -show_streams'), stdin=sp.PIPE, stdout=sp.PIPE, bufsize=10**8)
pthread = threading.Thread(target=writer)
pthread.start()
pthread.join()
in_bytes = process.stdout.read()
process.wait()
p = json.loads(in_bytes)
width = (p['streams'][0])['width']
height = (p['streams'][0])['height']
################################################################################
# Decoding the video using FFmpeg:
################################################################################
stream.seek(0)
# FFmpeg input PIPE: WebM encoded data as stream of bytes.
# FFmpeg output PIPE: decoded video frames in BGR format.
process = sp.Popen(shlex.split('ffmpeg -i pipe: -f rawvideo -pix_fmt bgr24 -an -sn pipe:'), stdin=sp.PIPE, stdout=sp.PIPE, bufsize=10**8)
thread = threading.Thread(target=writer)
thread.start()
# Read decoded video (frame by frame), and display each frame (using cv2.imshow)
while True:
# Read raw video frame from stdout as bytes array.
in_bytes = process.stdout.read(width * height * 3)
if not in_bytes:
break # Break loop if no more bytes.
# Transform the byte read into a NumPy array
in_frame = (np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3]))
# Display the frame (for testing)
cv2.imshow('in_frame', in_frame)
if cv2.waitKey(100) & 0xFF == ord('q'):
break
if not in_bytes:
# Wait for thread to end only if not exit loop by pressing 'q'
thread.join()
try:
process.wait(1)
except (sp.TimeoutExpired):
process.kill() # In case 'q' is pressed.
################################################################################
cv2.destroyAllWindows()
备注:
- 如果您收到类似“找不到文件:ffmpeg...”的错误,请尝试使用完整路径。
例如(在 Linux 中):'/usr/bin/ffmpeg -i pipe: -f rawvideo -pix_fmt bgr24 -an -sn pipe:'