有了这个方法群聊斗图你就不会输了(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官网https://www.lcdf.org/gifsicle/下载安装包,根据自己的系统选择相应的安装包,比如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文件里面选择你想要的内容拿来斗图了。

扩展阅读:

  1. 论如何把自己变成卡通人物(OpenCV制作卡通化头像)
  2. 来看看怎么用OpenCV解构Twitter大牛jagarikin的视觉错觉图
  3. OpenCV-Python教程:几何空间变换~缩放、转置、翻转(resize,transpose,flip)

发表评论

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