图像大小调整质量(Java)

15

我有一个开源应用程序,可以将照片上传到Facebook。为了节省带宽,在上传之前会自动调整照片大小(因为Facebook有最大尺寸限制)。但有些人抱怨照片质量不好,实际上确实可以看到差异(请参见此问题以查看一些演示图像)。

那么我的问题是,在Java中缩小图像(即照片)而不失去质量的“最佳”方法是什么,或者至少,具有最小的质量损失/伪影?

您可以在此处查看我当前使用的代码(通过此页面中的缩放代码)。

7个回答

17

Phil,我不知道你最终选择了哪种解决方案,但是如果您:

  • 避免使用JDK不支持的BufferedImage类型。
  • 使用渐进式缩放
  • 在使用渐进式缩放时坚持使用双三次插值算法。

我已经做了大量测试,并且渐进式缩放以及坚持使用得到良好支持的图像类型是关键——我看到亚历山大提到他仍然没有运气,这真是太糟糕了。

大约6个月前,我发布了imgscalr库(Apache 2),以解决“我想要这张图片的好看缩放副本,现在就做!”这样的问题,因为我在Stack Overflow上读到了10多个这样的问题。

标准用法如下:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 640);

第二个参数是imgscalr用来缩放图像的边界宽度和高度 - 即使您传入无效的尺寸,也会保持其比例正确 - 还有许多更详细的方法,但这是最简单的用法。
例如,如果Facebook将图像限制为800x600像素,您想要使用的用例将如下所示:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY, 800, 600);

这将确保图像保持在最佳支持的图像类型中,并使用Java可以达到的最高质量方法进行缩放。在我的高分辨率测试中,我没有注意到使用此库/这些方法缩放图像时存在任何明显的差异,除非您的图像被ImageIO加载程序放入了不受支持的图像类型中 - 例如,这在GIF中经常发生。如果您让它们保持原样并且不将它们从那些不受支持的类型中取出,最终会看起来很模糊和糟糕。这是因为Java2D团队实际上为JDK可以处理的所有不同类型的BufferedImage拥有不同的硬件加速管道 - 那些不太常见的图像类型的子集都会回退到在Java2D中底层使用相同的软件渲染管道,导致图像质量差,有时甚至完全不正确。这很难解释和理解,所以我直接将该逻辑写入库中。如果您感兴趣,最好支持的两种类型是BufferedImage.TYPE_INT_RGB和_ARGB。

嗨 Riyad,感谢您为此做出的努力。这很棒,我刚开始使用它。当我尝试将图像仅调整大小至特定宽度和高度时,我遇到了质量问题。我有一张240X320的图片,我必须将其调整为50X75和120X180。我分别尝试了以下代码(我正在使用imgscalr 4.2)。 - WowBow
1
image=resize(image,Method.ULTRA_QUALITY, 50,75, OP_ANTIALIAS, OP_BRIGHTER); saveImage(image, ImageFormatTypes.JPEG, DESTINATION + RESIZED_IMAGE + "."+ImageFormatTypes.JPEG);我有两个问题。第一个是与我手动调整图像大小的其他内部工具相比,这种方法得到的图像质量非常低。第二个问题是宽度从50增加到56,从120增加到135,这很奇怪。你有任何想法为什么会出现这些问题吗?感谢你的帮助。 - WowBow
2
@WowBow 啊!是的,我想我知道发生了什么...我讨厌imgscalr还没有将这个抽象化,但它操作的是原始像素值--当您使用ImageIO将图像写回时,它使用默认的JPG编码器设置,这些设置为75%的质量,我相信--您必须谷歌“java ImageIO JPEG quality”以获取示例代码,以将其设置为95%。测试是否是这种情况的“便宜简单”的方法是将图像写成PNG(无损格式)--如果它看起来“好多了”,那就是这样。 - Riyad Kalla
1
@WowBow 还有一个建议,尝试移除 AA 和 Brightness OP,以防它们对图像产生奇怪的影响,只输出缩放后的图像,看看效果如何。 - Riyad Kalla
谢谢。我会试一试的。 - WowBow
@riyad-kalla 感谢您的建议,我已经搜索了一下,在这里找到了答案 https://dev59.com/OGQn5IYBdhLWcg3wGD1s#26319958 - mulya

13

目前在Java中,最受欢迎的两个开源图像调整库是:

此外,还有使用Java的Graphics2D(请参见如何操作)的JDK方法,但其创建的结果常常质量较差,特别是缩小图像时。还有一个Java接口到ImageMagick,此处不再赘述,因为它需要外部工具。

视觉质量

这是将一个大小为580x852的png文件调整/缩小到145x213的结果比较。参考Photoshop CS5中的“保存为Web”调整大小方法。注意:结果与库创建的内容完全一致。缩放没有使用任何滤波器,只用了一个简单的最近邻算法。您可以在这里找到原始图片。

comparison

  1. 默认设置下的Thumbnailator 0.4.8,无尺寸调整
  2. Photoshop CS5采用双三次算法
  3. ULTRA_QUALITY模式下的imgscalr 4.2,无尺寸调整
  4. Graphics2D (Java 8)采用渲染提示VALUE_INTERPOLATION_BICUBIC、VALUE_RENDER_QUALITY和VALUE_ANTIALIAS_ON

我把选择最佳结果的工作留给读者,因为这是主观的。一般来说,除了Graphics2D之外,所有输出都很好。Thumbnailator生成的图像更锐利,非常类似于Photoshop的输出,而imgscalr的输出则相对柔和。对于图标/文本等,您需要更锐利的输出,而对于图片,则可能需要更柔和的输出。

