英文:
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:])
这个代码的结果是下面的动画图:
- 如何让每张图片只显示一次?目前它们每张都显示两次。
英文:
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:
- 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'])
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论