QA@IT
この質問・回答は、@ITの旧掲示板からインポートされたものです。

画像の縮小について

現在、画像(JPEG)を縮小するプログラムを作っています。

private BufferedImage readShrinkImage(ImageReader reader, double power) {
    BufferedImage shrinkImage = null;

    try {
        BufferedImage image = reader.read(0);

        shrinkImage = new BufferedImage((int)(image.getWidth()*power), (int)(image.getHeight()*power),
                                        image.getType());
        AffineTransformOp atOp = new AffineTransformOp(
                               AffineTransform.getScaleInstance(power, power), AffineTransformOp.TYPE_BILINEAR);
        atOp.filter(image, shrinkImage);

    } catch (IOException ex) {
        ex.printStackTrace();
    }

    return shrinkImage;
}

上記のようなメソッドを作って、画像(800x600)を縮小したところ、大きさが元画像の40%以上縮小すると、ジャギが非常に目立ってしまい満足する結果が得られませんでした。

そこで、画像のスケーリング性能の高いライブラリ、または手法を探していますが、よいものをご存知ではないでしょうか。
この後、JAI を試してみようと思いますが、すでにご使用になられた方の意見を聞いてみたいです。

よろしくご教示お願いいたします。
[ メッセージ編集済み 編集者: おちゃ 編集日時 2005-03-30 19:47 ]

質問者:おちゃ

回答

Java2Dに描画の品質を変えれる機能があります。
Graphics2D.addRenderingHints(Map hints)
RenderingHints.KEY_RENDERING
RenderingHints.VALUE_RENDER_QUALITY
ひょっとしたら、プラットフォームやバージョンで違うかもしれませんが、
Windows,Java1.4ではいけてます。

投稿者:a-san

編集 履歴 (0)

a-sanさんありがとうございます。

private BufferedImage readShrinkImage(ImageReader reader, double power) {
    BufferedImage shrinkImage = null;

    try {
        BufferedImage image = reader.read(0);

        shrinkImage = new BufferedImage((int)(image.getWidth()*power), (int)(image.getHeight()*power),
                                        image.getType());

        HashMap hints = new HashMap();
        hints.clear();
        hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);

        AffineTransformOp atOp = new AffineTransformOp(
                               AffineTransform.getScaleInstance(power, power), new RenderingHints(hints) );
        atOp.filter(image, shrinkImage);

    } catch (IOException ex) {
        ex.printStackTrace();
    }

    return shrinkImage;
}

こんな感じに変更してみましたが、画質が上がっている様子が見られませんでした。
どこか間違ってしまったのでしょうか…。

引き続き皆様のご教示、お願い申し上げます。

投稿者:おちゃ

編集 履歴 (0)
        hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

ジャギが問題なら、BILINEAR を BICUBIC にするだけで改善しませんか?

投稿者:holic

編集 履歴 (0)

holicさん、ありがとうございます。

BICUBICも試してみましたが、まったく変化がありません。
その他のパラメータも、変更してみたのですが、まったく画質が変わりません。

なにか、根本的に間違っているのでしょうか??

投稿者:おちゃ

編集 履歴 (0)

以下で試してみてください。


import java.io.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.imageio.*;

class TestRenderingHints extends JComponent {
    Image image;
    TestRenderingHints(Image image) {
        this.image = image;
    }
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;

