LoginSignup
0
0

More than 5 years have passed since last update.

Python: アフィン変換

Last updated at Posted at 2019-03-10

アフィン変換

opencvにアフィン変換はあるが, ちょいと遊びで自作してみた.
今回は画像を読み込んで, 回転しても範囲外に出ないように縁を付けた後変換するような処理で実装した.

affin.py
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')

lena0.jpg

エッジをつける処理はこんな感じ

display_cv_image(add_edge(img), '.png')

e_lena.jpg

アフィン変換で45度回転

display_cv_image(affin(img, np.pi/4), '.png')

45_lena.jpg

平行移動も一応実装. 第3引数でx方向, 第4引数でy方向に移動させることができる.
(今回は1.0が画像半分の長さ相当)

display_cv_image(affin(img, 0, 1.0), '.png')

x_lena.jpg

まとめ

とりあえず回転と平行移動を簡単に行うaffin変換を実装した.
また, tを変えると拡大縮小や画像をゆがませることもできる(確認済)

追記

今回作ったアフィン変換はラドン変換という別の変換行うために特化した仕様なので使い勝手は良くないかも・・・

0
0
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
0
0