Python 圖片簡單旋轉 - Image Rotate

在這邊紀錄一下簡單的圖片旋轉方式
雖然好像有 function 可以直接用
但理解後還是有幫助的
旋轉矩陣
首先先介紹旋轉矩陣:$\begin{bmatrix}cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\end{bmatrix}$ 其中,$\theta$為旋轉的角度。
這矩陣的由來可以從合角公式的地方看到:
$$ sin(\alpha + \beta) = sin(\alpha)cos(\beta) + cos(\alpha)sin(\beta) \\ sin(\alpha - \beta) = sin(\alpha)cos(\beta) - cos(\alpha)sin(\beta) \\ cos(\alpha + \beta) = cos(\alpha)cos(\beta) - sin(\alpha)sin(\beta) \\ cos(\alpha - \beta) = cos(\alpha)cos(\beta) + sin(\alpha)sin(\beta) $$
下方圖中 $\theta_1$ 為 $30^{\circ}$,因此綠色向量為 $(cos(30^{\circ}),sin(30^{\circ})) = (\sqrt 3 /2,0.5)$。
如今逆時針旋轉了 $30^{\circ}$,如下圖。
下方圖中 $\theta_1$ 為 $30^{\circ}$ 且 $\theta_2$ 也為 $30^{\circ}$。
因此橘色向量為 $(cos(60^{\circ}), sin(60^{\circ})) = (0.5, \sqrt 3 /2)$。
我們可以將 $\theta_1$ 和 $\theta_2$ 帶入合角公式驗證看看: $$ cos(30 + 30) = cos(30)cos(30) - sin(30)sin(30) = 0.5 \\ sin(30 + 30) = sin(30)cos(30) + cos(30)sin(30) = \sqrt 3 /2 $$
結果和現實的一樣。
那現在我們將原本的綠色向量一般化,改用未知數代替:向量為 $(x,y)$、角度為 $\theta_0$。
如今要旋轉 $\theta$ 度,成為新的向量 $(x’, y’)$。
完成之後替換一下合角公式。
$$ cos(\theta_0 + \theta) = cos(\theta_0)cos(\theta) - sin(\theta_0)sin(\theta) \\
sin(\theta_0 + \theta) = sin(\theta_0)cos(\theta) + cos(\theta_0)sin(\theta) $$
其中
$cos(\theta)$ 為 $x$;$sin(\theta)$ 為 $y$。
$cos(\theta_0 + \theta)$ 為 $x’$;$sin(\theta_0 + \theta)$ 為 $y’$。
因此又可以寫成:
$$ x’ = x\times cos(\theta) - y\times sin(\theta) \\
y’ = y\times cos(\theta) + x\times sin(\theta) $$
接下來我們將此化成矩陣的形式: $$\begin{bmatrix} x\times cos(\theta) - y\times sin(\theta) \\ y\times cos(\theta) + x\times sin(\theta) \end{bmatrix} = \begin{bmatrix} x’ \\ y’ \end{bmatrix} $$
最後將矩陣下方的 row 交換位子之後,將 $x$ 和 $y$ 提出來: $$\begin{bmatrix} x\times cos(\theta) - y\times sin(\theta) \\ y\times cos(\theta) + x\times sin(\theta) \end{bmatrix} = \begin{bmatrix} x\times cos(\theta) - y\times sin(\theta) \\ x\times sin(\theta) + y\times cos(\theta) \end{bmatrix} = \begin{bmatrix} cos(\theta) - sin(\theta) \\ sin(\theta) + cos(\theta) \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} x’ \\ y’ \end{bmatrix} $$
旋轉矩陣就誕生了~~
$$\begin{bmatrix} cos(\theta) - sin(\theta) \\ sin(\theta) + cos(\theta) \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} x’ \\ y’ \end{bmatrix} $$
轉換
經過剛剛的介紹,要求出新的座標 $\begin{bmatrix}x’ \\ y’\end{bmatrix}$ ,可以將原本的座標 $\begin{bmatrix}x \\ y\end{bmatrix}$ 乘上旋轉矩陣,也就是:
$$\begin{bmatrix}cos(\theta) & -sin(\theta)\\sin(\theta) & cos(\theta)\end{bmatrix} \begin{bmatrix}x \\ y\end{bmatrix} = \begin{bmatrix}x’ \\ y’\end{bmatrix}$$
但你發現你旋轉完成之後圖片有點小瑕疵 (如下圖)。

