Variable fps (frame per second) in cv2

Question:

I use cv2 for creating videos from different frames that I have. When I create the video, I cannot change the fps (frame per second). I want the video be slow at the beginning but fast towards the end, meaning small fps at the beginning but large ones towards the end. However, when I instantiate cv2.VideoWriter I cannot change the fps anymore. What should I do?

Replicable code

import numpy as np
import cv2, os
import matplotlib

image_size = 200
def create_image_array(image_size):
  image_array = np.random.randn(image_size, image_size)
  row = np.random.randint(0, image_size)
  image_array[row, :] = 100
  return image_array

frame_numbers = 200
for i in range(frame_numbers):
  image_array = create_image_array(image_size)
  matplotlib.image.imsave(f'./shots/frame_{i:03d}.png', image_array)

def make_a_video(shots_folder, video_path):

    shots_folder = 'shots'
    fps = 25
    images = [img for img in os.listdir(shots_folder) if img.endswith(".png")]

    images = sorted(images)[:]
    frame = cv2.imread(os.path.join(shots_folder, images[0]))
    height, width, layers = frame.shape

    video = cv2.VideoWriter(video_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))

    for image in images:
        video.write(cv2.imread(os.path.join(shots_folder, image)))

    cv2.destroyAllWindows()
    video.release()

shots_folder = 'shots'
video_path = 'video.mp4'  
make_a_video(shots_folder, video_path)
Asked By: Sepide

||

Answers:

Think of it as if you are copying one video to another. While doing this you actually don’t want to change the output frame rate but the input frame rate. Here is a very basic example of how you can continuesly change the read frame rate while you are writing a stream of constant frame rate.

The example generates a sample video of 200 frames and writes a video of 320 frames. The first 100 frames are read with 5 fps, the next 100 frames are read with an increasing rate from 5 to 25 fps and the last 120 frames are read with 25 fps.

import math
import numpy as np
import cv2

image_size = 200
src_fps = 25
dst_fps = 25


# create sample frame with four rotating balls
def create_sample_frame(size, frame_no, fps):
    img = np.zeros((size, size, 3), dtype=np.uint8)
    ctr = size // 2

    for i in range(1, 5):
        phi = -frame_no / fps * i
        r = size * (0.5 - 0.1 * i)
        cv2.circle(img, (round(math.sin(phi) * r + ctr), round(math.cos(phi) * r + ctr)), size // 30, (0, 255, 0), -1)

    return img


frames = [create_sample_frame(image_size, i, src_fps) for i in range(200)]
height, width, layers = frames[0].shape
video = cv2.VideoWriter('video.mp4', cv2.VideoWriter_fourcc(*'mp4v'), dst_fps, (width, height))

# ramp array with number of destination frames, start fps of source, end fps of source
fps_ramp = [[100, 5, 5], [100, 5, 25], [120, 25, 25]]

src_pos = 0
dst_pos = 0

for n, start_src_fps, end_src_fps in fps_ramp:
    for i in range(n):
        print(f"writing source frame {int(src_pos)} to destination frame {dst_pos}")
        video.write(frames[round(src_pos)])
        dst_pos += 1
        cur_fps = (end_src_fps - start_src_fps) * (i / n) + start_src_fps
        src_pos += cur_fps / src_fps

video.release()

Result:

enter image description here

Answered By: Markus

I didn’t understand your problem. @Markus answer is correct for variable FPS. I saw you asked

How can I directly pass the array to the video because I create them on the fly?

and as you said

Please use the replicable code

I think you had trouble with applying Markus answer for your case.
maybe this is the code you’re looking for

import numpy as np
import cv2

image_size = 200
src_fps = 25
dst_fps = 25


def create_image_array(image_size):
  image_array = np.random.randn(image_size, image_size, 3) * 255
  image_array = image_array.astype(np.uint8)
  row = np.random.randint(0, image_size)
  image_array[row, :] = 100

  if len(image_array.shape) == 2:
    image_array = cv2.cvtColor(image_array,cv2.COLOR_GRAY2RGB)
  return image_array


frames = [create_image_array(image_size) for i in range(200)]

height, width, layers = frames[0].shape

video = cv2.VideoWriter('video.mp4', cv2.VideoWriter_fourcc(*'mp4v'), dst_fps, (width, height))

# ramp array with number of destination frames, start fps of source, end fps of source
fps_ramp = [[100, 5, 5], [100, 5, 25], [120, 25, 25]]

src_pos = 0
dst_pos = 0

for n, start_src_fps, end_src_fps in fps_ramp:
    for i in range(n):
        print(f"writing source frame {int(src_pos)} to destination frame {dst_pos}")
        video.write(frames[round(src_pos)])
        dst_pos += 1
        cur_fps = (end_src_fps - start_src_fps) * (i / n) + start_src_fps
        src_pos += cur_fps / src_fps

video.release()

this code generates a colorful video (I couldn’t upload GIF because of StackOverflow upload limits)

enter image description here

if you want grayscale, change image_array = np.random.randn(image_size, image_size, 3) * 255 to image_array = np.random.randn(image_size, image_size) * 255

enter image description here

Answered By: Ali Ent