5.2. 座標與區域¶
影像處理作用於像素之上,而要對某個像素進行運算,演算法必須以座標來定位它。要對一個矩形範圍內的像素進行運算也是同樣的道理 —— 這個矩形必須以演算法與應用程式碼都認同的方式來描述。image 模組對座標與矩形所採用的慣例相當直觀,但其中有一個細節會讓習慣數學慣例(而非電腦繪圖慣例)的讀者感到困惑,值得在一開始就明確說明。
5.2.1. 像素網格¶
像素 (0, 0) 是影像的左上角。x 軸往右延伸,因此 x 越大表示越靠右。y 軸向下延伸,因此 y 越大表示在影像中越往下。一張寬乘高的影像,其像素位於從 (0, 0) 到 (width - 1, height - 1) 的整數座標上;在 (width, 0) 或 (0, height) 處並沒有像素 —— 這些位置是右邊緣與下邊緣,分別比每個方向上最後一個實際存在的像素再往前一步。
向下的 y 軸正是前面提到的細節。習慣方格紙幾何的讀者會預期 y 越大表示位置越高;在這裡這個直覺正好相反。會反過來的原因在於,數位感測器與數位顯示器都是從左上角開始、逐列由左往右、由上而下地運作,而以相同的順序將像素排放在記憶體中,能讓「緩衝區中的位置 i」與「影像的第 r 列、第 c 行」之間的關係成為盡可能簡單的算術 —— 像素 (x, y) 的位置 i 就只是 y * width + x。每一套影像函式庫在數十年前都基於同樣的理由認同了這種排列方式,而代價只是初次處理影像時要做一個小小的心態調整。
影像座標系統:原點位於左上角,x 往右延伸,y 向下延伸。影像內的一個矩形區域以其左上角 (x, y) 及其尺寸 (w, h) 來命名。¶
5.2.2. 矩形¶
對影像進行的大多數運算,關注的往往不是單一像素,而是一個像素矩形 —— 一塊要檢視的區域、一塊要複製出來的區域、影格之中用來計算統計量的一格範圍。命名矩形的形式採用了對單像素慣例最簡單的延伸方式:給出左上角的座標,後面接著矩形的尺寸,打包成一個四元組 (x, y, w, h)。矩形內的像素位於第 x 行到第 x + w - 1 行、以及第 y 列到第 y + h - 1 列。
這裡值得明確說明的細節是,w 與 h 是尺寸,而非右下角座標。矩形 (10, 20, 4, 3) 涵蓋第 10、11、12、13 行與第 20、21、22 列 —— 總共十二個像素 —— 而不是一個從 (10, 20) 延伸到 (4, 3) 的區域。這個慣例在整個模組中是一致的,所以一旦內化之後就不會再出錯,但第一次接觸時確實容易讓人搞混。
(x, y, w, h) 這種形式出現在三個看似不同、卻共用同一慣例的地方。第一個是影像描述自身的範圍時:涵蓋整張影像的矩形是 (0, 0, width, height)。第二個是當偵測方法回傳帶有邊界框的結果時 —— 例如一個 blob、一個 rect、一個 apriltag —— 該框會以 (x, y, w, h) 的形式回報。第三個是當某個方法必須被告知要在影像的子區域上、而非整個影格上運作時;用來界定運算範圍的 roi 關鍵字引數也採用同樣的四元組。
從某個方法取得一個邊界框,再把它丟進下一個方法的 roi,是影像處理中最常見的模式之一。粗略的第一次偵測所得的邊界框,可以為更精細的第二次偵測縮小搜尋範圍,而偵測結果與方法引數之間統一的詞彙,正是讓這個模式如此直接的關鍵 —— 同一種元組形式,在交接的兩端以相同的方式使用。
5.2.3. 整數位址、分數質心¶
像素位址本身是整數。一個像素在某個整數行列上要嘛存在、要嘛不存在,而問「座標 (40.5, 30.7) 上有什麼」並不是一個格式正確的問題 —— 並沒有任何像素正好坐落在那個位置。不過,image 模組從像素位置推導出來的少數幾種量值是分數形式的,值得了解其原因,以免這個區別日後讓應用程式措手不及。
最常見的情形是質心 —— 一個區域的質量中心。對於一塊連通的像素區域,浮點數形式的質心是各成員像素位置依其密度加權後的平均值。一個像素跨越兩行的區域,其質心 x 可能是例如 41.6 —— 這是一個真實的位置,眼睛會把它描述為「那塊區域的中間」,即使並沒有任何實際的像素正好坐落在那個 x 上。偵測結果物件以唯讀屬性同時帶有這兩種形式:一組整數對(cx / cy,當要把位置回饋給需要整數像素座標的東西時很有用),以及一組浮點數對(cxf / cyf,當位置要送入受益於次像素解析度的控制迴路時很有用)。
另一種情形是兩個影格之間、在頻率域中量測的位移。分析影像頻譜內容而非直接分析其像素的技術,能夠解析出比一個像素更細微的位移,並以浮點數的 (dx, dy) 值回報這些位移。
經驗法則:像素位址是整數;而從演算法產出的位置與位移可以是浮點數。繪製方法兩種形式都接受,並在結果必須落到網格上時,將浮點數向下取整到最接近的整數像素。
5.2.4. 笛卡兒座標與極座標¶
目前為止所描述的系統是笛卡兒式的:每個像素都以其相對於原點的水平與垂直偏移量來命名。這也是位元組所儲存的系統 —— 緩衝區中的像素 i 對應到第 i % width 行、第 i // width 列的像素,由上往下逐列走訪 —— 同時也是每個方法預設運作所在的系統。
第二種表示方式值得了解,因為某些演算法在它之中運作起來好得多。極座標以每個像素相對於某個選定中心點的距離、以及它與某個參考方向之間的角度來命名。影像的像素並沒有移動 —— 位元組仍然位於同一個以列為主的緩衝區中 —— 但定位方式已從「往右多遠、往下多遠」切換成「離中心多遠、繞著中心呈什麼角度」。
同一個點 P,以兩種方式命名:從左上角原點起算的笛卡兒座標 (x, y),以及從某個選定中心起算的極座標 (r, theta)。¶
為什麼要費事切換?因為有兩個等價關係,能把困難的搜尋變成容易的搜尋。
在極座標中,將影像繞著選定中心旋轉,與沿著角度軸平移其像素是同一個運算 —— 也就是重新投影後影像中的 x 方向。一份旋轉過的副本,就是原影像在極座標形式下向左或向右平移的結果。
在對數極座標這個變體中 —— 距離軸採用對數刻度,角度軸維持線性 —— 將影像繞著選定中心縮放,與沿著距離軸平移其像素是同一個運算 —— 也就是 y 方向。一份縮放過的副本,就是原影像在對數極座標形式下向上或向下平移的結果。
因此,必須在旋轉或縮放下辨識某個已知圖樣的演算法,可以在極座標空間中進行搜尋,因為在那裡這兩種變換都化為一般的平移。平移比旋轉與縮放更容易搜尋,而極座標的重新投影正是讓這種替換得以成立的關鍵。
極座標並不取代笛卡兒座標來儲存像素;位元組始終存在於笛卡兒網格上。模組提供了一對方法,能依需求將影像從笛卡兒形式重新投影成極座標形式,讓需要極座標的演算法完成其工作,接著要嘛將結果投影回去,要嘛直接使用極座標空間中的量測值。這個機制正是極座標會出現在模組對外介面任何地方的唯一原因。
有了用來命名個別像素的笛卡兒座標、用來命名其矩形的 (x, y, w, h) 四元組,以及在演算法能從中受益時可用的極座標,應用程式就擁有了一套完整的詞彙,能夠命名影像中某個東西位於何處。至於那些位置上實際儲存的是什麼,則是這個基礎的下一層。