为什么许多相似的png图像的这些(无损)压缩方法无效?


21

我碰到了以下事情:我将png图像的多个相同副本放入文件夹中,然后尝试使用以下方法压缩该文件夹:

  • tar czf folder.tar.gz folder/
  • tar cf folder.tar folder/ && xz --stdout folder.tar > folder.tar.xz (这对于相同的图像效果很好,但是对于相似的图像,增益为零)
  • zip -r folder.zip folder/

当我检查的大小.tar.gz.tar.xz.zip我意识到,这是几乎相同的一个folder/
我了解png图像本身可能具有很高的压缩率,因此无法进一步压缩。但是,当将许多相似(在这种情况下甚至相同)的png图像合并到存档中,然后压缩存档时,我希望所需的大小会显着减小。在相同的图像的情况下,我期望的尺寸大约是单个图像的尺寸。


2
此行为仅存在于png文件中吗?
pdexter'7

7
不能回答这个问题,因为它回答了一个未解决的问题,但是如果您知道要压缩很多几乎相同的图像,则可以始终用第一张图像的二进制差异替换除第一张图像以外的所有图像。假设图像没有噪点,您最终将获得非常可压缩的输出,并且原始图像仍将可重现。
Baldrickk

如果您使用未压缩的文件(例如.bmp),则tar.gz文件应该能够利用相似性。(至少如果相似度是很多相同的像素)
CodesInChaos

1
我对此一无所知,但是根据Wikipedia所说,“ ZPAQ”存档格式支持重复数据删除,我相信这是您要追求的目标。en.wikipedia.org/wiki/ZPAQ#Deduplication
coneslayer

您正在尝试压缩已压缩的内容。看到这里
Kyle Khalaf

Answers:


34

看看压缩算法是如何工作的。至少在Lempel-Ziv系列中的那些(gzip 使用LZ77zip显然xz 使用LZMA,并且也使用LZMA)在局部进行了局部压缩:彼此之间距离很近的相似性无法确定。

两种方法的细节不同,但是最重要的是,当算法到达第二张图像时,它已经“忘记”了第一张图像的开始。等等。

您可以尝试手动更改压缩方法的参数。如果窗口大小(LZ77)分别 块/块大小(后来的方法)至少与两个图像一样大,您可能会看到进一步的压缩。


请注意,上述内容仅在具有相同图像或几乎相同的未压缩图像时才适用。如果存在差异,则压缩后的图像在内存中看起来可能不会相似。我不知道PNG压缩是如何工作的。您可能需要手动检查共享子串的图像的十六进制表示形式。

还要注意,即使更改了参数并进行了冗余利用,您也不会缩小到一张图像的大小。字典越大,代码字的大小就越大,即使两个图像完全相同,您也可能必须使用多个代码字(指向第一个)对第二个图像进行编码。


3
一个更准确的答案:gzip和zip使用相同的基础DEFLATE编解码器,该编解码器基于LZ77 + Huffman理论。
Nayuki '16

对!那是故事的一半;请看的另一半答案,或者Nayuki的绝妙答案
DW

1
为后人:档案格式,通过将文件拼接成一个单一的blob和压缩利用文件中的冗余被称为固体。不知道是否有其他条款“坚固”,等的中间水平
underscore_d

22

为什么会这样。实际上,这里发生两种不同的影响:

  • 每个文件独立压缩。 一些存档程序-包括zip-独立压缩每个文件,没有一个文件到另一个文件的内存。换句话说,将每个文件分别压缩,然后将压缩后的文件连接到存档中。

  • 短期记忆。 某些存档程序可以使用有关一个文件的信息来帮助更好地压缩下一个文件。它们有效地串联文件,然后压缩结果。这是一个改进。

    另请参阅Nayuki的答案以获取更多讨论。

    但是,还有第二个问题。某些压缩方案(包括zip,gzip和bzip2)的内存有限。他们即时压缩数据,并记住过去的32KB数据,但他们不记得文件中更早出现的数据。换句话说,如果重复发生的间隔大于32KB,则他们将找不到重复的数据。结果,如果相同的文件很短(少于32KB),则压缩算法可以删除重复的数据,但是如果相同的文件很长,则压缩算法会变得毫无用处:它无法检测到任何您数据中的重复项。(Bzip会记住过去大约900KB的数据,而不是32KB。)

    所有标准压缩算法都具有一定的最大内存大小,如果超过该大小,它们将无法检测模式...但是对于某些情况,此数字要大得多。对于Bzip,大约为900KB。对于xz,大小约为8MB(使用默认设置)。对于7z,这大约是2GB。2GB足够大,足以识别PNG文件的重复副本(通常远小于2GB)。此外,7z还尝试巧妙地将可能彼此相似的文件放置在存档中彼此相邻,以帮助压缩程序更好地工作。tar对此一无所知。

    另请参见Raphael的答案Nayuki的答案,以获取有关此效果的更多说明。

