CuPyを使った画像の幾何学変換with OpenCV
はじめに
魚眼カメラで撮影した等距離射影方式の画像を、普通のカメラの中心射影方式のへの変換をするなどの、画像を幾何学的変換することがあります。僕は研究で、中央付近を大きく、外側を小さくする変換をするために使いました。
ここでは、等距離射影方式の画像を中心射影方式の画像に変換する場合を取り上げ、紹介します。
なぜ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)
結果
結果は次のようになりました。
魚眼画像 | 補正後 |
---|---|
ひとこと
この記事は僕が研究で使った内容の一部でもありますが、どちらかと言ったら後輩のために書いたメモです。Qiitaに投稿したら僕の中で一番LGTMが多くなったんですよね。やっぱり変人が好む物を書くより、一般的でメジャーな物の方がいいんですね。
文献
脚注
-
i
、は2次元を1次元に変えた状態の座標なので、2次元で表すと、i/size2, i%size2
、となります。 ↩