OpenCV 要將歪斜圖形轉正,通常不是只用 `cv2.rotate()`。比較完整的做法是先用輪廓擬合取得物件角度,再用 `cv2.getRotationMatrix2D()` 建立旋轉矩陣,最後用 `cv2.warpAffine()` 把影像轉回正向並裁切。
`cv2.rotate()` 適合什麼旋轉?
`cv2.rotate()` 適合固定角度旋轉,例如 90 度、180 度或 270 度。若影像只是方向放錯,`cv2.rotate()` 是最簡單的選擇。
```python
import cv2
image = cv2.imread("your_image.jpg")
rotated_image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
cv2.imshow("Rotated Image", rotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
`cv2.rotate()` 不適合處理任意角度歪斜,因為函數本身只處理幾種固定旋轉模式。若物件角度來自輪廓偵測,就要改用仿射轉換。
`cv2.flip()` 和 `cv2.rotate()` 差在哪?
`cv2.flip()` 是軸對稱翻轉,`cv2.rotate()` 是旋轉變換。兩者在 180 度時可能看起來相似,但幾何意義與可控制的方向不同。
```python
dst = cv2.flip(src, flipCode)
```
| `flipCode` | 效果 |
|---|---|
| `0` | 沿水平軸上下翻轉 |
| `1` | 沿垂直軸左右翻轉 |
| `-1` | 同時上下與左右翻轉 |
如果任務是修正鏡像問題,用 `cv2.flip()`。如果任務是修正拍攝角度或物件歪斜,用輪廓角度搭配 `cv2.warpAffine()`。
為什麼我偏好用橢圓擬合取得角度?
橢圓擬合適合左右或上下對稱、長寬不同的物件。`cv2.minAreaRect()` 雖然能取得最小旋轉矩形,但輪廓有小缺口時,矩形角度可能跳動。
原文的經驗是:最小擬合矩形很容易受到輪廓細微變化影響,同一個形狀可能出現不同外框方向。對稱但非正方的物件,我會優先試 `cv2.fitEllipse()`,因為橢圓軸向通常比較符合物件本身的主方向。
```python
import cv2
image = cv2.imread("./333_2023-06-08_19-57-30.jpg")
canny = cv2.Canny(image, 50, 250)
cnts, hier = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
ellipse = cv2.fitEllipse(cnts[0])
(center, axes, angle) = ellipse
cv2.ellipse(image, ellipse, (0, 255, 0), 2)
```
如何用 `warpAffine()` 將物件轉正並裁切?
`cv2.warpAffine()` 可以依旋轉矩陣重新取樣影像。先用 `cv2.getRotationMatrix2D()` 取得矩陣,再旋轉原圖與輪廓 mask,最後用 bounding box 裁出轉正後的物件。
```python
def rotatedDice(image, cnt):
ellipse = cv2.fitEllipse(cnt)
(center, axes, angle) = ellipse
angle = angle + 90
rotation_matrix = cv2.getRotationMatrix2D(tuple(center), angle, 1)
image = cv2.warpAffine(image, rotation_matrix, (image.shape[1], image.shape[0]))
mark = np.zeros_like(image)
cv2.drawContours(mark, [cnt], 0, (255, 255, 255), -1)
mark = cv2.warpAffine(mark, rotation_matrix, (mark.shape[1], mark.shape[0]))
mark = cv2.cvtColor(mark, cv2.COLOR_RGB2GRAY)
cnts, hier = cv2.findContours(mark, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(cnts[0])
matting_result = image[y:y + h, x:x + w, :]
return matting_result
```
這段做法的重點是同步旋轉影像與 mask。只旋轉影像會失去原本輪廓位置;同步旋轉 mask,才能在新座標中重新找到物件裁切範圍。
什麼時候需要 `cv2.getAffineTransform()`?
`cv2.getAffineTransform()` 適合已知三組對應點的仿射校正。若真實世界拍攝角度包含透視或深度變化,只用單一中心點與角度旋轉會不夠。
原文提到,大部分 3D 場景的角度轉換會帶有深度變化。這時可以用三個點建立仿射矩陣,或在透視變形更明顯時改用 perspective transform。
```python
import cv2
import numpy as np
point1 = (106, 92)
point2 = (28, 91)
point3 = (154, 33)
rotation_angle = -45
image = np.zeros((500, 500), dtype=np.uint8)
cv2.drawContours(image, [np.array([point1, point2, point3])], 0, 255, thickness=2)
center = np.mean([point1, point2, point3], axis=0)
rotation_matrix = cv2.getRotationMatrix2D(tuple(center), rotation_angle, 1)
rotated_image = cv2.warpAffine(image, rotation_matrix, (image.shape[1], image.shape[0]))
```
常見問題
OpenCV 圖形轉正可以只用 `cv2.rotate()` 嗎?
只有固定 90 度倍數旋轉時適合只用 `cv2.rotate()`。若物件歪斜角度不是固定值,應先估角度再用 `cv2.warpAffine()`。
`cv2.minAreaRect()` 為什麼角度會不穩?
`cv2.minAreaRect()` 會找最小面積旋轉矩形,輪廓上的小凹洞或邊緣雜訊可能改變最小矩形方向。物件近似橢圓或對稱時,`cv2.fitEllipse()` 有時更穩。
`angle + 90` 一定正確嗎?
不一定。`angle + 90` 是原文案例中的修正方式,實務上要依物件長軸方向、OpenCV 回傳角度定義與你想要的正向結果調整。
轉正後為什麼還要重新裁切?
旋轉後物件位置與外框都會改變。用同步旋轉後的 mask 重新找 `cv2.boundingRect()`,可以裁出比較乾淨的物件區域。
參考資料
- OpenCV Documentation, Geometric Image Transformations: <https://docs.opencv.org/4.x/da/d54/groupimgproctransform.html>
- OpenCV Documentation, Affine Transform Tutorial: <https://docs.opencv.org/4.x/d4/d61/tutorial_warp_affine.html>
最後更新
Fri Jun 09 2023 08:00:00 GMT+0800 (Taiwan Standard Time)
延伸閱讀
關於作者 {#author}
Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。
首次發布:2023-06-09
