Search code examples
pythonopencvffmpegcomputer-visionvideo-processing

Variable fps (frame per second) in cv2


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)

Solution

  • 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