動機
- CUDA有効化OpenCV(Python)をインストールしたはいいものの、(日/英)ドキュメント、サンプルコードが見つからなかったので、c++版を参考に書いてみた
- CUDA万歳がしてみたかった
- Qiita記事を書いてみたかった
まとめ
- UbuntuにCUDAが有効化されたOpenCVをインストールした
- Python上のOpenCVで、リサイズ及びテンプレートマッチングを行った
- 結論
- CUDA万歳
- 処理や画像サイズの大きさによってはCPUの方が速いこともある(今の環境だと尚更かもしれない)
- Template MatchingはCUDAが圧倒的に速い
- 転送が意外とかかる
環境
- 2*Xeon E5-2667 v3 @3.20 GHz
- DDR4-2133 4*8 GB RAM
- ubuntu LTS 20.04 @ Samsung Evo Plus 970 (500GB)
- GTX 1080 (RTX 30XXがほしい)
- OpenCV 4.5.0 pre (preなことに特に意味はない。気が向いたら直したい環境構築)
- cuda 10.2, cnDNN 7.6.5
#コード
##前処理
まず、諸々を読み込んでから200*200のmaharo.jpgを取得します。
これはサンシャイン水族館にいるカワウソのマハロくんの画像です。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
src = cv.imread("maharo.jpg", cv.IMREAD_GRAYSCALE)
h, w = src.shape[::-1] # w=200, h=200
plt.subplot(111),plt.imshow(src,cmap = 'gray')
plt.title('otter image'), plt.xticks([]), plt.yticks([])
plt.show()
次にGPU周りの変数を設定します。
GPUが有効になっているかどうかは、cv2.cuda.getCudaEnabledDeviceCount()
で1以上の数字が返ってくるかどうかで確認できます。
基本的には、CUDA有効化されたOpenCVは、cv2.function
をcv2.cuda.function
と書き換えることで用いることができます。(例外がいくつかあり、今回のtemplate matchingなどがそうです。詳しくはdir(cv2.cuda)
参照)
また、変数とGPUのメモリをcv2.cuda_GpuMat()
により確保し、.upload()/.download()
によりメモリ間の情報のやり取りを行います。
print(cv.cuda.getCudaEnabledDeviceCount())
g_src = cv.cuda_GpuMat()
g_dst = cv.cuda_GpuMat()
g_src.upload(src)
とりあえずマハロくんの画像をGPUにも移しました。
Resize
情報の読み出し/書き出しをループに含めないただのResize (512 -> 2K)で、GPUの素材の味を実感してみようと思います。
ちなみに、CUDAの方はLanczos補間に対応していません。(結構全体的にこういうのある)
CPU
%%timeit
cpu_dst = cv.resize(src, (h*10, w*10), interpolation=cv.INTER_CUBIC)
# 777 µs ± 8.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
CUDA
%%timeit
g_dst = cv.cuda.resize(g_src, (h*4, w*4), interpolation=cv.INTER_CUBIC)
# 611 µs ± 6.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
素材の味が遅い
これはちょっとGPUの恩恵を受けていない結果になってしまいました。ちなみにCPUからの読み込み書き出しを考慮した実行速度はもっと遅いです。
%%timeit
g_src.upload(src)
g_dst = cv.cuda.resize(g_src, (h*4, w*4), interpolation=cv.INTER_CUBIC)
gpu_dst = g_dst.download()
# 1.51 ms ± 16.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
苦労してCUDA入れたのにこんなんだと嫌になっちゃいますね。まあそんな並列じゃなさそうだし難しいのかな?
読み込み書き出しは800 μs程度かかるようです。これ何依存なんだろう。PCIe 3.0がボトルネックになってそうな気がする(根拠なし)
##Template Matching
気を取り直して並列が活きそうなtemplate matchingをやります。
ex_src = cv.resize(src, (h*10, w*10), interpolation=cv.INTER_CUBIC)
tmpl = ex_src[1000:1200, 1000:1200]
th, tw = tmpl.shape[::-1]
CPU
%%timeit
result = cv.matchTemplate(ex_src, tmpl, cv.TM_CCOEFF_NORMED)
# 138 ms ± 5.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
思ったより遅くない… (追記: 多分32 threadsで処理が動いているからです。)
result = cv.matchTemplate(ex_src, tmpl, cv.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
top_left = max_loc
bottom_right = (top_left[0] + th, top_left[1] + tw)
cv.rectangle(ex_src,top_left, bottom_right, 255, 2)
plt.subplot(121),plt.imshow(result,cmap = 'gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(ex_src,cmap = 'gray')
plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
plt.show()
CUDA
CUDA版のtemplate maching は(Pythonとしては)若干特殊ですが、createTemplateMatching(precision, METHOD)
のように定義します。
gsrc = cv.cuda_GpuMat()
gtmpl = cv.cuda_GpuMat()
gresult = cv.cuda_GpuMat()
gsrc.upload(ex_src)
gtmpl.upload(tmpl)
matcher = cv.cuda.createTemplateMatching(cv.CV_8UC1, cv.TM_CCOEFF_NORMED)
%%timeit
gresult = matcher.match(gsrc, gtmpl)
# 10.5 ms ± 406 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
素材の味が良い
CPU: 138 msに比べて、CUDAは10.5 msと10倍程度の高速化が達成できています。
%%timeit
gsrc.upload(ex_src)
gtmpl.upload(tmpl)
matcher = cv.cuda.createTemplateMatching(cv.CV_8UC1, cv.TM_CCOEFF_NORMED)
gresult = matcher.match(gsrc, gtmpl)
resultg = gresult.download()
# 16.6 ms ± 197 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
読み込み書き出しを計算しても、やはり8倍程度高速になります。GTX 1080でこうなので、最近話題のAmpere世代GPUなら更に高速に…?
自分はmax_locgだけしか要らないんですが、gresultの段階でmax_locgを取り出す方法ってないのでしょうか。
gresult = matcher.match(gsrc, gtmpl)
resultg = gresult.download()
min_valg, max_valg, min_locg, max_locg = cv.minMaxLoc(resultg)
top_leftg = max_locg
bottom_rightg = (top_leftg[0] + tw, top_leftg[1] + th)
cv.rectangle(src,top_leftg, bottom_rightg, 255, 2)
plt.subplot(121),plt.imshow(resultg,cmap = 'gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(ex_src,cmap = 'gray')
plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
plt.show()
print(max_loc,max_locg)
# (1000, 1000) (1000, 1000)
同じ結果が得られています。
今回使用したコード(Jupyter Notebook)はGithubに公開しています。