PILの画素アクセスの高速化検討

PILのImageの画素に(x,y)座標でアクセスする速度を計測した。

目的

Pythonでfor文を使ったら負け」とも言われるが、PILやOpenCVの画像処理機能を使わずに自前で画像処理を実装する場合、画素へのアクセスはどのような方法が高速かを比較検討する。

画素へのアクセス方法

  • 方法1: Image.getpixel/putpixel
  • 方法2: Image.load
  • 方法3: Image.getdata/putdata
  • 方法4: Image.tobytes/frombytes
  • 方法5: NumPy配列, Image.fromarray
  • 方法6: 方法5 + NumbaによるJITコンパイル

実験方法

1024x1024ピクセルのカラー画像の各画素をモノクロ2値化する処理を上記の方法1~6で実装し、処理時間を計測した。

実験結果

方法 処理時間[msec]
方法1 1717
方法2 382
方法3 340
方法4 534
方法5 2899
方法6(初回) 369
方法6(2回目以降) 3.7

※ 方法6に関しては、初回の関数呼び出し時にJITコンパイルされるので、初回だけ実行時間が長くなる。

【方法1】
Image.getpixel/putpixel は、画素の値を取得/設定する。
(x, y)でアクセスでき、最も素直な方法だが、かなり遅い。
【方法2】
Image.load は、画素アクセス用のオブジェクトを返す。
方法1よりかなり速く、[x, y] でアクセスできるので面倒もない。
【方法3】
Image.getdata/putdata は、画素の値の1次元配列を取得/設定する。
方法2よりも少し速いが、[y * (1ラインの画素数) + x]でアクセスするのが少し面倒。
【方法4】
Image.tobytes/frombytes は、画像データのバイト配列を取得/設定する。
[(y * (1ラインの画素数) + x) * (1画素のバイト数)]でアクセスするのが面倒なうえ、速くもない。
【方法5】
numpy.asarray / Image.fromarray は、 画像データ ⇔ NumPy配列 の変換をおこなう。
[y, x] でアクセスできる。( [x, y] でないことに注意 )
しかし、NumPy配列はfor文で回すとかえって低速であり、この例では方法1より遅くなった。
【方法6】
NumbaによるJITコンパイルで方法5を高速化した。
制約や注意点は多々あるものの、けた違いな高速化ができた。

結論

パフォーマンをそれほど求めないのであれば、方法2(Image.load) か 方法3(Image.getdata/putdata) が良さそう。手間をかけてもパフォーマンスを求めるのであれば 方法6(NumPy + Numba)が圧倒的に高速。

参考