画像のDataAugmentationで最速のライブラリを探してみた
皆さんこんにちは
お元気ですか。年末に向けて燃え尽きようとしています。
OpenCVアドベントカレンダー第22日です。
今回、PythonのDataAugmentationで最速の方法を探してみます。
画像処理のDeepLearningでは、頻繁にDataAugmentationが使われています。
このData Augmentationは画像に結果がない程度に変換を加え、
ニューラルネットワークに入力する手法です。
以前、こんな資料を作りました。
www.slideshare.net
DataAugmentationにも様々な手法がありますが、次の処理する場合が非常に多いです。
- 画像をファイルから取得する。
- リサイズ
- アフィン変換
- 左右反転
この処理ですが、入力時に一定の確率で実行されます。
そのため、この処理が遅い場合には性能のボトルネックになります。
そのため、実装を高速化することは学習時間の短縮に非常に有用となります。
GPUが関わる処理で学習を高速化したい場合はCPU部も高速化することが必要でしょう。
速度を計測するにあたり、使用したライブラリをご紹介
OpenCV
当記事のアドベントカレンダーの掲載先となるフレームワーク、
かつ、最も画像処理で有名なライブラリである言わずもがなOpenCVです。
Wikipediaの概要では、次のように記載されています。
計測
では、早速計測を行います。
本手法における計測はtimeitを利用して実施します。
対象となる画像は皆さん大好きLennaさん(高225x幅225)です。
※ライブラリによって実装方法が異なるので、結果が多少ことなることがあるかもしれません。
その点は確認して、利用してください。
画像読み込み
画像読み込みとすると非常に曖昧のため、今回は次の定義とします。
- 画像をファイルから読み込む
- 0-1へ画像を正規化
- float32に変換する
OpenCV
%timeit cv2.imread("./lenna.jpeg").astype(np.float32) / 255.0 2.06 ms ± 39.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Scikit-Image
import skimage.io %timeit skimage.io.imread("./lenna.jpeg") 2.1 ms ± 40.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
PIL
from PIL import Image def load_img(path): with open(path, 'rb') as f: with Image.open(f) as img: return img.convert('RGB') %timeit load_img("./lenna.jpeg") 2.02 ms ± 61.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
画像のリサイズ
続いて画像のリサイズです。
ニューラルネットワークの入力は固定長になる場合が多いので、基本的にはどこかでリサイズ処理が必要になり、
ImageNetを利用する場合は必須に近い技術となるでしょう。
そのため、この部分の速度を改善できるとDataAugmentationに非常に有用な手法となります。
OpenCV
%timeit cv2.resize(img,(128,128)) 271 µs ± 4.26 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Scikit-image
%timeit skimage.transform.resize(img, (128,128)) 4.32 ms ± 142 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
PIL
%timeit p_img.resize((128,128)) 23.2 µs ± 850 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
アフィン変換を行う
アフィン変換を実施します。
回転、平行移動、スケール変化、引き伸ばしをこのアフィン変換で実現できます。
DataAugmentationにおいても非常にメジャーな技術と言えるでしょう。
時間の都合もあり、条件が若干ばらばらですが、アフィン変換の計測をしました。
全て微妙に違う画像ですが、こんな感じで変換を行っています。
OpenCV
import cv2 def transform_opencv(img): size = int(img.shape[0] / 2) affine = cv2.getRotationMatrix2D((size, size), 45, 0.8) affine[0][2] += 10 affine[1][2] += 15 img_afn = cv2.warpAffine(img, affine, img.shape[0:2],flags=cv2.INTER_LINEAR) %timeit transform_opencv(img) 771 µs ± 139 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Scikit-Image
def tranform_skimage(img): rotation = 45 rotation = np.deg2rad(rotation) tform = AffineTransform(scale=(1.3, 1.3), rotation=rotation, shear=1.0, translation=(3, 3)) warped_img = warp(img, tform) %timeit tranform_skimage(img) 4.69 ms ± 72.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
PIL
from PIL import Image def load_img(path): with open(path, 'rb') as f: with Image.open(f) as img: return img.convert('RGB') def ScaleRotateTranslate(image, angle, center = None, new_center = None, scale = None,expand=False): if center is None: return image.rotate(angle) angle = -angle/180.0*math.pi nx,ny = x,y = center sx=sy=1.0 if new_center: (nx,ny) = new_center if scale: (sx,sy) = scale cosine = math.cos(angle) sine = math.sin(angle) a = cosine/sx b = sine/sx c = x-nx*a-ny*b d = -sine/sy e = cosine/sy f = y-nx*d-ny*e return image.transform(image.size, Image.AFFINE, (a,b,c,d,e,f), resample=Image.BICUBIC) def scale(): image =load_img("./lenna.jpeg") transformed = ScaleRotateTranslate(image, 15, None, (0, 0), scale=0.9) %timeit scale() 3.03 ms ± 83 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
左右反転
左右反転を行います。処理後の画像は次の通り
OpenCV
%timeit cv2.flip(img,0) 13 µs ± 162 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Numpyの方式
%timeit img[:, ::-1] 404 ns ± 12.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
PIL
%timeit img.transpose(p_img.FLIP_LEFT_RIGHT) 56.5 µs ± 4.97 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)