動機
過去記事参照
まとめ
- Python上のOpenCVで、affine変換を行った
- 結論
- CUDA万歳。
- OpenCVのmultithreadingすごい。
環境
- 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 (4.5.0 preなことに特に意味はない。気が向いたら4.4.0あたりに直したい。環境構築)
- 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
print('Enabled CUDA devices:',cv.cuda.getCudaEnabledDeviceCount()) # 1
src = cv.cvtColor(cv.imread("maharo.jpg"),cv.COLOR_BGR2RGB)
h, w, c = src.shape #200, 200, 3
plt.subplot(111),plt.imshow(src)
plt.title('otter image'), plt.xticks([]), plt.yticks([])
plt.show()
g_src = cv.cuda_GpuMat()
g_dst = cv.cuda_GpuMat()
g_src.upload(src)
先にaffine変換の行列を定義しておきます。中心を軸にした回転と並行移動を組み合わせて、scale倍rot度の中心軸回転を行います。
def get_rot_affine(src, rot, scale):
h, w = src.shape[::-1]
rot_affine = cv.getRotationMatrix2D((h/2, w/2), rot, scale)
rot_affine[:2,2] -= [h/2, w/2]
rot_affine[:2,2] += [h/2*scale, w/2*scale]
return rot_affine
等倍20度回転
等倍20度回転についてはaffine行列を以下のように定義します。
ちなみに、CUDAの方はLanczos補間に対応していません。
rot_affine = get_rot_affine(src, 20, 1)
CPU
まずはCPUで試してみます。
%%timeit
img_dst = cv.warpAffine(src, rot_affine, (w*1, h*1), flags=cv.INTER_CUBIC)
# 1.08 ms ± 7.24 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
CUDA
情報の読み出し/書き出しをループに含めない処理オンリーで、GPUの素材の味を実感してみようと思います。
例によってcv2.cuda.wrapAffine
を使うのですが、引数はsource画像src
とoutput画像dst
だけGPU上に置いて、affine行列やdstサイズは通常のcv.wrapAffineと共通のものを使うようです。(GPU上に置くと怒られる)。
%%timeit
g_dst = cv.cuda.warpAffine(g_src, rot_affine, (w*1, h*1), flags=cv.INTER_CUBIC)
# 383 µs ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
まあ、こんなもんでしょう…やはり画像が小さいと2~3倍程度しか効果はないようです。
%%timeit
g_src.upload(src)
g_dst = cv.cuda.warpAffine(g_src, rot_affine, (w*1, h*1), flags=cv.INTER_CUBIC)
gpu_dst = g_dst.download()
# 450 µs ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
読み込み/書き出しを考慮してもそこまで速度が変わりません。(画像が軽いから)
10倍20度回転
マハロくんを10倍(2K)にして、20度回転してみます。
affine行列は以下のように呼んでおきました。
rot_affine = get_rot_affine(src, 20, 10)
CPU
まずはCPUで試してみます。
%%timeit
img_dst = cv.warpAffine(src, rot_affine, (w*10, h*10), flags=cv.INTER_CUBIC)
# 7.29 ms ± 36.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
10倍拡大したにしては結構速いです。
これは環境依存で、200 px画像の処理時は並列化が十分に働かず、CPUが1 threadで動いていたのですが、2K画像を処理する際には32 threadsの並列化がきちんと動いて高速化されていることによります。
(普通のCore i5/7等でやるともっと遅くなります。ちなみに検証環境の2Xeon E5-2667 v3 @3.20 GHzはCinebench曰く1Ryzen 9 3900XT程度だそうです。時代だ…)
CUDA
%%timeit
g_dst = cv.cuda.warpAffine(g_src, rot_affine, (w*10, h*10), flags=cv.INTER_CUBIC)
# 1.42 ms ± 16.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
CPUと比較すると5倍程度速いです。まあ、相手がXeonじゃないとかなりわかりやすく速いんですが、GTX1080ではちょっと分が悪いみたいです。
%%timeit
g_src.upload(src)
g_dst = cv.cuda.warpAffine(g_src, rot_affine, (w*10, h*10), flags=cv.INTER_CUBIC)
gpu_dst = g_dst.download()
# 2.64 ms ± 163 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
読み込み/書き出しをすると、画像が大きい分少し時間がかかっています。
今回使用したコード(Jupyter Notebook)はGithubに公開しています。