白旗製作所

BufferedImageが遅い

Javaでの画像ファイルの読み書きにBufferedImageというクラスを用いているのですが
このクラスで画像のピクセルデータを読み出すのがとても遅いことに気づきました。

ちょっとピクセルごとに処理しようとすると、とてもじゃないけど待ってられないくらい遅くなります。
この問題は、こことかにあるように、BufferedImageの中身であるDataBufferクラスを取り出して
処理すれば早くなるはずです。また、データをint[]やbyte[]の配列にまで取り出してやっても早くなると思います。

そこで、画像のネガポジ反転処理(1ピクセルごとに、赤青緑データを255から引いた値にする)を例に、
以下の条件でどの程度早く出来るかを試してみました。
ただし、処理後の出力はBufferedImageクラスとします。

①1ピクセルごとにBufferedImage.getRGB(), setRGB()を実行して処理
②最初にBufferedImage.getRGBでデータをint[]型に変換してから処理
③BufferedImageをDataBufferByteに変換してから処理
④BufferedImageをbyte[]に変換してから処理 ※1
※3:ソースコードは最後に書いておきます。

1600×900のサイズのpngデータを用い、それぞれ100回ループした時の平均時間を計測しました。
結果、
①171.29ms
②158.88ms
③72.35ms
④85.04ms
となり、DataBuffereByteに変換して処理するのが最も高速となりました。

本来、byte[]に変換する④が高速になるはずなのですが、
再度BufferedImageに変換し直す処理がネックになっているようで、今回は多少遅くなってしまいました。
例えば出力がBufferedImageで無くても良い処理(画像マッチング等)などでは、
④の方が処理が早いのではないかと思います。※2

まあBufferedImageは読み書きに便利ですし、ARGBやABGRと言った並び順、alpha値の有無等の
画像ごとのデータ・タイプの差異を吸収してくれるため、遅くても仕方ない部分はあるかと思いますが、
ピクセル処理をする場合には少なくともDataBufferに変換してやったほうが、速く処理できるという結果となりました。
まあそれでもあまり速いとは言い難いですが、そこを求めるならそもそもjava使うなよということで…

※1
④のbyte配列だと処理速度は速いはずなのですが、データ範囲が0~255ではなく-128~127
(unsigned→signed)になってしまうため、少し工夫をしないと処理結果が他の関数と異なる場合があります。
まあめんどくさいので、多少処理速度が落ちてもint[]の配列にするのが楽で良いでしょうか。

※2
実際に同じ処理を、BufferedImageで返さないで10回ほどループさせた結果、
③1183ms
④1072ms
となり、byte配列にしたほうが処理速度は高速になりました。


※3
最後に時間計測に用いたソースを添付します。
ここここここを参考にさせていただきました。

①1ピクセルごとにBufferedImage.getRGB(), setRGB()を実行して処理

public static void negaposi_bufferedImageEach(BufferedImage image)
{
 int height = image.getHeight();
 int width = image.getWidth();
 int color = 0;
 int red, blue, green, alpha;
 //反転する
 for(int h=0; h<height; h++){
  for(int w=0; w<width; w++){
   color = image.getRGB(w, h);//色の読込
   //反転
   alpha = ImageUtility.alpha(color);
   red = 255-ImageUtility.red(color);
   blue = 255-ImageUtility.blue(color);
   green = 255-ImageUtility.green(color);
   image.setRGB(w, h, ImageUtility.argb(alpha, red, green, blue));
  }
 }
}


②最初にBufferedImage.getRGBでデータをint[]型に変換してから処理

public static void negaposi_bufferedImagePicelArray(BufferedImage image)
{
 int height = image.getHeight();
 int width = image.getWidth();
 int color = 0;
 int red, blue, green, alpha;
 //ピクセルデータをgetRGBを使ってint配列で取得
 int[] pixel = image.getRGB(0, 0, width, height, null, 0, width);
 //反転する
 for(int h=0; h<height; h++){
  for(int w=0; w<width; w++){
   color = pixel[h*width+w];//色の読込
   //反転
   alpha = ImageUtility.alpha(color);
   red = 255-ImageUtility.red(color);
   blue = 255-ImageUtility.blue(color);
   green = 255-ImageUtility.green(color);
   pixel[h*width+w] = ImageUtility.argb(alpha, red, green, blue);
  }
 }
 image.setRGB(0, 0, width, height, pixel, 0, width);
}


③BufferedImageをDataBufferByteに変換してから処理

