ImageSequence 模块

下面给你一个 Pillow(PIL Fork)中 ImageSequence 模块 的完整入门指南,包含 GIF/APNG 动图解析、帧提取、帧率控制、帧合并、动图优化、转视频、帧动画生成等高级应用。
(基于 Pillow ≥ 9.0,Python 3.8+)


1. 安装 Pillow

pip install --upgrade pillow

2. 基本导入

from PIL import Image, ImageSequence

3. ImageSequence 模块概述

ImageSequence 提供对 多帧图像(如 GIF、APNG、TIFF) 的帧级访问,支持:

  • 逐帧遍历
  • 获取每帧的 info(持续时间、处置方式)
  • 帧提取、合并、编辑
  • 动图优化(减少颜色、压缩)
  • 转为视频(配合 imageioopencv

适用场景

  • 动图拆帧做数据集
  • 提取表情包帧
  • 优化 GIF 体积
  • 动图转视频/倒放/加速
  • 帧级特效处理

4. 核心功能:ImageSequence.Iterator

img = Image.open("animation.gif")  # 自动识别多帧

# 遍历所有帧
for i, frame in enumerate(ImageSequence.Iterator(img)):
    frame.save(f"frame_{i:03d}.png")
    print(f"第 {i} 帧: {frame.size}, 模式: {frame.mode}")

5. 获取帧信息(info 字典)

说明
duration每帧显示时间(毫秒)
disposal处置方式(0=无,1=不处置,2=恢复背景,3=恢复前一帧)
loop循环次数(0=无限)
background背景色索引
img = Image.open("animation.gif")
print(f"总帧数: {img.n_frames}")
print(f"是否动图: {img.is_animated}")
print(f"循环次数: {img.info.get('loop', '无限')}")

for i, frame in enumerate(ImageSequence.Iterator(img)):
    duration = frame.info.get('duration', 100)  # 默认 100ms
    print(f"帧 {i}: 持续 {duration}ms")

6. 帧提取 + 优化保存

def extract_frames(gif_path, output_dir="frames"):
    import os
    os.makedirs(output_dir, exist_ok=True)

    img = Image.open(gif_path)
    palette = img.getpalette()  # 保留调色板

    for i, frame in enumerate(ImageSequence.Iterator(img)):
        # 转换为 RGB 避免调色板问题
        frame_rgb = frame.convert("RGB")
        frame_rgb.save(f"{output_dir}/frame_{i:03d}.png")

    print(f"提取完成: {img.n_frames} 帧")

extract_frames("dance.gif")

7. 高级应用

7.1 动图倒放

def reverse_gif(input_path, output_path):
    img = Image.open(input_path)
    frames = [frame.copy() for frame in ImageSequence.Iterator(img)]
    frames.reverse()

    # 保持第一帧的调色板
    frames[0].save(
        output_path,
        save_all=True,
        append_images=frames[1:],
        duration=img.info.get('duration', 100),
        loop=img.info.get('loop', 0),
        disposal=2
    )
    print(f"倒放完成: {output_path}")

reverse_gif("forward.gif", "reverse.gif")

7.2 调整播放速度(加速/减慢)

def change_speed(gif_path, output_path, speed_factor=2.0):
    # speed_factor >1 加速,<1 减慢
    img = Image.open(gif_path)
    original_duration = img.info.get('duration', 100)
    new_duration = max(1, int(original_duration / speed_factor))  # 最小 1ms

    frames = list(ImageSequence.Iterator(img))
    frames[0].save(
        output_path,
        save_all=True,
        append_images=frames[1:],
        duration=new_duration,
        loop=img.info.get('loop', 0)
    )
    print(f"速度调整完成: {speed_factor}x")

change_speed("normal.gif", "fast.gif", 3.0)  # 3倍速

7.3 动图转视频(MP4)

pip install imageio[ffmpeg]
import imageio
from PIL import ImageSequence

def gif_to_mp4(gif_path, mp4_path, fps=10):
    img = Image.open(gif_path)
    writer = imageio.get_writer(mp4_path, fps=fps, codec='libx264', quality=8)

    for frame in ImageSequence.Iterator(img):
        writer.append_data(np.array(frame.convert("RGB")))

    writer.close()
    print(f"转为视频: {mp4_path}")

gif_to_mp4("anim.gif", "anim.mp4", fps=15)

7.4 帧级滤镜(每帧锐化)

from PIL import ImageFilter

def apply_filter_to_frames(gif_path, output_path, filter_obj=ImageFilter.SHARPEN):
    img = Image.open(gif_path)
    frames = []

    for frame in ImageSequence.Iterator(img):
        filtered = frame.convert("RGB").filter(filter_obj)
        frames.append(filtered)

    frames[0].save(
        output_path,
        save_all=True,
        append_images=frames[1:],
        duration=img.info.get('duration', 100),
        loop=0
    )

apply_filter_to_frames("blurry.gif", "sharp.gif")

7.5 动图优化(减小体积)

def optimize_gif(input_path, output_path, optimize=True):
    img = Image.open(input_path)

    # 减少颜色(量化)
    quantized = img.quantize(colors=64, method=2)  # 64 色

    quantized.save(
        output_path,
        optimize=optimize,
        quality=85
    )
    print(f"优化完成: {output_path}")

optimize_gif("large.gif", "small.gif")

7.6 从图片序列生成 GIF

def images_to_gif(image_paths, output_path, duration=200):
    images = [Image.open(p).convert("RGB") for p in image_paths]

    images[0].save(
        output_path,
        save_all=True,
        append_images=images[1:],
        duration=duration,
        loop=0
    )
    print(f"合成 GIF: {output_path}")

# 使用
images_to_gif([f"frame_{i:03d}.png" for i in range(30)], "new.gif", 100)

8. 完整示例:动图处理工具箱

def gif_toolbox(input_gif, output_dir="output"):
    import os
    os.makedirs(output_dir, exist_ok=True)

    img = Image.open(input_gif)
    print(f"动图信息: {img.n_frames} 帧, {img.size}, 循环: {img.info.get('loop', '无限')}")

    # 1. 提取帧
    for i, frame in enumerate(ImageSequence.Iterator(img)):
        frame.convert("RGB").save(f"{output_dir}/frame_{i:03d}.png")

    # 2. 生成倒放
    frames = [f.copy() for f in ImageSequence.Iterator(img)]
    frames.reverse()
    frames[0].save(f"{output_dir}/reverse.gif", save_all=True, append_images=frames[1:], duration=img.info.get('duration', 100))

    # 3. 加速 2x
    frames = list(ImageSequence.Iterator(img))
    frames[0].save(f"{output_dir}/fast.gif", save_all=True, append_images=frames[1:], duration=max(1, img.info.get('duration', 100)//2))

    print(f"工具箱处理完成: {output_dir}/")

gif_toolbox("input.gif")

9. 常见问题 & 解决方案

问题原因解决
n_frames 为 1不是动图检查 img.is_animated
帧颜色失真调色板丢失使用 frame.convert("RGB")
duration 为 0部分 GIF 未设置设默认值 100
合成后闪烁disposal 未处理手动处理背景恢复
体积未减小optimizesave(optimize=True)

10. 官方文档

  • https://pillow.readthedocs.io/en/stable/reference/ImageSequence.html
  • Image.save 动图参数:https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#gif

一键命令行工具:giftool.py

#!/usr/bin/env python3
import argparse
from PIL import Image, ImageSequence
import os

def main():
    parser = argparse.ArgumentParser(description="GIF 动图处理工具")
    parser.add_argument("input")
    parser.add_argument("--extract", action="store_true", help="提取帧")
    parser.add_argument("--reverse", action="store_true", help="倒放")
    parser.add_argument("--speed", type=float, default=1.0, help="速度倍率")
    parser.add_argument("-o", "--output", help="输出目录/文件")

    args = parser.parse_args()
    output = args.output or "gif_output"
    os.makedirs(output, exist_ok=True)

    img = Image.open(args.input)

    if args.extract:
        for i, frame in enumerate(ImageSequence.Iterator(img)):
            frame.convert("RGB").save(f"{output}/frame_{i:03d}.png")
        print(f"帧已提取到 {output}/")

    if args.reverse:
        frames = [f.copy() for f in ImageSequence.Iterator(img)][::-1]
        frames[0].save(f"{output}/reverse.gif", save_all=True, append_images=frames[1:], duration=img.info.get('duration', 100))
        print(f"倒放保存: {output}/reverse.gif")

    if args.speed != 1.0:
        duration = max(1, int((img.info.get('duration', 100)) / args.speed))
        frames = list(ImageSequence.Iterator(img))
        frames[0].save(f"{output}/speed_{args.speed}x.gif", save_all=True, append_images=frames[1:], duration=duration)
        print(f"速度调整: {args.speed}x")

if __name__ == "__main__":
    main()

使用示例

python giftool.py dance.gif --extract --reverse --speed 2.0 -o result

需要我帮你实现 表情包批量处理、动图加水印、帧差视频、动图转 WebP、帧级 AI 上色 等功能吗?直接说需求,我给你完整代码!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注