OpenCV アフィン変換(動画あり)

AI
OpenCV アフィン変換
スポンサーリンク

アフィン変換とは

アフィン変換は線形変換(拡大、縮小、回転など)と平行移動を組み合わせた変換のことをいいます。

アフィン変換は次のような行列で表すことができます。
$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
\end{array}
\right) = \left(
\begin{array}{ccc}
a & b \\
c & d \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
\end{array}
\right)+
\left(
\begin{array}{ccc}
t_x \\
t_y \\
\end{array}
\right)
$$

線形変換をまとめた行列、同時変換といいます。
$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
a & b & t_x \\
c & d & t_y \\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

簡単な変換行列の例

A = np.eye(2,2)
X = np.array([[1], [2]])
T = np.array([[3], [4]])
Y = np.dot(A,X) + T
print('A:', A, sep='\n')
print('----------------------')
print('X:', X, sep='\n')
print('----------------------')
print('T:', T, sep='\n')
print('----------------------')
print('Y:', Y, sep='\n')

結果
A:
[[1. 0.]
[0. 1.]]
———————-
X:
[[1]
[2]]
———————-
T:
[[3]
[4]]
———————-
Y:
[[4.]
[6.]]

上の変換行列を同時変換した例

AA = np.concatenate([A, T],axis=1)
B = np.array([0, 0, 1]).reshape(1,3)
BB = np.concatenate([AA, B], axis=0)
XX = np.array([[1], [2], [1]]) 
YY = np.dot(BB,XX)
print('AA:', AA, sep='\n')
print('----------------------')
print('BB:', BB, sep='\n')
print('----------------------')
print('XX:', XX, sep='\n')
print('----------------------')
print('YY:', YY, sep='\n')

結果
AA:
[[1. 0. 3.]
[0. 1. 4.]]
———————-
BB:
[[1. 0. 3.]
[0. 1. 4.]
[0. 0. 1.]]
———————-
XX:
[[1]
[2]
[1]]
———————-
YY:
[[4.]
[6.]
[1.]]

結果が上の変換行列と同時変換では同じ部分[4.][6.]が確認できます。

アフィン変換の拡大縮小

x軸方向にa倍、y軸方向にd倍するには次のようにします。
$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
a & 0 & 0 \\
0 & d & 0 \\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

アフィン変換の平行移動

a,b,c,d部分を単位行列にします。
$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

アフィン変換の回転

原点周りにθ回転させます。
$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
cosθ & -sinθ & 0 \\
sinθ & cosθ & 0\\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

アフィン変換のスキュー

四角形を平行四辺形に変形します。
$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
1 & 0 & 0 \\
tanθ & 1 & 0 \\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

アフィン変換の原理と使い方

OpenCVでアフィン変換を行う際に必要な関数

  • cv2.getAffineTransform(src, dest)
  • cv2.warpAffine(image, af, (size_x, size_y))
  • cv2.getRotationMatrix2D((w/2.0, h/2.0), angle, 1.0)

アフィン変換の変換行列を求める

変換行列は上で紹介した公式を使うと良いのですが、直感的に何を行っているのか理解するためには、変換行列を求めるに小さな三角形の座標を使います。
手順は次の通りです。

  1. 左上を原点にする三角形の座標を作成したものをsrcとする。
  2. 水平移動した座標をdestとするNumpy配列を作成する。
  3. cv2.getAffineTransform(src, dest)で変換行列を作成します。

事前に画像ファイルを用意することと、インポートを行っておきます。

import cv2
import matplotlib.pyplot as plt
import numpy as np

平行移動の例

# 右方向へ移動
mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)
h, w = tuple(mika_rgb.shape[:2])
print(h,w)
src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)
dest = src.copy()
dest[:,0] += 100 # シフトするピクセル値
affine = cv2.getAffineTransform(src, dest)
print(affine)
result_img = cv2.warpAffine(mika_rgb, affine, (w, h))
plt.imshow(result_img);
plt.show()

結果
720 480
[[ 1. 0. 100.]
[ 0. 1. 0.]]

注意:src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)ではfloat32型にしておかないと、cv2.getAffineTransformでエラーがでます。

上のコードと同じことをgetAffineTransformを使わないで記述できます。
線形変換をまとめた行列を直接作成します。
abcd部分は単位行列にするだけです。

$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
a & b & t_x \\
c & d & t_y \\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

変換行列をまとめた同時変換を直接記述した例

# 下方向へ移動
mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)
h, w = tuple(mika_rgb.shape[:2])
print(h,w)
affine = np.array([[1,0,100],[0,1,0]],dtype='float32')
print(affine)
result_img = cv2.warpAffine(mika_rgb, affine, (w, h))
plt.imshow(result_img);
plt.show()

