GIF压缩小记

2021-11-25 13:17:43 浏览数 (1)

1. 背景

   广告素材中,图片类素材都是以静态图片为主,缺少交互感和吸引力,可能导致点击率偏低。为此,腾讯广告多媒体AI团队使用AI技术在图片焦点区域生成动态效果,以提升点击率。在落地页中,如果是以视频的形式不但交互过重,并且影响页面加载速度。因此,需要在保证展示效果的前提下使用压缩比尽可能大的GIF来做落地页展示。

2. GIF格式浅析

   GIF(Graphics Interchange Format)图像互换格式,是一种位图图形文件格式,以8位色(即256种颜色)重现真彩色的图像。它实际上是一种压缩文档,采用LZW压缩算法进行编码,有效地减少了图像文件在网络上传输的时间。是目前广泛应用于网络传输的图像格式之一。

GIF格式的文件结构整体上主要分为三个部分:文件头、GIF数据流、文件结尾。其中,GIF数据流是本文分析的重点,主要包含全局调色盘、局部调色盘以及多个连续的图像块。

2.1. 调色盘

   什么是调色盘?我们先考虑最直观的图像存储方式,一张分辨率为M×N的图像,本质上是一张点阵,如果采用常见的RGB三色方式存储,每个颜色用8bit表示,那么一个点就可以由三个字节(3Byte = 24bit)表示。比如0xFFFFFF可以表示一个白色像素点,0x000000表示一个黑色像素点。如果我们采用最原始的存储方式,把每个点的颜色值写进文件,光图像信息就要占据3×M×N个字节。这还只是静态图的情况,GIF图一般包含K帧,在不做任何压缩的情况下总大小就是3×M×N×K,非常占用存储空间。

实际情况中,GIF图具有如下两个特征

(1)一张图像最多只包含256个RGB值。

(2)在一张连续动态GIF里,每一帧之间信息差异不大,颜色是被大量重复使用的。

针对这两个特性,做过存储的同学可能会想到,我们一般会采用内存索引 磁盘实际存储的方式来减少内存空间占用。如果在存储时,我们用一个公共的索引表,把图片中用到的颜色提取出来,组成一个调色盘,这样,在存储真正的图片点阵时,只需要存储每个点在调色盘里的索引值。GIF确实也是这么做的,如果调色盘放在文件头,作为所有帧公用的信息,就是公共(全局)调色盘;如果放在每一帧的帧信息中,就是局部调色盘。GIF格式允许两种调色盘同时存在,在没有局部调色盘的情况下,使用公共调色盘来渲染。

这样,我们就可以用调色盘里的索引来代表实际的颜色值:一个256色的调色盘,24bit的颜色只需要用9bit就可以表达了。调色盘所包含的色彩数还可以进一步减少,128色,64色,etc,相应的压缩率就会越来越大。

2.2. 帧信息描述

   帧信息描述就是每一帧的图像信息和相关标志位,在逐项了解它之前,我们首先探究一下帧的存储方式。我们已经知道调色盘相关的定义,除了全局调色盘,每一帧可以拥有自己的局部调色盘,渲染顺序更优先,它的定义方式和全局调色盘一致,只是作用范围不同。直观来说,帧信息应该由一系列的点阵数据组成,点阵中存储着一系列的颜色值。

点阵数据本身的存储也是可以进行压缩的,GIF图所采用的是LZW压缩算法。这样的压缩和图像本身性质无关,是字节层面的,文本信息也可以采用(比如常见的gzip,就是LZW和哈夫曼树的一个实现)。基于表查询的无损压缩是如何进行的?基本思路是,对于原始数据,将每个第一次出现的串放在一个串表中,用索引来表示串,后续遇到同样的串,简化为索引来存储(串表压缩法)。

举一个简单的例子来说明LZW算法的核心思路。

有原始数据:ABCCAABCDDAACCDB

可以看出,原始数据里只包括4个字符A,B,C,D,四个字符可以用2bit的索引来表示,0-A,1-B,2-C,3-D。原始字符串存在重复字符,比如AB,CC,都重复出现过。用4代表AB,5代表CC,上面的字符串可以替代表示为45A4CDDAA5DB,这样就完成了压缩,串长度从16缩减到12。对原始信息来说,LZW压缩是无损的。

除了采用LZW之外,帧信息存储过程中还采取了一些和图像相关的优化手段,以减小文件的体积,直观表述就是——公共区域排除、透明区域叠加等。

3. 压缩方式

   知其然必知其所以然,因此第二章节花了很大篇幅来介绍GIF格式的存储特性,这对于我们如何进一步压缩GIF的大小非常有指导意义。由上文可见,GIF格式自身已经在压缩上下了不少功夫,那么我们一般可以从哪些角度进一步压缩GIF的大小,而又尽量不影响GIF的效果呢?

  • 图像分辨率 分辨率是最容易想到的压缩方法,可以根据不同的展示平台做不同的图片分辨率压缩方案,比如针对小屏幕设备可以采用更小的分辨率。
  • 色彩数量 色彩数量直接影响到上文提到的全局和局部调色盘大小。由于图片情况各异,一般情况下建议保持256色不变。极端情况下,比如修改为2则变为黑白GIF。
  • GIF帧数 通过提取一些间隔帧,比如对于一张10帧的动画,只提取其中的提取1,3,5,7,9帧,来减少图片的整体体积。一般情况建议至少保持15FPS以上,才能确保相对自然的衔接和过渡。
  • 压缩算法 GIF默认的压缩算法为LZW算法,理论上我们也可以尝试其他压缩算法来获得更高的压缩比。比如LZW算法只针对完全相等的数据,那么我们是否可以使用一定程度的近似值来做更极值的压缩?

4. 压缩工具

   调研过现有的GIF压缩工具,gifsicle实属业内口碑不错的命令行工具,支持对GIF文件进行修改尺寸大小、颜色、帧率等功能,效果和效率都还可以。

最常用的压缩GIF命令:

gifsicle -03 想要压缩的图片 -o 新图片名

(注:O3第一个为大写字母,第二个为数字,o为字母)

这条命令的好处就是让程序自动为你选择压缩方案,一般能在画质和体积之间取得平衡,并且第一帧之后的每一帧都能得到优化。

用gifsicle将3D微动GIF从原始的1.9M压缩到758k,前后对比如下:

压缩前:

压缩后:

可见部分局部色表被自动压缩到了64色,并且做了部分重复区域透明处理。但是视觉上差异很小,效果很不错。

具体使用方式可参考gifsicle的API官方文档,有不少参数可供调整:https://www.lcdf.org/gifsicle/man.html

5. 具体实现

   笔者已将GIF生成以及压缩的代码封装成Python工具类,git地址:https://github.com/jesonxiang/gif_maker。由于投入的时间非常有限,且gifsicle已能满足业务场景需求,笔者未对GIF压缩做深入研究。在此,也期待大家后续能探索出更为理想的压缩方式,欢迎随时交流。

0 人点赞