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()