概要
OpenCV のアフィン変換を適用する関数 cv2.warpAffine() を使用して、画像を回転する方法について解説します。
関連記事
画像に適用するアフィン変換については、OpenCV – 画像に適用するアフィン変換について で解説しています。
cv2.warpAffine
dst = cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
公式リファレンス: cv2.warpAffine()
名前 | 型 | デフォルト値 |
---|---|---|
src | ndarray | |
入力画像 | ||
M | ndarray | |
(2, 3) のアフィン変換行列 | ||
dsize | tuple of 2-ints | |
出力画像の大きさ | ||
dst | ndarray | None |
出力画像を作成する際に使用する画像 | ||
flags | int | cv2.INTER_LINEAR |
補間方法 | ||
borderMode | int | cv2.BORDER_CONSTANT |
外挿方法 | ||
borderValue | int or tuple of 3-ints | 0 |
外挿する値 |
名前 | 説明 | ||
---|---|---|---|
dst | リサイズ後の画像 |
画像を回転する
引数 M
には、$(3, 3)$ のアフィン変換行列のうち、3 行目を除いた $(2, 3)$ の行列を指定します。
回転を表すアフィン変換行列を作成するには cv2.getRotationMatrix2D()
を使用します。
dsize
には出力画像の大きさを指定します。今回は入力画像と同じ大きさを指定しました。
import cv2
import numpy as np
from IPython.display import Image, display
def imshow(img):
"""ndarray 配列をインラインで Notebook 上に表示する。"""
ret, encoded = cv2.imencode(".jpg", img)
display(Image(encoded))
def rotate_img(img, angle):
# 画像の中心周りに angle 度回転するアフィン変換行列を求める。
img_h, img_w = img.shape[:2]
center = img_w / 2, img_h / 2
M = cv2.getRotationMatrix2D(center=center, angle=angle, scale=1)
# アフィン変換を適用する。
dst = cv2.warpAffine(img, M, dsize=(img_w, img_h))
return dst
img = cv2.imread("sample.jpg")
dst = rotate_img(img, 30)
imshow(dst)
回転後の画像が収まるように、画像サイズを調整する。
最初のコードだと、回転後の画像は、はみ出た部分が切れてしまいます。そのため、回転後の画素がすべて収まるように、画像の大きさ及び位置を調整するコードを紹介します。
import cv2
import numpy as np
def rotate_img(img, angle, bg_color=(0, 0, 0)):
# 画像を中心周りに angle 回転するアフィン変換行列を作成する。
img_h, img_w = img.shape[:2]
center = img_w / 2, img_h / 2
M = cv2.getRotationMatrix2D(center=center, angle=angle, scale=1)
# 回転後の画像の四隅の座標を計算する。
corners_rotated = cv2.boxPoints((center, (img_w, img_h), -angle))
# 回転後の画像の四隅の最小値、最大値を計算する。
min_x, min_y = corners_rotated.min(axis=0)
max_x, max_y = corners_rotated.max(axis=0)
# 回転後の画像の四隅が収まる大きさを計算する。
dst_w, dst_h = int(max_x - min_x), int(max_y - min_y)
# はみ出している分だけ平行移動する。
M[0, 2] += -min_x
M[1, 2] += -min_y
# 画像にアフィン変換行列を適用する。
dst = cv2.warpAffine(img, M, dsize=(dst_w, dst_h), borderValue=bg_color)
return dst
img = cv2.imread("sample.jpg")
dst = rotate_img(img, angle=30)
imshow(dst)
flags – 補間方法の指定など
InterpolationFlags 及び cv2.WARP_INVERSE_MAP の組み合わせを指定できます。
補間方法は次の種類があります。
- cv2.INTER_NEAREST: 最近傍補間
- cv2.INTER_LINEAR: バイリニア補間
- cv2.INTER_CUBIC: バイキュービック補間
- cv2.INTER_AREA: ピクセル領域の関係を利用したリサンプリング
- cv2.INTER_LANCZOS4: Lanczos 補間
また、cv2.WARP_INVERSE_MAP
を指定した場合、アフィン変換行列 M
は入力画像から出力画像の変換でなく、出力画像から入力画像への変換として解釈されます。
img = cv2.imread("sample.jpg")
# 画像の中央を中心に 30 度回転するアフィン変換行列を作成する。
img_h, img_w = img.shape[:2]
center = img_w / 2, img_h / 2
M = cv2.getRotationMatrix2D(center=center, angle=30, scale=1)
# flags=cv2.WARP_INVERSE_MAP を指定しない場合、M がそのまま適用される。
dst = cv2.warpAffine(img, M, dsize=(img_w, img_h))
imshow(dst)
# flags=cv2.WARP_INVERSE_MAP を指定した場合、M の逆行列が適用される。
dst = cv2.warpAffine(img, M, dsize=(img_w, img_h), flags=cv2.WARP_INVERSE_MAP)
imshow(dst)
borderMode – 外挿方法
変換後に値がない画素を外挿する方法を BorderTypes から指定できます。
デフォルトでは、borderMode=cv2.BORDER_CONSTANT
、borderValue=0
となっており、値がない画素は黒になります。
img = cv2.imread("sample.jpg")
# 画像の中央を中心に 30 度回転するアフィン変換行列を作成する。
img_h, img_w = img.shape[:2]
center = img_w / 2, img_h / 2
M = cv2.getRotationMatrix2D(center=center, angle=30, scale=1)
# 青色で外挿する。
dst = cv2.warpAffine(img, M, dsize=(img_w, img_h), borderValue=(255, 0, 0))
imshow(dst)
borderMode=cv2.BORDER_TRANSPARENT
を指定すると、dst
引数に指定した画像の画素値が外挿に使用されます。これを利用することである画像を別の画像に埋め込むことができます。
# 前景画像を読み込む。
fg_img = cv2.imread("sample.jpg")
# 背景画像を読み込む。
bg_img = cv2.imread("background.jpg")
# 前景画像を、画像の中央を中心に120度回転し、0.5倍に縮小するアフィン変換行列を作成する。
fg_h, fg_w = fg_img.shape[:2]
M = cv2.getRotationMatrix2D(center=(fg_w / 2, fg_h / 2), angle=120, scale=0.5)
# 画像にアフィン変換行列を適用する。
bg_h, bg_w = bg_img.shape[:2]
dst = cv2.warpAffine(
fg_img, M, dsize=(bg_w, bg_h), dst=bg_img, borderMode=cv2.BORDER_TRANSPARENT
)
imshow(dst)
cv2.getRotationMatrix2D
cv2.getRotationMatrix2D
は、指定した点を中心に回転するアフィン変換行列を作成する関数です。
この関数は、回転の中心を $center = (c_x, c_y)^T$、回転角度を $\theta$、スケールを $s$ としたとき、次の 4 つの変換をまとめたアフィン変換行列が作成されます。
- 回転の中心が原点となるように、$(-c_x, -c_y)$ だけ平行移動する $$ A_1 = \begin{pmatrix} 1 & 0 & -c_x \\ 0 & 1 & -c_y \\ 0 & 0 & 1 \\ \end{pmatrix} $$
- 原点を中心に反時計回りに $\theta$ だけ回転する $$ A_2 = \begin{pmatrix} \cos \theta & \sin \theta & 0 \\ -\sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} $$
- $s$ 倍だけスケールする $$ A_3 = \begin{pmatrix} s & 1 & 0 \\ 1 & s & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} $$
- 1 で平行移動した分を戻すため、$(c_x, c_y)$ だけ平行移動する $$ A_4 = \begin{pmatrix} 1 & 0 & c_x \\ 0 & 1 & c_y \\ 0 & 0 & 1 \\ \end{pmatrix} $$
以上 4 つのアフィン変換を 1 つにまとめると、
$$ \begin{aligned} A_4 A_3 A_2 A_1 &= \begin{pmatrix} 1 & 0 & c_x \\ 0 & 1 & c_y \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} s & 1 & 0 \\ 1 & s & 0 \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} \cos \theta & \sin \theta & 0 \\ -\sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} 1 & 0 & -c_x \\ 0 & 1 & -c_y \\ 0 & 0 & 1 \end{pmatrix} \\ &= \begin{pmatrix} s \cos \theta & s \sin \theta & -s c_x \cos \theta -s c_y \sin \theta + c_x \\ – s \sin \theta & s \cos \theta & s c_x \sin \theta -s c_y \cos \theta + c_y \\ 0 & 0 & 1 \end{pmatrix} \end{aligned} $$ここで、$\alpha = s \cos \theta, \beta = s \sin \theta$ とおくと、
$$ \begin{pmatrix} \alpha & \beta & (1 – \alpha) c_x – \beta c_y \\ -\beta & \alpha & \beta c_x + (1 – \alpha) c_y \\ 0 & 0 & 1 \\ \end{pmatrix} $$retval = cv2.getRotationMatrix2D(center, angle, scale)
名前 | 型 | デフォルト値 |
---|---|---|
center | tuple of 2-floats | |
回転する中心 | ||
angle | float | |
回転角度 (degree) | ||
scale | float | |
拡大縮小率 |
名前 | 説明 | ||
---|---|---|---|
retval | アフィン変換行列 |
import cv2
import numpy as np
cx, cy = (100, 100)
angle = 120
scale = 3
M = cv2.getRotationMatrix2D((cx, cy), angle, scale)
print(M)
# cv2.getRotationMatrix2D() が作成する関数が上の式と一致することを確認する。
a = scale * np.cos(np.deg2rad(angle))
b = scale * np.sin(np.deg2rad(angle))
M2 = np.array([[a, b, (1 - a) * cx - b * cy], [-b, a, b * cx + (1 - a) * cy]])
print(M2)
assert np.allclose(M, M2)
[[ -1.5 2.59807621 -9.80762114] [ -2.59807621 -1.5 509.80762114]] [[ -1.5 2.59807621 -9.80762114] [ -2.59807621 -1.5 509.80762114]]
画像を 90° 単位で回転する
画像を 90° 単位で回転させる場合は、cv2.warpAffine()
ではなく、cv2.rotate()
を使用します。
cv2.rotate
dst = cv2.rotate(src, rotateCode[, dst])
名前 | 型 | デフォルト値 |
---|---|---|
rotateCode | int | |
|
名前 | 説明 | ||
---|---|---|---|
dst | 出力画像 |
時計回りに 90° (反時計回りに 270°) 回転する
img = cv2.imread("sample.jpg")
# 反時計回りに270° (時計回りに90°) 回転する。
dst = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
imshow(dst)
時計回りに 180° (反時計回りに 180°) 回転する
img = cv2.imread("sample.jpg")
# 反時計回りに270° (時計回りに90°) 回転する。
dst = cv2.rotate(img, cv2.ROTATE_180)
imshow(dst)
反時計回りに 90° (時計回りに 270°) 回転する
img = cv2.imread("sample.jpg")
# 反時計回りに90° (時計回りに270°) 回転する。
dst = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
imshow(dst)
コメント