有了这个方法群聊斗图你就不会输了(Python imageio制作gif动图)
原文链接:http://www.juzicode.com/python-funny-imageio-make-gif
先说需要用到的3个模块,imageio用来读写图像文件、imageio-ffmpeg是imageio的扩展模块,用来处理视频文件、pygifsicle用来对gif文件做优化,可以裁剪文件大小。
通过pip命令完成库的安装:
python -m pip install imageio imageio-ffmpeg pygifsicle
或者:
pip install imageio imageio-ffmpeg pygifsicle
导入模块时imageio-ffmpeg不需要再单独导入,用一句import imageio即可包含:
import imageio
import pygifsicle
gifsicle的安装
因为pygifsicle只是gifsicle工具的封装,该模块的使用需要先安装了gifsicle。
找到gifsicle官网Gifsicle: Command-Line Animated GIFs下载安装包,根据自己的系统选择相应的安装包,比如windows系统选择“Windows ports”:
安装包下载后在本地完成解压,桔子菌解压后的路径为D:\juzicode\gifsicle-1.92-win64\gifsicle-1.92,然后在环境变量的PATH中添加该路径,添加完成后打开新的命令行才能使用新的环境变量,输入gifsicle –version 确认是否完成安装,如果看到相应的版本号表示安装完成:
D: > gifsicle --version
LCDF Gifsicle 1.92
Copyright (C) 1997-2019 Eddie Kohler
This is free software; see the source for copying conditions.
There is NO warranty, not even for merchantability or fitness for a
particular purpose.
注意因为重新设置了环境变量,需要重新开启命令行界面重新运行程序,如果使用的是jupyter(Notebook)也需要重新启动jupyter,否则也会出现和没有安装gifsicle时一样的异常:“FileNotFoundError: The gifsicle library was not found on your system”。
多个静态文件生成gif
先介绍下如何从多个静态文件生成gif动图。
可以将静态图片都保存在pic_path所指的目录下,要生成的gif文件名称保存在gif_name变量中:
pic_path = 'tom\\'#静态图片路径
gif_name = 'tom.gif' #生成gif的文件名称
利用imageio.imread(文件路径名称)的方式读文件,返回的数据类型为class ‘imageio.core.util.Array’等价于numpy数组,numpy数组的方法、属性也可以用在它上面,比如获取极值,求和等等 :
x=imageio.imread('tom\\001.jpg')
print('type(x):',type(x))
print('x.shape:',x.shape)
print('x.max():',x.max())
print('x.sum():',x.sum())
运行结果:
type(x): <class 'imageio.core.util.Array'>
x.shape: (432, 624, 3)
x.max(): 253
x.sum(): 124952013
pic_path所指目录下的图片文件按照要写入帧的顺序命名:
先获取该目录下所有的文件名存入到images列表中,然后用sort()排序,再通过imageio.imread()逐一读出文件存入到列表frames中:
#读文件
images = os.listdir(pic_path)
images.sort()
frames = [imageio.imread(pic_path+f) for f in images]
然后利用imageio.mimwrite()将frames写入到gif文件中,duration表示gif文件各帧之间的时间间隔:
#图片帧写入gif文件
imageio.mimwrite(gif_name, frames, 'GIF', duration= 0.1)
直接生成的文件会比较大,可以再使用pygifsicle进行优化,优化后的文件大小可以减小一半左右:
#优化gif文件大小
gif_name_opt = 'tom-opt.gif' #优化后生成的gif文件名称
pygifsicle.optimize(gif_name, gif_name_opt)
完整的代码是这样的:
#juzicode.com / VX公众号:桔子code
import os
import imageio
import pygifsicle
pic_path = 'tom\\'#静态图片路径
gif_name = 'tom.gif' #生成gif的文件名称
gif_name_opt = 'tom-opt.gif' #优化后生成的gif文件名称
#读文件
images = os.listdir(pic_path)
images.sort()
frames = [imageio.imread(pic_path+f) for f in images]
#图片帧写入gif文件
imageio.mimwrite(gif_name, frames, 'GIF', duration= 0.1)
#优化gif文件大小
pygifsicle.optimize(gif_name, gif_name_opt)
生成的效果图:
另外一种方法是先用imageio.get_writer()创建writer对象,接下来每读出一幅图像用writer.append_data()方法写入到gif文件,创建write对象时mode=’I’表示要创建的对象为多图模式,gif选择该模式(经过桔子菌测试mode=’i’也是可行的,不过建议和官方文档保持一致):
#juzicode.com / VX公众号:桔子code
import os
import imageio
import pygifsicle
pic_path = 'tom\\'#静态图片路径
gif_name = 'tom2.gif' #生成gif的文件名称
gif_name_opt = 'tom-opt2.gif' #优化后生成的gif文件名称
images = os.listdir(pic_path)
images.sort()
#创建写方法
writer= imageio.get_writer(gif_name, mode='I',duration=0.1)
#逐帧写入
for f in images:
frame = imageio.imread(pic_path+f)
writer.append_data(frame)
#关闭writer
writer.close()
#优化gif文件大小
pygifsicle.optimize(gif_name, gif_name_opt)
在用静态图片生成gif时,如果静态图片的尺寸大小不一致,也是能正常得到gif文件的,但是其中尺寸较小的图像在左上角对齐后,右方或下方会出现“留白”,要解决这个问题,可以借助OpenCV的resize()等方法将图像的尺寸统一起来,这里不再做展开。
视频文件生成gif
前面介绍了从多个静态图生成动图的方式,下面聊聊从视频文件生成gif动态图。
首先要解决从视频文件读出的问题,这里需要用到imageio.get_reader()创建读文件对象reader,传入的参数是视频文件的名称:
vedio_name = 'jerry.mp4'
#创建读视频对象
reader = imageio.get_reader(vedio_name)
生成的reader就可以用作迭代器每循环一次取出一帧图像。
和用静态文件生成gif动态图一样,也可以用前面的imageio.mimwrite()方法和imageio.get_writer()创建写对象的方法,这里用后者:
gif_name = 'jerry.gif'
#创建写gif对象
writer= imageio.get_writer(gif_name, mode='I',duration=0.1)
然后用reader迭代,在循环里面每次调用writer.append_data()方法写入gif文件:
for img in reader:
#添加图像到writer对象
writer.append_data(img)
最后是关闭reader和writer对象,调用gifsicle优化:
writer.close()
reader.close()
#优化gif文件大小
pygifsicle.optimize(gif_name, gif_name_opt)
虽然用上面的步骤可以生成gif图片,但是一个内容20s左右大小不到1M的mp4文件生成的gif,经过优化后也有10几M,这是不能接受的。
前面的方法每读出一帧都写入到了gif文件中,但是视频文件有个特点是相邻的帧有可能差别很小,这种细微差别的帧对于gif图片是可以丢弃掉的。我们可以在每次循环时对比上一次写入帧的差异,如果差异小于一定数值这一帧就不做写入,差异量可以是平均值、差值绝对值、差值比等等,这里我们用差值比来实现。
经过修改后完整的示例代码如下:
#juzicode.com / VX公众号:桔子code
import imageio
import numpy as np
import pygifsicle
vedio_name = 'jerry.mp4'
gif_name = 'jerry.gif'
gif_name_opt = 'jerry-opt.gif'
#创建读视频对象
reader = imageio.get_reader(vedio_name)
meta_data = reader.get_meta_data()
print('meta_data',meta_data)
#创建写gif对象
writer= imageio.get_writer(gif_name, mode='I',duration=0.1)
for i,img in enumerate(reader):
if i==0: img_last = np.ones(img.shape)#第一次进来时需要创建img_last
#检查画面变化,如果变化不大,该帧不保留,继续下一幅图像
diff = np.abs(img-img_last) #计算当前帧和上一帧差异的绝对值
diff_sum = np.sum(diff) #计算差异绝对值的和
print('np.sum(diff)',diff_sum)
img_last_sum = np.sum(img_last) #计算上一帧的总和
ratio = diff_sum / img_last_sum #计算差异比
print('ratio',ratio)
if ratio <0.25: #如果差异比小于0.25跳过这一帧
continue
#添加图像到writer对象
writer.append_data(img)
#保留上一幅图像用来对比
img_last = img.copy()
writer.close()
reader.close()
#优化gif文件大小
pygifsicle.optimize(gif_name, gif_name_opt)
经过优化后,生成的gif文件缩小到了原来的1/4左右:
上效果图:
有了今天介绍的方法,你可以稍作改动,比如打开一个视频文件每次读入100帧图片生成一个gif文件,一个视频文件就可能生成N个gif文件,然后从这些gif文件里面选择你想要的内容拿来斗图了。
动动手指,bug敲起来。