白旗製作所

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ブログユーザー)