Python初心者がScikit-imageの例題を自分用に使うためのヒント８処理時間の計測とプロファイラ

[Scikit-image](http://scikit-image.org/), [scikit-learn](http://scikit-learn.org/stable/), OpenCV-Pythonを使っていると、処理時間が気になる。
そこで、今回の記事では、処理時間を計測し、プロファイリングをすることへの入り口を紹介しようと思う。

###**処理時間の計測**

import cv2
と実行して、エラーを生じない環境では既にOpenCVのバインディングが利用可能になっている。その中にあるcv2.getTickCount(),cv2.getTickFrequency()
を使って、処理時間を計測できます。
記事の１例　[OpenCVでのパフォーマンスの計測](http://asdm.hatenablog.com/entry/2015/12/09/110425)
 この関数は、WindowsでもLinuxでも同様に使えるので、スクリプトの移植性を損なわずに、処理時間の計測ができます。

　今までに繰り返し用いた例題を元に、入力画像サイズを変えたときに処理時間がどのように推移するのかを計測してみる。
[Normalized Cut]
(http://scikit-image.org/docs/dev/auto_examples/segmentation/plot_ncut.html#example-segmentation-plot-ncut-py)
![Normalized Cut](http://scikit-image.org/docs/dev/_images/plot_ncut.png)
の例題で、入力画像の大きさを変えてみよう。


###**実施例：画像サイズによる実行時間の変化**

```.py:ex_getTickCount.py
from skimage import data, segmentation, color
from matplotlib import pyplot as plt
import cv2

img0 = data.coffee()

x=[]
y=[]

a=1.2
for i in range(10):
    r=a**i
    newSize=(int(img0.shape[1]/r), int(img0.shape[0]/r))
    img=cv2.resize(img0, newSize)
    t1=cv2.getTickCount()
    labels1 = segmentation.slic(img, compactness=30, n_segments=400)
    out1 = color.label2rgb(labels1, img, kind='avg')
    t2=cv2.getTickCount()
    dt=(t2-t1)/cv2.getTickFrequency()
    print newSize, dt
    x.append(newSize[0])
    y.append(dt)
    
plt.figure(1)
plt.plot(x,y, "*-")
plt.xlabel("width")
plt.ylabel("time [sec]")
plt.grid(True)
plt.show()
```

![ex_getTickCount.png](https://qiita-image-store.s3.amazonaws.com/0/83761/fbb4f009-97f7-d57d-3e03-5a7fcf6d2cd4.png)

図　画像サイズによる実行時間の変化


グラフにしてみると、画像の幅が2倍になると、処理時間が4倍になっていることがわかる。
つまり、画像の幅を半分にすると、処理時間が1/4倍になることがわかる。これを画像の幅を同じまま、同じ処理時間にするには、処理時間の75％を削減することに匹敵する。一般にそのような高速化は容易ではなく、画像を縮小してもよい処理の場合には、画像の大きさを小さくすることが、処理時間を短縮する上での定石となる。


###**実施例：動画の各フレームでの実行時間の変化(時系列とヒストグラム)**

先に [Python初心者がScikit-imageの例題を自分用に使うためのヒント2　複数のファイルを処理する](http://qiita.com/nonbiri15/items/f50857dc555064134db6)
で Normalized Cutを各フレームに対して処理するスクリプトを紹介した。今回は、それを元に、各フレームでの処理時間を測定してみて、その結果をグラフにしてみた。


```.py:ex_plot_ncut2_time.py
from skimage import data, io, segmentation, color
from skimage.future import graph
import cv2

def plotNcut(img):    
    labels1 = segmentation.slic(img, compactness=30, n_segments=200)
    out1 = color.label2rgb(labels1, img, kind='avg')
    
    g = graph.rag_mean_color(img, labels1, mode='similarity')
    labels2 = graph.cut_normalized(labels1, g)
    out2 = color.label2rgb(labels2, img, kind='avg')

    return out1, out2

name="768x576.avi"
cap=cv2.VideoCapture(name)
i= -1
out=open("time_data.txt", "wt")
while cap.isOpened():
    i += 1
    ret, img=cap.read()
    if ret != True:
        break
    if i>100:
        break
    [h, w] = img.shape[:2]
    img = cv2.resize(img, (w/2, h/2))
    t1=cv2.getTickCount()
    out1, out2 = plotNcut(img)
    t2=cv2.getTickCount()
    dt=(t2-t1)/cv2.getTickFrequency()
    out.write("%f\n" % dt)
    cv2.imshow("img", out1)
    cv2.waitKey(100)
    cv2.imwrite("org_%04d.png" % i,  img)
    cv2.imwrite("img_%04d.png" % i,  out1)
    print i
    
out.close()
```

```.py:view_data.py
import numpy as np
import pylab

name="time_data.txt"

data=np.loadtxt(name)

print data

pylab.figure(1)
pylab.subplot(1,2,1)
pylab.plot(data)
pylab.ylabel("time[s]")
pylab.grid(True)
pylab.subplot(1,2,2)
pylab.hist(data)
pylab.grid(True)
pylab.xlim([3, 4])
pylab.xlabel("time[s]")
pylab.show()

```


![figure_1.png](https://qiita-image-store.s3.amazonaws.com/0/83761/43ea8646-3d53-5ec8-7f38-0ec2e7cfab8e.png)

このように、時系列やヒストグラムで表示してみることで、１回限り処理時間の計測よりも、処理時間がシーンによってどの程度変わりうるのかを評価しやすくなる。使ってみることは、その手法を理解する糸口の１つとなる。


###**プロファイラの利用**
Pythonには、標準のライブラリの中にプロファイラがあるので、pythonスクリプトのプロファイリングを、WindowsやLinuxなどOSやCPUの種類によらずプロファイリングができます。C++の場合だと、OSやコンパイラの種類、ツールの種類によってプロファイリングの手法が異なってきます。同じ名称のツールであってもバージョンによって使い方が極端に違っていたりして、不自由な思いをすることがあります。
その点Pythonのプロファイラは、
 Python 標準ライブラリ [26.4. Python プロファイラ](http://docs.python.jp/2/library/profile.html)
に示すように、簡単なやり方でプロファイラを実行することができます。

>プロファイラの主要なエントリポイントはグローバル関数 profile.run() (または cProfile.run()) です。

ブログ記事 [Pythonコードのプロファイリング](http://shkh.hatenablog.com/entry/2013/08/16/221205)

などの例を見て使い方をまねてみてください。

profile.run()で処理時間の内訳で大きいものは何であるのか、
関数の呼び出し回数は、思ったとおりのものであるか、思っていた回数よりも余分に関数の呼び出しをしていないか。そういった部分をチェックしたりしています。

移植元のPython（たとえばWindows）と移植先のpythonで、同じpythonスクリプトの動作時間と動作時間の内訳を同じプロファイラで比較することができます。

OpenCV-Pythonの機能(たとえば、顔検出や人検出)は、使用されているOpenCVのcv2.pydのビルドのされ方や、CPUがマルチコアかどうかなどによって処理時間が著しく変わってきます。
そういったこともPythonのプロファイラを使ってテストすると明らかになってくるはずです。


###**課題：**
[Python初心者がScikit-imageの例題を自分用に使うためのヒント2　複数のファイルを処理する](http://qiita.com/nonbiri15/items/f50857dc555064134db6)

にある複数のファイルの処理や、複数のフレームでの処理の例題を元に、
実行時間を計測してみよう。

matplotlibにはhist()関数があって容易にヒストグラムを作成することができます。処理時間がどの程度ばらつくのか実際に計測して、ヒストグラムを作成してみましょう。


###**参考記事**
[detectMultiScale()はRaspberryPiではなぜ遅いか](http://qiita.com/nonbiri15/items/26786b4c0fc6fee947bb)

[parallel_for を調べてみよう](http://qiita.com/nonbiri15/items/b646749dda6a23da72c9)

###**参考図書**
[Micha Gorelick、Ian Ozsvald　著、相川 愛三　訳「ハイパフォーマンスPython」](https://www.oreilly.co.jp/books/9784873117409/)

[scikit-learnのSVM（SVC）の処理速度について](http://qiita.com/canard0328/items/45450701c1dc0adb0d39)
が時間計測についての実際的な記事の1例です。



付記：
　画像処理で処理時間がかかりすぎている部分が、縮小した画像で置き換えてもよさそうなことに気づいたら、画像サイズを縮小してみた。劇的に処理時間が減った。Pythonはプロファイルが簡単なので、どこの処理が時間がかかっているかがすぐに特定できる。それが特定できたら、その部分の高速化だけをする。たくさんある行のうち、その1箇所に画像を縮小する処理を書き加えるだけで、処理速度が10倍以上になることがある。



[ヒント9 C言語からの利用](http://qiita.com/nonbiri15/items/3e99b726348e0ccf0552)