图像缩小之后文件体积增大之谜

昨天帮师姐处理数据时,发现一个很奇怪的现象:有一组长宽各6000 px、相互比较相似的图片在裁剪掉3/4面积的白边(还剩约3000×3000左右)、外加缩图到1000 px宽之后,对原始图和处理过的图分别打包,居然是前者(原图)的总体积比较小,而且小很多。

Laplacian_Gaussian_restored at t=1
当事图片范例(已经缩图)

 

稍微有点信息熵以及压缩原理概念的人应该已经看得出这件事儿的诡异之处,不过这里还是啰嗦几句。这整个过程有三个步骤:

  1. 裁剪白边
  2. 缩图
  3. 7z压缩

无损图像本身当然是尺寸越大体积越大,不过白边的信息量极低,约等于没有。所以,在7z压缩过程中,这部分“虚”的体积会被压缩掉。所以裁剪掉它并不会减少多少极限压缩后的体积是可以预料到的。事实上,将这一步骤去掉(即对仅裁剪的图像7z压缩和裁剪后又缩图图像7z压缩两者进行对比),可以看到这一步骤几乎没有任何影响。

7z的压缩原理(LZMA)基本综合了常见的无损压缩的方法,应该已经没有多少余量可言,可以认为基本已经达到或接近了原始数据的压缩极限值。另外,除了对单个图片本身的压缩以外,不同文件间的差别也会对predictor的效率造成影响。不过由于本次的图片在缩放前后都是同一组(仅缩放)图片,所以这上面的压缩率应该基本保持一致(具体来说,会保持一个很高的文件间压缩率,因为都是相似图像)。

那么问题显然就只能出在缩图上了。换句话说,缩图(而且是长度上3->1的缩小)反而增加了图像的熵。

我第一个想法是原图的TIFF其实是JPEG压缩。TIFF格式可选很多压缩算法,虽然一般用无损(或者无压缩),但是也支持有损的JPEG。如果我将一组事实上为JPEG的图片转换为无损格式(BMP/PNG)之后再压缩,其体积当然会增大,因为我把之前JPEG算法中无意引入的许多噪声也当成了信息本身来对待。但是感觉上,这个增加的噪声应该不足以抵消甚至超过1/3(长度上)的缩图才对。

事实上,在用imagemagick的identify utility查看图像的信息后,可以看到

D:\>magick identify -verbose abc.tiff
Image: abc.tiff
Format: TIFF (Tagged Image File Format)
Mime type: image/tiff
Class: DirectClass
Geometry: 6251×6196+0+0
Resolution: 1000×1000
Print size: 6.251×6.196
Units: PixelsPerInch
Type: Grayscale
Base type: TrueColor
Endianess: LSB
Colorspace: sRGB
Depth: 8-bit
Channel depth:
Gray: 8-bit
Channel statistics:
Pixels: 38731196
<略>
Compression: RLE

其使用的是RLE(游程编码,根据TIFF的标准应该具体是PackBits这个实现),也就是无损压缩。这也很容易证明:将原始图像直接转换成BMP后再压缩,和缩图后的BMP再压缩比,依然体积要领先。

这里插播一段:一般而言,对于压缩过的图像再压缩,基本不会再有什么改善,因为已经逼近极限。例如,对于图像,你先用PNG压缩之后再用7z,体积几乎不会有丝毫变化。而且,在常见的算法中PNG应该是体积最小的(除去新千年兴起的webP之类的新算法),所以对单张图片基本就直接转换成PNG就好。但是有个例外就是文件间压缩:在固实包足够大的前提下,多个相似图像压缩最好先保持图像是无压缩的raw的状态(BMP),然后直接上7z,这样一般效果会比先PNG再7z压缩好不少——因为PNG压缩后的“脱水”数据反而不利于文件间压缩的predictor工作。

这里拿一组eroge CG(同一个Event的16张差分)做了个实验:

