How to output x265 compressed video with cv2.VideoWriter

As far as I know, OpenCV VideoWriter has no support for HEVC encoding (yet).

I recommend you using FFmpeg as sub-process, and PIPE the rendered frames to stdin input stream of ffmpeg.

You may use Python binding for ffmpeg like ffmpeg-python, or execute ffmpeg using Python subprocess.

Using ffmpeg, you have much more control over video encoding parameters compared to cv2.VideoWriter (cv2.VideoWriter is designed for simplicity on expanse of flexibility).

Here is a sample code that renders 50 frames, stream frames to ffmpeg that encodes MP4 video file with HEVC video codec:

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()

Notes:

  • ffmpeg executable must be in the execution path of the Python script.

  • For Linux, in case ffmpeg is not in the execution path, you may use the full path:

     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)
    

    (Assuming ffmpeg executable is in /usr/bin/).

  • Python 3’s f-Strings syntax requires Python version 3.6 or above.

Leave a Comment