【Numba】NumPy + for文の高速化

この記事では、Pythonライブラリ「Numba」でNumPy配列とfor文の処理速度を高速化してみたので紹介します。

Numbaで高速化

Python用数値計算ライブラリ「NumPy」の配列の要素にfor文でアクセスすると、処理速度が急低下する問題があります。
for文を使わずに「NumPyのメソッド」や「他のライブラリ」で処理を実装するのが鉄則ですがどうしてもfor文を使いたい場合があります。
そんなときは「Numba」ライブラリを使うことで元のソースコードをほとんど弄らずに高速化できます。

ソースコード

サンプルプログラムのソースコードです。

NumPy

#-*- coding:utf-8 -*-
import cv2
import numpy as np
import time

def filter2d(src, kernel, fill_value=-1):
    # カーネルサイズ
    m, n = kernel.shape

    # 畳み込み演算をしない領域の幅
    d = int((m-1)/2)
    h, w = src.shape[0], src.shape[1]

    # 出力画像用の配列
    if fill_value == -1: dst = src.copy()
    elif fill_value == 0: dst = np.zeros((h, w))
    else:
        dst = np.zeros((h, w))
        dst.fill(fill_value)


    for y in range(d, h - d):
        for x in range(d, w - d):
            # 畳み込み演算
            dst[y][x] = np.sum(src[y-d:y+d+1, x-d:x+d+1]*kernel)

    return dst

def main():
    # 入力画像をグレースケールで読み込み
    gray = cv2.imread("input2.jpg", 0)

    # 処理開始時間の計測
    start = time.time()

    # カーネル(縦方向の輪郭検出用)
    kernel = np.array([[1/9, 1/9, 1/9],
                       [1/9, 1/9, 1/9],
                       [1/9, 1/9, 1/9]])

    # 方法1(NumPyで実装)
    dst1 = filter2d(gray, kernel, -1)

    # 処理終了時刻の計測
    end = time.time()

    # 処理時間の表示
    print("処理時間" + str(end-start) + "[sec]")

    # 結果を出力
    cv2.imwrite("output1.jpg", dst1)


if __name__ == "__main__":
    main()

NumPy+Numba

#-*- coding:utf-8 -*-
import cv2
import numpy as np
import time
import numba

@numba.jit
def filter2d(src, kernel, fill_value=-1):
    # カーネルサイズ
    m, n = kernel.shape

    # 畳み込み演算をしない領域の幅
    d = int((m-1)/2)
    h, w = src.shape[0], src.shape[1]

    # 出力画像用の配列
    if fill_value == -1: dst = src.copy()
    elif fill_value == 0: dst = np.zeros((h, w))
    else:
        dst = np.zeros((h, w))
        dst.fill(fill_value)


    for y in range(d, h - d):
        for x in range(d, w - d):
            # 畳み込み演算
            dst[y][x] = np.sum(src[y-d:y+d+1, x-d:x+d+1]*kernel)

    return dst

def main():
    # 入力画像をグレースケールで読み込み
    gray = cv2.imread("input2.jpg", 0)

    # 処理開始時間の計測
    start = time.time()

    # カーネル(縦方向の輪郭検出用)
    kernel = np.array([[1/9, 1/9, 1/9],
                       [1/9, 1/9, 1/9],
                       [1/9, 1/9, 1/9]])

    # 方法1(NumPyで実装)
    dst1 = filter2d(gray, kernel, -1)

    # 処理終了時刻の計測
    end = time.time()

    # 処理時間の表示
    print("処理時間" + str(end-start) + "[sec]")

    # 結果を出力
    cv2.imwrite("output1.jpg", dst1)


if __name__ == "__main__":
    main()

OpenCV

#-*- coding:utf-8 -*-
import cv2
import numpy as np
import time

def main():
    # 入力画像をグレースケールで読み込み
    gray = cv2.imread("input.jpg", 0)

    # 処理開始時間の計測
    start = time.time()

    # 方法3(OpenCVで実装)
    dst3 = cv2.blur(gray, ksize=(3,3))

    # 処理終了時刻の計測
    end = time.time()

    # 処理時間の表示
    print("処理時間" + str(end-start) + "[sec]")

    # 結果を出力
    cv2.imwrite("output3.jpg", dst3)


if __name__ == "__main__":
    main()

実行結果

結果は以下の通りになりました。

■実行環境

項目 説明
入力画像 グレースケール画像(440×450[px])
OS Windows10 Home Premium 64bit
メモリ容量 4GB
CPU Core i3-2330M 2.20GHz

■結果

処理速度[sec]
NumPy 3.2753376960754395[sec]
NumPy+Numba 1.225313425064087[sec]
OpenCV 0.13230109214782715[sec]

Numbaを使うと3倍くらい速くなりました。
ただし、今回の画像処理の場合ではOpenCVの方がさらに10倍程度速くなりました。

NumbaはGPUを使うなど色々な設定が出来るので、もうちょっと弄ればより速くなると思います。

おすすめ記事

PythonでOpenCV入門 サンプル集
【Python】画像処理プログラミング入門
【画像処理入門】アルゴリズム&プログラミング

コメント