        // レンダリング時の参考値を設定する。
        HashMap hints = new HashMap(); 
        hints.clear(); 
        hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 
        hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 
        hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 
        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 
        hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); 
        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
        hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); 
        g2.setRenderingHints(hints);

        double scale = 0.1;
        AffineTransform tx = AffineTransform.getScaleInstance(scale, scale);
        g2.drawImage(image, tx, null);
    }
    // 引数に画像のファイル名を指定
    public static void main(String[] args) throws Exception {
        BufferedImage image = ImageIO.read(new File(args[0]));
        TestRenderingHints comp = new TestRenderingHints(image);
        JFrame frame = new JFrame();
        frame.getContentPane().add(comp);
        frame.setSize(640, 480);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

[ メッセージ編集済み 編集者: a-san 編集日時 2005-04-01 16:30 ]

投稿者:a-san

編集 履歴 (0)

a-sanさん、サンプルコードありがとうございます。
ご提供いただいたサンプルコードをそのままコンパイルし、実行してみたのですが
望んでいたクオリティの縮小画像を得ることができませんでした。(私の作ったプログラムと同程度)

Windows の GDI+ 程度のクオリティを求めているのですが、標準のAPIのみでは難しいのでしょうか・・・

投稿者:おちゃ

編集 履歴 (0)

ちなみに実行環境は

Windows XP SP2
J2SDK 1.4.2_6

です。

投稿者:おちゃ

編集 履歴 (0)

unibon です。こんにちわ。

画像ライブラリーのことは良くは知りませんが、どうやらおちゃさんはかなりの高画質を求められているのですよね。a-sanさんのご回答の中で提示されたプログラムを Windows 98 + JDK 1.4.2 で動かしてみましたが、一般向けのサムネールとしては十分な画質に見えます。
#最初は、プラットフォームが違ったりすると、とても低品質の縮小になるとか、そういう話かと推測していました。

しかし、コントラストが強い境界線が、水平線や垂直線から少しだけ傾いていると、たしかにおっしゃるようなジャギーが目立ちますね。逆に、コントラストが弱かったり、角度が45度に近いとジャギーは目立たないようです。
Windows 98 の拡大鏡で見てみましたが、どうやらガンマ値とかそういった要因のような気がします。
たとえば、

□□□□□□□□□□□□□□□■■■■■
□□□□□□□□□□■■■■■■■■■■
□□□□□■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■

のような斜線を、アンチエイリアスするときに、理想的には、

00000000000123455555
00000012345555555555
01234555555555555555
55555555555555555555

のようにすると良いと思います(数字の大きさが黒さを示します)が、これが、

00000000000002455555
00000000245555555555
00024555555555555555
55555555555555555555

のような感じになっていて、階調を十分に使いこなしていないような感じがします。

投稿者:unibon

編集 履歴 (0)

うーむ。
こちらは、Windows2000, J2SDK 1.4.2_01です。
確か、WindowsXPでもうまくいけてました。
拡大してみると、ちゃんと元の画像の数画素分の色が混ざって、
中間色になっているのがわかります。
Hintsを無効にしてみると、こんどは単純に間引かれているので、
Hintsが機能していることもわかります。
GDI+は知らないのですが、もっとすごいクオリティを求めているのでしょうか?

投稿者:a-san

編集 履歴 (0)

unibonさん、a-sanさん、いろいろ考えてくださってありがとうございます。

クオリティ的には、わりと普通レベルだとおもっています。
というのは、前の話に出ていた GDI+ というのはWindowsXPに標準で入っている描画用のコンポーネントです。
ですから、WindowsXPで画像ファイルの入っているフォルダを開くとサムネイルが表示されますよね。
あの程度のクオリティでよいのです。

いろいろ考えると、なんだかHintsの設定が反映されていない気がするのです。
(目視で感じているだけので、このへん再検証の必要があるかもしれません)

環境とか関係あるのでしょうか…。
マシン、OS、J2SDK の各種バージョンで検証・調査してみます。

最悪、スケーリングの自前実装も視野に入れつつ、調査してみます。

投稿者:おちゃ

編集 履歴 (0)

WindowsXP SP2
J2SDK 1.4.1_07

java version "1.5.0_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_01-b08)
Java HotSpot(TM) Client VM (build 1.5.0_01-b08, mixed mode, sharing)

別のマシンでa-sanさんのプログラムを動かしてみました。
やはり状況は変わりませんでした。
出力された画像をPhotoShopで拡大してみたのですが
Hintsが指定されているもの。Hintsの指定をコメントアウトしたもの。
を比べてみたところ、両者は変わりがないようでした。
Hintsの指定が無視されているようです。

先日試したマシンとの共通項は
OS が WindowsXP SP2
java version が "1.5.0_01"
です。

J2SDK と Runtime のバージョンが違っているのがまずいのかな…。
継続調査します。

投稿者:おちゃ

編集 履歴 (0)

unibon です。こんにちわ。

おちゃさんの書き込み (2005-04-04 06:20) より:

出力された画像をPhotoShopで拡大してみたのですが

Hintsが指定されているもの。Hintsの指定をコメントアウトしたもの。

を比べてみたところ、両者は変わりがないようでした。

Hintsの指定が無視されているようです。

試してみたのですが、scale が小さすぎる(たとえば0.25位)と Hints が効かないようですが、scale が大きめ(0.5以上)ならば、Hints は目に見えて効くような感じです。おちゃさんの環境で scale を大きめにしても Hints は効かないでしょうか?もし大き目の scale でなら Hints が効くようならば、やっぱりライブラリー(Java2D)の性能(?)なのかもしれません。

投稿者:unibon

編集 履歴 (0)

unibonさん、試してくれてありがとございます。

私の環境でも、unibonさんのおっしゃるとおり、scaleが大きめ(0.5以上)だと、非常にきれいな出力が得られています。
目標としているscale = 0.3 ぐらいの出力を得ようとするとHintsが効いていません。

やはりライブラリの仕様なのか…。

J2SDKを入れなおしたり、別のOSで試したり、いろいろな環境で検証してみたのですが、変化はありませんでした。Javaの理念からいえば、ある意味当然なのですが。

今後は「別のライブラリを試してみる or 自分で実装」の方向で検証してみます。

投稿者:おちゃ

編集 履歴 (0)

ふーん、scale = 0.5 ならうまくいくんだ?

scale 0.3 = √0.3 * √0.3 = 0.54 * 0.54

scale = 0.54 で 2回縮小するんじゃだめかね?

投稿者:未記入

編集 履歴 (0)

もしかして、Graphics#drawImage()に対してはRenderingHintは効かなかったりしませんか?
ラスタ画像の縮小再描画ではなくて、draw(),fill()等を使用したベクトル描画を行えば、
アンチエイリアス等のRenderingHintの効果があったりしません?

昔その辺をいじったとき、私の環境(Win2000+JDK1.3だったかな)では、ベクトル描画命令
ならきれいにアンチエイリアスしてくれていたので…
その発想で行くと、Apache-Batikを使用してSVGフォーマットで定義したベクトル画像を
描画、なんてのも良いのかもしれません。
[ メッセージ編集済み 編集者: シュン 編集日時 2005-04-04 18:55 ]

投稿者:シュン

編集 履歴 (0)

こんにちは、さくらばです。

ヒントは画像の拡大には効くのですが、縮小にはあまり有効に働かないです。
とくに RenderingHints.VALUE_INTERPOLATION_BILINEAR とか
RenderingHints.VALUE_INTERPOLATION_BICUBIC は縮小の時には役に立ちません。

問題はジャギーですよね。
それならば、縮小する前にいちどぼかしをかけるとうまくいきます。
ぼかしをかけるには ConvolveOp クラスを使います。

添付したプログラムで行ったサンプルを示しておきます。
元の画像は 3008 x 2000 で、それを 300x200 にしています。

オリジナル: http://www5.airnet.ne.jp/sakuraba/java/temp/sample.jpg
単純に縮小: http://www5.airnet.ne.jp/sakuraba/java/temp/result1.jpg
ぼかしてから縮小: http://www5.airnet.ne.jp/sakuraba/java/temp/result2.jpg

この方法は時間がかかるという欠点がありますが、処理時間が気になら
ない場合であれば有効だと思います。



import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import javax.imageio.ImageIO;

public class ScalingSample {
    public ScalingSample(String file, double scale) throws Exception {
        BufferedImage orig = ImageIO.read(new File(file));
        AffineTransformOp atOp = new AffineTransformOp(
                                           AffineTransform.getScaleInstance(scale, scale), null);

        // 単純に縮小
        BufferedImage dest1 = new BufferedImage((int)(orig.getWidth() * scale),
                                                (int)(orig.getHeight() * scale),
                                                orig.getType()); 
        atOp.filter(orig, dest1);
        ImageIO.write(dest1, "jpg", new File("result1.jpg")); 


        // 一度ぼかしてから縮小
        BufferedImage dest2 = new BufferedImage((int)(orig.getWidth() * scale),
                                                (int)(orig.getHeight() * scale),
                                                orig.getType()); 
        int size = (int)(1.0/scale);
        float[] kernelData = new float[size*size];
        for (int i = 0; i < size * size; i++) {
            kernelData[i] = 1.0f / size / size;
        }

        Kernel kernel = new Kernel(size, size, kernelData);
        ConvolveOp coOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); 
        BufferedImage inter = new BufferedImage(orig.getWidth(), orig.getHeight(), orig.getType()); 
        coOp.filter(orig, inter);
        atOp.filter(inter, dest2);
        ImageIO.write(dest2, "jpg", new File("result2.jpg")); 
    }

    public static void main(String[] args) throws Exception {
        String file = args[0];
        double scale = Double.parseDouble(args[1]);
        new ScalingSample(file, scale);
    }
}