結果
720 480
[[ 1. 0. 100.]
[ 0. 1. 0.]]

結果は上のコードと全く同じになりました。

拡大縮小

拡大縮小も同様の手順になります。

$$
\left(
\begin{array}{ccc}
x’ \\
y’ \\
1 \\
\end{array}
\right) = \left(
\begin{array}{ccc}
a & 0 & 0 \\
0 & d & 0 \\
0 & 0 & 1 \\
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
1 \\
\end{array}
\right)
$$

拡大の例

mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)
h, w = tuple(mika_rgb.shape[:2])
print(h,w)
src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)
dest = src * 2
affine = cv2.getAffineTransform(src, dest)
print(affine)
result_img = cv2.warpAffine(mika_rgb, affine, (2*w, 2*h), cv2.INTER_LANCZOS4)
plt.imshow(result_img);
plt.show()

結果
720 480
[[2. 0. 0.]
[0. 2. 0.]]

縮小の例

# 縮小
mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)
h, w = tuple(mika_rgb.shape[:2])
print(h,w)
src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)
dest = src * 0.5
affine = cv2.getAffineTransform(src, dest)
result_img = cv2.warpAffine(mika_rgb, affine, (w//2, h//2), cv2.INTER_LANCZOS4)
plt.imshow(result_img);
plt.show()

結果
720 480

画像の回転

画像の回転は変換行列を作成するのが面倒ですから、OpenCVが用意した関数を使います。

アフィン変換変換行列を作成する関数
cv2.getRotationMatrix2D(Point center,double angle,double scale)

  • Point center : 回転の中心座標
  • double angle : 回転角度(度)+は左回り
  • double scale : 拡大倍率

cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])

  • src :(NumPy配列ndarray)
  • M : 2 x 3の変換行列(NumPy配列ndarray)
  • dsize : 出力画像のサイズ(タプル)

画像を回転するコード

mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)

mat=cv2.getRotationMatrix2D(tuple(np.array(mika_rgb.shape[:2])/2),45,1.0)
print(mat)
result_img=cv2.warpAffine(mika_rgb,mat,mika_rgb.shape[:2])

plt.imshow(result_img);
plt.show()

結果
[[ 0.70710678 0.70710678 -64.26406871]
[ -0.70710678 0.70710678 324.85281374]]

縮小しながら回転するコード

mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)

mat=cv2.getRotationMatrix2D(tuple(np.array(mika_rgb.shape[:2])/2),135,0.5)

result_img=cv2.warpAffine(mika_rgb,mat,mika_rgb.shape[:2])

plt.imshow(result_img);
plt.show()

結果

変換行列を使用して回転させる

回転のための変換行列は次のようになります。

$$
\begin{pmatrix} x’ \\ y’ \end{pmatrix}=
{\begin{bmatrix}
\cos\theta & -\sin\theta \\
\sin\theta & \cos\theta
\end{bmatrix}\begin{pmatrix} x \\ y \end{pmatrix}
}
$$

三角形の座標は次のようになります。

変換行列を使って回転した例

mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)
size = mika_rgb.shape
print('サイズ:', size)
h, w = tuple(mika_rgb.shape[:2])
src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)
# 三角関数を使った書き方(π/3=45度回転させる場合)
dest = np.array([[0.0, 0.0], [np.sin(np.deg2rad(45)),np.cos(np.deg2rad(45))], [np.cos(np.deg2rad(45)),-np.sin(np.deg2rad(45))]], np.float32)
affine = cv2.getAffineTransform(src, dest)
print(affine)
result_img=cv2.warpAffine(mika_rgb, affine, (w,h))

plt.imshow(result_img);
plt.show()

結果
サイズ: (720, 480, 3)
[[ 0.70710677 0.70710677 0. ]
[-0.70710677 0.70710677 0. ]]

上と同じ回転をOpenCVの関数を使って実現した例

mika_bgr = cv2.imread('mika.jpg')
mika_rgb = cv2.cvtColor(mika_bgr, cv2.COLOR_BGR2RGB)
h, w = tuple(mika_rgb.shape[:2])
mat=cv2.getRotationMatrix2D((0,0),45,1.0)
print(mat)
result_img=cv2.warpAffine(mika_rgb,mat, (w,h))

plt.imshow(result_img);
plt.show()

結果
サイズ: (720, 480, 3)
[[ 0.70710677 0.70710677 0. ]
[-0.70710677 0.70710677 0. ]]

参考になった書籍

OpenCVというよりも、画像認識の理論面が幅広く数式を使って説明されています。画像分析を自分のものにしたい場合に最適です。

Pythonサンプルのダウンロード

ここでダウンロードする「opencv_affine.ipynb」ファイルは、このPython動画で使用したものです。

opencv_affine.ipynb

OpenCV動画一覧

タイトルとURLをコピーしました