一文搞懂仿射變換原理及Python實現程式碼

語言: CN / TW / HK

影象的仿射變換經常用於計算機視覺的資料增強中,我們說的仿射變換指的是剛體變換,即兩條平行的線經過仿射變換之後還是平行的,而透視變換就不是剛體變換,不在本篇文章的討論範圍內。通常翻轉、平移、旋轉、放縮、錯切變換的任意組合都屬於仿射變換。仿射變換可以用如下公式來表示:

其中旋轉角度作用於a b d e,scale作用於a e,剪下變換作用於b d,平移作用於c f,如下圖所示:

1. cv2.getRotationMatrix2D詳解

opencv的cv2.getRotationMatrix2D函式可以獲取旋轉變換矩陣。輸入中心點座標(centerX,centerY),旋轉角度θ和縮放比例1,給出M變換矩陣: $$ \left[ \begin{matrix} cosθ & -sinθ & (1 - cosθ) * centerX + sinθ * centerY \ sinθ & cosθ & (1 - cosθ) * centerY + sinθ * centerX \ 0 & 0 & 1 \end{matrix} \right] $$

這個仿射變換矩陣是怎麼推導得來的呢?仿射變換矩陣採用極座標系比較容易推理。 我們先對一個點基於原點進行旋轉,如下圖,將V1點逆時針旋轉θ角度到V2點,縮放比例我們先假定為1。

V1點和原點連線與水平線夾角a,V2點和原點連線與水平線夾角b=a+θ。計算旋轉變換矩陣: 記V1=(x1, y1), V2=(x2, y2)

image.png

但是通常我們會基於中心點進行旋轉,如果是需要繞任意點(tx,ty)旋轉,我們可以
1.先把旋轉點平移到原點
2.然後進行以上旋轉操作
3.按1的逆操作平移回去
就可以得到繞任意點旋轉點變換矩陣:

image.png

2. warpAffine操作

2.1 獲取M矩陣進行仿射變換

得到變換矩陣M,對影象每個點進行M變換就可以得到旋轉後的影象,這一步可以通過opencv的warpAffine得到。但是通過以上操作,旋轉後大影象會丟失資訊,如下圖所示:

2.2 擴大畫布

畫布大小不變的情況下,會有一部分影象超出,顯示不全,所以我們需要將畫布擴大為: 新的高由圖片中兩段藍色線組合:new_H=int(w∗abs(sin(radias(angle)))+h∗abs(cos(radias(angle))))

新的寬由圖片中兩段紅色線組: new_W=int(h∗fabs(sin(radians(angle)))+w∗fabs(cos(radians(angle))))

新的畫布擴大是基於原圖左上角點擴大。所以,在變換矩陣M上,我們可以調整平移引數:

M[0,2]+=(new_W−w)/2

M[1,2]+=(new_H−h)/2

3. 影象仿射變換程式碼

Talk is cheap, show me the code. 直接上程式碼: ```python def affine_transform(image, angle, scale=1.0): h, w = image.shape[:2] center_x, center_y = width // 2, height // 2

M = cv2.getRotationMatrix2D((center_x, center_y), angle, scale)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])

new_h = int(h * cos + w * sin)
new_w = int(h * sin + w * cos)
M[0, 2] += (new_w − w) / 2
M[0, 1] += (new_h − h) / 2

new_image = cv2.warpAffine(image, M, (new_w, new_h))
return new_image

```

4. 單點仿射變換程式碼

我們在進行仿射變換資料增強時,需要將目標的座標點也進行相應的轉換。 ```python def affine_points(points, center_x, center_y, h, w, scale=1.0): points = np.array(points, dtype=np.float32) points = np.hstack((points, np.ones((points.shape[0], 1))))

M = cv2.getRotationMatrix2D((center_x, center_y), angle, scale)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])

new_h = int(h * cos + w * sin)
new_w = int(h * sin + w * cos)
M[0, 2] += (new_w − w) / 2
M[0, 1] += (new_h − h) / 2

new_points = np.dot(M, points.T).T
return new_points

```