投稿者:さくらば

編集 履歴 (0)

未記入さん、ありがとうございます。
えー、そんなばかな…とも思いつつ試してみました。
得られた出力は、通常のスケーリングで縮小したものと同程度のクオリティでした。残念。

シュンさん、ありがとうございます。
そうですね、ベクター画像なら、いくら縮小拡大してもクオリティは変わりませんものね。
今回、使用しているのはjpegのラスタ画像なので、ラスタ画像をベクター画像に変換するものはないかなと探してみましたが、さすがにそんなに都合のよいものはありませんでした。

さくらばさん、ありがとうございます。
私のほうでもPhotoShopではどうやって縮小しているんだろう、と思いまして調べたところ縮小するだけでなく、ぼかし等のフィルタをかけていることがわかりました。
サンプルソースためさせていただきました。たしかに使えそうですね。
フィルタの種類や順番などに、いろいろノウハウがあるようなので
その辺調べてみます。

みなさま、いろいろ考えてくださってありがとうございます。
調査結果は随時書き込んでいきますので、よろしくおねがいします。

投稿者:おちゃ

編集 履歴 (0)

unibon です。こんにちわ。

おちゃさんの書き込み (2005-04-05 18:55) より:

未記入さん、ありがとうございます。

えー、そんなばかな…とも思いつつ試してみました。

