如何制作一个动态GIF,每张图片只显示一次。

huangapple go评论144阅读模式
英文:

How to make an animated gif that shows each picture only once

问题

我想制作一个每张图片显示一秒钟的动画 gif,每张图片都只显示一次。这里是一个最小化工作示例:

from PIL import Image
import seaborn as sns
import random
import matplotlib.pyplot as plt

# 生成三个随机热力图并保存为 "pic0.png"、"pic1.png" 和 "pic2.png"。
# 你也可以使用任何你已有的三张图片。
for i in range(3):
    new_list = [random.sample(range(80), 10)]*6
    sns.heatmap(new_list)
    plt.savefig("pic"+str(i)+".png")
    plt.clf()

# 将图片添加到列表中
images = []
for i in range(3):
    images.append(Image.open("pic"+str(i)+".png"))

# 创建动画 GIF。我希望设置 loop=1 可以让每张图片只显示一次,但似乎不起作用。
images[0].save("out.gif", save_all=True, duration=1000, loop=1, append_images=images[1:])

这个代码的结果是下面的动画图:

如何制作一个动态GIF,每张图片只显示一次。

  • 如何让每张图片只显示一次?目前它们每张都显示两次。
英文:

I want to make an animated gif with one second per picture that shows each picture exactly once. Here is a MWE:

from PIL import Image
import seaborn as sns
import random
import matplotlib.pyplot as plt

# Make three random heatmaps and save them as "pic0.png", "pic1.png" and "pic2.png".
# You can use any three pictures you have instead.
for i in range(3):
    new_list = [random.sample(range(80), 10)]*6
    sns.heatmap(new_list)
    plt.savefig("pic"+str(i)+".png")
    plt.clf()

# Add the images to a list
images = []
for i in range(3):
    images.append(Image.open("pic"+str(i)+".png"))

# Create the animated GIF. I was hoping loop=1 would show each image once but it doesn't.
images[0].save("out.gif", save_all=True, duration=1000,loop=1, append_images=images[1:])

The result of this is the following:

如何制作一个动态GIF,每张图片只显示一次。

  • How do I make it show each image exactly once? Currently it shows them all twice.

答案1

得分: 2

根据以下帖子,这是Pillow中已知的一个bug。

"loop=1"实际上会循环两次!


我们可以使用FFmpeg的命令行界面(CLI)而不是Pillow:

import subprocess as sp

...