格式 图像大小 相对BMP大小 7z极限压缩后大小 压缩率 相对BMP压缩包大小
BMP 44,237,664 100% 4,945,976 11% 100%
PNG 16,713,122 38% 15,193,005 90% 307%
TIFF (PackBits) 43,265,681 98% 5,203,355 12% 105%
TIFF (ZIP) 24,878,172 56% 15,940,445 64% 322%
TIFF (LZW) 36,758,162 83% 16,832,042 45% 340%

确实支持之前的说法。不过这次的图像有点古怪,BMP+打包的体积居然不是最小的,反而是TIFF (PackBits)+打包的体积最小,而且小得多:3.53MB vs 1.40MB。具体原因?留在最后揭秘。

回到正题,那么,为什么缩图之后图像的信息量反而增大呢?这得回到图像本身来。仔细观察图像:

QQ截图20160720224543
图像的一部分,1:1比例

你会发现,有很大的“像素”点。事实上,这个图像是从一张小图用硬边缘法(Nearest neighbour)放大至9倍(长度上)之后形成的。因此,其有效信息量其实远没有他的尺寸那么多。当你缩图的时候,反而会在粗大的“像素”点的交界处产生一些新的“平均”(各种意义上的)像素点出来。Pattern的复杂度增加,因此熵也就增加了。而且,当你的缩图算法越“精细”,这种副作用就越强烈。当然,如果完整地缩小回1/9,新增加的像素倒不是问题,因为反正“老的”也干掉了,总体熵并不会有太大变化;但是在缩小到非整数倍时,这一副作用会非常明显。

上述效应的影响其实还和这图的颜色有蛮大关系。本图颜色很单调,本质上是一灰度图(虽然是用的RGB格式),而且只有离散地少数几种颜色:

original
原始图片的直方图

然而在缩图之后,其调色板大小急剧上升:

new
Lanczos缩图后的直方图

这显然完全不利于压缩。StackOverflow有个类似的问题谈到了这一点。

应该也很容易看到,如果用简单的线性采样(也就是Nearest neighbour)来缩小的话,可以完美地规避这一副作用(仅对这一特例而言。对于正常图像来说用NN缩图只会产生锯齿成狗的结果)。

做了个简单的测试,同样是BMP的前提下,使用4种不同的缩图算法缩图至50%再打包的结果如下:

缩图算法* 压缩后大小
Nearest Neighbour 2.28MB
Lanczos 11.1MB
B-spline 10.3MB
Bilinear 4.09MB
Cubic 10.3MB

[*]:缩图使用xnview。对于某些算法的具体参数不详。

这也符合我们的猜测。

长话短说,当一张图片真实信息量较低(有大量重复pattern,颜色单一,etc.)时,缩图可能反而会由于像素间平均的缘故使图像信息量增加,进而增加脱水后的体积。

哦对了,还有上面的思考题呢。为什么TIFF (Packbits)的压缩包最新小?个人的猜想是这种硬放大外加颜色少的图像,可以说是游程编码最理想的情况之一了:试想当你的文本文件是aaaaabbbbbccccdddddeeeee…时,在游程编码下可以变成5a5b5c5d5e…甚至5(abcde..)!更完美的是,经过了这样游程编码的的每张图像之间的相似性(可预测性)完全没有被破坏!所以在接下来的7z压缩中依然可以火力全开。其结果就是压缩率非常高的压缩包。

补记

成文之后,才发现其实文中那个有古怪的图哪怕直接单张压缩成PNG都会出现原图体积小于缩图的问题……也就是说根本没必要引入关于7z的部分,增加复杂度。不过既然写都写了就不改了。既然可以直接用PNG,那就顺手生成了几个例子测试。

图像范例是一张一般的ACG图,1000px宽。

测试1(重复patteren):先将图像用NN放大800%倍,保存PNG(1)。然后用Lanczos缩图至70%,保存PNG(2)。

结果:1=4.16MB,2=10.35MB,符合猜想

测试2(颜色调色板增加):先将图像用PS的色调分离分离成为25种颜色,保存PNG(1)。然后用Lanczos缩图至70%,保存PNG(2)。

结果:1=979.06KB,2=1.14MB,符合猜想

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s