web-dev-qa-db-ja.com

Java-品質を損なわずに画像のサイズを変更します

サイズを変更する必要がある写真が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

これは私のプログラムからの出力です[双線形]:

resize - using Java program - small

PDATE:BICUBICを使用しても大きな違いはありません

これは私のプログラムからの出力です[bicubic]:

enter image description here

とにかくプログラム出力の品質を向上させるために、すべての写真を手動でサイズ変更する必要はありませんか?

前もって感謝します!

54
user3362954

残念ながら、視覚的に良好な結果を提供するJavaには、すぐに使用できる推奨スケーリングはありません。とりわけ、スケーリングに推奨する方法は次のとおりです。

  • Lanczos3リサンプリング(通常は視覚的には優れていますが、遅い)
  • プログレッシブダウンスケーリング(通常は視覚的には問題ありませんが、非常に高速です)
  • アップスケーリングのためのワンステップスケーリング(Graphics2dバイキュービックの高速で良好な結果、通常Lanczos3ほど良好ではない)

すべての方法の例は、この回答に記載されています。

視覚的比較

異なるメソッド/ライブラリを使用して96x140にスケーリングした画像を次に示します。画像をクリックしてフルサイズを取得します。

comparison

comparison zoom

  1. Morten Nobelのlib Lanczos3
  2. Thumbnailatorバイリニアプログレッシブスケーリング
  3. Imgscalr ULTRA_QUALTY(1/7ステップバイキュービックプログレッシブスケーリング)
  4. Imgscalr QUALTY(1/2ステップバイキュービックプログレッシブスケーリング)
  5. モーテンノーベルのlibバイリニアプログレッシブスケーリング
  6. Graphics2dバイキュービック補間
  7. Graphics2d最近傍補間
  8. 参照としてのPhotoshop CS5バイキュービック

残念ながら、スケーリングアルゴリズムを判断するには単一の画像では不十分です。シャープなエッジのアイコン、テキストのある写真などをテストする必要があります。

ランチョスのリサンプリング

アップスケーリング、特にダウンスケーリングに適していると言われています。残念ながら 現在のJDKにはネイティブ実装はありません ですから、自分で実装して Morten Nobel's lib のようなlibを使用してください。上記のlibを使用した簡単な例:

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

Libは maven-centralで公開 ですが、残念ながら言及されていません。欠点は、高度に最適化されたハードウェアアクセラレーションの実装が私に知られていなければ、通常非常に遅いことです。ノーベルの実装は、Graphics2dを使用した1/2ステップのプログレッシブスケーリングアルゴリズムよりも約8倍遅いです。 彼のブログでこのライブラリの詳細を読んでください

プログレッシブスケーリング

スケーリングに関するChris Campbellのブログ で述べられているように、プログレッシブスケーリングは基本的に最終的なサイズに達するまで画像を小さなステップで段階的にスケーリングします。キャンベルは、目標に到達するまで、幅/高さを半分にすることで説明します。これにより、良好な結果が得られ、Graphics2Dとともに使用できます。これはハードウェアアクセラレーションが可能なため、ほとんどの場合、許容できる結果で非常に優れたパフォーマンスを発揮します。これの主な欠点は、Graphics2Dを使用して半分以下にダウンスケールすると、1回しかスケーリングされないため、同じ平凡な結果が得られることです。

仕組みの簡単な例を次に示します。

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が平均6.9秒を記録する1ステップのスケーリングと同じか、わずかに高速です。

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の実装よりも少し遅くなります。私の単純な ベンチマーク は、それぞれ平均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スケーリングメソッドに関する一言

画像をスケーリングする現在の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バージョン(90年代v1.1の話)でImage.getScaledInstance()が導入され、パラメーターSCALE_AREA_AVERAGINGで良好な視覚的結果が得られましたが、使用することはお勧めしません- 詳細な説明はこちら =。

62
patrickf

Thumbnailator は、高品質のサムネイルを簡単な方法で作成するために作成されたライブラリです。既存の画像のバッチ変換を行うことは、そのユースケースの1つです。

バッチサイズ変更の実行

たとえば、Thumbnailatorを使用して例を適合させるには、次のコードで同様の結果を達成できる必要があります。

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

これは先に進み、imagesディレクトリ内のすべてのファイルを取得し、それらを1つずつ処理し、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の答え で述べたように、画質に関しては、Chris Campbellが The Imagers 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もBufferedImagesを作成できることを示しています。


免責事項:私は Thumbnailator ライブラリのメンテナーです。

37
coobird

入力画像が与えられると、コメントの最初のリンクの回答(Chris Campbellに対するクドス)のメソッドは、次のサムネイルのいずれかを生成します。

enter image description hereenter image description here

(もう1つは、MS Paintで作成したサムネイルです。他のものよりも「良い」と呼ぶのは難しいです...)

編集:これも指摘するために:あなたの元のコードの主な問題は、実際には複数のステップでスケールしなかったことでした。奇妙なループを使用して、ターゲットサイズを「計算」しました。重要な点は、実際に複数のステップでスケーリングを実行することです。

完全を期すために、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();
    }    
}
16
Marco13

数日間の研究の後、私はjavaxtを好むでしょう。

使用 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

5
MaMaRo N0

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);
4
Quark

サイズ変更の前にガウスぼかしを適用すると、結果は(プログラムの結果よりも)良いようです:

これは、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);
    }

}

ところで:必要なのはij-1.49d.jar(または他のバージョンの同等物)だけです。 ImageJをinstallする必要はありません。

1
fabian

以下は、外部ライブラリを使用しない、プログレッシブスケーリングの独自の実装です。この助けを願っています。

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;
}
1
Maxwell Cheng