0

I use OpenCV to capture webcam video as frames to a list variable, then write that list to disk both as image sequence and as a video file. The image quality is fine. But the video is horribly compressed, with a bitrate of less than 4mbit, sometimes even less than 3mbit.

I've spent days and nights on Google finding solutions, tried many fourccs and other wrappers like WriteGear and ffmpeg-python, but to no avail.

Can anyone please tell me how I

a) specify the bitrate for my H.264 file, and

b) choose a lossless compression codec?

(Python 3.10 on MacOS MacBook Pro M1 / MacOS 13.0.1)

# ::::: import libraries and set variables

import cv2, time, os, shutil, datetime as dt
frames = []
frame_dimensions_known = False

# ::::: define file and folder names

output_folder_name = "output"
frame_folder_name = "frames"
video_file_name = "video.mp4"
frame_file_name = "frame_x.jpg"

# ::::: create output folders and paths

path_script = os.path.abspath(os.path.dirname(__file__))
path_output = os.path.join(path_script,output_folder_name)
if not os.path.exists(path_output):
    os.mkdir(path_output)
    path_session_fold
path_session_folder  = os.path.join(
    path_script,
    output_folder_name,
    dt.datetime.now().strftime("%y%m%d_%H%M%S"))
os.mkdir(path_session_folder)
path_video =  os.path.join(path_session_folder,video_file_name)
path_frames = os.path.join(path_session_folder,frame_folder_name)
os.mkdir(path_frames)


# ::::: open webcam stream

vcap = cv2.VideoCapture(0)
start_time = (time.time() * 1000)
if vcap.isOpened() is False:
    print("[Exiting]: Error accessing webcam stream.")
    exit(0)

# ::::: read frames

while True:
    _grabbed, _frame = vcap.read()
    if _grabbed is False:
        print('[Exiting] No more frames to read')
        exit(0)

    # ::::: get frame dimensions on first read
    if not frame_dimensions_known:
        height, width, channels = _frame.shape
        frame_dimensions_known = True

    # ::::: append frame to list
    frames.append(_frame)

    # ::::: process frames
    pass

    # ::::: display frame
    cv2.imshow("cam", _frame)
    if cv2.waitKey(1) == ord("q"):
        break

# ::::: close webcam stream

vcap.release()
cv2.destroyAllWindows()

# ::::: calculate frame rate

stop_time = (time.time() * 1000)
total_seconds = (stop_time - start_time) / 1000
fps = len(frames) / total_seconds

# ::::: open video writer

fourcc = cv2.VideoWriter_fourcc(*'mp4v')
video_writer = cv2.VideoWriter(
    path_video,
    fourcc,
    fps,
    (width, height)
)

# ::::: write frames to disk as files and video

frame_number = 0
for frame in frames:

    # ::::: write video

    video_writer.write(frame)  # Write out frame to video

    # ::::: construct image file path

    frame_number = frame_number + 1
    frame_number_file = str(frame_number).zfill(4)
    frame_name = frame_file_name.replace('x',frame_number_file)
    frame_path = os.path.join(path_frames,frame_name)

    # ::::: write image

    cv2.imwrite(
        frame_path,
        frame,
        [cv2.IMWRITE_JPEG_QUALITY, 100]
    )

# ::::: clean up

video_writer.release
print (f"Written {frame_number} frames at {fps} FPS.")

bozolino
  • 11
  • 2
  • 1
    You could try to set the quality to 100%: `video_writer.set(cv2.VIDEOWRITER_PROP_QUALITY, 100)` though this is not supported by all codecs – MSpiller Mar 24 '23 at 10:34
  • thanks! tried it, but it didn't increase the quality or the bitrate... – bozolino Mar 24 '23 at 18:19
  • In case you choose to use FFmpeg directly, you may take a look at my [following answer](https://stackoverflow.com/a/61281547/4926757). For near-lossless compression use `-crf 17`. – Rotem Mar 24 '23 at 20:39

2 Answers2

0

Try using "avc1" as the fourcc, "mp4v" was using the mpeg4 video codec for me.

Also video_writer.release should be video_writer.release()

If neither of these changes help, you can try downloading and installing FFmpeg.


Since you're already writing frames to jpeg, you can use ffmpeg to turn those frames into a video. While not quite lossless, hevc video codec is very efficient. You could use h264 or mpeg4 as well.

import subprocess

subprocess.run([
  "ffmpeg", "-start_number", "1", "-i", "frames/frame_%04d.jpg", 
  "-c:v", "hevc", "-r", f"{fps}", path_video,
])
WyattBlue
  • 591
  • 1
  • 5
  • 21
  • Thanks for the suggestion. I have tried "avc1" before and tried again now, but it did not help. And thanks for the bug correction : ) I have FFmpeg installed, and my OpenCV is compiled with FFmpeg support. How can I write a list of OpenCV frames to a video file using FFmpeg - and choose (near-)lossless compression? – bozolino Mar 24 '23 at 18:23
  • When I run your code, my video bitrate is okay. I am just using a regular `opencv-python` pip install on my M1 Mac – WyattBlue Mar 24 '23 at 21:24
  • can you please post the bitrate and image resolution of the video file you get? – bozolino Mar 24 '23 at 22:36
  • Sure! video-codec: h264 resolution: 1920x1080 and bitrate: 6187413 – WyattBlue Mar 24 '23 at 23:04
0

After many attempts, I've decided that calling ffmpeg directly gives me the best results.

Hardware accelerated version for Mac M1 Max:

import subprocess, os

image_folder = '/path/to/my/folder'
frame_rate = 25.8 # or whatever my script has computed
output_file = '/path/to/my/file.mp4'

inputs = os.path.join(image_folder, '*.jpg')

       subprocess.run([
                "ffmpeg",
                "-framerate", f"{frame_rate}",
                "-threads", "16",
                "-pattern_type", "glob",
                "-i", inputs,
                "-c:v", "h264_videotoolbox",
                "-q:v", "85",
                output_file,
            ]) 

For other platforms, use:

       subprocess.run([
                "ffmpeg",
                "-framerate", f"{frame_rate}",
                "-threads", "16",
                "-pattern_type", "glob",
                "-i", inputs,
                "-c:v", "libx264",
                "-crf", "18",
                output_file,
            ]) 
bozolino
  • 11
  • 2