Affine Transformation Visual Experience by Python

2023.01.13

図形を変形するために、線形代数の「アフィン変換」という方法があります。
今回は「アフィン変換」をビジュアルに体験するためのPythonプログラムです。
Python 3.10以降の環境に、以下のソースコードをコピペしてご試用ください。
サンプルアニメ、コンソールとグラフ窓、ソースコードの順に表示します。
# 2次元図形「box」のアフィン変換。2023.01.11 by Kero

import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation

# アフィン変換の宣言。
print('2次元座標サンプル(台形)のアフィン変換を行列演算で実行します。')
print('座標行列 x に 変換処理(T) を Tx の形で行います。')
print('変換は、A:回転、B:拡大、C:せん断、D:鏡映、E:平行移動、です。')
print('各変換のパラメータ入力は、空リターンで標準値が入ります。')
print('変換順は、英記号で入力してください。空リターンでおわりです。')
print('')

# プロットエリアの設定。xy軸は同じ長さ。
def gurafu():
    plt.xlim(-10.,10.)
    plt.ylim(-10.,10.)
    plt.xticks(np.arange(-10, 11, 2))
    plt.yticks(np.arange(-10, 11, 2))
    ax.set_aspect('equal', adjustable='box')
    plt.grid(which='major')

# 座標群を行列にします。一筆書きになります。szはすべて1。
def tensen(t):
    sx = np.array([])
    sy = np.array([])
    sz = np.array([])
    for i in range(t.shape[0]): # shape は配列の長さ。
        sx = np.append(sx, t[i,0])
        sy = np.append(sy, t[i,1])
        sz = np.append(sz, 1)
    return np.concatenate([[sx],[sy],[sz]])

# 入力値が数値かチェック。
def isnum(t: str) -> bool:
    try:
        float(t)
        return True
    except ValueError:
        return False

# 回転のマトリックス設定。
def A(drd,i):
    rd = drd * i
    Am = np.array([[np.cos(rd), -np.sin(rd), 0],
                  [np.sin(rd),  np.cos(rd), 0],
                  [0., 0., 1.]])
    return Am

# 拡大のマトリックス設定。
def B(dkax, dkay,i):
    Bm = np.array([[dkax * i + 1., 0, 0],
                   [0, dkay * i + 1., 0],
                   [0, 0, 1]])
    return Bm

# せん断のマトリックス設定。
def C(dsex, dsey, i):
    sex = dsex * i
    sey = dsey * i
    Cm = np.array([[1., np.tan(sex), 0.],
                   [np.tan(sey), 1., 0.],
                   [0., 0., 1.]])
    return Cm

# 鏡映のマトリックス設定
def D(dkyx, dkyy, i, kyx, kyy):
    if kyx == '1':
        a22 = dkyx * i * 2 + 1.
    else:
        a22 = 1.
    if kyy == '1':
        a11 = dkyy * i * 2 + 1.
    else:
        a11 = 1.
        
    Dm = np.array([[a11, 0., 0.],
                   [0., a22, 0.],
                   [0., 0., 1.]])
    return Dm

# 平行移動のマトリックス設定。
def E(didx, didy, i):
    idx = didx * i
    idy = didy * i
    Em = np.array([[1., 0., idx],
                   [0., 1., idy],
                   [0., 0., 1.]])
    return Em

# サンプルとしての形状座標群(台形)。
box = np.array([[0., 0.],
                [3., 0.],
                [3., 2.],
                [0., 3.],
                [0., 0.]])

# パラメータ入力からのループ
loop = "P"
while loop.upper() == "P":
    
# パラメータ入力。
    print('【パラメータ入力】')
# 1.回転
    do = input('A:回転:回転角度?(度):')
    if (do == '') or (not(isnum(do))):
        drd = 0
    else:
        drd = 0.01 * np.radians(float(do))

# 2.拡大
    kax = input('B:拡大:xの拡大率? :')
    if (kax == '') or (not(isnum(kax))):
        dkax = 0.
    else:
        dkax = 0.01 * (float(kax) - 1.)
    kay = input('B:拡大:yの拡大率? :')
    if (kay == '') or (not(isnum(kay))):
        dkay = 0.
    else:
        dkay = 0.01 * (float(kay) - 1.)

# 3.せん断
    sex = input('C:せん断:y軸をx軸方向へ何度?:')
    if (sex == '') or (not(isnum(sex))):
        dsex = 0.
    else:
        dsex = 0.01 * np.radians(float(sex))
    sey = input('C:せん断:x軸をy軸方向へ何度?:')
    if (sey == '') or (not(isnum(sey))):
        dsey = 0.
    else:
        dsey = 0.01 * np.radians(float(sey))

# 4.鏡映
    kyx = input('D:鏡映:x軸対称(0/1)?:')
    kyy = input('D:鏡映:y軸対称(0/1)?:')
    if kyx == '1':
        dkyx = -0.01
    else:
        dkyx = 0.01
        kyx = '0'
    if kyy == '1':
        dkyy = -0.01
    else:
        dkyy = 0.01
        kyy = '0'

# 5.平行移動
    idx = input('E:平行移動:x方向?:')
    if (idx == '') or (not(isnum(idx))):
        didx = 0.
    else:
        didx = 0.01 * float(idx)
    idy = input('E:平行移動:y方向?:')
    if (idy == '') or (not(isnum(idy))):
        didy = 0.
    else:
        didy = 0.01 * float(idy)

# 変換順入力からのループ
    loop = "J"
    while loop.upper() == "J":

# 変換順の入力
        print('')
        print('【変換順(英記号)入力】')
        jun = []
        while True:
            hen = input('変換は?:')
            if hen == '':
                break
            jun.append(hen.upper())

# アニメ化。
        fig, ax = plt.subplots()
        artists = []
        gurafu()
        
# 原型はブルーで表示。
        boxline = tensen(box) # 座標行列を作成。
        plt.plot(boxline[0], boxline[1], c='b')

# 入力順に変換の実行。
        for j in jun:
            print(j)
            match j:
                case "A":
                    for d in range(101):
                        Am = A(drd,d)
                        boxline2 = np.dot(Am, boxline)
                        artist = ax.plot(boxline2[0], boxline2[1], c='r')
                        artists.append(artist)

                case "B":
                    for d in range(101):
                        Bm = B(dkax,dkay,d)
                        boxline2 = np.dot(Bm, boxline)
                        artist = ax.plot(boxline2[0], boxline2[1], c='r')
                        artists.append(artist)

                case "C":
                    for d in range(101):
                        Cm = C(dsex,dsey,d)
                        boxline2 = np.dot(Cm, boxline)
                        artist = ax.plot(boxline2[0], boxline2[1], c='r')
                        artists.append(artist)

                case "D":
                    for d in range(101):
                        Dm = D(dkyx,dkyy,d, kyx, kyy)
                        boxline2 = np.dot(Dm, boxline)
                        artist = ax.plot(boxline2[0], boxline2[1], c='r')
                        artists.append(artist)

                case "E":
                    for d in range(101):
                        Em = E(didx,didy,d)
                        boxline2 = np.dot(Em, boxline)
                        artist = ax.plot(boxline2[0], boxline2[1], c='r')
                        artists.append(artist)

            boxline = boxline2
    

        print('グラフ窓閉じるボタンで、コンソールに選択肢が出ます。')
        print('')
        anim = ArtistAnimation(fig, artists, interval=30, repeat=False)

        plt.show()

        loop = input('おわり:空リターン、変換順変更:J、パラメータ変更:P ?:')

        if loop.upper() == "J":
            continue
    if loop.upper() == "P":
        continue

sys.exit()

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です