2D変換は点を動かす計算
2D変換とは、図形の点を動かして、移動、回転、拡大縮小をする計算です。ゲームの絵が動くのも、地図が拡大されるのも、この変換でできています。
変換は、3 × 3 の数の表、行列を使うと、すべて同じ形の計算で書けます。このレッスンでは、行列で三角形の点を動かします。
このレッスンのゴール
メモ
行列を使って、図形の点を移動、拡大縮小、回転できる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 変換 | 図形の点を、決まった規則で動かすこと | |
| 行列 | 数を行と列に並べた表 | 3 × 3 |
| 同次座標 | 点 (x, y) を (x, y, 1) と書く方法 | |
| 移動 | すべての点を、同じだけずらすこと | |
| 拡大縮小 | x と y を何倍かにすること | |
| 回転 | 原点を中心に、点をまわすこと |
学生が入力するセルは、このプレビューでは空白です。
移動は足し算、拡大縮小はかけ算
点 (x, y) を右へ 5、下へ 2 ずらすには、x に 5、y に 2 を足します。これが移動です。
拡大縮小は、x と y に倍率をかけます。倍率が 2 なら、原点から見て 2 倍の遠さになります。
点 (3, 1) を動かす3つの例
| 変換 | 規則 | 結果 |
|---|---|---|
| 右へ 5、下へ 2 の移動 | x + 5、y + 2 | (8, 3) |
| 2 倍の拡大 | x × 2、y × 2 | (6, 2) |
| x だけ 3 倍 | x × 3、y はそのまま | (9, 1) |
学生が入力するセルは、このプレビューでは空白です。
行列を使うと、すべて同じ形で書ける
移動は足し算、拡大縮小はかけ算で、形が違います。そこで点を (x, y, 1) と書き、3 × 3 の行列をかけると、どちらも同じかけ算になります。この書き方が同次座標です。
新しい x は、行列の1行目と (x, y, 1) の、対応する数をかけて足した値です。2行目から新しい y が求まり、3行目は、いつも 1 になります。
3つの行列の形
| 変換 | 行列の行 | 点の結果 |
|---|---|---|
| 移動 (dx, dy) | (1, 0, dx)、(0, 1, dy)、(0, 0, 1) | (x + dx, y + dy) |
| 拡大縮小 (sx, sy) | (sx, 0, 0)、(0, sy, 0)、(0, 0, 1) | (x × sx, y × sy) |
| 90 度の回転 | (0, -1, 0)、(1, 0, 0)、(0, 0, 1) | (-y, x) |
学生が入力するセルは、このプレビューでは空白です。
行列と点をかけ算する
move = [[1, 0, 4], [0, 1, 2], [0, 0, 1]]
point = [3, 1, 1]
result = []
for row in range(3):
total = 0
for column in range(3):
total = total + move[row][column] * point[column]
result.append(total)
print(result)
# move の 4 を 10 に変えて、もう一度実行します。
# x の結果は、いくつになりますか。
90 度の回転は、整数だけで書ける
原点を中心に点を 90 度回す行列は、0 と 1 と -1 だけでできています。点 (3, 1) を回すと、(-1, 3) になります。
数学の図では y が上向きなので、反時計回りです。画面では y が下向きなので、見た目は時計回りになります。次のコマンドで、numpy でも確かめます。
90 度の行列を点 (3, 1) にかける
docker exec jupyter python3 -c "import numpy as np; m = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]]); print(m @ np.array([3, 1, 1]))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
かける順番で、結果が変わる
2つの変換を続けるには、行列どうしをかけて1つにまとめます。ただし、かける順番を変えると、結果が変わります。
点にいちばん近い、右側の行列が、最初に働きます。「拡大してから移動」と「移動してから拡大」は、別の変換です。次のコマンドは、点 (1, 1) で2つの順を比べます。
拡大と移動の順番を入れかえる
docker exec jupyter python3 -c "import numpy as np; move = np.array([[1, 0, 5], [0, 1, 0], [0, 0, 1]]); big = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]]); p = np.array([1, 1, 1]); print(move @ big @ p, big @ move @ p)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
行列をかける順番は、読む順と同じと考えがちです。実際は、点に近い右側の行列が先に働きます。「拡大してから移動」は、移動の行列 × 拡大の行列 と書きます。
三角形を動かして、画像に描く
次のコマンドは、三角形の3つの点を、右へ 40、下へ 10 動かします。元の三角形と動かした三角形を、白い線で同じ画像に描き、moved.png に保存します。
moved.png を開くと、黒い地に、同じ形の三角形が2つ見えます。右の三角形は、左の三角形より右下にずれています。
三角形を動かして moved.png に保存
docker exec jupyter python3 -c "import numpy as np; from PIL import Image, ImageDraw; im = Image.new('L', (80, 60), 0); d = ImageDraw.Draw(im); tri = [(10, 10), (30, 10), (10, 40)]; d.polygon(tri, outline=255); m = np.array([[1, 0, 40], [0, 1, 10], [0, 0, 1]]); moved = [tuple(int(v) for v in (m @ np.array([x, y, 1]))[:2]) for x, y in tri]; d.polygon(moved, outline=255); im.resize((320, 240), Image.NEAREST).save('moved.png'); print(moved); print(im.getpixel((10, 10)), im.getpixel((50, 20)), im.getpixel((30, 30)))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。