BufferedImage 减少图像大小

BufferedImage reduces Image size

我正在使用 java ImageIO 和 BufferedImage 进行一些图像操作,想看看当我将图像输出为 jpg 时它的表现如何。在那里我发现了一些我无法解释的有趣行为。我有以下代码。该代码读取图像,然后在同一文件夹中输出与 "copy.jpg" 相同的图像。代码在Kotlin中,但是使用的函数是java个函数:

val image = File("some/image/path.jpg")
val bufImage = ImageIO.read(image.inputStream())
FileOutputStream(File(image.parentFile, "copy.jpg")).use { os ->
    ImageIO.write(bufImage, "jpg", os)
}

我希望它输出完全相同的文件,除了元信息。然而,生成的文件几乎是原始文件的十分之一。我怀疑元信息会有那么多。确切的大小差异取决于我使用的图像文件,但每次输出图像都会更小。但我看不出与旧文件有质量差异。放大时我会看到相同的像素。

为什么文件大小减少如此之大?

JPEG 是 lossy compression:它丢弃了大量信息以保持文件较小。 (未压缩的图像文件可能大几个数量级。)

当然,它旨在丢弃您不太可能看到或关心的信息;但它仍然会丢失一些图像数据。

并且损失是世代相传的:如果您有一张来自 JPEG 文件的图像,然后将其重新压缩为 JPEG 文件,它通常会丢失 更多 数据,给出比第一个 JPEG 文件质量更差的结果——即使压缩设置完全相同。 (尝试近似已压缩的图像与尝试近似原始源图像的效果不同。并且无法恢复已经丢失的信息!)

几乎可以肯定这就是这里发生的事情。您的代码读取 JPEG 文件并将其扩展为 BufferedImage(其中包含未压缩的图像数据),然后再次将其压缩为新的 JPEG 文件,这进一步降低了质量。它可能比第一个文件使用了更高的压缩率,因此尺寸更小。

如果您在图像查看器或编辑器中放大后看不到两个 JPEG 文件之间的任何差异,我会感到惊讶。 (JPEG 伪像在尖锐的边缘和边界周围最为明显,但如果您知道要寻找什么,有时可以在其他地方看到它们。如果您可以将两个图像排列在屏幕的完全相同区域并翻转,则细微的变化会更容易看到直接在它们之间。)

创建 JPEG 时,您可以控制丢失的信息量 — 但ImageIO.write() method you're using doesn't provide a way to do that.  See this question 如何做到这一点。 (它在 Java 中,但您应该可以跟随它。)

显然,您准备丢失的信息越多,最终得到的文件就越小。但请注意,如果您选择高质量设置,结果可能会比第一个 JPEG 大很多,尽管它可能仍会损失更多质量。

(这就是为什么,如果您要对图像进行任何类型的处理,最好将其保持为无损格式直到最后,并且只压缩一次像 JPEG 这样的有损格式,以避免质量下降每次保存并重新加载。)

如您所说,另一个原因可能是非图像数据丢失 — 您不太可能注意到相机设置等元数据丢失,但文件可能也有相当大的缩略图。