アフィン変換
opencvにアフィン変換はあるが, ちょいと遊びで自作してみた.
今回は画像を読み込んで, 回転しても範囲外に出ないように縁を付けた後変換するような処理で実装した.
import cv2
import numpy as np
from IPython.display import display, Image
def display_cv_image(image, format='.png'):
decoded_bytes = cv2.imencode(format, image)[1].tobytes()
display(Image(data=decoded_bytes))
def add_edge(img):
H, W = img.shape
WID = int(np.max(img.shape) * 2**0.5)
e_img = np.zeros((WID, WID))
e_img[int((WID-H)/2):int((WID+H)/2),
int((WID-W)/2):int((WID+W)/2)] = img
return e_img
def affin(img, a, tx=0, ty=0):
e_img = add_edge(img)
WID = np.max(e_img.shape)
x = np.tile(np.linspace(-1, 1, WID).reshape(1, -1), (WID, 1))
y = np.tile(np.linspace(-1, 1, WID).reshape(-1, 1), (1, WID))
p = np.array([[x, y, np.ones(x.shape)]])
t = np.array([[np.cos(a), -np.sin(a), -tx],
[np.sin(a), np.cos(a), -ty],
[ 0, 0, 1]])
dx, dy, _ = np.sum(p * t.reshape(*t.shape, 1, 1), axis=1)
u = np.clip((dx + 1) * WID / 2, 0, WID-1).astype('i')
v = np.clip((dy + 1) * WID / 2, 0, WID-1).astype('i')
return e_img[v, u]
今回はJupyter上でOpenCVを用いて画像表示を行うために以下を参考にdisplay_cv_imageを用意した.
http://uphy.hatenablog.com/entry/2016/12/11/110703
def display_cv_image(image, format='.png'):
decoded_bytes = cv2.imencode(format, image)[1].tobytes()
display(Image(data=decoded_bytes))
回転させても範囲外に出ないように縁をつける処理はこんな感じ.
まぁアフィン変換の本質の部分じゃないので( ´_ゝ`)フーンってみてもらえれば・・・
(ただ,縁をつけると正方形になるようになっていることはそこそこ大事かも?)
def add_edge(img):
H, W = img.shape
WID = int(np.max(img.shape) * 2**0.5)
e_img = np.zeros((WID, WID))
e_img[int((WID-H)/2):int((WID+H)/2),
int((WID-W)/2):int((WID+W)/2)] = img
return e_img
これが実装したアフィン変換
def affin(img, a, tx=0, ty=0):
e_img = add_edge(img)
WID = np.max(e_img.shape)
x = np.tile(np.linspace(-1, 1, WID).reshape(1, -1), (WID, 1))
y = np.tile(np.linspace(-1, 1, WID).reshape(-1, 1), (1, WID))
p = np.array([[x, y, np.ones(x.shape)]])
t = np.array([[np.cos(a), -np.sin(a), -tx],
[np.sin(a), np.cos(a), -ty],
[ 0, 0, 1]])
dx, dy, _ = np.sum(p * t.reshape(*t.shape, 1, 1), axis=1)
u = np.clip((dx + 1) * WID / 2, 0, WID-1).astype('i')
v = np.clip((dy + 1) * WID / 2, 0, WID-1).astype('i')
return e_img[v, u]
まず縁をつけて幅x高=WIDxWIDの画像にする.
e_img = add_edge(img)
WID = np.max(e_img.shape)
次に上端から下端まで-1から1に変化するxと左端から右端まで-1から1に変化するyをもつ点のpをWIDxWIDのマップとして作る.
この時pの構造は3×WID×WID
x = np.tile(np.linspace(-1, 1, WID).reshape(1, -1), (WID, 1))
y = np.tile(np.linspace(-1, 1, WID).reshape(-1, 1), (1, WID))
p = np.array([[x, y, np.ones(x.shape)]])
あとはこの各点をアフィン変換で目的の座標に変換する. 簡単化のため今回は引数で回転と並行移動のみあたえる.
(引数でこのtを渡す形式にすれば他の変換もできるようになる)
t = np.array([[np.cos(a), -np.sin(a), -tx],
[np.sin(a), np.cos(a), -ty],
[ 0, 0, 1]])
dx, dy, _ = np.sum(p * t.reshape(*t.shape, 1, 1), axis=1)
変換後のdx, dyは画像の座標系ではないので画像の座標系に変換して元画像の対応する点を取ってくると完成!
u = np.clip((dx + 1) * WID / 2, 0, WID-1).astype('i')
v = np.clip((dy + 1) * WID / 2, 0, WID-1).astype('i')
return e_img[v, u]
テスト
テストに用いる画像を読み込む. 今回はlenaさんを用いた.
img = cv2.imread('lena.jpg', 0)
display_cv_image(img, '.png')
エッジをつける処理はこんな感じ
display_cv_image(add_edge(img), '.png')
アフィン変換で45度回転
display_cv_image(affin(img, np.pi/4), '.png')
平行移動も一応実装. 第3引数でx方向, 第4引数でy方向に移動させることができる.
(今回は1.0が画像半分の長さ相当)
display_cv_image(affin(img, 0, 1.0), '.png')
まとめ
とりあえず回転と平行移動を簡単に行うaffin変換を実装した.
また, tを変えると拡大縮小や画像をゆがませることもできる(確認済)
追記
今回作ったアフィン変換はラドン変換という別の変換行うために特化した仕様なので使い勝手は良くないかも・・・