Surprisingly I couldn't see any coverage of this.
I've found 3 recognised ways of performing this - Pillow, OpenCV, and Imageio. The results surprised me, so I've posted them as a self-answering Q&A (below).
Your code using Pillow is very inefficient! Image
s are compatibable with Numpy's array interface so your conversion code is complicating things.
I'd use the following helper to get the frames out into a Numpy array:
from PIL import Image, ImageSequence
import numpy as np
def load_frames(image: Image, mode='RGBA'):
return np.array([
np.array(frame.convert(mode))
for frame in ImageSequence.Iterator(image)
])
with Image.open('animated.gif') as im:
frames = load_frames(im)
This runs in basically the same time as the others. For example, with a 400x400 pixel, 21 frame, GIF I have, it takes mimread ~140ms, while Pillow takes ~130ms.
Update: I've just had a play with CV2 and noticed its "wall clock" time is better (i.e. what you were measuring) because it's doing work in other threads. For example, if I run using the Jupyter %time
magic, I get the following output:
ImageIO
CPU times: user 135 ms, sys: 9.81 ms, total: 145 ms
Wall time: 145 ms
PIL
CPU times: user 127 ms, sys: 3.03 ms, total: 130 ms
Wall time: 130 ms
CV2
CPU times: user 309 ms, sys: 95 ms, total: 404 ms
Wall time: 89.7 ms
I.e. although it's finishing the loop in 90ms, it's used ~4.5x that CPU time in total.
So if you're interested in the time to complete for a single large image, you might want to use CV2. But if you were batch processing lots of images, I'd suggest using Pillow in a multiprocessing Pool
.