Java - 调整图像大小而不会降低质量

时间:2014-07-14 20:15:23

标签: java image image-processing bufferedimage

我有10,000张照片需要调整大小,所以我有一个Java程序来做。不幸的是,图像的质量很差,我无法访问未压缩的图像。

import java.awt.Graphics;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;


import javax.imageio.ImageIO;
/**
 * This class will resize all the images in a given folder
 * @author 
 *
 */
public class JavaImageResizer {

    public static void main(String[] args) throws IOException {

        File folder = new File("/Users/me/Desktop/images/");
        File[] listOfFiles = folder.listFiles();
        System.out.println("Total No of Files:"+listOfFiles.length);
        BufferedImage img = null;
        BufferedImage tempPNG = null;
        BufferedImage tempJPG = null;
        File newFilePNG = null;
        File newFileJPG = null;
        for (int i = 0; i < listOfFiles.length; i++) {
              if (listOfFiles[i].isFile()) {
                System.out.println("File " + listOfFiles[i].getName());
                img = ImageIO.read(new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()));
                tempJPG = resizeImage(img, img.getWidth(), img.getHeight());
                newFileJPG = new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()+"_New");
                ImageIO.write(tempJPG, "jpg", newFileJPG);
              }
        }
        System.out.println("DONE");
    }

    /**
     * This function resize the image file and returns the BufferedImage object that can be saved to file system.
     */
        public static BufferedImage resizeImage(final Image image, int width, int height) {
    int targetw = 0;
    int targeth = 75;

    if (width > height)targetw = 112;
    else targetw = 50;

    do {
        if (width > targetw) {
            width /= 2;
            if (width < targetw) width = targetw;
        }

        if (height > targeth) {
            height /= 2;
            if (height < targeth) height = targeth;
        }
    } while (width != targetw || height != targeth);

    final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    final Graphics2D graphics2D = bufferedImage.createGraphics();
    graphics2D.setComposite(AlphaComposite.Src);
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    graphics2D.drawImage(image, 0, 0, width, height, null);
    graphics2D.dispose();

    return bufferedImage;
}

我正在使用的图片是这样的: Firwork - original - large

这是我在Microsoft Paint中完成的手动调整大小:

resize - using Paint - small

这是我的程序[bilinear]的输出:

resize - using java program - small

更新:使用BICUBIC

无显着差异

这是我的程序[bicubic]的输出:

enter image description here

无论如何都要提高节目输出的质量,所以我不必手动调整所有照片的大小?

提前谢谢!

7 个答案:

答案 0 :(得分:59)

不幸的是,Java中没有推荐的开箱即用扩展,可提供视觉上良好的结果。其中,以下是我推荐用于缩放的方法:

  • Lanczos3重新取样(通常在视觉上更好,但更慢)
  • 渐进向下缩放(通常在视觉上很好,可以非常快)
  • 向上缩放的一步缩放(Graphics2d双三次快速且结果良好,通常不如Lanczos3好)

每个方法的例子都可以在这个答案中找到。

视觉比较

这是使用不同方法/库缩放到96x140的图像。点击图片即可获得完整尺寸:

comparison