public static void negaposi_dataBufferByte(BufferedImage image)
{
 int height = image.getHeight();
 int width = image.getWidth();
 int[] ints = new int[width * height];
 int red, blue, green, alpha, count=0;
 //ピクセルデータをDataBufferByteで取得
 DataBufferByte buf = ImageUtility.convertARGB(image);
 DataBuffer iBuf = new DataBufferInt(ints, width*height);
 //反転する
 for(int i=0; i<buf.getSize(); i+=4, count++){
  alpha = buf.getElem(i);
  red = 255-buf.getElem(i+1);
  green = 255-buf.getElem(i+2);
  blue = 255-buf.getElem(i+3);
  iBuf.setElem(count, ImageUtility.argb(alpha, red, green, blue));
 }
 //BufferedImageに戻す
 int[] bandMasks = new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff };
 WritableRaster writableRaster = Raster.createPackedRaster(iBuf, width, height, width, bandMasks, null);
 image.setData(writableRaster);
}

※以下のBufferedImageをDataBufferByteに変換するコードは省略してますが、
 実際はBufferedImageのtypeごとに別個の処理をすることになります。
public static DataBufferByte convertARGB(BufferedImage image)
{
 int alpha=1, count=0, color=0;
 DataBufferByte rBuf=null;
 DataBuffer iBuf = image.getRaster().getDataBuffer();
 switch(image.getType()){
 case BufferedImage.TYPE_INT_ARGB:
  rBuf = new DataBufferByte(iBuf.getSize()*4);
  for(int i=0; i<iBuf.getSize(); i++){
   color = iBuf.getElem(i);
   rBuf.setElem( i*4+0, color>>24&0xff );
   rBuf.setElem( i*4+1, color>>16&0xff );
   rBuf.setElem( i*4+2, color>>8&0xff );
   rBuf.setElem( i*4+3, color&0xff );
  }
  break;
 }
 return rBuf;
}

④BufferedImageをbyte[]に変換してから処理

public static void negaposi_Array(BufferedImage image)
{
 int height = image.getHeight();
 int width = image.getWidth();
 int red, blue, green, alpha, count=0;
 //ピクセルデータをByte配列で取得
 byte[] bufa = ImageUtility.convertByteARGB(image);
 int[] iBuf = new int[width*height];
 //反転する
 for(int i=0; i<bufa.length; i+=4, count++){
  alpha = bufa[i];
  red = 255-bufa[i+1];
  green = 255-bufa[i+2];
  blue = 255-bufa[i+3];
  iBuf[count] = ImageUtility.argb(alpha, red, green, blue);
 }
 //BufferedImageに戻す
 int[] bandMasks = new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff };
 WritableRaster writableRaster = Raster.createPackedRaster(new DataBufferInt(iBuf, width*height), width, height, width,  bandMasks, null);
 image.setData(writableRaster);
}

※以下の処理も③同様省略してます。
public static byte[] convertByteARGB(BufferedImage image)
{
 int alpha=1, count=0, color=0;
 byte[] rBuf=null;
 DataBuffer iBuf = image.getRaster().getDataBuffer();
 switch(image.getType()){
 case BufferedImage.TYPE_INT_ARGB:
  rBuf = new byte[iBuf.getSize()*4];
  for(int i=0; i<iBuf.getSize(); i++){
   color = iBuf.getElem(i);
   rBuf[ i*4+0] = (byte)(color>>24&0xff );
   rBuf[ i*4+1] = (byte)(color>>16&0xff );
   rBuf[ i*4+2] = (byte)(color>>8&0xff );
   rBuf[ i*4+3] = (byte)(color&0xff );
  }
  break;
 }
 return rBuf;
}





スポンサーサイト
  1. 2014/10/22(水) 00:52:52|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
<<JavaでJPEGのEXIF情報を読み取り | ホーム | javaで、型の違う配列での四則演算がしたい>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://dededemio.blog.fc2.com/tb.php/19-8e0787e0
この記事にトラックバックする(FC2ブログユーザー)

twitter

プロフィール

dededemio

Author:dededemio
某電機メーカーエンジニア。
真空管からプログラミングまでゆるゆると。

最新記事

最新コメント

最新トラックバック

月別アーカイブ

カテゴリ

Java (8)
未分類 (1)
真空管 (2)
キー配列 (2)
電子回路 (1)
マイコン (1)
PC部品 (2)
VPN (1)
Android (2)
opera (3)
C# (0)
プログラミング (1)
FTP (1)
メール (1)
tex (1)
音源 (2)
Ubuntu (1)
Python (5)
ブログパーツ (1)
GitHub (0)
Gist (1)
Vivaldi (1)
WICED Sense (2)
害虫駆除 (1)
自炊 (0)

検索フォーム

RSSリンクの表示

リンク

このブログをリンクに追加する

QRコード

QR