计算时间

这是一个非科学的基准测试,使用了工具和114个尺寸从大约96x962560x1440的图像,将其视为425%的图像创建:100%、150%、200%、300%和400%的比例版本(因此进行了114 * 5次缩放操作)。所有库都使用与质量比较相同的设置(因此是最高质量)。时间仅涉及缩放而不是整个过程。在i5-2520M上完成,配备8GB RAM和5次运行。

  • Thumbnailator: 7003.0毫秒 | 6581.3毫秒 | 6019.1毫秒 | 6375.3毫秒 | 8700.3毫秒
  • imgscalr: 25218.5毫秒 | 25786.6毫秒 | 25095.7毫秒 | 25790.4毫秒 | 29296.3毫秒
  • Graphics2D: 7387.6毫秒 | 7177.0毫秒 | 7048.2毫秒 | 7132.3毫秒 | 7510.3毫秒

这是基准测试中使用的代码。

有趣的是,Thumbnailator 平均时间最快,只需6.9秒,其次是 Java2D,需要7.2秒,而 imgscalr 则需要26.2秒,这可能不公平,因为 imgscalr 设置为 ULTRA_QUALITY,这似乎非常昂贵;将其设置为 QUALITY 后,平均时间更具竞争力,为11.1秒。


性能测量很困难(我在这个答案中写了一些关于此的话)。尽管如此,对于这个和其他答案点赞。 - Marco13
@Marco13 是的,但我认为我的样本大小已经足够大且多元化,至少能捕捉到一些趋势。 - Patrick
我自己完成了测试,并观察到与您相似的模式(Thumbnailator提供了最快的性能)。我建议您将“imgscalr”行写两次(一次为ULTRA_QUALITY,一次为QUALITY),以便在快速查看时更容易比较基准测试。 - Rauni Lillemets

12
我尝试了所有方法,包括这里的技巧,但我只能说最好使用ImageMagick和任何界面。当涉及到这个时,Java图像库表现不佳。您需要支持许多格式和算法才能做对。

我本来希望避免外部调用,但我认为你可能是对的。Java似乎还没有完全掌握图像处理! - user7094
2
这也是我的结论!Java图像API太糟糕了!我可以解决它的迟缓问题,但是质量不佳就无法接受了!我希望相关的API能够得到一些必要的关注,包括JavaFX 2.0。 - Kimble
1
你显然还没有遇到过 java-image-scaling - Steve
尝试使用java-image-scaling库,图片比我自己的方法更清晰。感谢提供这个链接。 - chrome

5

1
测试了org.imgscalr.Scalr和com.mortennobel.imagescaling,结合更改压缩级别(http://blog.carsoncheng.ca/2011/02/how-to-change-jpeg-compression-in-java.html),但都没有成功。thumbnailator在质量方面非常棒。谢谢! - daker

2
你正在使用哪种渲染提示?通常双三次重采样会是最好的选择。从你提供的照片来看,它们非常锯齿状,这让我想到你可能正在使用最近邻居作为提示。
在你提供的PictureScaler类中,在paintComponent方法中,它使用了六种不同的方法来调整图像大小。你尝试过所有六种方法来确定哪一种效果最好吗?

我认为在代码中,提问者似乎有一个“高质量”的模式,需要通过多次调整图像大小来实现。这看起来不太好,原因跟你不会想要反复压缩和解压音频样本是一样的;图像质量实际上会变得更差而不是更好。 - jprete
嗨,实际上我还没有尝试过所有六种方法,这可能是我应该做的。虽然我正在使用双三次重采样,就像你所说的那样,这“应该”是最好的。我也没有使用“高质量”模式,因为在那种情况下它不起作用,Java图像API似乎容易死锁!可能是我的代码有问题,不确定是什么。 - user7094

0

我想要保持纵横比的最高质量调整大小。

尝试了几种方法并阅读了几篇文章。花费了两天时间,最后用简单的Java方法得到了最好的结果(也尝试了ImageMagick和java-image-scaling库):

public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
  BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
  double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
  if (width < 1) {
    width = (int) (height * ratio + 0.4);
  } else if (height < 1) {
    height = (int) (width /ratio + 0.4);
  }

  Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
  BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
  Graphics2D g2d = bufferedScaled.createGraphics();
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  g2d.drawImage(scaled, 0, 0, width, height, null);
  dest.createNewFile();
  writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
  return true;
}


/**
* Write a JPEG file setting the compression quality.
*
* @param image a BufferedImage to be saved
* @param destFile destination file (absolute or relative path)
* @param quality a float between 0 and 1, where 1 means uncompressed.
* @throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
      throws IOException {
  ImageWriter writer = null;
  FileImageOutputStream output = null;
  try {
    writer = ImageIO.getImageWritersByFormatName("jpeg").next();
    ImageWriteParam param = writer.getDefaultWriteParam();
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(quality);
    output = new FileImageOutputStream(new File(destFile));
    writer.setOutput(output);
    IIOImage iioImage = new IIOImage(image, null, null);
    writer.write(null, iioImage, param);
  } catch (IOException ex) {
    throw ex;
  } finally {
    if (writer != null) {
      writer.dispose();
    }
    if (output != null) {
      output.close();
    }
  }
}

0

经过几次令人沮丧的实验,我找到了以下 缩放评估 ,并在我的项目中采用了多通道方法。

为了做到这一点,我将 getScaledInstance() 方法复制到了我的缩略图生成器类中,改变了我的图像读取方法以使用 ImageIO(它返回一个 BufferedImage),现在我非常满意!

我将结果与在 Photoshop CS3 中进行的调整进行了比较,结果非常相似。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接