sp.run(['ffmpeg', '-y', '-r', '1', '-i', 'pic%1d.png', '-vf', 'split[s0][s1];[s0]palettegen

;[s1]

paletteuse', '-loop', '-1', 'out.gif'])


FFmpeg命令基于以下答案:

  • pic%1d.png 使用以数字结尾的所有PNG文件 - 考虑将PNG图像存储在一个“干净”的文件夹中。
  • -r 1 将帧率设置为1fps。
  • -loop -1 不应用循环。

更新的代码示例(假设FFmpeg CLI在可执行路径中):

from PIL import Image
import seaborn as sns
import random
import matplotlib.pyplot as plt
import subprocess as sp

# 生成三个随机热图并保存为"pic0.png"、"pic1.png"和"pic2.png"。
# 您可以使用您拥有的任意三张图片。
for i in range(3):
    new_list = [random.sample(range(80), 10)] * 6
    sns.heatmap(new_list)
    plt.savefig("pic" + str(i) + ".png")
    plt.clf()

# 将图像添加到列表中
images = []
for i in range(3):
    images.append(Image.open("pic" + str(i) + ".png"))

# 创建动画GIF。我希望"loop=1"会显示每个图像一次,但实际上并不会。
# images[0].save("out.gif", save_all=True, duration=1000, loop=1, append_images=images[1:])

# 使用FFmpeg CLI而不是使用Pillow
sp.run(['ffmpeg', '-y', '-r', '1', '-i', 'pic%1d.png', '-vf', 'split[s0][s1];[s0]palettegen

;[s1]

paletteuse', '-loop', '-1', 'out.gif'])


更干净的解决方案是将图像写入FFmpeg子进程的stdin管道:

from PIL import Image
import seaborn as sns
import random
import matplotlib.pyplot as plt
import subprocess as sp

p = sp.Popen(['ffmpeg', '-y', '-r', '1', '-f', 'image2pipe', '-i', 'pipe:', '-vf', 'split[s0][s1];[s0]palettegen

;[s1]

paletteuse', '-loop', '-1', 'out.gif'], stdin=sp.PIPE) # 生成三个随机热图并保存为"pic0.png"、"pic1.png"和"pic2.png"。 # 您可以使用您拥有的任意三张图片。 for i in range(3): new_list = [random.sample(range(80), 10)] * 6 sns.heatmap(new_list) #plt.savefig("pic" + str(i) + ".png") plt.savefig(p.stdin, format='png') # 将图像写入FFmpeg子进程的stdin管道。 plt.clf() p.stdin.close()


我们还可以尝试使用Matplotlib动画。

英文:

According to the following post it's a known bug in Pillow.

>loop=1 actually loops twice!


We may use FFmpeg CLI instead of Pillow:

import subprocess as sp

...

sp.run(['ffmpeg', '-y', '-r', '1', '-i', 'pic%1d.png', '-vf', 'split[s0][s1];[s0]palettegen

;[s1]

paletteuse', '-loop' '-1' 'out.gif'])


The FFmpeg command is based on the following answer.

  • pic%1d.png Uses all png files that ends with one digit - consider storing the PNG images in a "clean" foder.
  • -r 1 Sets the frame rate to 1fps
  • -loop -1 Applies no looping

Updated code sample (assume FFmpeg CLI is in the executable path):

from PIL import Image
import seaborn as sns
import random
import matplotlib.pyplot as plt
import subprocess as sp

# Make three random heatmaps and save them as "pic0.png", "pic1.png" and "pic2.png".
# You can use any three pictures you have instead.
for i in range(3):
    new_list = [random.sample(range(80), 10)]*6
    sns.heatmap(new_list)
    plt.savefig("pic"+str(i)+".png")
    plt.clf()

# Add the images to a list
images = []
for i in range(3):
    images.append(Image.open("pic"+str(i)+".png"))

# Create the animated GIF. I was hoping loop=1 would show each image once but it doesn't.
#images[0].save("out.gif", save_all=True, duration=1000, loop=1, append_images=images[1:])

# Use FFmpeg CLI instead of using Pillow
sp.run(['ffmpeg', '-y', '-r', '1', '-i', 'pic%1d.png', '-vf', 'split[s0][s1];[s0]palettegen

;[s1]

paletteuse', '-loop', '-1', 'out.gif'])


如何制作一个动态GIF,每张图片只显示一次。


A cleaner solution is writing the images to stdin pipe of FFmpeg subprocess:

from PIL import Image
import seaborn as sns
import random
import matplotlib.pyplot as plt
import subprocess as sp

p = sp.Popen(['ffmpeg', '-y', '-r', '1', '-f', 'image2pipe', '-i', 'pipe:', '-vf', 'split[s0][s1];[s0]palettegen

;[s1]

paletteuse', '-loop', '-1', 'out.gif'], stdin=sp.PIPE) # Make three random heatmaps and save them as "pic0.png", "pic1.png" and "pic2.png". # You can use any three pictures you have instead. for i in range(3): new_list = [random.sample(range(80), 10)]*6 sns.heatmap(new_list) #plt.savefig("pic"+str(i)+".png") plt.savefig(p.stdin, format='png') # Write image to stdin pipe of FFmpeg subprocess. plt.clf() p.stdin.close()


We may also try using matplotlib animation.

huangapple
  • 本文由 发表于 2023年6月2日 03:17:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76385039.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定