Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to output x265 compressed video with cv2.VideoWriter

I'm doing some rendering on a 45-min 1.2GB video 80,0000 frames of size 1344x756 each and the video is in the mp4 format, I'm trying to output a video with x265 compression the problem is when I'm using cv2.VideoWriter the output size for 10 minutes of the video exceeds 2GB which is not what I'm intending to end up with so I tried the following on mac osx and ubuntu 18:

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

All I'm getting is runtime warnings:

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

What I'm trying to achieve is not necessarily the highest quality output but should be a good quality and the lowest size possible.

like image 385
sK500 Avatar asked Apr 16 '20 21:04

sK500


1 Answers

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.

like image 189
Rotem Avatar answered Sep 29 '22 09:09

Rotem