LoginSignup
2
1

More than 3 years have passed since last update.

OpenCV(CUDA)によるaffine transformation

Last updated at Posted at 2020-09-19

動機

過去記事参照

まとめ

  • 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)

image.png
先に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)

image.png

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)

image.png

読み込み/書き出しを考慮してもそこまで速度が変わりません。(画像が軽いから)

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等でやるともっと遅くなります。ちなみに検証環境の2*Xeon E5-2667 v3 @3.20 GHzはCinebench曰く1*Ryzen 9 3900XT程度だそうです。時代だ…)

image.png

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)

読み込み/書き出しをすると、画像が大きい分少し時間がかかっています。

image.png

今回使用したコード(Jupyter Notebook)はGithubに公開しています。

参考

完全に理解するアフィン変換

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1