====== 【数値情報処理b】第4回 変形 ======
===== 前回の課題 =====
[[ans3]]
===== 変形 =====
画像の変形の方法は色々あるが、ここでは基本となるリサイズ(拡大・縮小)、平行移動、鏡映、回転について扱う。
ここでは画素の座標と画素値を以下のようにする。
* 変形前の画像(元画像):画素の座標 $(x_{0},y_{0})$ 、画素値 $g_{0}(x_{0},y_{0})$
* 変形後の画像(新画像):画素の座標 $(x_{1},y_{1})$ 、画素値 $g_{1}(x_{1},y_{1})$
===== GIMP による変形 =====
GIMP でリサイズ(拡大・縮小)を行うには、メニューの ''**[画像]**''%%→%%''**[画像の拡大・縮小]**'' を選択する。
GIMP で鏡映や回転を行うには、メニューの ''**[画像]**''%%→%%''**[変形]**'' で以下の中から選択する。
* 水平反転
* 垂直反転
* 時計回りに90度回転
* 反時計回りに90度回転
* 180度回転
* 任意の回転
GIMP で平行移動を行うには、ツールボックスの ''**[移動]**'' アイコンをクリックして、マウスで画像をドラッグする。
===== リサイズ =====
画像をリサイズ(拡大・縮小)するということは、各画素の座標を以下のように変換することである。
\begin{align*}
x_{1}&=a_{x}x_{0}\\
y_{1}&=a_{y}y_{0}
\end{align*}
ここで、$a_{x}, a_{y}$ はそれぞれ $x, y$ 方向の倍率である。
倍率が 1 より大きければ拡大、1 より小さければ縮小ということになる。
計算した $(x_{1},y_{1})$ を実際に使うときは、四捨五入などで整数化する。
上の式を行列の演算で表すと以下のようになる。
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}a_{x}&0\\0&a_{y}\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
=
\left(\begin{array}{c}a_{x}x_{0}\\a_{y}y_{0}\end{array}\right)
\]
これで元画像の $(x_{0},y_{0})$ の画素値を新画像の $(x_{1},y_{1})$ の画素値に代入する。
\[
g_{1}(x_{1},y_{1})=g_{0}(x_{0},y_{0})
\]
計算上はこれでよさそうだが、単純に元画像の $(x_{0},y_{0})$ の画素値を一つずつ新画像の $(x_{1},y_{1})$ の画素に代入していくと問題が起こる。
次のプログラム resize0.R は boats の画像を $a_{x}=a_{y}=2$ として縦横2倍に拡大するプログラムである。
演算は cimg 変数でもできるが遅いので、普通の配列に変換して演算し、その後 cimg 変数に変換している。
# ダメな方法
library(imager)
im <- grayscale(boats)
# 倍率
ax <- 2
ay <- 2
# 元画像の次元
dim0 <- dim(im)
# 新画像の次元
dim1 <- dim0
# 横幅のリサイズ
dim1[1] <- round(ax * dim0[1])
# 縦幅のリサイズ
dim1[2] <- round(ay * dim0[2])
# cimg変数で処理すると遅いので配列で処理する
g0 <- array(im, dim0)
g1 <- array(0, dim1)
# 元画像の座標でループ
for (x0 in 1 : dim0[1]) {
# 変換 x0→x1
x1 <- round(ax * x0)
for (y0 in 1 : dim0[2]) {
# 変換 y0→y1
y1 <- round(ay * y0)
# 画素値の代入
g1[x1, y1, ,] <- g0[x0, y0, ,]
}
}
# 配列→cimg変数
im.resize <- as.cimg(g1)
# 同時に2枚並べるレイアウト
layout(t(1 : 2))
xl <- c(1, width(im.resize))
yl <- c(height(im.resize), 1)
plot(im, xlim = xl, ylim = yl, interpolate = FALSE, main = "元の画像")
plot(im.resize, xlim = xl, ylim = yl, interpolate = FALSE, main = "リサイズ後の画像")
# レイアウトを元に戻す
layout(1)
画像サイズは大きくなったが、黒い線が入ってスカスカになったように見える。
元画像と新画像の座標の対応は以下の通りである。
^ 元画像の座標 ^ 新画像の座標 ^
| $(1,1)$ | $(2,2)$ |
| $(1,2)$ | $(2,4)$ |
| $(2,1)$ | $(4,2)$ |
| $(2,2)$ | $(4,4)$ |
| ... | ... |
{{ resize0.png?nolink |2倍に拡大}}
例えば、新画像の $(3,3)$ の画素は元画像との対応がないため、画素値が代入されずに真っ黒になってしまう。
このような画素が現れるために黒い線が入ってスカスカに見える。
これを防ぐためには、元画像から新画像の対応を考えるのではなく、逆に新画像から元画像の対応を考えて画素値を補う。
これを__**補間**__という。
補間のためには新画像の座標から元画像の座標への変換、つまり__**逆変換**__を考える。
\begin{align*}
x_{0}&=\frac{1}{a_{x}}x_{1}\\
y_{0}&=\frac{1}{a_{y}}y_{1}
\end{align*}
\[
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
=
\left(\begin{array}{cc}1/a_{x}&0\\0&1/a_{y}\end{array}\right)
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
\]
このとき、$(x_{0},y_{0})$ は整数になるとは限らない。
例えば、$a_{x}=a_{y}=2$ の場合、$(x_{1},y_{1})=(3,3)$ ならば、$(x_{0},y_{0})=(1.5,1.5)$ となる。
元画像の $(x_{0},y_{0})$ の画素値を新画像の $(x_{1},y_{1})$ の画素値に代入するのは同じである。
\[
g_{1}(x_{1},y_{1})=g_{0}(x_{0},y_{0})
\]
ただし、$(x_{0},y_{0})$ が整数でない場合、元画像にその座標の画素は存在しない(元画像の画素の座標は整数なので)。
そこで、その存在しない画素を周辺の画素から補間することを考える。
補間の方法として、ここでは最近傍法と線形補間法を扱う。
{{ resize1.png?nolink |逆変換}}
----
==== 最近傍法 ====
新画像の座標 $(x_{1},y_{1})$ から逆変換で求めた $(x_{0},y_{0})$ は整数になるとは限らない。
そこで、逆変換で求めた座標 $(x_{0},y_{0})$ の元画像の画素値を、それに**最も近い座標の画素値で代替する**のが簡単である。
この方法を__**最近傍法**__という。
{{ resize_nn.png?nolink |最近傍法}}
例として $a_{x}=a_{y}=2$ の場合を考える。
新画像の $(3,3)$ の画素は逆変換で元の画像の $(1.5,1.5)$ であった。
これに最も近い座標はそれぞれを**四捨五入**で得られる $(2,2)$ である。
したがって、最近傍法では元画像の $(2,2)$ の画素値を新画像の $(3,3)$ の画素に代入する。
^ 新画像の座標 ^ 逆変換 ^ 元画像の座標 ^
| $(2,2)$ | $(1,1)$ | $(1,1)$ |
| $(2,3)$ | $(1,1.5)$ | $(1,2)$ |
| $(3,2)$ | $(1.5,1)$ | $(2,1)$ |
| $(3,3)$ | $(1.5,1.5)$ | $(2,2)$ |
| $(3,4)$ | $(1.5,2)$ | $(2,2)$ |
| $(4,3)$ | $(2,1.5)$ | $(2,2)$ |
| $(4,4)$ | $(2,2)$ | $(2,2)$ |
| ... | ... | ... |
次のプログラム resize_nn.R は boats の画像を $a_{x}=a_{y}=2$ として最近傍法で縦横2倍に拡大するプログラムである。
前のプログラムでは元画像の座標でループしていたが、このプログラムでは新画像の座標でループしている。
# リサイズ(最近傍法)
library(imager)
im <- grayscale(boats)
# 倍率
ax <- 2
ay <- 2
# 元画像の次元
dim0 <- dim(im)
# 新画像の次元
dim1 <- dim0
# 横幅のリサイズ
dim1[1] <- round(ax * dim0[1])
# 縦幅のリサイズ
dim1[2] <- round(ay * dim0[2])
# cimg変数で処理すると遅いので配列で処理する
g0 <- array(im, dim0)
g1 <- array(0, dim1)
# 新画像の座標でループ
for (x1 in 1 : dim1[1]) {
# 逆変換 x1→x0
x0 <- round(x1 / ax)
if (x0 < 1 || x0 > dim0[1]) next
for (y1 in 1 : dim1[2]) {
# 逆変換 y1→y0
y0 <- round(y1 / ay)
if (y0 < 1 || y0 > dim0[2]) next
g1[x1, y1, ,] <- g0[x0, y0, ,]
}
}
# 配列→cimg変数
im.resize <- as.cimg(g1)
# 同時に2枚並べるレイアウト
layout(t(1 : 2))
xl <- c(1, width(im.resize))
yl <- c(height(im.resize), 1)
plot(im, xlim = xl, ylim = yl, interpolate = FALSE, main = "元の画像")
plot(im.resize, xlim = xl, ylim = yl, interpolate = FALSE, main = "最近傍法")
# レイアウトを元に戻す
layout(1)
----
==== 線形補間法(バイリニア) ====
最近傍法では拡大したときに元の画像の画素がそのまま大きくなってジャギー(ギザギザ)が現れてしまう。
これを改善するための方法として、逆変換で求めた元画像の座標 $(x_{0},y_{0})$ の画素値を、**その周辺の 4 個の画素値との距離に応じた重みをかけて補間**する。
これを__**線形補間法(バイリニア)**__という。
\begin{align*}
g_{1}(x_{1},y_{1})&=(1-p)(1-q)\times g_{0}(\lfloor x_{0}\rfloor,\lfloor y_{0}\rfloor)
&&+p(1-q)\times g_{0}(\lfloor x_{0}\rfloor+1,\lfloor y_{0}\rfloor)\\
&+(1-p)q\times g_{0}(\lfloor x_{0}\rfloor,\lfloor y_{0}\rfloor+1)
&&+pq\times g_{0}(\lfloor x_{0}\rfloor +1,\lfloor y_{0}\rfloor +1)
\end{align*}
ここで $\lfloor x_{0}\rfloor$ はガウス記号といい、$x_{0}$ を超えない最大の整数という意味である。
床関数ともいう。
R では ''as.integer()'' である。
$p,q$ はそれぞれ横方向と縦方向の重みで、以下のようにとる。
\begin{eqnarray*}
p&=&x_{0}-\lfloor x_{0}\rfloor\\
q&=&y_{0}-\lfloor y_{0}\rfloor
\end{eqnarray*}
{{ resize_lerp.png?nolink |線形補間法}}
例として $a_{x}=a_{y}=2$ の場合を考える。
新画像の $(3,3)$ の画素は逆変換で $(1.5,1.5)$ である。
$\lfloor 1.5\rfloor=1$ なので
\begin{eqnarray*}
p&=&1.5-1=0.5\\
q&=&1.5-1=0.5
\end{eqnarray*}
である。
これより
\begin{align*}
g_{1}(3,3)
&=0.5\times 0.5\times g_{0}(1,1)&&+0.5\times 0.5\times g_{0}(2,1)\\
&+0.5\times 0.5\times g_{0}(1,2)&&+0.5\times 0.5\times g_{0}(2,2)
\end{align*}
\[
=0.25\times\{g_{0}(1,1)+g_{0}(2,1)+g_{0}(1,2)+g_{0}(2,2)\}
\]
となる。
つまり、4 つの画素値を等しい割合(0.25)で混ぜ合わせていることになる。
次のプログラム resize_nn.R は boats の画像を $a_{x}=a_{y}=2$ として線形補間法で縦横2倍に拡大するプログラムである。
# リサイズ(線形補間法)
library(imager)
im <- grayscale(boats)
# 倍率
ax <- 2
ay <- 2
# 元画像の次元
dim0 <- dim(im)
# 新画像の次元
dim1 <- dim0
# 横幅のリサイズ
dim1[1] <- round(ax * dim0[1])
# 縦幅のリサイズ
dim1[2] <- round(ay * dim0[2])
# cimg変数で処理すると遅いので配列で処理する
g0 <- array(im, dim0)
g1 <- array(0, dim1)
# 新画像の座標でループ
for (x1 in 1 : dim1[1]) {
# 逆変換 x1→x0
x0 <- x1 / ax
xi <- as.integer(x0)
if (xi < 1 || xi + 1 > dim0[1]) next
p <- x0 - xi
for (y1 in 1 : dim1[2]) {
# 逆変換 y1→y0
y0 <- y1 / ay
yi <- as.integer(y0)
if (yi < 1 || yi + 1 > dim0[2]) next
q = y0 - yi
g1[x1, y1, ,] <-
(1 - p) * (1 - q) * g0[xi, yi, ,] +
p * (1 - q) * g0[xi + 1, yi, ,] +
(1 - p) * q * g0[xi, yi + 1, ,] +
p * q * g0[xi + 1, yi + 1, ,]
}
}
# 配列→cimg変数
im.resize <- as.cimg(g1)
# 同時に2枚並べるレイアウト
layout(t(1 : 2))
xl <- c(1, width(im.resize))
yl <- c(height(im.resize), 1)
plot(im, xlim = xl, ylim = yl, interpolate = FALSE, main = "元の画像")
plot(im.resize, xlim = xl, ylim = yl, interpolate = FALSE, main = "線形補間法")
# レイアウトを元に戻す
layout(1)
----
==== キュービック(バイキュービック) ====
線形補間法をさらに改良して、周辺の__**16個の画素値**__で補間する方法である。
線形補間法より良い結果が得られるが、計算に時間がかかるというデメリットがある。
===== 小テスト① =====
[[KMS>|Moodle Server(非公式)]]で第4回の小テスト①を受験しなさい。
===== 平行移動 =====
画像を平行移動させるためには、各画素の座標に移動させたい量を足せばよい。
\[
g_{1}(x_{1},y_{1})=g_{0}(x_{0},y_{0})
\]
\begin{align*}
x_{1}&=x_{0}+b_{x}\\
y_{1}&=y_{0}+b_{y}
\end{align*}
ここで $b_{x}, b_{y}$ はそれぞれ $x, y$ 方向の移動量である。
行列の演算で表すと以下のようになる。
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}b_{x}\\b_{y}\end{array}\right)
\]
実際の変換はリサイズと同様に逆変換で行う。
\begin{align*}
x_{0}&=x_{1}-b_{x}\\
y_{0}&=y_{1}-b_{y}
\end{align*}
\[
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
=
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
-
\left(\begin{array}{c}b_{x}\\b_{y}\end{array}\right)
\]
次のプログラム shift.R は boats の画像を平行移動させるプログラムである。
# 平行移動
library(imager)
im <- grayscale(boats)
# 移動量
bx <- 50
by <- 100
dim0 <- dim(im)
# cimg変数で処理すると遅いので配列で処理する
g0 <- array(im, dim0)
g1 <- array(0, dim0)
# 平行移動
for (x1 in 1 : dim0[1]) {
x0 <- x1 - bx
if (x0 < 1 || x0 > dim0[1]) next
for (y1 in 1 : dim0[2]) {
y0 <- y1 - by
if (y0 < 1 || y0 > dim0[2]) next
g1[x1, y1, ,] <- g0[x0, y0, ,]
}
}
# 配列→cimg変数
im.shift <- as.cimg(g1)
# 同時に2枚並べるレイアウト
layout(t(1 : 2))
plot(im, interpolate = FALSE, main = "元の画像")
plot(im.shift, interpolate = FALSE, main = "平行移動後の画像")
# レイアウトを元に戻す
layout(1)
===== 鏡映 =====
画像の鏡映とは、鏡に映したように画像を反転させることである。
どこに鏡を置くかで反転の方向が変わるが、ここでは水平方向と垂直方向の反転を考える。
{{ flip.png?nolink |鏡映}}
----
==== 水平反転 ====
水平方向に反転させるには、各画素の $x$ 座標にマイナスをかければよい。
\[
g_{1}(x_{1},y_{1})=g_{0}(x_{0},y_{0})
\]
\begin{align*}
x_{1}&=-x_{0}\\
y_{1}&=y_{0}
\end{align*}
行列で表すと以下のようになる。
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}-1&0\\0&1\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
\]
実際の変換は逆変換で行う。
\begin{align*}
x_{0}&=-x_{1}\\
y_{0}&=y_{1}
\end{align*}
\[
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
=
\left(\begin{array}{cc}-1&0\\0&1\end{array}\right)
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
\]
ただし、これだけだと座標がマイナスになってしまうので、この後に $x$ 方向に平行移動してプラスにする必要がある。
次のプログラム flip_x.R は boats の画像を水平反転させるプログラムである。
# 鏡映
library(imager)
im <- grayscale(boats)
dim0 <- dim(im)
# cimg変数で処理すると遅いので配列で処理する
g0 <- array(im, dim0)
g1 <- array(0, dim0)
# 移動量
bx <- -dim0[1]
by <- 0
# 水平反転と平行移動
for (x1 in 1 : dim0[1]) {
x0 <- -x1 - bx # 水平反転と平行移動
if (x0 < 1 || x0 > dim0[1]) next
for (y1 in 1 : dim0[2]) {
y0 <- y1 - by
if (y0 < 1 || y0 > dim0[2]) next
g1[x1, y1, ,] <- g0[x0, y0, ,]
}
}
# 配列→cimg変数
im.flip_x <- as.cimg(g1)
# 同時に2枚並べるレイアウト
layout(t(1 : 2))
plot(im, interpolate = FALSE, main = "元の画像")
plot(im.flip_x, interpolate = FALSE, main = "水平反転後の画像")
# レイアウトを元に戻す
layout(1)
----
==== 垂直反転 ====
垂直方向に反転させるには、各画素の $y$ 座標にマイナスをかければよい。
\[
g_{1}(x_{1},y_{1})=g_{0}(x_{0},y_{0})
\]
\begin{align*}
x_{1}&=x_{0}\\
y_{1}&=-y_{0}
\end{align*}
行列で表すと以下のようになる。
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}1&0\\0&-1\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
\]
ただし、これだけだと座標がマイナスになってしまうので、この後に $y$ 方向に平行移動してプラスにする必要がある。
===== 回転 =====
==== 原点中心の回転 ====
画像を原点 $(0,0)$ を中心に時計回りに角度 $\theta$ だけ回転させることを考える。
回転を行うには三角関数($\cos\theta$, $\sin\theta$)を利用する。
\[
g_{1}(x_{1},y_{1})=g_{0}(x_{0},y_{0})
\]
\begin{align*}
x_{1}&=x_{0}\cos\theta-y_{0}\sin\theta\\
y_{1}&=x_{0}\sin\theta+y_{0}\cos\theta
\end{align*}
行列の演算で表すと以下のようになる。
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
R(\theta)\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
\]
$R(\theta)$ は $\theta$ の回転を行うための行列である。
\[
R(\theta)=
\left(\begin{array}{cc}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta\end{array}\right)
\]
画像では下方向が y の正の方向なので注意。
{{ rotation1.png?nolink |原点中心の回転}}
例えば、$\theta=30^{\circ}$ の場合、$\cos 30^{\circ}=\frac{\sqrt{3}}{2},\ \sin 30^{\circ}=\frac{1}{2}$ なので
\begin{align*}
x_{1}&=\frac{\sqrt{3}}{2}x_{0}-\frac{1}{2}y_{0}\\
y_{1}&=\frac{1}{2}x_{0}+\frac{\sqrt{3}}{2}y_{0}
\end{align*}
となる。
実際の変換は逆変換で行う。
\begin{align*}
x_{0}&=x_{1}\cos\theta+y_{1}\sin\theta\\
y_{0}&=-x_{1}\sin\theta+y_{1}\cos\theta
\end{align*}
行列の演算で表すと次のようになる。
\[
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
=
R(-\theta)
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
\]
$R(-\theta)$ は $R(\theta)$ の角度をマイナスにした行列である。
\[
R(-\theta)=
\left(\begin{array}{cc}\cos(-\theta)&-\sin(-\theta)\\\sin(-\theta)&\cos(-\theta)\end{array}\right)
=
\left(\begin{array}{cc}\cos\theta&\sin\theta\\-\sin\theta&\cos\theta\end{array}\right)
\]
ここで $\cos(-\theta)=\cos\theta$ と $\sin(-\theta)=-\sin\theta$ という性質を使っている。
$R(-\theta)$ は $R(\theta)$ と行列同士のかけ算をすると単位行列になる。
\[
R(\theta)R(-\theta)=
\left(\begin{array}{cc}\cos^{2}\theta+\sin^{2}\theta&0\\0&\cos^{2}\theta+\sin^{2}\theta\end{array}\right)
=
\left(\begin{array}{cc}1&0\\0&1\end{array}\right)
\]
つまり $R(-\theta)$ は $R(\theta)$ の__**逆行列**__((元の行列にかけると単位行列になる行列を逆行列という。))である。
これは、角度 $\theta$ だけ回転させた画像を同じ角度で逆回転させると元の画像に戻ることを意味している。
次のプログラム rotation.R は boats の画像を原点を中心に時計回りに回転するプログラムである。
三角関数 ''cos()'', ''sin()'' の引数はラジアンでなくてはならないので、度に $\pi/180$ をかけてラジアンに変換している。
\[
ラジアン=度\times\frac{\pi}{180}
\]
# 原点中心の回転
library(imager)
# 度からラジアンに変換する関数
rad <- function(deg) {
return(deg * pi / 180)
}
im <- grayscale(boats)
# 回転角(度)
theta <- 30
dim0 <- dim(im)
# cimg変数で処理すると遅いので配列で処理する
g0 <- array(im, dim0)
g1 <- array(0, dim0)
# 回転
ct <- cos(rad(theta))
st <- sin(rad(theta))
for (x1 in 1 : dim0[1]) {
for (y1 in 1 : dim0[2]) {
x0 <- round( x1 * ct + y1 * st)
y0 <- round(-x1 * st + y1 * ct)
if (x0 < 1 || x0 > dim0[1] || y0 < 1 || y0 > dim0[2]) next
g1[x1, y1, ,] <- g0[x0, y0, ,]
}
}
# 配列→cimg変数
im.rotate <- as.cimg(g1)
# 同時に2枚並べるレイアウト
layout(t(1 : 2))
plot(im, interpolate = FALSE, main = "元の画像")
plot(im.rotate, interpolate = FALSE, main = "回転後の画像")
# レイアウトを元に戻す
layout(1)
----
==== 任意の座標中心の回転 ====
画像を任意の座標 $(c_{x},c_{y})$ を中心として回転したい場合は、平行移動と原点中心の回転を組み合わせればよい。
手順は以下の通りである。
- $x,y$ 方向にそれぞれ $-c_{x}, -c_{y}$ 平行移動する。これで $(c_{x},c_{y})$ 平行移動でが原点になる。
- 原点中心の回転を行う。
- $x,y$ 方向にそれぞれ $c_{x}, c_{y}$ 平行移動する。これで原点が平行移動で元の $(c_{x},c_{y})$ に戻る。
{{ rotation2.png?nolink |任意の回転中心}}
これを行列の演算で表すと以下のようになる。
\begin{align*}
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
&=
\left(\begin{array}{cc}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta\end{array}\right)
\left(\begin{array}{c}x_{0}-c_{x}\\y_{0}-c_{y}\end{array}\right)
+
\left(\begin{array}{c}c_{x}\\c_{y}\end{array}\right)\\
&=
\left(\begin{array}{cc}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}c_{x}(1-\cos\theta)+c_{y}\sin\theta\\-c_{x}\sin\theta+c_{y}(1-\cos\theta)\end{array}\right)
\end{align*}
実際の変換は逆変換で行う。
\begin{eqnarray}
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
&=&
\left(\begin{array}{cc}\cos\theta&\sin\theta\\-\sin\theta&\cos\theta\end{array}\right)
\left(
\begin{array}{c}
x_{1}-c_{x}(1-\cos\theta)-c_{y}\sin\theta\\
y_{1}+c_{x}\sin\theta-c_{y}(1-\cos\theta)
\end{array}
\right)
\\
&=&
\left(
\begin{array}{c}
\left(x_{1}-c_{x}\right)\cos\theta+\left(y_{1}-c_{y}\right)\sin\theta+c_{x}\\
-\left(x_{1}-c_{x}\right)\sin\theta+\left(y_{1}-c_{y}\right)\cos\theta+c_{y}
\end{array}
\right)
\end{eqnarray}
===== 小テスト② =====
[[KMS>|Moodle Server(非公式)]]で第4回の小テスト②を受験しなさい。
===== アフィン変換 =====
いままでの変換は行列の演算で以下のようになる。
=== リサイズ(拡大・縮小) ===
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}a_{x}&0\\0&a_{y}\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}0\\0\end{array}\right)
\]
=== 平行移動 ===
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}1&0\\0&1\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}b_{x}\\b_{y}\end{array}\right)
\]
=== 水平反転 ===
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}-1&0\\0&1\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}0\\0\end{array}\right)
\]
=== 垂直反転 ===
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}1&0\\0&-1\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}0\\0\end{array}\right)
\]
=== 回転 ===
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}0\\0\end{array}\right)
\]
これらはすべて以下の形をしていることが分かる。
\[
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
=
\left(\begin{array}{cc}p&q\\r&s\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}u\\v\end{array}\right)
\]
この形の変換を__**アフィン変換**__という。
アフィン変換は何回行ってもアフィン変換である。
アフィン変換を複数回行う場合、変換の順序によって結果が異なることがある。
=== 例1 ===
平行移動したあとにリサイズ(拡大・縮小)を行う。
\begin{eqnarray*}
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
&=&\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+\left(\begin{array}{c}b_{x}\\b_{y}\end{array}\right)\\
\left(\begin{array}{c}x_{2}\\y_{2}\end{array}\right)
&=&
\left(\begin{array}{cc}a_{x}&0\\0&a_{y}\end{array}\right)
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)\\
&=&
\left(\begin{array}{cc}a_{x}&0\\0&a_{y}\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}a_{x}b_{x}\\a_{y}b_{y}\end{array}\right)\\
&=&
\left(\begin{array}{c}a_{x}x_{0}+a_{x}b_{x}\\a_{y}y_{0}+a_{y}b_{y}\end{array}\right)
\end{eqnarray*}
=== 例2 ===
リサイズ(拡大・縮小)したあとに平行移動を行う。
\begin{eqnarray*}
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
&=&\left(\begin{array}{cc}a_{x}&0\\0&a_{y}\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)\\
\left(\begin{array}{c}x_{2}\\y_{2}\end{array}\right)
&=&
\left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right)
+\left(\begin{array}{c}b_{x}\\b_{y}\end{array}\right)
\\
&=&
\left(\begin{array}{cc}a_{x}&0\\0&a_{y}\end{array}\right)
\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right)
+
\left(\begin{array}{c}b_{x}\\b_{y}\end{array}\right)\\
&=&
\left(\begin{array}{c}a_{x}x_{0}+b_{x}\\a_{y}y_{0}+b_{y}\end{array}\right)
\end{eqnarray*}
リサイズと平行移動の順序が異なる例1と例2 を比べると、結果 $(x_{2},y_{2})$ が異なることが分かる。
===== 変形の関数 =====
imager には、あらかじめ変形用の関数が用意されている。
^ 関数 ^ 機能 ^
| ''imresize(cimg変数, scale, interpolation = 3)'' | リサイズを行う。\\ ''scale'' は倍率である。\\ ''interpolation'' は補間アルゴリズムで、-1 は「補間なし」、1 は「最近接法」、3 は「線形補間法」、5 は「キュービック(バイキュービック)」、6 は「ランチョス」になる。 |
| ''imshift(cimg変数, delta_x = 0, delta_y = 0)'' | 平行移動を行う。\\ ''delta_x'', ''delta_y'' はそれぞれ x 方向と y 方向の移動量である。 |
| ''mirror(cimg変数, axis)'' | 鏡映を行う。\\ ''axis'' は反転の方向で、"x" は x 方向、"y" は y 方向の反転になる。 |
| ''imrotate(cimg変数, angle, cx, cy)'' | 時計回りに回転を行う。\\ 座標 (''cx'', ''cy'') は回転の中心で、''angle'' は回転の角度(単位は度)である。 |
===== 課題 =====
[[KMS>|Moodle Server(非公式)]] で第4回の課題を行いなさい。
締め切り:2025年7月9日(水)20時