这如何适用于您的设置。 对于您的特定示例,您正在使用PNG图像。PNG图像本身是经过压缩的,因此您可以将每个PNG文件视为基本上是随机序列的字节序列,文件中没有模式或重复项。如果压缩器查看单个PNG图像,则压缩器没有任何可利用的空间。因此,如果您尝试压缩单个PNG文件(或创建仅包含单个PNG文件的zip / tar / ...存档),则不会得到任何压缩。

现在让我们看看如果您尝试存储同一PNG文件的多个副本会发生什么:

  • 小文件。如果PNG文件很小,那么除zip以外的所有内容都可以正常工作。Zip将会严重失败:它将独立压缩每个文件,因此它没有机会检测文件之间的冗余/重复。此外,由于它尝试压缩每个PNG文件,因此无法实现压缩。zip存档的大小将非常庞大。相比之下,tar归档文件(无论是使用gzip,bzip2还是xz压缩)和7z归档文件的大小都较小,因为它基本上存储了文件的一个副本,然后注意到其他文件都是相同的,它们对他们有利。从保留一个文件到另一个文件的内存。

  • 大文件。如果PNG文件很大,则只有7z可以正常工作。特别是,zip继续严重失败。同样,tar.zip和tar.bzip2严重失败,因为文件的大小大于压缩程序的内存窗口:由于压缩程序看到了文件的第一个副本,因此无法缩小它(因为它已经被压缩了) ); 到开始看到文件第二个副本的开头时,它已经忘记了在第一个文件开头看到的字节序列,并且无法使该连接实际上是重复的。

    相比之下,tar.xz和7z对于大型PNG文件的多个副本仍然表现出色。它们没有“较小的内存大小”限制,并且能够注意到文件的第二个副本与第一个副本相同,因此无需再次存储它。

您可以为此做些什么。使用7z。它具有许多启发式功能,可以帮助检测相同或相似的文件,并且在这种情况下可以很好地压缩。您也可以使用lzop压缩查看lrzip。

我怎么知道? 我可以通过对包含随机字节的文件的100个副本进行一些实验来验证这一点。我尝试了100个4KB文件的副本,100个1MB文件的副本和100个16MB文件的副本。这是我发现的:

Size of file      Size of compressed archive (with 100 copies)
                  zip  tar.gz  tar.bz2  tar.xz    7z
         4KB    414KB     8KB     10KB     5KB    5KB
         1MB    101MB   101MB    101MB     1MB    2MB
        16MB    1.6G    1.6GB    1.6GB   1.6GB  401MB

如您所见,无论文件多么小,zip都是可怕的。如果图像不是太大,则7z和xz都很好(但是xz会很脆弱,并且取决于将图像放置在存档中的顺序,如果您将某些重复项和一些非重复项混合在一起)。即使对于大文件,7z也相当不错。

参考文献。 超级用户的大量帖子对此也做了很好的解释。看一看:


5
也许还应该记住ZIP格式是在1990年左右设计的(维基百科说1989年PKZIP引入了ZIP格式,1993年引入了DEFLATE)。在这段时间内,运行DOS且内存可能为2-4 MB的DOS的合理普通PC可能是286或386(486于1989年推出,但一如既往,要花一些时间才能赶上)。如果没有智能编程(EMS,XMS)的支持,则不能直接使用其中的500 KB。在那种环境下,很小的压缩窗口大小是非常必要的。
CVn

“每个文件独立压缩”-在标准和工具之间似乎有很大差异。我对Ubuntu默认包装软件的经验是,打开存档时它似乎可以解压缩所有内容。我经常认为它应该独立地压缩每个文件,因为可用性的提高通常超过了压缩的缺点。
拉斐尔

“一个包含随机字节的文件的100个副本”-“相似”文件呢?(向实际问题,多么相似相似图像的PNG格式?)
拉斐尔

