画像処理では、単位時間あたり膨大な量の計算が必要となるので,正しい答えを得るだけではなく、高速に計算する必要がある.そこでこの章では
処理時間の計測のため関数・モジュールとして、OpenCVが用意しているもの以外にPython自体には time モジュールがある。また, profile モジュールを使えば,コード内のそれぞれの関数が何回実行されたか,何回呼び出されたかの詳細なレポートを取得できる.さらに,IPythonを使えば,もっと易しい形でこれらの機能を使うことができる.ここでは、これらの内で重要なものを紹介する.詳しくは 補足資料 に載っている資料を参照すること.
cv2.getTickCount 関数は(コンピュータがON状態になったというような)「参照イベント」後からこの関数が呼ばれるまでの経過時間をクロック数で返す.つまり,ある関数を実行する前と後でこの関数を呼び、差を取れば,その関数の処理時間をクロック数で知ることができる.
cv2.getTickFrequency 関数は、1秒あたりのクロック数(クロック周波数)を返す関数である.したがって、ある関数の処理時間を秒単位で知るには,以下のようにする:
import cv2
e1 = cv2.getTickCount()
# ここに処理時間を測りたい関数呼び出しを書く
e2 = cv2.getTickCount()
time = (e2 - e1)/ cv2.getTickFrequency()
print(time)
次の例ではカーネルサイズを5から49と変化させながら中央値フィルタを適用した時にかかる処理速度を表示する. (結果がどのようなものかはここでは重要ではないので、気にしないこと):
img1 = cv2.imread('messi5.jpg')
e1 = cv2.getTickCount()
for i in range(5,49,2):
img1 = cv2.medianBlur(img1,i)
e2 = cv2.getTickCount()
t = (e2 - e1)/cv2.getTickFrequency()
print (t)
# Result I got is 0.521107655 seconds
Note: Pythonのtime
モジュールを使ってもできる。そのためにはcv2.getTickCount
の代わりに time.time()
関数を使う.
多くのOpenCVの関数はSSE2やAVX(コンピュータの並列化の仕組み、ベクトル演算の高速化が可能)による最適化を取り入れているが,中には最適化されていない関数もある.もしもシステムで最適化機能をサポートしているのであれば、それを利用しない手はない(今のプロセッサであれば、その大半はサポートしているはず)。最適化機能はデフォルトではコンパイル時に有効化される.そして有効化されていればOpenCVは最適化されたコードを実行し,そうでなければ最適化されていないコードを実行することになる.最適化が有効になっているか無効なのかは cv2.useOptimized(onoff) 関数を使って調べられ、cv2.setUseOptimized() 関数を使えば有効・無効を切り替えられる.そこで,以下のコードを見てみよう.
# check if optimization is enabled
import cv2
img = cv2.imread('messi5.jpg')
cv2.useOptimized()
%timeit res = cv2.medianBlur(img,49)
cv2.setUseOptimized(True)
cv2.useOptimized()
%timeit res = cv2.medianBlur(img,49)
最適化が有効になっていると,中央値フィルタの処理速度は無効時に比べおよそ2倍ほど速くなる.関数の実装を見れば中央値フィルタがSIMD最適化されていることが分かる.この例が示すように,ライブラリの中身を変更すること無く、コードの先頭からコード最適化を有効にできるす(繰り返しになるが,デフォルトではコードの最適化が有効になっている).
注: Jupyter notebookはIPythonから派生したもの
プログラミングをしていると,二つの似たような処理をするコードの性能を比較することがある.IPythonはこの目的のため %timeit
というマジック・コマンドを用意している.このコマンドでは,処理速度計測を高精度に行うため,コードを数回実行する.ただし,この機能は主に1行の命令を計測する作業に向いている.
例えば,以下に示す加算処理の内,どの処理が良い処理だと考えるだろうか: x = 5; y = x**2
とx = 5; y = x*x
と x = np.uint8([5]); y = x*x
と y = np.square(x)
。 IPython上で %timeit
コマンドを使えば比較が容易にできます.
x = 5
%timeit y=x**2
%timeit y=x*x
import numpy as np
z = np.uint8([5])
%timeit y=z*z
%timeit y=np.square(z)
この結果を見ると, x = 5 ; y = x*x
の処理が最も速く,Numpyに比べて20倍ほど高速であることが分かる.配列の作成も処理速度に含めると100倍速い.すごくないだろうか? (Numpyの開発チームは,この計算に対して処理の高速化を行っている)
Note: Pythonのスカラー計算はNumpyのスカラー計算より高速なので,1個か2個の要素だけを含む処理であればPythonのスカラー計算を使うほうがNumpyの配列を使って計算するより高速に処理ができる.だから配列のサイズが大きくなった時にNumpyを使うと良いだろう.
もう一つ例を示す.この例では同じ画像に対して cv2.countNonZero(src) 関数と np.count_nonzero(src)
関数を適用した時の性能を比較する.
import cv2
img = cv2.imread('messi5.jpg',0) # gray scale
print(img.shape)
%timeit z = cv2.countNonZero(img)
%timeit z = np.count_nonzero(img)
結果が示すように,OpenCVの関数の方が25倍ほど高速である.
Note: 一般的にOpenCVの関数はNumpyの関数より高速に実行されるため,同一の処理であればOpenCVを使った方が良いだろう.ただし例外がある.コピーではなくデータの値を見る時はNumpyの方が高速に処理ができる.
PythonとNumpyの最大限の性能を引き出すための技術が幾つかある.ここでは関連性のある技術のみ紹介する.ここでの大事な点は,アルゴリズムを実装する時は,まず初めに単純な実装を試してみるということである.処理のボトルネックを見つけたりコードを最適化したりするのは,アルゴリズムが正しく動作することを確認してからにする.
上記の点を全て意識したとしても、あなたの書いたコードの処理速度が遅い,もしくは大きなループの使用が避けられないのであれば,ループ処理を高速に行うCythonのようなライブラリの使用を検討したほうが良いだろう.