白旗製作所

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で、型の違う配列での四則演算がしたい

javaで、型の違う配列での四則演算をやりたくていろいろ調べたんですが、やっぱそのままじゃ無理そう。

なぜこんなことをやろうと思ったかといえば、
このブログの最初の記事でmatrixクラスとかを作っていたんですが、
int[][]型のmatrixでも、double[][]型のmatrixでも、1つの関数とかで
同じ挙動を実現したいと思ったのが発端。

たとえば以下のようなメソッドを考えます。

public static double[][] plus(double[][] matrix, double[][] target)
{
double[][] result = new double[matrix.length][matrix[0].length];

for(int i=0; i<matrix.length; i++ )
for(int j=0; j<matrix[0].length; j++ )
result[i][j] = matrix[i][j]+target[i][j];
return result;
}



この処理を、以下のようにint[][]配列でも使いたいのです。
int[][] ho = {1, 2};
int[][] ge = {3, 4};
double[][] hoge = plus(ho,ge);

かといって、関数のオーバーロードを型の種類ごとに用意するのは
保守性が悪くなりますし、コード量も多くなりますので、
なるべくどの型でも使える関数みたいなものを用意したいです。

ただ、どの型でも使える関数というのが、どうもjavaでは容易ではないようです。
配列であってもキャストで(int[][])double[][]と書ければ良いんですがね。

候補として考えたのは以下の3つ。
①Objecti[][]型を使う
②Generic型を使う
③型変換メソッドを作る

他にもラムダ式を用いるとかがあると思いますが
java 1.5上での開発のためここでは考えません。

①、②の方法ではObject型やジェネリック型自体に四則演算がないので、
そのままでは使えません。配列要素の入れ替えとかなら出来ると思います。

結局、ちょっとオーバーヘッドが大きいけど、③の型変換メソッドを作って
入れるのが一番手っ取り早いし実現性も高いかなと思います。


#そもそもこんなmatrixのメソッドはdouble以外で使わなければいいんですが、
 使いたくなるシチュエーションがあるんですよね。。。。





  1. 2014/10/20(月) 01:13:52|
  2. Java
  3. | トラックバック:0
  4. | コメント:0

twitter

プロフィール

dededemio

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

最新記事

最新コメント

最新トラックバック

月別アーカイブ

カテゴリ

Java (8)
未分類 (0)
真空管 (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)

検索フォーム

RSSリンクの表示

リンク

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

QRコード

QR