拉斐尔(Raphael)在回答中对此提出了一个很好的观点。实际上,我有很多要存储的相似(不同)图像。就其相似而言,它们显示出相同的结构,但略有变化(还涉及强度和背景)。但是,差异是如此之小,以至于几乎看不到。我尝试使用tar它们,然后进行压缩xz(对于相同的图像效果非常好),但是如果图像相似,则增益为零。我尝试了71张图像,每个图像的大小约为831KB。
a_guest

2
@a_guest-进行得不好。外观相似的PNG图像将具有非常不同的字节内容(由于PNG压缩)。又见superuser.com/q/730592/93541superuser.com/q/418286/93541superuser.com/q/893206/93541superuser.com/q/921140/93541 -基本上,有没有好的解决办法。
DW

10

首先,请注意,PNG图像格式基本上是通过DEFLATE压缩格式推送的原始RGB像素(经过一些滤光)。一般来说,压缩文件(PNG,JPEG,MP3等)将不会再受益。因此,出于实际目的,在其余的实验中,我们可以将您的PNG文件视为不可压缩的随机数据。

其次,请注意ZIP和gzip格式也使用DEFLATE编解码器。(这可以解释为什么将单个文件压缩与gzip压缩会产生基本上相同的输出大小。)


现在,让我分别评论每个测试用例:

  • tar czf folder.tar.gz folder/

    这将创建一个(未压缩的)TAR文件,该文件连接所有相同的PNG文件(添加了少量的元数据和填充)。然后,通过gzip压缩器发送该单个文件,以创建一个压缩的输出文件。

    不幸的是,DEFLATE格式仅支持32768字节的LZ77词典窗口。因此,即使TAR包含重复数据,如果您的PNG文件大于32 KiB,那么请确保DEFLATE压缩器无法将数据记住得足够远,以充分利用重复发生的相同数据这一事实。

    另一方面,如果使用20 KB的PNG文件(重复10次)重试此实验,则很有可能会得到一个仅比20 KB大一点的gzip文件。

  • tar cf folder.tar folder/ && xz --stdout folder.tar > folder.tar.xz

    这将像以前一样创建一个TAR文件,然后使用xz格式和LZMA / LZMA2压缩器。在这种情况下,我找不到有关LZMA的信息,但是从Windows的7-Zip中,我知道它可以支持较大的词典窗口大小(例如64 MiB)。因此,您使用的可能不是最佳设置,并且LZMA编解码器可能已经能够将TAR文件缩小为一个PNG文件的大小。

  • zip -r folder.zip folder/

    ZIP格式不支持“固定”存档;也就是说,每个文件都是独立压缩的。我们假设每个文件都是不可压缩的。因此,无法利用每个文件相同的事实,并且ZIP文件的大小将与所有文件的直接串联一样大。


xz默认情况下在xz -6模式下运行,该模式使用8 MiB LZMA2 字典。我无法立即在Debian系统上的手册页中找到压缩器的默认窗口大小。
CVn

好答案!对于第二种情况,我实际上在执行以下操作:tar czf folder.tar.gz folder/ && xz --stdout folder.tar.gz > folder.tar.gz.xz没有任何效果(根据您的解释是有道理的)。我想我在所有这些压缩方面都有些失落了:D使用时tar cf folder.tar folder/ && xz --stdout folder.tar > folder.tar.xz,实际上最终比一个图像的大小还要大一点(根据默认的dict窗口大小64 MiB也很有意义)。我相应地更新了我的问题。谢谢!
a_guest

@a_guest好的,您的评论描述了另一种第二种情况。问题在于,在中tar -> gzip -> xz,gzip DEFLATE可能以不同的方式压缩PNG数据的每个副本,因此xz将无法检测到冗余。
Nayuki '16

6

问题是,(大多数)压缩方案缺乏对您所拥有数据的了解。即使将PNG解压缩为位图并在tarball中压缩它们,也不会(显着)获得较小的结果。

在许多相似图像的情况下,适当的压缩方案将是视频编解码器。

使用无损编码,您应该获得预期的几乎完美的压缩结果。

如果要测试,请使用以下方法:

ffmpeg -i img%03d.png -c:v libx264 -c:v libx264 -profile:v high444 -crf 0 out.mp4

https://trac.ffmpeg.org/wiki/Create%20a%20video%20slideshow%20from%20images


