102

I currently try to make a movie out of images, but i could not find anything helpful .

Here is my code so far:

import time

from PIL import  ImageGrab

x =0

while True:
    try:
        x+= 1
        ImageGrab().grab().save('img{}.png'.format(str(x))
    except:
        movie = #Idontknow
        for _ in range(x):
            movie.save("img{}.png".format(str(_)))

movie.save()
0

9 Answers 9

169

You could consider using an external tool like ffmpeg to merge the images into a movie (see answer here) or you could try to use OpenCv to combine the images into a movie like the example here.

I'm attaching below a code snipped I used to combine all png files from a folder called "images" into a video.

import cv2
import os

image_folder = 'images'
video_name = 'video.avi'

images = [img for img in os.listdir(image_folder) if img.endswith(".png")]
frame = cv2.imread(os.path.join(image_folder, images[0]))
height, width, layers = frame.shape

video = cv2.VideoWriter(video_name, 0, 1, (width,height))

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

cv2.destroyAllWindows()
video.release()

It seems that the most commented section of this answer is the use of VideoWriter. You can look up it's documentation in the link of this answer (static) or you can do a bit of digging of your own. The first parameter is the filename, followed by an integer (fourcc in the documentation, the codec used), the FPS count and a tuple of the dimensions of the frame. If you really like digging in that can of worms, here's the fourcc video codecs list.

10
  • 4
    This actually not always does work for ubuntu github.com/ContinuumIO/anaconda-issues/issues/223 Commented Dec 19, 2017 at 15:49
  • 13
    It worked on Ubuntu when i created like video = cv2.VideoWriter(video_name, cv2.VideoWriter_fourcc(*'XVID'), 30, (width,height)) But didn't work when: video = cv2.VideoWriter(video_name, -1, 1, (width,height)) Commented Oct 11, 2018 at 9:54
  • 1
    FWIW, on my Window 10 with Python 3.6 and opencv 3.3.1, I got a ffmpeg conversion error. Changing the "fourcc" argument, which handles video codecs (2nd argument) of cv2.VideoWriter from -1 to 0 solved it.
    – Soltius
    Commented Jan 7, 2019 at 14:56
  • 1
    how can you precise the framerate ?
    – nassim
    Commented Nov 24, 2019 at 20:10
  • 8
    @nassim cv2.VideoWriter(output_filename, fourcc, fps, self._window_shape) found Here
    – Bolli
    Commented Nov 19, 2020 at 13:34
65

I found an alternative solution using ffmpeg:

def save():
    os.system("ffmpeg -r 1 -i img%01d.png -vcodec mpeg4 -y movie.mp4")
5
  • 1
    how to define fps while merging all images
    – Aman Jain
    Commented Mar 4, 2018 at 19:49
  • 6
    fps is defined with the "-r" bit - so "-r 1" would be 1 fps, "-r 30" would be 30, etc. Commented Mar 26, 2018 at 1:36
  • 90
    This is not python, you are just running ffmpeg command through python!
    – Azim
    Commented May 3, 2019 at 20:58
  • 3
    why does the image quality does horrible after fps is increased to 60 or over ? Commented Nov 18, 2019 at 12:16
  • 1
    use -vb option to increase bit rate. "ffmpeg -r 1 -i img%01d.png -vcodec mpeg4 -y -vb 40M movie.mp4"
    – Jariani
    Commented Jan 21, 2020 at 11:52
38

Here is a minimal example using moviepy. For me this was the easiest solution.

import os
import moviepy.video.io.ImageSequenceClip
image_folder='folder_with_images'
fps=1

image_files = [os.path.join(image_folder,img)
               for img in os.listdir(image_folder)
               if img.endswith(".png")]
clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(image_files, fps=fps)
clip.write_videofile('my_video.mp4')
2
21

I use the ffmpeg-python binding. You can find more information here.

import ffmpeg
(
    ffmpeg
    .input('/path/to/jpegs/*.jpg', pattern_type='glob', framerate=25)
    .output('movie.mp4')
    .run()
)
0
4

@Wei Shan Lee (and others): Sure, my whole code looks like this

import os
import moviepy.video.io.ImageSequenceClip
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True

image_files = []

for img_number in range(1,20): 
    image_files.append(path_to_images + 'image_folder/image_' + str(img_number) + '.png') 

fps = 30

clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(image_files, fps=fps)
clip.write_videofile(path_to_videos + 'my_new_video.mp4')
0
1

When using moviepy's ImageSequenceClip it is important that the images are in an ordered sequence.

While the documentation states that the frames can be ordered alphanumerically under the hood, I found this not to be the case.

So, if you are having problems, make sure to manually order the frames first.

0

I've created a function to do this. Similar to the first answer (using opencv) but wanted to add that for me, ".mp4" format did not work. That's why I use the raise within the function.

import cv2
import typing

def write_video(video_path_out:str,
                frames_sequence:typing.Tuple[np.ndarray,...]):
  
  if ".mp4" in video_path_out: raise ValueError("[ERROR] This method does not support .mp4; try .avi instead")
  height, width, _ = frames_sequence[0].shape
  # 0 means no preprocesing
  # 1 means  each image will be played with 1 sec delay (1fps)
  out = cv2.VideoWriter(video_path_out,0, 1,(width,height))
  for frame in frames_sequence:
      out.write(frame)
  out.release()

# you can use as much images as you need, I just use 3 for this example
# put your img1_path,img2_path, img3_path

img1 = cv2.imread(img1_path)
img2 = cv2.imread(img2_path)
img3 = cv2.imread(img3_path)
# img1 can be cv2.imread out; which is a np.ndarray; you can also se PIL
# if you'd like to.
frames_sequence = [img1,img2,img3]

write_video(video_path_out = "mypath_outvideo.avi",
frames_sequence = frames_sequence
)

Hope it's useful!

0

Little hacky but avoids creating the file and just lets you watch it in real time.

import glob 
from PIL import Image
import cv2 
import numpy as np 
import time 
####### PARAMS 

imgs_path = "/Users/user/Desktop/lidar_rig/ouster_data_wide_angle_cam_v9/imgs/*"

cur_img_index = 0
ds_st_index = 0
ds_en_index = -1

fps = 35 # tweak this 


###### PARAMS 

def cnvt_pil_to_cv2(pil_img):
    open_cv_image = np.array(pil_img) 
    # Convert RGB to BGR 
    open_cv_image = open_cv_image[:, :, ::-1].copy()     
    return open_cv_image




img_files = sorted(glob.glob(imgs_path), key = lambda x: int(x.split('/')[-1].split('.')[0]))[ds_st_index:ds_en_index][cur_img_index:]
cnt = 0 

for img_pth in img_files:
    if not cnt %50: ## DDD -- avoid mem overflow  
        cv2.destroyAllWindows()
    img = Image.open(img_pth).resize((750,750))
    cv2.imshow(img_pth.split("/")[-1], cnvt_pil_to_cv2(img))
    time.sleep(float(1.0/float(fps)))
    cnt+=1 
0

As an additional contribution, if anyone struggles to get the loaded images to be sorted alphabetically, here’s a version that uses the built-in sorted(os.listdir(image_folder)) function.

import os
import moviepy.video.io.ImageSequenceClip
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True

image_folder='frames'
fps=10


image_files = [os.path.join(image_folder,img)
               for img in sorted(os.listdir(image_folder))
               if img.endswith(".png")]
print(image_files)

clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(image_files, fps=fps)
clip.write_videofile('myvideo.mp4')

However, note that it is filesystem/filename dependant and will sort numbers wrong (1,10,2,3…). Below is another solution with a sorted_alphanumeric(data) function that solves that (from another thread)

import os
import moviepy.video.io.ImageSequenceClip
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True

image_folder='frames'
fps=10

import re
def sorted_alphanumeric(data):
    convert = lambda text: int(text) if text.isdigit() else text.lower()
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(data, key=alphanum_key)

image_files = [os.path.join(image_folder,img)
               for img in sorted_alphanumeric(os.listdir(image_folder))
               if img.endswith(".png")]
print(image_files)

clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(image_files, fps=fps)
clip.write_videofile('myvideo.mp4')

Not the answer you're looking for? Browse other questions tagged or ask your own question.