- PR -

大きいjpegの色調調整

1
投稿者投稿内容
cap
会議室デビュー日: 2003/08/19
投稿数: 15
投稿日時: 2003-08-29 00:01
お世話になります。
画像の扱い方について質問させてください。

現在、画像ファイルを一つ読み込み、
明るさ・コントラストを変更し表示するアプリケーションを作っています。
ひととおり完成し、とりあえず動く状態にはなったのですが、
1MB程のjpegを読み込むと、java.lang.OutOfMemoryError が発生します。

PixelGrabber で int配列に取り込んでから変更をかけていたのですが、
この取り込み先の配列を宣言する部分で上記エラーが起こっています。

PixelGrabber では無理なのかと思い、BufferedImageでやろうとしたのですが、
やはり宣言部分で OutOfMemoryError が出ます。

1ファイルめから起きるので、GCが原因とは思えないのですが・・・

実行時に -Xmx128mを指定しても変わらずでした。

うまいメモリの使い方をご存知の方
またPixelGrabber、BufferedImage以外のやり方をご存知の方いらっしゃいましたら
ご教授おねがいします。
-------
public void paint(Graphics g){
 int[] pixels;

image = Toolkit.getDefaultToolkit().getImage(filepath);
 pixels = new int[image.getWidth(this)*image.getHeight(this)];←←ここでエラー
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-08-29 00:45
こんにちは。

Toolkit#getImage()は非同期なので、すぐに読み込みが完了するとは限りません。その場合、Image#getWidth()は-1を返します。

幅を得る前に、MediaTrackerで読み込み完了を待つ必要があると思います。

#追記
的外れだったみたいです。すみません。


[ メッセージ編集済み 編集者: ocean 編集日時 2003-08-29 10:36 ]
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-08-29 01:23
unibon です。こんにちわ。

引用:

capさんの書き込み (2003-08-29 00:01) より:
現在、画像ファイルを一つ読み込み、
明るさ・コントラストを変更し表示するアプリケーションを作っています。
ひととおり完成し、とりあえず動く状態にはなったのですが、
1MB程のjpegを読み込むと、java.lang.OutOfMemoryError が発生します。


もし JPEG ファイルの容量が 1MB だとすれば、
それを展開したイメージが使うメモリ量は、数十MBのはずです。
すでに getImage でメモリを消費している上に、
PixelGrabber 用に新たに同程度のメモリを確保しようとしていて、
実際にメモリが足りないだけなのではないでしょうか。

引用:

capさんの書き込み (2003-08-29 00:01) より:
public void paint(Graphics g){
 int[] pixels;

image = Toolkit.getDefaultToolkit().getImage(filepath);
 pixels = new int[image.getWidth(this)*image.getHeight(this)];←←ここでエラー


の中の、
image.getWidth(this)*image.getHeight(this)
の値が実際にいくつなのかを確認されてはどうでしょうか。
また、エラーになる直前の時点でのメモリ使用量を、調べてみてはどうでしょか。
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=3772&forum=12
などのやりかたで Runtime.totalMemory や Runtime.freeMemory の値を見ます。

もしメモリが本当に足りないようならば、
PixelGrabber はイメージ全体ではなくイメージの一部を対象にできるので、
分割して使うことで対処できるかもしれません。
#あるいは、他のイメージ処理用の API などを使う手もあるのかもしれません(良くは知らないですが)。
さくらば
大ベテラン
会議室デビュー日: 2002/11/12
投稿数: 145
投稿日時: 2003-08-29 05:01
こんにちは。さくらばです。

引用:

capさんの書き込み (2003-08-29 00:01) より:

現在、画像ファイルを一つ読み込み、
明るさ・コントラストを変更し表示するアプリケーションを作っています。
ひととおり完成し、とりあえず動く状態にはなったのですが、
1MB程のjpegを読み込むと、java.lang.OutOfMemoryError が発生します。

PixelGrabber で int配列に取り込んでから変更をかけていたのですが、
この取り込み先の配列を宣言する部分で上記エラーが起こっています。



この手の処理を PixelGrabber でやるのが間違いの元だったりします

どうすればいいかというと、Java2D でイメージ処理のクラスが提供されているのでそれを使えばいいと思います。

Java2D のイメージ処理は基本的にはフィルタ処理になり、BufferedImageOp というインタフェースをインプリ
メントしたクラスを使用します。

明るさ、コントラストを変更したいのであれば RescaleOp が使用できます。

サンプルを添付したので、試してみてください。Image IO を使っているので J2SE 1.4 以上でお願いします。

引数として ファイル名、スケーリングファクタ、オフセット の順になっています。

いちおう、200 万画素 1MB の JPEG ではデフォルトで OK でした。また、Canon EOS-1Ds のサンプルを Canon
のサイトからダウンロードしてやって見ましたが、-Xmx96m をつければ OK でした。ちなみに 1110 万画素、
ファイルサイズは 4.6 MB でした。

参考
http://java.sun.com/j2se/1.4/ja/docs/ja/guide/2d/spec/j2d-image.fm8.html

コード:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;

public class RescaleSample {
    private BufferedImage src;
    private RescaleOp rescapeOp;

    public RescaleSample(String filename, float scale, float offset) {
        try {
            JComponent comp = initImage(filename, scale, offset);

            JFrame frame = new JFrame("Rescale");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(800, 400);

            frame.getContentPane().add(comp);

            frame.setVisible(true);
        } catch (IOException ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }
    
    private JComponent initImage(String filename, float scale, float offset) throws IOException {
        // 入力画像の読み込み
        src = ImageIO.read(new File(filename));

        // スケーリングファクタ、オフセットの準備
        // RGB に対してそれぞれスケーリングを行う
        float[] scales = new float[] {scale, scale, scale};
        float[] offsets = new float[] {offset, offset, offset};
        rescapeOp = new RescaleOp(scales, offsets, null);

        // 出力画像を用意しておく
        BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());

        // フィルタ処理
        rescapeOp.filter(src, dest);

        // 後は GUI に貼るだけ
        JLabel srcLabel = new JLabel(new ImageIcon(src, "Before"));
        srcLabel.setMinimumSize(new Dimension(200, 200));

        JLabel destLabel = new JLabel(new ImageIcon(dest, "After"));
        destLabel.setMinimumSize(new Dimension(200, 200));
        
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                              new JScrollPane(srcLabel), new JScrollPane(destLabel));
        splitPane.setContinuousLayout(true);
        splitPane.setOneTouchExpandable(true);

        splitPane.setDividerLocation(400);

        return splitPane;
    }

    public static void main(String[] args) {
        String filename = args[0];
        float scale = Float.parseFloat(args[1]);
        float offset = Float.parseFloat(args[2]);

        new RescaleSample(filename, scale, offset);
    }
}

unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-08-29 12:48
unibon です。こんにちわ。

引用:

さくらばさんの書き込み (2003-08-29 05:01) より:
いちおう、200 万画素 1MB の JPEG ではデフォルトで OK でした。また、Canon EOS-1Ds のサンプルを Canon
のサイトからダウンロードしてやって見ましたが、-Xmx96m をつければ OK でした。ちなみに 1110 万画素、
ファイルサイズは 4.6 MB でした。



引用:

capさんの書き込み (2003-08-29 00:01) より:
実行時に -Xmx128mを指定しても変わらずでした。


推測になりますが、128MB もあれば、
たいていの JPEG ファイルは扱えそうな気もします。
もしかしたら、たとえば、
java MyImageConverter MyBigFile.jpg -Xmx128m
のような感じでcapさんのオプションの指定が誤っている可能性もわずかにあるかも、
と思いました。
(ただしくはつぎのような感じです。)
java -Xmx128m MyImageConverter MyBigFile.jpg

#あるいはもともとそれに見合った物理メモリがないなど。
cap
会議室デビュー日: 2003/08/19
投稿数: 15
投稿日時: 2003-08-30 01:17
oceanさん、unibonさん、さくらばさん ご回答ありがとうございます。
返信遅れまして申し訳ありません


oceanさん、ご指摘ありがとうございます。
原文の方ではgetImageの直後にMediaTracker.addImageと.waitForAllをつけています
ソース提示を省いてしまいスイマセン


unibonさん、ご忠告ありがとうございます。
エラー直前の状況をしらべたところ、
totalMemory :66650112
freememory :41546328
画像幅 :7680
画像高 :1920
幅*高 :14745600
(テスト環境 WIN2K メモリ192M 982KBのイメージ)
でした。
確かにこの幅*高のint配列っていったら持ち切れなさそうですね・・・
PixelGrabberをImage分割で行う方法も試してみようと思います。


さくらばさん、サンプル提示ありがとうございます。
RescaleOpですか・・・知らなかった・・・
勉強してからこの方法も試してみようと思います。


追記:スイマセンRescaleOpでも解決せずです
   単純にサンプルでかすぎるんでしょうか
   5Mのjpgでも3000*2000ぐらいのものなら表示できるんですが・・・


[ メッセージ編集済み 編集者: cap 編集日時 2003-09-05 18:23 ]
さくらば
大ベテラン
会議室デビュー日: 2002/11/12
投稿数: 145
投稿日時: 2003-09-08 17:42
こんにちは、さくらばです。

引用:

capさんの書き込み (2003-08-30 01:17) より:

追記:スイマセンRescaleOpでも解決せずです
   単純にサンプルでかすぎるんでしょうか
   5Mのjpgでも3000*2000ぐらいのものなら表示できるんですが・・・


[ メッセージ編集済み 編集者: cap 編集日時 2003-09-05 18:23 ]



試しに Image I/O と com.sun.image.codec.jpeg.JPEGImageDecoder を使ってどのくらい
メモリが使われているか調べてみました。基本的には前に示したサンプルですが、GUI の
部分は削ってあり純粋に画像のロード (RescapeOp を使用していない場合) と画像変換
(RescapeOp を使用している場合) を調べてみました。

測定にはプロファイラの JProbe を使用しました。単位は KB です。

コード:
                     Image I/O                       JPEGImageDecoder
              RescapeOp なし RescapeOp あり  RescapeOp なし  RescapeOp あり
-------------------------------------------------------------------------
 550万画素        28,859       34,877           22,926         46,694
1100万画素        54,047       69,055           44,424         92,344
2200万画素       105,765      137,423           87,313        183,575



JPEGImageDecoder で RescapeOp を使うとメモリが倍になるのは、Image I/O とカラー
モデルが違うからのようです。

ともあれ、大体のメモリ使用量がはっきりすれば、読み込む画像のサイズによって
-Xmx で調節できそうです。

もっと自動的に行いたい場合は Image I/O を使えば画像をロードする前に、画像の
大きさを取得することができる & 分割ロードができるので、大きさを見て OutOfMemory
になりそうな場合は分割ロードをするなどの対処法が考えられれます。
1

スキルアップ/キャリアアップ(JOB@IT)