Search code examples
pythonnumpyopencvpython-imaging-librarypython-imageio

Fastest way to load an animated GIF in Python into a numpy array


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).


Solution

  • Your code using Pillow is very inefficient! Images 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.