Python,如何获取gif动画帧

13

我正在寻找一种获取gif帧数的方法。我在谷歌、StackOverflow和其他网站上搜索,但找到的都是垃圾!有人知道该怎么做吗?我只需要简单的gif帧数。

7个回答

23

你使用的是哪种方法来加载/操作帧?你在使用PIL吗?如果没有,我建议你去看看它:Python Imaging Library,尤其是PIL gif页面

假设你正在使用PIL读取gif文件,那么确定你所看到的帧非常简单。使用seek将转到特定帧,tell将返回你正在查看的帧。

from PIL import Image
im = Image.open("animation.gif")

# To iterate through the entire gif
try:
    while 1:
        im.seek(im.tell()+1)
        # do something to im
except EOFError:
    pass # end of sequence
否则,我认为你只能通过寻找直到出现异常(EOFError)来找到gif中的帧数。

2
我不得不将“import Image”更改为“from PIL import Image”才能使它工作(我猜测我安装了最新的Pillow版本)。 - sellibitze
如果您需要从字节数组加载图像,请参见我下面的答案 - asm
1
我只想补充一下,你可以使用im.n_frames获取帧数(因此上面的代码可以改为简单的“for循环”)。 - Abang F.
警告:如果相邻的两个帧是相同的,则此方法仅会产生一个帧! - Daniel Chin

13

只需解析文件,GIF文件相当简单:

class GIFError(Exception): pass

def get_gif_num_frames(filename):
    frames = 0
    with open(filename, 'rb') as f:
        if f.read(6) not in ('GIF87a', 'GIF89a'):
            raise GIFError('not a valid GIF file')
        f.seek(4, 1)
        def skip_color_table(flags):
            if flags & 0x80: f.seek(3 << ((flags & 7) + 1), 1)
        flags = ord(f.read(1))
        f.seek(2, 1)
        skip_color_table(flags)
        while True:
            block = f.read(1)
            if block == ';': break
            if block == '!': f.seek(1, 1)
            elif block == ',':
                frames += 1
                f.seek(8, 1)
                skip_color_table(ord(f.read(1)))
                f.seek(1, 1)
            else: raise GIFError('unknown block type')
            while True:
                l = ord(f.read(1))
                if not l: break
                f.seek(l, 1)
    return frames

我猜这需要用到PIL,我曾尝试在一个包含44帧gif文件上使用它,但返回了0...(我使用Preview数了一下帧数,但我需要一些代码来获取2500个图像的信息)... - Zizouz212
1
@Zizouz212:这对我来说很有效,我在我的答案中使用了small_globe.gif测试文件。请将您的44帧gif图像文件上传到某个地方(比如在您的问题中),然后让我知道,以便我可以下载它并查看是否能找出问题的原因。 - martineau
在Python 3+中,你必须将字符串更改为字节字符串,否则比较会返回“False”。例如:'GIF87a' >> b'GIF87a', >> b',',等等。 - Benedikt

6
最近我也遇到了同样的问题,发现有关GIF的文档特别缺乏。这是我的解决方案,使用imageio's get_reader读取图像的字节(例如,如果您刚刚通过HTTP 获取图像),它会方便地将帧存储在numpy matrices中:
import imageio
gif = imageio.get_reader(image_bytes, '.gif')

# Here's the number you're looking for
number_of_frames = len(gif)

for frame in gif:
  # each frame is a numpy matrix

如果您只需要打开一个文件,请使用:
gif = imageio.get_reader('cat.gif')

2

1

好的,也许9年时间有点太长了,但这是我的答案。

import tkinter as tk
from PIL import Image  

    

def number_of_frames(gif):
    "Prints and returns the number of frames of the gif"
    print(gif.n_frames)
    return gif.n_frames


def update(ind):
    global root, label

    frame = frames[ind]
    ind += 1
    if ind == frameCnt:
        ind = 0
    label.configure(image=frame)
    root.after(100, update, ind)


file = Image.open("001.gif")
frameCnt = number_of_frames(file)
root = tk.Tk()
frames = [tk.PhotoImage( file='001.gif', format = f'gif -index {i}')
            for i in range(frameCnt)]
label = tk.Label(root)
label.pack()
root.after(0, update, 0)
root.mainloop()

1