有黑點的旋轉圖
會有這個原因主要是因為將原本的座標乘上旋轉矩陣之後會出現小數點,那在圖片上面的座標不會有小數點,因此我們會將小數點去除,不管是直接捨去還是四捨五入,都有可能造成某些在新圖上面的點沒有任何的原圖點對應到,為了改善這個現象,使用反向的方法來轉換。
以前是將原圖座標轉成新圖座標,將原圖的點看要塞進新圖的哪一個位置;現在是將新圖座標轉成原圖座標,看新圖的點要拿哪一個舊圖的位置的點來填。
為了達到上述目的,我們要將公式調整一下:
$$\begin{bmatrix}cos(\theta) & -sin(\theta)\\sin(\theta) & cos(\theta)\end{bmatrix}^{-1} \begin{bmatrix}cos(\theta) & -sin(\theta)\\sin(\theta) & cos(\theta)\end{bmatrix} \begin{bmatrix}x \\ y\end{bmatrix} = \begin{bmatrix}cos(\theta) & -sin(\theta)\\sin(\theta) & cos(\theta)\end{bmatrix}^{-1}\begin{bmatrix}x’ \\ y’\end{bmatrix}$$
$$\Rightarrow \begin{bmatrix}x \\ y\end{bmatrix} = \begin{bmatrix}cos(\theta) & -sin(\theta)\\sin(\theta) & cos(\theta)\end{bmatrix}^{-1}\begin{bmatrix}x’ \\ y’\end{bmatrix}$$
那至於 $\begin{bmatrix}cos(\theta) & -sin(\theta)\\sin(\theta) & cos(\theta)\end{bmatrix}^{-1}$ 這是什麼東西呢?
原本的旋轉矩陣是將座標旋轉 $\theta$ 度,反矩陣就是轉反方向 $\theta$ 度。
所以 $$\begin{bmatrix}cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\end{bmatrix}^{-1} = \begin{bmatrix}cos(-\theta) & -sin(-\theta) \\ sin(-\theta) & cos(-\theta)\end{bmatrix} = \begin{bmatrix}cos(\theta) & sin(\theta) \\ -sin(\theta) & cos(\theta)\end{bmatrix}$$
- 最終公式:
$$\begin{bmatrix}x \\ y\end{bmatrix} = \begin{bmatrix}cos(\theta) & sin(\theta) \\ -sin(\theta) & cos(\theta)\end{bmatrix}\begin{bmatrix}x’ \\ y’\end{bmatrix}$$
Python Code
# 順時針旋轉 45 度
def rotate_45(img):
# 建立空白圖片
new_img = np.zeros(img.shape, np.uint8)
# 取得圖片的長寬
H, W = img.shape[:2]
H_2 = int(H/2)
W_2 = int(W/2)
# 預先計算 √2/2
sqrt_2_div_2 = math.sqrt(2)/2
for i in range(H):
for j in range(W):
# 計算原本圖片中的位置
x = int(sqrt_2_div_2*(i - H_2) - sqrt_2_div_2*(j - W_2))
y = int(sqrt_2_div_2*(i - H_2) + sqrt_2_div_2*(j - W_2))
# 確認是否要跳過超出原本圖片的位置
if x + H_2 < H and y + W_2 < W and x + H_2 >= 0 and y + W_2 >= 0:
new_img[i][j] = img[x + H_2][y + W_2]
return new_img
Example
原圖
旋轉 45 度
Reference
如果你覺得這篇文章有用 可以考慮贊助飲料給大貓咪