画像ファイルの全画素値を配列にコピーする場合の速度比較を行った。
比較するのは以下の3手法。
・方法1: C#
- C# で Bitmap クラスの GetPixel メソッドを使った
- 普通の方法
・方法2: C#(unsafe)
- C# で unsafe を使った
- ちょっと工夫した方法
・方法3: C++
- C++ のクラスライブラリを作成し、C# から参照して使った
- 画像処理ライブラリとして、OpenCV を用いた
一応、比較用に書いたソースは記事末に載っけときました。
557x850 の 24ビットカラー JPEG画像の場合
方法1: 21.6388260748251 (秒)
方法2: 5.06153430618396 (秒)
方法3: 0.0533208579219269 (秒)
方法1の20秒は、ありえないぐらい遅い。
unsafe を用いた方法2で処理時間は1/4になったが、C++を用いた方法3と比べると100倍近く遅い。
方法1はポインタが使えないから遅いというのは分かるような気がするが、ポインタが使える方法2と方法3がこんなに違うのは不思議だ。
JPEG のデコードで時間が掛かってるのか?と思って、ビットマップ画像でも比較。
557x850 の 24ビットカラー ビットマップ画像の場合
方法1: 21.81745337468 (秒)
方法2: 5.01866893115639 (秒)
方法3: 0.0364773279243997 (秒)
あれ?方法1と方法2があんまり変わらんのに、方法3がますます速くなってるし。
まとめ
・大量の画像を相手にしなければいけないときは C++ を使おう。
【追記(2007/11/24)】
Bitmap.Width, Bitmap.Height は意外にコストが高い(内部的にDllImportしたgdiplus.dllを利用している)ので、forループ前に一旦変数に入れてやると多少パフォーマンスが改善されるそうです。(コメントからの情報)
比較するのは以下の3手法。
・方法1: C#
- C# で Bitmap クラスの GetPixel メソッドを使った
- 普通の方法
・方法2: C#(unsafe)
- C# で unsafe を使った
- ちょっと工夫した方法
・方法3: C++
- C++ のクラスライブラリを作成し、C# から参照して使った
- 画像処理ライブラリとして、OpenCV を用いた
一応、比較用に書いたソースは記事末に載っけときました。
557x850 の 24ビットカラー JPEG画像の場合
方法1: 21.6388260748251 (秒)
方法2: 5.06153430618396 (秒)
方法3: 0.0533208579219269 (秒)
方法1の20秒は、ありえないぐらい遅い。
unsafe を用いた方法2で処理時間は1/4になったが、C++を用いた方法3と比べると100倍近く遅い。
方法1はポインタが使えないから遅いというのは分かるような気がするが、ポインタが使える方法2と方法3がこんなに違うのは不思議だ。
JPEG のデコードで時間が掛かってるのか?と思って、ビットマップ画像でも比較。
557x850 の 24ビットカラー ビットマップ画像の場合
方法1: 21.81745337468 (秒)
方法2: 5.01866893115639 (秒)
方法3: 0.0364773279243997 (秒)
あれ?方法1と方法2があんまり変わらんのに、方法3がますます速くなってるし。
まとめ
・大量の画像を相手にしなければいけないときは C++ を使おう。
【追記(2007/11/24)】
Bitmap.Width, Bitmap.Height は意外にコストが高い(内部的にDllImportしたgdiplus.dllを利用している)ので、forループ前に一旦変数に入れてやると多少パフォーマンスが改善されるそうです。(コメントからの情報)
C#
C++
string fileName = @"C:\test\test.jpg";
Bitmap src = new Bitmap(fileName);
int[, ,] srcArray0 = new int[src.Width, src.Height, 3];
int[, ,] srcArray1 = new int[src.Width, src.Height, 3];
int[, ,] srcArray2;
Stopwatch sw = new Stopwatch();
sw.Start();
for (int x = 0; x < src.Width; x++)
{
for (int y = 0; y < src.Height; y++)
{
Color c = src.GetPixel(x, y);
srcArray0[x, y, 0] = c.R;
srcArray0[x, y, 1] = c.G;
srcArray0[x, y, 2] = c.B;
}
}
sw.Stop();
double sec = (double)sw.ElapsedTicks / (double)Stopwatch.Frequency;
Console.WriteLine("process0: " + sec.ToString());
sw.Reset();
sw.Start();
BitmapData imgData = src.LockBits(new Rectangle(0, 0, src.Width, src.Height), ImageLockMode.ReadWrite, src.PixelFormat);
unsafe
{
byte* p = (byte*)imgData.Scan0.ToPointer();
for (int i = 0; i < src.Height; i++)
{
for (int j = 0; j < src.Width; j++)
{
int offset = imgData.Stride * i + (imgData.Stride / imgData.Width) * j;
srcArray1[j, i, 0] = p[offset + 2];
srcArray1[j, i, 1] = p[offset + 1];
srcArray1[j, i, 2] = p[offset + 0];
}
}
}
src.UnlockBits(imgData);
sw.Stop();
sec = (double)sw.ElapsedTicks / (double)Stopwatch.Frequency;
Console.WriteLine("process1: " + sec.ToString());
sw.Reset();
sw.Start();
srcArray2 = Class1.TestFunc(fileName);
sw.Stop();
sec = (double)sw.ElapsedTicks / (double)Stopwatch.Frequency;
Console.WriteLine("process2: " + sec.ToString());
C++
array<int,3>^ Class1::TestFunc(System::String ^fileName) {
IplImage* srcImg = cvLoadImage((char*)(void*)Marshal::StringToHGlobalAnsi(fileName));
array<int,3>^ tmp = gcnew array<int,3>(srcImg->width,srcImg->height,3);
CvPixelPosition8u pos_srcImg;
CV_INIT_PIXEL_POS( pos_srcImg,
(unsigned char *) srcImg->imageData,
srcImg->widthStep,
cvGetSize(srcImg),
0, 0,
srcImg->origin);
uchar *ptr_srcImg;
for(int x=0 ; x<srcImg->width ; x++){
for(int y=0 ; y<srcImg->height ; y++) {
ptr_srcImg = CV_MOVE_TO(pos_srcImg, x, y, 3);
tmp[x,y,0] = ptr_srcImg[2];
tmp[x,y,1] = ptr_srcImg[1];
tmp[x,y,2] = ptr_srcImg[0];
}
}
return tmp;
}


コメントする