我对目前提出的答案进行了一些计时,以下是结果:

  • Pillow seek:每次循环13.2毫秒±58.3微秒(7次运行的平均值±标准偏差,每个循环100次)
  • 自定义解析:每次循环115微秒±647纳秒(7次运行的平均值±标准偏差,每个循环10000次)
  • Pillow n_frames:每次循环13.2毫秒±169微秒(7次运行的平均值±标准偏差,每个循环100次)
  • ImageIO improps(通过Pillow):每次循环13.1毫秒±23.1微秒(7次运行的平均值±标准偏差,每个循环100次)

因此,尽管是纯Python,但自定义解析比使用Pillow快大约100倍...有趣。在其他解决方案中,我喜欢ImageIO,因为它很简短;但是,我是开发人员之一,所以显然有偏见。


设置代码:

# get a test GIF (as in-memory stream to avoid measuring file IO)
import imageio.v3 as iio
import io

gif_array = iio.imread("imageio:newtonscradle.gif", index=None)
test_file = io.BytesIO()
iio.imwrite(test_file, gif_array, format="GIF")

# ImageIO is more strict with file handling and would close test_file
# It does handle byte strings natively though, so we can pass that for timings
gif_bytes = iio.imwrite("<bytes>", gif_array, format="GIF")

Pillow seek:

%%timeit

from PIL import Image

test_file.seek(0) # reset file

n_frames = 1  # opening returns the first frame
with Image.open(test_file) as file:
    # To iterate through the entire gif
    try:
        while 1:
            file.seek(file.tell()+1)
            n_frames += 1
            # do something to im
    except EOFError:
        pass # end of sequence

assert n_frames == 36

自定义解析

%%timeit

class GIFError(Exception): pass

def get_gif_num_frames(filename):
    frames = 0
    with io.BytesIO(filename) as f:  # I modified this line to side-step measuring IO
        if f.read(6) not in (b'GIF87a', b'GIF89a'): # I added b to mark these as byte strings
            raise GIFError('not a valid GIF file')
        f.seek(4, 1)
        def skip_color_table(flags):
            if flags & 0x80: f.seek(3 << ((flags & 7) + 1), 1)
        flags = ord(f.read(1))
        f.seek(2, 1)
        skip_color_table(flags)
        while True:
            block = f.read(1)
            if block == b';': break  # I also added a b'' here
            if block == b'!': f.seek(1, 1) # I also added a b'' here 
            elif block == b',':  # I also added a b'' here
                frames += 1
                f.seek(8, 1)
                skip_color_table(ord(f.read(1)))
                f.seek(1, 1)
            else: raise GIFError('unknown block type')
            while True:
                l = ord(f.read(1))
                if not l: break
                f.seek(l, 1)
    return frames

n_frames = get_gif_num_frames(gif_bytes)
assert n_frames == 36

Pillow n_frames:

%%timeit

from PIL import Image

test_file.seek(0) # reset file

with Image.open(test_file) as file:
    # To iterate through the entire gif
    n_frames = file.n_frames

assert n_frames == 36

ImageIO的改进(通过pillow):

%%timeit

import imageio.v3 as iio

props = iio.improps(gif_bytes, index=None)
n_frames = props.shape[0]
assert n_frames == 36

我正在编写一个基于PyAV的新插件,它比pillow更快,但仍然比纯Python方法慢。从语法上讲,它与ImageIO(通过pillow)的方法非常相似:

%%timeit
# IIO improps (via PyAV)
# 507 µs ± 17.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# Note: at the time of this writing this approach is still a PR.
#       it needs documentation and test coverage before being merged.

import imageio.v3 as iio

props = iio.improps(gif_bytes, index=None, plugin="pyav")
n_frames = props.shape[0]
assert n_frames == 36

0
这是一些代码,可以让您获得GIF中每个帧的持续时间值列表:
from PIL import Image
gif_image = Image.open("animation.gif")
metadata = []

for i in range(gif_image.n_frames):
    gif_image.seek(i)
    duration = gif_image.info.get("duration", 0)
    metadata.append(duration)

您可以修改上述代码,以捕获每个帧中的其他数据,例如背景颜色索引、透明度或版本。每个帧上的info字典如下所示:

{'version': b'GIF89a', 'background': 0, 'transparency': 100, 'duration': 70}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接