如何使用 cv2.VideoWriter 输出 x265 压缩视频

How to output x265 compressed video with cv2.VideoWriter

我正在对一个 45 分钟的 1.2GB 视频进行一些渲染,每个视频有 80,0000 帧,每个帧大小为 1344x756,并且视频是 mp4 格式,我正在尝试输出 x265 压缩的视频问题是当我使用 cv2.VideoWriter 时,10 分钟的视频输出大小超过 2GB,这不是我想要的结果,所以我在 mac osx 上尝试了以下操作并且ubuntu 18:

codec = cv2.VideoWriter_fourcc(*'HEVC')
out = cv2.VideoWriter('output.mp4', 'HEVC', fps, (width, height))

我得到的只是运行时警告:

OpenCV: FFMPEG: tag 0x43564548/'HEVC' is not found (format 'mp4 / MP4 (MPEG-4 Part 14)')'

我要实现的目标不一定是最高质量的输出,但应该是质量好且尺寸尽可能小。

据我所知,OpenCV VideoWriter 不支持 HEVC 编码(目前)。

我建议您使用 FFmpeg 作为子进程,并将渲染的帧通过 PIPE 传输到 ffmpegstdin 输入流。

您可以对 ffmpeg 使用 Python 绑定,例如 ffmpeg-python, or execute ffmpeg using Python subprocess

使用 ffmpeg,与 cv2.VideoWriter 相比,您可以更好地控制视频编码参数(cv2.VideoWriter 的设计目的是为了简化和扩展灵活性)。

这是一个渲染 50 帧的示例代码,将帧流式传输到 ffmpeg,使用 HEVC 视频编解码器对 MP4 视频文件进行编码:

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

width, height, n_frames, fps = 1344, 756, 50, 25  # 50 frames, resolution 1344x756, and 25 fps

output_filename = 'output.mp4'

# Open ffmpeg application as sub-process
# FFmpeg input PIPE: RAW images in BGR color format
# FFmpeg output MP4 file encoded with HEVC codec.
# Arguments list:
# -y                   Overwrite output file without asking
# -s {width}x{height}  Input resolution width x height (1344x756)
# -pixel_format bgr24  Input frame color format is BGR with 8 bits per color component
# -f rawvideo          Input format: raw video
# -r {fps}             Frame rate: fps (25fps)
# -i pipe:             ffmpeg input is a PIPE
# -vcodec libx265      Video codec: H.265 (HEVC)
# -pix_fmt yuv420p     Output video color space YUV420 (saving space compared to YUV444)
# -crf 24              Constant quality encoding (lower value for higher quality and larger output file).
# {output_filename}    Output file name: output_filename (output.mp4)
process = sp.Popen(shlex.split(f'ffmpeg -y -s {width}x{height} -pixel_format bgr24 -f rawvideo -r {fps} -i pipe: -vcodec libx265 -pix_fmt yuv420p -crf 24 {output_filename}'), stdin=sp.PIPE)

# Build synthetic video frames and write them to ffmpeg input stream.
for i in range(n_frames):
    # Build synthetic image for testing ("render" a video frame).
    img = np.full((height, width, 3), 60, np.uint8)
    cv2.putText(img, str(i+1), (width//2-100*len(str(i+1)), height//2+100), cv2.FONT_HERSHEY_DUPLEX, 10, (255, 30, 30), 20)  # Blue number

    # Write raw video frame to input stream of ffmpeg sub-process.
    process.stdin.write(img.tobytes())

# Close and flush stdin
process.stdin.close()

# Wait for sub-process to finish
process.wait()

# Terminate the sub-process
process.terminate()

备注:

  • ffmpeg 可执行文件必须在 Python 脚本的执行路径中。

  • 对于Linux,如果ffmpeg不在执行路径中,可以使用完整路径:

     process = sp.Popen(shlex.split(f'/usr/bin/ffmpeg -y -s {width}x{height} -pixel_format bgr24 -f rawvideo -r {fps} -i pipe: -vcodec libx265 -pix_fmt yuv420p -crf 24 {output_filename}'), stdin=sp.PIPE)
    

    (假设 ffmpeg 可执行文件在 /usr/bin/ 中)。

  • Python 3's f-Strings 语法需要 Python 3.6 或以上版本。