使用视频编码器的好处!升级Ubuntu时,我会尝试一下,因为默认情况下14.04不包含ffmpeg。我猜这个视频编码器正在使用无损压缩,或者至少有一个开关?你知道吗?
a_guest

是的,-crf 0使其无损(或者像文档中提到的-qp 0一样(优选-qp 0))。trac.ffmpeg.org/wiki/Encode/H.264
乔纳斯(Jonas)

4

PNG按以下顺序是Filters + LZ77 + Huffman的组合(LZ77 + Huffman的组合称为Deflate):

第1步),如果滤镜不同于“无”,则将像素值替换为与相邻像素的差值(有关更多详细信息,请参见http://www.libpng.org/pub/png/book/chapter09.html) 。这会增加具有梯度的图像的压缩率(因此... 4 5 6 7变为... 1 1 1 1),并且可能在相同颜色的区域中有所帮助(... 3 3 3 5 5 5 5 5变为0 0 0 2 0 0 0 0 0)。默认情况下,使用调色板在24位图像中启用过滤器,并在8位图像中禁用过滤器。

步骤2)使用LZ77压缩数据,该LZ77用包含到匹配距离和匹配长度的元组替换重复的(匹配)字节字符串。

步骤3)使用霍夫曼码对步骤2的结果进行编码,该霍夫曼码将可变长码替换为固定长度符号,符号越频繁,代码越短。

存在多个问题:

一个影响很少像素的小变化将导致png压缩的3个步骤的结果发生变化:

1)相邻像素的滤波值将改变(取决于所使用的滤波器)。这将放大小的变化的影响。

2)更改将意味着与该区域的匹配将有所不同。例如,将333333更改为333533会导致333333的另一个匹配项不再匹配,因此它将选择另一个与333333匹配的对象,且匹配距离不同,或者将选择相同的匹配对象,但长度较短,然后再匹配最后3个字节。本身将极大地改变结果。

3)最大的问题是在第3步中。霍夫曼代码使用可变数量的位,因此即使很小的变化也将导致后面的所有内容不再对齐。AFAIK大多数压缩算法无法检测到未按字节对齐的匹配项,因此除非压缩程序可以检测到未按字节对齐的匹配项,否则将防止(或至少减少很多)对更改后已压缩的数据的压缩。

其他答复已涵盖其他问题:

4)Gzip使用具有32KB字典的相同Deflate算法,因此,如果png文件大于32KB,则即使它们相同也不会检测到匹配项。Bzip2在这方面更好,因为它使用900 KB的块。XZ使用LZMA,IIRC在默认压缩级别具有4 MB的字典。5)Zip格式不使用实体压缩,因此不会更好地压缩相似或相同的文件。

也许来自PAQ或PPMD系列的压缩器可以更好地压缩,但是如果您需要压缩大量相似的图像文件,则可以考虑以下三种方法:

1)存储未压缩的图像(使用PNG -0或未压缩的格式),并使用具有较大字典或块大小的压缩器进行压缩。(LZMA会很好地工作)

2)另一个选择是保留过滤器,但从PNG中删除Deflate压缩。例如,可以使用(AdvDef)实用程序来完成。然后,压缩生成的未压缩PNG。解压缩后,您可以保留未压缩的PNG或使用AdvDef再次对其进行压缩(但这将需要一些时间)。

您需要测试两种方法,以查看压缩率最高的方法。

3)最后一种选择是将视频中的png图像转换,使用无损视频压缩器(例如x264 lossless)进行压缩(特别注意使用正确的颜色格式),然后在提取时将帧提取为单个png图像。可以使用ffmpeg完成。您还需要保留帧号和原始名称之间的映射。

那将是最复杂的方法,但是如果png都是动画的一部分,则可能是最有效的。但是,如果需要,您将需要一种支持透明性的视频格式。

编辑:还有MNG格式将不经常使用。


2

当您拥有特殊的数据集时,您将使用特殊的算法,而不是多用途工具。

答案是,您选择的无损压缩并非针对您的工作。没有人期望您将同一张图像压缩两次,即使您(无意间)对所有先前的输入进行检查也会使您的算法为O(n ^ 2)(可能会更好一些,但是最简单的方法至少是n ^ 2)。

您在O(n)上运行时测试的大多数压缩程序,它们都强调速度超过最佳压缩率。没有人愿意为了节省几MB的内存而将其计算机运行5个小时,尤其是在这些天。对于较大的输入,大于O(n)的任何内容都会成为运行时的问题。

