OpenCV 4.13.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 4.13.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
性能計測と改善の手法

目標

画像処理では毎秒膨大な数の演算を扱うため、コードが正しい解を返すだけでなく、それを最速で返すことも必須である。そこでこの章では、次のことを学ぶ:

  • コードの性能を計測する。
  • コードの性能を向上させるためのいくつかのヒント。
  • 次の関数を扱う: cv.getTickCount, cv.getTickFrequency など。

OpenCV のほかに、Python も実行時間の計測に役立つ time モジュールを提供している。別のモジュール profile は、コード中の各関数がどれだけの時間を要したか、関数が何回呼び出されたかなど、コードに関する詳細なレポートを得るのに役立つ。しかし IPython を使っていれば、これらの機能はすべて使いやすい形で統合されている。重要なものをいくつか見ていく。詳細については 追加資料 の節のリンクを参照すること。

OpenCV による性能計測

cv.getTickCount 関数は、ある基準イベント(マシンの電源が入った瞬間など)からこの関数が呼び出された瞬間までのクロックサイクル数を返す。したがって、関数の実行前後にこれを呼び出せば、関数の実行に使われたクロックサイクル数が得られる。

cv.getTickFrequency 関数は、クロックサイクルの周波数、すなわち毎秒のクロックサイクル数を返す。よって実行時間を秒単位で求めるには、次のようにすればよい:

# your code execution
time = (e2 - e1)/ cv.getTickFrequency()
double getTickFrequency()
Returns the number of ticks per second.
int64 getTickCount()
Returns the number of ticks.

次の例で実演する。以下の例では、5 から 49 までの奇数サイズのカーネルでメディアンフィルタリングを適用する。(結果がどのような見た目になるかは気にしなくてよい。それはここでの目的ではない):

img1 = cv.imread('messi5.jpg')
assert img1 is not None, "file could not be read, check with os.path.exists()"
for i in range(5,49,2):
img1 = cv.medianBlur(img1,i)
t = (e2 - e1)/cv.getTickFrequency()
print( t )
# Result I got is 0.521107655 seconds
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.
void medianBlur(InputArray src, OutputArray dst, int ksize)
Blurs an image using the median filter.
覚え書き
同じことを time モジュールでも行える。cv.getTickCount の代わりに time.time() 関数を使い、2つの時刻の差を取る。

OpenCV における既定の最適化

OpenCV の関数の多くは SSE2、AVX などを用いて最適化されている。最適化されていないコードも含まれている。したがって、システムがこれらの機能をサポートしていれば(ほぼすべての最新プロセッサがサポートしている)、それらを活用すべきである。これはコンパイル時に既定で有効になっている。そのため OpenCV は、有効なら最適化されたコードを、そうでなければ最適化されていないコードを実行する。cv.useOptimized() で有効・無効を確認でき、cv.setUseOptimized() で有効・無効を切り替えられる。簡単な例を見てみよう。

# check if optimization is enabled
In [5]: cv.useOptimized()
Out[5]: True
In [6]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
# Disable it
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop
void setUseOptimized(bool onoff)
Enables or disables the optimized code.
bool useOptimized()
Returns the status of optimized code usage.

ご覧のとおり、最適化されたメディアンフィルタリングは最適化されていない版より2倍高速である。ソースを調べると、メディアンフィルタリングが SIMD 最適化されていることがわかる。そのため、これを使ってコードの先頭で最適化を有効にできる(既定で有効になっている点は覚えておくこと)。

IPython による性能計測

似たような2つの処理の性能を比較したくなることがある。IPython にはこれを行うためのマジックコマンド timeit がある。より正確な結果を得るためにコードを複数回実行する。これも単一行のコードの計測に適している。

たとえば、次の加算演算 x = 5; y = x**2、x = 5; y = x*x、x = np.uint8([5]); y = x*x、y = np.square(x) のうちどれが優れているか分かるだろうか。IPython シェルで timeit を使って確かめてみよう。

In [10]: x = 5
In [11]: %timeit y=x**2
10000000 loops, best of 3: 73 ns per loop
In [12]: %timeit y=x*x
10000000 loops, best of 3: 58.3 ns per loop
In [15]: z = np.uint8([5])
In [17]: %timeit y=z*z
1000000 loops, best of 3: 1.25 us per loop
In [19]: %timeit y=np.square(z)
1000000 loops, best of 3: 1.16 us per loop

x = 5 ; y = x*x が最速であり、Numpy と比べて約 20 倍高速であることがわかる。配列の生成も考慮すれば、最大 100 倍まで高速になることもある。すごいだろう? (Numpy 開発者がこの問題に取り組んでいる)

覚え書き
Python のスカラー演算は Numpy のスカラー演算より高速である。したがって、1つか2つの要素を含む演算では、Numpy 配列より Python のスカラーのほうが優れている。配列のサイズが少し大きくなると Numpy が有利になる。

もう1つ例を試してみよう。今回は、同じ画像に対する cv.countNonZero()np.count_nonzero() の性能を比較する。

In [35]: %timeit z = cv.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop
In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop
int countNonZero(InputArray src)
Counts non-zero array elements.

見てのとおり、OpenCV の関数は Numpy の関数より約 25 倍高速である。

覚え書き
通常、OpenCV の関数は Numpy の関数より高速である。そのため、同じ処理では OpenCV の関数が好ましい。ただし、特に Numpy がコピーではなくビューを扱う場合などには例外もありうる。

その他の IPython マジックコマンド

性能計測、プロファイリング、行単位のプロファイリング、メモリ計測など、性能を測るためのマジックコマンドはほかにもいくつかある。いずれも十分に文書化されている。そのため、ここではそれらのドキュメントへのリンクのみを示す。関心のある読者は試してみることを勧める。

性能最適化の手法

Python と Numpy の最大限の性能を引き出すための手法やコーディング方法がいくつかある。ここでは関連するものだけを挙げ、重要な情報源へのリンクを示す。ここで注意すべき主な点は、まずアルゴリズムを単純な形で実装することである。動作するようになったらプロファイルを取り、ボトルネックを見つけて最適化する。

  1. Python では、できる限りループの使用を避けること。特に二重・三重ループなどは避ける。これらは本質的に遅い。
  2. Numpy と OpenCV はベクトル演算に最適化されているため、アルゴリズム・コードを可能な限りベクトル化すること。
  3. キャッシュコヒーレンシを活用すること。
  4. 必要でない限り配列のコピーを作成しないこと。代わりにビューを使うようにする。配列のコピーはコストの高い操作である。

これらの操作をすべて行ってもコードがまだ遅い場合、あるいは大きなループの使用が避けられない場合は、Cythonのような追加ライブラリを使って高速化する。

追加リソース

  1. Python Optimization Techniques
  2. Scipy Lecture Notes - Advanced Numpy
  3. Timing and Profiling in IPython