comparison zoom

  1. Morten Nobel的lib Lanczos3
  2. Thumbnailator Bilinear Progressive Scaling
  3. Imgscalr ULTRA_QUALTY(1/7步双立方渐进式缩放)
  4. Imgscalr QUALTY(1/2步双立方渐进式缩放)
  5. Morten Nobel的lib Bilinear Progressive Scaling
  6. Graphics2d双立方插值
  7. Graphics2d最近邻插值
  8. Photoshop CS5 bicubic作为参考
  9. 不幸的是,单个图像不足以判断缩放算法,您应该测试具有锐边的图标,带有文本的照片等。

    Lanczos重新取样

    据说有利于上升,特别是降尺度。不幸的是there is no native implementation in current JDK所以你要么自己实现它并使用类似Morten Nobel's lib的lib。使用所述lib的一个简单示例:

    ResampleOp resizeOp = new ResampleOp(dWidth, dHeight);
    resizeOp.setFilter(ResampleFilters.getLanczos3Filter());
    BufferedImage scaledImage = resizeOp.filter(imageToScale, null);
    

    lib是published on maven-central,遗憾的是没有提到。缺点是它通常非常慢,没有任何高度优化或硬件加速的实现。诺贝尔的实现比具有Graphics2d的1/2步进渐进缩放算法慢约8倍。 Read more about this lib on his blog

    渐进式缩放

    在Java Chris Campbell's blog about scaling中提到,渐进式缩放基本上是以较小的步长逐步缩放图像,直到达到最终尺寸。坎贝尔将其描述为将宽度/高度减半,直到达到目标。这可以产生良好的结果,并且可以与Graphics2D一起使用,它可以是硬件加速的,因此在大多数情况下通常具有非常好的性能和可接受的结果。这样做的主要缺点是,如果使用Graphics2D缩小不到一半,则会提供相同的平庸结果,因为它只缩放一次。

    以下是一个有关其工作原理的简单示例:

    progressive scaling

    以下库包含基于Graphics2d的渐进式缩放形式:

    Thumbnailator v0.4.8

    如果目标是每个维度的至少一半,则使用渐进双线性算法,否则它使用简单的Graphics2d双线性缩放和双三次扩展。

    Resizer resizer = DefaultResizerFactory.getInstance().getResizer(
      new Dimension(imageToScale.getWidth(), imageToScale.getHeight()), 
      new Dimension(dWidth, dHeight))
    BufferedImage scaledImage = new FixedSizeThumbnailMaker(
      dWidth, dHeight, false, true).resizer(resizer).make(imageToScale);
    

    Graphics2d benchmark平均评分为6.9秒相比,快于或略快于一步缩放。

    Imgscalr v4.2

    使用渐进式双三次缩放。在QUALITY设置中,它使用Campbell样式算法,每个步骤将尺寸减半,而ULTRA_QUALITY具有更精细的步长,将每个增量的大小减小1/7,从而生成通常更柔和的图像,但最小化仅显示实例的情况使用1次迭代。

    BufferedImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray);
    

    主要缺点是表现。 ULTRA_QUALITY比其他库慢得多。甚至QUALITY比Thumbnailator的实现慢一点。我的简单benchmark分别平均得出26.2秒和11.1秒。

    Morten Nobel's lib v0.8.6

    还有针对所有基本Graphics2d(双线性,双三次和最近邻居)的渐进式缩放的实现

    BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);
    

    关于JDK Scaling Methods的一个词

    缩放图像的当前jdk方式将是这样的

    scaledImage = new BufferedImage(dWidth, dHeight, imageType);
    Graphics2D graphics2D = scaledImage.createGraphics();
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
    graphics2D.dispose();
    

    但是无论使用什么插值或其他RenderHints,大多数人对降尺度的结果都非常失望。另一方面,放大似乎产生可接受的图像(最好是双三次)。在以前的JDK版本中(我们说90s v1.1)Image.getScaledInstance()引入了SCALE_AREA_AVERAGING,它提供了参数{{1}}的良好视觉效果,但不鼓励您使用它read the full explanation here

答案 1 :(得分:36)

Thumbnailator是一个用于以简单方式创建高质量缩略图的库,并且对现有图像进行批量转换是其使用案例之一。

执行批量调整大小

例如,要使用Thumbnailator调整您的示例,您应该能够使用以下代码获得类似的结果:

File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
    .size(112, 75)
    .outputFormat("jpg")
    .toFiles(Rename.PREFIX_DOT_THUMBNAIL);

这将继续执行并获取images目录中的所有文件并继续逐个处理它们,尝试调整它们以适应112 x 75的尺寸,并且它将尝试保留方面原始图像的比例,以防止图像“翘曲”。

Thumbnailator将继续读取所有文件,无论图像类型如何(只要Java Image IO支持格式,Thumbnailator将处理它),执行调整大小操作并将缩略图输出为JPEG文件,同时添加thumbnail.到文件名的开头。

以下是如果执行上述代码,将如何在缩略图的文件名中使用原件的文件名。

images/fireworks.jpg     ->  images/thumbnail.fireworks.jpg
images/illustration.png  ->  images/thumbnail.illustration.png
images/mountains.jpg     ->  images/thumbnail.mountains.jpg

生成高质量缩略图

在图像质量方面,如Marco13's answer所述,Chris Campbell在他的The Perils of Image.getScaledInstance()中描述的技术是在Thumbnailator中实现的,从而产生高质量的缩略图,无需任何复杂的处理。

以下是使用Thumbnailator调整原始问题中显示的烟花图像时生成的缩略图:

Thumbnail of image in original question

上面的图片是使用以下代码创建的:

BufferedImage thumbnail = 
    Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg"))
        .height(75)
        .asBufferedImage();

ImageIO.write(thumbnail, "png", new File("24745147.png"));

代码显示它也可以接受URL作为输入,并且Thumbnailator也能够创建BufferedImage


免责声明:我是Thumbnailator库的维护者。

答案 2 :(得分:16)

根据您的输入图像,评论中第一个链接中的答案方法(对Chris Campbell的称赞)会产生以下缩略图之一:

enter image description here enter image description here

