【Python】PILで高速に全画素アクセスを行う
Pythonでの画像処理はOpenCVやPIL(Pillow)等のライブラリが揃っており主要なロジックに困る事はありませんが、オリジナルな画像処理を自前実装する機会はあるかと思います。画像の全画素を舐める様なアクセスでは、僅かな非効率が積み重なり処理時間が爆増することも…。
ここでは、スポット的な画素アクセスではなく全画素アクセス + x,yの軸情報が必要な場合に、効率的にアクセスする方法を下記パターンで比較します。
- Image.getpixel() でアクセス
- Numpy配列でアクセス
- Image.getdata()でアクセス
・一重ループ
・二重ループ
・二重ループ(インデックスをキャッシュ)
実験ソースは以下です。 1000x1000の画像の全ピクセルのアクセス時間を比較します。
from PIL import Image import sys, time import numpy as np # getpixelで画素アクセス def getpixel(): for y in range(height): for x in range(width): pixdata = img.getpixel((x,y)) # print("[{},{}] : {}".format(y,x,pixdata)) # numpyで画素アクセス def numpy(): imgArray = np.array(img) for y in range(height): for x in range(width): pixdata = imgArray[y][x] # print("[{},{}] : {}".format(y,x,pixdata)) # getdataで画素アクセス def getdata1(): imgdata = img.getdata() for y in range(height): for x in range(width): pixdata = imgdata[y * width + x] # print("[{},{}] : {}".format(y,x,pixdata)) # getdataで画素アクセス(行インデックスのキャッシュあり) def getdata2(): imgdata = img.getdata() for y in range(height): ycache = y * width for x in range(width): pixdata = imgdata[ycache + x] # print("[{},{}] : {}".format(y,x,pixdata)) # getdataで画素アクセス(一重ループ) def getdata3(): imgdata = img.getdata() for i in range(height * width): x = i % width y = int(i / width) pixdata = imgdata[i] # print("[{},{}] : {}".format(y,x,pixdata)) if __name__ == '__main__': img = Image.open("1000x1000.png") width, height =img.size # 処理時間を比較 for func in (getpixel, numpy, getdata1, getdata2, getdata3): start = time.time() func() end = time.time() print("{} : {:4.1f}ms".format(func.__name__, (end - start) * 1000))
-
アクセス時間の比較結果
MacbookPro2017 i5 2.3Ghzでの実行結果を載せます。
・getpixel : 1270.1ms
・numpy : 280.9ms
・getdata1 : 176.6ms
・getdata2 : 135.8ms
・getdata3 : 332.3ms
getdata2()が一番速い結果となりました。ソースを見れば納得ですが、getdata1()からループ内の不要な乗算をキャッシュしています。
一方、getpixel()はgetdata2()に比べ10倍近く遅い結果に。そもそもループ毎に関数をコールしているのでそうなりますよね。
getdata3()は除算とmodの計算コストが加算・乗算に比べ大きいため時間がかかっています。
-
結論
連続した画素アクセスはImage.getdata()で取得した一次元データを効率良くループするのが良いです。
一方、スポット的なアクセスであればImage.getpixel()で構わないでしょう。
冒頭でも述べましたが、まずはC/C++で内部実装された高速なライブラリがあるか探し、無ければ自前実装といった風に、基本はライブラリに頼るのが確実です。