得られた出力は、通常のスケーリングで縮小したものと同程度のクオリティでした。残念。

ダメですか?私はキレイに見えますが。以下、試したコードです。
拡大率は、0.5倍×0.5倍=0.25倍 です(面積が 1/16 になる)。



import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.imageio.*;

public class Chiccha extends JPanel {

    private BufferedImage image;

    public Chiccha(BufferedImage anImage) {
        image = anImage;
        setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
    }

    public void paintComponent(Graphics g) {
        g.drawImage(image, 0, 0, null);
    }

    private static BufferedImage enlarge(BufferedImage srcImage, double scale) {
        int w = srcImage.getWidth();
        int h = srcImage.getHeight();
        Graphics2D srcGraphics = (Graphics2D) srcImage.getGraphics();
        BufferedImage dstImage = new BufferedImage((int) (w * scale), (int) (h * scale), BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D dstGraphics = (Graphics2D) dstImage.getGraphics();
        Map hints = new HashMap(); 
        hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 
        // hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); 
        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
        dstGraphics.setRenderingHints(hints);
        AffineTransform tx = AffineTransform.getScaleInstance(scale, scale);
        dstGraphics.drawImage(srcImage, tx, null);
        dstGraphics.dispose();
        return dstImage;
    }

    public static void main(String[] args) throws IOException {
        BufferedImage image0 = ImageIO.read(new File(args[0]));

        BufferedImage image1 = enlarge(image0, 0.5);
        BufferedImage image2 = enlarge(image1, 0.5);

        JPanel panel = new Chiccha(image2);

        final JFrame frame = new JFrame();
        frame.addWindowListener(new WindowListener() {

            public void windowActivated(WindowEvent e) {
            }
            public void windowClosed(WindowEvent e) {
            }
            public void windowClosing(WindowEvent e) {
                frame.dispose();
            }
            public void windowDeactivated(WindowEvent e) {
            }
            public void windowDeiconified(WindowEvent e) {
            }
            public void windowIconified(WindowEvent e) {
            }
            public void windowOpened(WindowEvent e) {
            }
        });
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setVisible(true);
    }
}

投稿者:unibon

編集 履歴 (0)

ImageFilterでの縮小はだめでしょうか?

        ImageFilter imgfilter = new AreaAveragingScaleFilter(500,500);
        W_Image = createImage(new FilteredImageSource(readImage.getSource(), imgfilter));

投稿者:未記入じゃけんど

編集 履歴 (0)

unibon です。こんにちわ。

未記入さんさんの書き込み (2005-04-07 12:13) より:

ImageFilterでの縮小はだめでしょうか?

      ImageFilter imgfilter = new AreaAveragingScaleFilter(500,500);

      W_Image = createImage(new FilteredImageSource(readImage.getSource(), imgfilter));

おお、こんな便利なクラスがあるのですね。動作原理は私には良く分かりませんが、でも動かしてみると、1/4ほどの縮小(面積で1/16ほど)でも確かにキレイな画像です。勉強になりました。

投稿者:unibon

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。