5.23. 透視校正¶
警告
任意 3x3 transform 矩陣僅在 OpenMV Cam N6 上受支援——在其他所有開發板上此關鍵字都會被無聲忽略。需要在其他平台上執行的應用程式必須改用現成的 rotation_corr() 方法(搭配其 corners= 形式),或在裝置外預先計算校正後的影像。
現成的 rotation_corr() 方法將一類特定的透視扭曲封裝在一小組參數之後,並可在每一款受支援的開發板上執行。有些應用程式需要不符合該形式的扭曲:從一個四邊形到另一個四邊形的任意投影重映射、針對已知安裝且已離線算出的校準校正,或是由某個上游演算法直接提供的現成扭曲矩陣。對於這些需求,draw_image()——連同 copy()、crop() 與 scale()——皆接受一個 transform 關鍵字,可直接接收手動建構、用以描述扭曲的 3x3 矩陣。
5.23.1. 仿射與投影變換¶
幾何扭曲以齊次座標(homogeneous coordinates)表示:將像素位置 (x, y) 附加一個 1 後,再乘以一個 3x3 矩陣。
仿射(affine)形式是起點。其底列固定為 \((0, 0, 1)\):
展開後,每個輸出座標都是輸入座標的線性組合再加上一個常數:
這涵蓋了縮放、旋轉、剪切與平移的任意組合——而在所有這些變換下,平行線都維持平行。
投影(透視)形式則解放了底列:
展開後:
正是除以 \(w' = g x + h y + 1\) 這一步,使該變換成為投影變換而非僅是仿射變換。當 \(g\) 與 \(h\) 皆為零時,\(w'\) 維持為一,除法不起作用——便回到仿射形式。當其中任一者非零時,\(w'\) 會隨輸入位置變化,使不同位置的像素受到不同程度的透視縮短,因而不再維持平行線平行——這正是從斜角觀看平面所產生的梯形效應。投影變換是能將直線映射為直線的最一般幾何扭曲;縮放、翻轉、轉置、旋轉以及四角點旋轉校正全都是它的特例。
那些具名變換可直接從仿射形式推導而出。恆等變換即單位矩陣,而:
對於大多數手動建構的變換,應用程式會以其中一個作為基底,再為每個額外運算乘入更多矩陣,最終得到一個描述複合扭曲的單一 3x3 矩陣。矩陣由右向左套用:\(M = T R S\) 會先執行縮放、再旋轉、最後平移。每個人最終都會需要的複合運算是繞影像中心的旋轉——單純的旋轉矩陣會讓影像繞左上角的像素原點旋轉,因此置中版本會將中心 \((c_x, c_y)\) 移到原點、旋轉、再移回:
5.23.2. transform 關鍵字¶
矩陣透過 transform 關鍵字傳入,須提供為一個 3x3 的 ulab.numpy.ndarray。應採用的方法是 draw_image(),它會在將來源繪製到目的地時透過矩陣對其進行扭曲——結果會落在應用程式所控制的緩衝區中,且該扭曲會與呼叫上的其他一切一同合成:縮放、alpha 混合、遮罩。
import ulab.numpy as np
M = np.array([[1.2, 0.0, -20.0],
[0.0, 1.2, -15.0],
[0.0, 0.0, 1.0]])
canvas.draw_image(img, transform=M)
此範例將 img 扭曲繪製到 canvas 上,在各方向縮放 1.2 倍,並分別向左與向上移動 20 與 15 個像素——這是一個直接從上述矩陣項目建構而成的仿射扭曲。在 copy()、crop() 與 scale() 上使用相同的關鍵字,則會將該扭曲套用至影像本身。