(另一个是您使用MS Paint创建的缩略图。很难将其中一个称为“更好”而不是其他...)

编辑:也是指出这一点:原始代码的主要问题在于您没有真正按多个步骤缩放图像。您刚刚使用了一个奇怪的循环来“计算”目标大小。关键是你实际上是在多个步骤中执行 scaling

为了完整,MVCE

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;

public class ResizeQuality
{
    public static void main(String[] args) throws IOException
    {
        BufferedImage image = ImageIO.read(new File("X0aPT.jpg"));
        BufferedImage scaled = getScaledInstance(
            image, 51, 75, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
        writeJPG(scaled, new FileOutputStream("X0aPT_tn.jpg"), 0.85f);
    }

    public static BufferedImage getScaledInstance(
        BufferedImage img, int targetWidth,
        int targetHeight, Object hint, 
        boolean higherQuality)
    {
        int type =
            (img.getTransparency() == Transparency.OPAQUE)
            ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage ret = (BufferedImage) img;
        int w, h;
        if (higherQuality)
        {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = img.getWidth();
            h = img.getHeight();
        }
        else
        {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = targetWidth;
            h = targetHeight;
        }

        do
        {
            if (higherQuality && w > targetWidth)
            {
                w /= 2;
                if (w < targetWidth)
                {
                    w = targetWidth;
                }
            }

            if (higherQuality && h > targetHeight)
            {
                h /= 2;
                if (h < targetHeight)
                {
                    h = targetHeight;
                }
            }

            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
        } while (w != targetWidth || h != targetHeight);

        return ret;
    }

    public static void writeJPG(
        BufferedImage bufferedImage,
        OutputStream outputStream,
        float quality) throws IOException
    {
        Iterator<ImageWriter> iterator =
            ImageIO.getImageWritersByFormatName("jpg");
        ImageWriter imageWriter = iterator.next();
        ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
        imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        imageWriteParam.setCompressionQuality(quality);
        ImageOutputStream imageOutputStream =
            new MemoryCacheImageOutputStream(outputStream);
        imageWriter.setOutput(imageOutputStream);
        IIOImage iioimage = new IIOImage(bufferedImage, null, null);
        imageWriter.write(null, iioimage, imageWriteParam);
        imageOutputStream.flush();
    }    
}

答案 3 :(得分:5)

经过几天的研究,我更喜欢javaxt。

use javaxt.io.Image类有一个构造函数,如:

public Image(java.awt.image.BufferedImage bufferedImage)

所以你可以做(​​another example):

javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);

这是输出:

enter image description here

答案 4 :(得分:4)

我们不应忘记TwelveMonkeys Library

它包含一个非常令人印象深刻的过滤器集合。

用法示例:

BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height

BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS);
BufferedImage output = resampler.filter(input, null);

答案 5 :(得分:1)

如果您在调整大小之前应用高斯模糊,结果似乎会更好(比您的程序结果):

这是我得到的结果sigma * (scale factor) = 0.3

Thumbnail when bluring first(sigma=15.0)

使用ImageJ,执行此操作的代码非常简短:

import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;

public class Resizer {

    public static void main(String[] args) {
        processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3);
    }

    public static void processPicture(String inputFile, String outputFilePath, double scaleFactor, int interpolationMethod, double sigmaFactor) {
        Opener opener = new Opener();
        ImageProcessor ip = opener.openImage(inputFile).getProcessor();
        ip.blurGaussian(sigmaFactor / scaleFactor);
        ip.setInterpolationMethod(interpolationMethod);
        ImageProcessor outputProcessor = ip.resize((int)(ip.getWidth() * scaleFactor), (int)(ip.getHeight()*scaleFactor));
        IJ.saveAs(new ImagePlus("", outputProcessor), outputFilePath.substring(outputFilePath.lastIndexOf('.')+1), outputFilePath);
    }

}

BTW:你只需要ij-1.49d.jar(或其他版本的等价物);没有必要安装 ImageJ。

答案 6 :(得分:1)

以下是我自己的Progressive Scaling实现,不使用任何外部库。希望这有帮助。

private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
    if (before != null) {
        Integer w = before.getWidth();
        Integer h = before.getHeight();

        Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;

        //Multi Step Rescale operation
        //This technique is describen in Chris Campbell’s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
        while (ratio < 0.5) {
            BufferedImage tmp = scale(before, 0.5);
            before = tmp;
            w = before.getWidth();
            h = before.getHeight();
            ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
        }
        BufferedImage after = scale(before, ratio);
        return after;
    }
    return null;
}

private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
    Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
    Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
    BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics2D = scaledImage.createGraphics();
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
    graphics2D.dispose();
    return scaledImage;
}