はじめに
魚眼カメラで撮影した等距離射影方式の画像を,普通のカメラの中心射影方式のへの変換をするなどの,画像を幾何学的変換することがあります。僕は研究で,中央付近を大きく,外側を小さくする変換をするために使いました。
ここでは,等距離射影方式の画像を中心射影方式の画像に変換する場合を取り上げ,紹介していきます。
なぜ CuPy を使うのか
CuPy は NumPy と互換性を持つ NVIDIA の GPU で動作することのできるライブラリです。幾何学変換は大量の画素を移動させるので,安直なループで実装すると処理時間が長いです。なので,GPU を使って並列化しようということです。
実装
作成したコード等は ここ にあります。
import
今回は画像を扱うライブラリに OpenCV を使います。import
するのはこの 2 つだけです。
import cv2
import cupy as cp
ElementwiseKernel
CuPy で並列化を行うには ElementwiseKernel で簡単に実装できます。詳しくは CuPyのElementwiseKernelで楽にGPUの恩恵を受ける で詳しく説明されています。
入力を変換前の画像 img1
とそのサイズ size1
と変換後のサイズ size2
,出力を変換後の画像 img2
としています。ここでは簡単にするため入出力する画像は正方形を前提にします。
5 ~ 10 行目では,変換前後の画素の関係を計算しています。計算式については説明を省きます。
11 ~ 13 行目で変換後の画像に画素情報を格納しています。入力画像は RGB の 3 色あるのでこうなっています。変換後の i
1 には,変換前の i2, j2
にある画素を格納することになります。
trans_kernel = cp.ElementwiseKernel(
in_params='raw uint8 img1, int16 size1, int16 size2',
out_params='raw uint8 img2',
operation='''
float x = (i % size2) - (size2 / 2.0) + 0.5;
float y = (i / size2) - (size2 / 2.0) + 0.5;
float ang1 = sqrt(float(x * x + y * y));
float ang2 = 800 * atan(ang1 / 784); // Magic number: 800, 784
int j2 = (ang2 * x / ang1) + (size1 / 2);
int i2 = (ang2 * y / ang1) + (size1 / 2);
img2[i * 3 + 0] = img1[(i2 * size1 + j2)*3 + 0];
img2[i * 3 + 1] = img1[(i2 * size1 + j2)*3 + 1];
img2[i * 3 + 2] = img1[(i2 * size1 + j2)*3 + 2];
''',
name='trans_kernel'
)
入出力
1,2 行目では imread
で読み込んだ画像 img1
を CuPy の配列に変換しています。3 行目では変換後の画像を格納するための空の画像 img2_cp
を生成しています。
5 行目では,trans_kernel
を呼び出し,GPU を使って並列処理をして変換します。
7,8 行目で img2_cp
を OpenCV で扱える NumPy 形式に変換し,出力します。
img1 = cv2.imread('img1.png')
img1_cp = cp.asarray(img1).astype(cp.uint8)
img2_cp = cp.zeros((1920, 1920, 3)).astype(cp.uint8)
trans_kernel(img1_cp, img1.shape[1], 1920, img2_cp, size=(1920 * 1920))
img2 = cp.asnumpy(img2_cp)
cv2.imwrite('img2.png', img2)
結果
結果は次のようになりました。
魚眼画像 | 補正後 |
---|---|
文献
-
i
は 2 次元を 1 次元に変えた状態の座標なので,2 次元で表すとi/size2, i%size2
となります。 ↩