另一个问题是ram。当输入变得足够大时,您将无法在任何时间访问输入的每个部分。即使忽略这一点,大多数人也不想为了压缩某些东西而放弃整个ram或cpu。

如果文件中有要压缩的模式,则必须对其进行手动操作,编写自己的压缩或可能使用“归档”类型压缩(nano)。长期存储的压缩,对于日常使用来说太慢了。

另一个选择可能是无损视频压缩。


1
鉴于目录结构在不同位置包含多个相同文件的情况非常普遍,似乎一个好的zip样式实用程序应该提供一个选项来检查要添加到存档中的文件是否具有压缩/未压缩的哈希值和大小与现有文件匹配的文件。如果两个哈希值和两个大小都匹配,则将第二个名称附加到与第一个文件关联的数据块似乎是值得的。即使ZIP无法容纳它,它在任何将来的格式中似乎都是有用的功能。
超级猫

1
您的答案暗示tar的压缩算法有助于压缩某些类型的冗余,但不适用于OP场景中出现的那种冗余。您可能想描述一下您认为哪种冗余有益的,因为这一点都不明显。对于可能从未成功使用过该压缩机的人来说,他们所看到的只是他们在理论上可压缩的东西上进行了尝试,但没有用,那么,这台压缩机到底有什么用呢?
唐·哈奇

1
@leftaroundabout:在我所知道的任何Unix中,都没有办法在匹配文件中使用“写时复制”语义。在许多情况下,存在冗余副本来处理这样的事实:今天可能相同的事物,明天可能不相同的事物,在这种情况下,符号链接或硬链接都不适合。
超级猫

1
@supercat:对于许多这样的文件,使用符号链接到一个“官方”只读版本是一个很好的解决方案。如果然后要更改副本,请用物理副本替换符号链接。
左右约

1
@leftaroundabout:如果可以将工程哈希冲突的危险降低到可接受的水平,我有时会想到的一件事是拥有基于哈希的通用引用标识符,而不是符号链接到“逻辑”文件名一个会基于哈希创建一个链接。然后,归档文件将存储256个字节左右的哈希值,而不是存储真正的大文件。这种方法的一种变体也可以用于启用需要防止更改的文件的缓存。
超级猫

2

PNG文件格式已在内部使用DEFLATE压缩算法。这与xz,gzip和zip使用的算法相同-只是有些变化。tar.gz并且tar.xz利用文件之间的相似性,而zip事实并非如此。

因此,实际上,您对DEFLATE压缩文件执行DEFLATE压缩-这就是为什么文件几乎保持原始大小的原因。

bzip2当涉及(几乎)相同的文件时,该程序(也是一个相关的算法)会更好。

# for i in $(seq 4); do cp test.png test$i.png; done
# tar -cjf archive.tar.bz2 *.png
# ls -l
-rw-r--r-- 1 abcde users  43813 15. Jul 08:45 test.png
-rw-r--r-- 1 abcde users  43813 15. Jul 08:45 test1.png
-rw-r--r-- 1 abcde users  43813 15. Jul 08:46 test2.png
-rw-r--r-- 1 abcde users  43813 15. Jul 08:46 test3.png
-rw-r--r-- 1 abcde users  43813 15. Jul 08:46 test4.png
-rw-r--r-- 1 abcde users  68115 15. Jul 08:47 archive.tar.bz2

PNG-请记住,有使用过的过滤器,非标准的deflate(反正是标准的吗?),并且您正确地运行了两次相同的算法没有任何好处(或者至少不应该有好处),但是运行具有不同设置的相同算法不保证会失败。deflate32,deflate64,LZW,LZMA之间也存在差异,您不能仅仅说它们都使用相同的deflate。
Evil

这就是为什么我说“有些变化”。当然,DEFLATE是指一种算法,而不是某种实现。
rexkogitans

3
据我了解,这错了要点。是的,一个 PNG文件已经被压缩了,所以我不希望任何形式的进一步压缩都会产生很大的效果。但是,可以合理预期将几个相同的PNG文件(实际上就是这种情况)的串联压缩到不超过其中一个文件的大小。
唐·哈奇

显然,这些压缩算法忽略了这一点。bzip2抓住它:tar -cjf archive.tar.bz2 *.png。更新了我的答案。
rexkogitans
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.