5.9. 算術運算

上一節的繪圖系列是將內容繪製影像之中。算術系列則是將兩張影像合併為第三張——把它們的像素值相加、彼此相減、或在每個位置取最小值或最大值。正是這一小組逐像素的算術運算,構成了影格差分、背景減除、曝光堆疊,以及其他若干經典模式的基礎。

Image 類別上的算術系列小到足以一次列舉完畢:

  • add()——逐像素的 self + other,截限至該格式的最大值。

  • sub()——逐像素的 self - other,於底端截限至 0

  • rsub()——逐像素的 other - self,截限至 0(與 sub 相同的算術,但運算元順序相反)。

  • min()——逐像素取兩值的最小值。

  • max()——逐像素取最大值。

  • difference()——逐像素的 |self - other|,即絕對差。

外加兩項相關的單影像運算:

  • invert()——將每個像素替換為 255 - pixel(或該格式對應的最大值)。

  • negate()——invert() 的別名。

頂端為兩條水平漸層條, 代表來源影像 A 與 B——A 由左至右由暗轉亮,B 由左至右由亮轉暗。 其下方為五條漸層條, 代表將各成對運算套用於 A 與 B 的結果:A.add(B) 呈現均勻的白色,因為每個 位置相加後皆超過 255 而被截限; A.sub(B) 在左半部為零, 並朝右側逐漸變亮; A.difference(B) 呈 V 字形,兩端 明亮而中間黝暗; A.min(B) 兩端黝暗而 中間較亮;A.max(B) 兩端明亮 而中間為灰色。

兩條來源漸層 A 與 B,以及將各成對運算套用於它們的結果。每項運算皆逐位置執行——結果中任一位置所顯示的內容,僅取決於該位置上的兩個來源像素。

5.9.1. 兩種運算元形式

每個雙影像方法的第二個運算元皆可接受以下任一形式:

  • 另一張相同尺寸的 Image。算術逐位置執行——(x, y) 處的結果,即為套用於兩張影像 (x, y) 處來源像素的運算結果。

  • 一個純量值——灰階為整數,RGB565 則為 (r, g, b) 元組。同一純量套用於每個位置。

當應用程式想將每個像素移動一個固定量時,純量形式十分有用。img.add(40) 將整張影像增亮 40;img.sub((20, 20, 20)) 將每個像素的每個通道調暗 20;img.max(50) 將任何低於 50 的像素提升至 50,其餘則保持不變——這類運算可將接近全黑的感測器底色轉為平坦的暗灰,供後續階段據以處理。

5.9.2. 截限

像素值在每項運算中皆維持於該格式的範圍內。對於 8 位元通道而言,這代表 0——255:任何會溢位超過 255 的值都會被截限回 255,而任何會低於 0 的值則會被截限提升至 0。不存在環繞行為。

這項選擇在實務上至關重要。add 增亮像素時,在亮端絕不會因數學運算溢位而產生突然變暗的假影;sub 調暗像素時,在暗端也絕不會因數學運算下溢而產生突然變亮的假影。其結果在視覺上始終具有意義,代價則是在飽和極端處損失部分資訊。

截限也正是 subrsub 彼此回傳不同結果的原因。img_a.sub(img_b) 給出 a 中比 b 更亮的部分,其餘各處皆為零;img_a.rsub(img_b) 則給出 b 中比 a 更亮的部分。兩者對於單向變化偵測皆有用——若應用程式只關心變亮的像素,或只關心變暗的像素——但兩者皆無法捕捉兩影格之間的所有變化。

5.9.3. 差分運算

對於雙向變化偵測,應採用的運算是 difference(),它在每個位置計算 |self - other|——即無正負號的絕對差。每個朝任一方向變化的像素,皆會在結果中以非零值呈現,其大小與該位置的變化量成正比。

這項特性——恰在兩張影像不一致之處為非零——正是使 difference 成為逐影格變化偵測主力的原因。將啟動時所儲存的參考影格與一張新擷取的影像一同經過 difference,會產生一張影像,其非零像素標示出場景中每個發生移動或亮度變化的位置。

5.9.4. 以遮罩限定範圍

所有算術方法皆接受區域與遮罩頁面所引入的 mask 關鍵字引數。當傳入遮罩時,運算僅在遮罩為非零的位置執行;其餘各處的目標影像則保持不變。

這種組合方式出現於兩種模式中。第一種是將運算限制於已知區域:例如僅在偵測到的標記邊界框內將兩影格相加。第二種是逐塊建構合成影格——在前景遮罩內對一系列影格取 min、在互補遮罩內對同一系列取 max——這類模式。

5.9.5. 就地運算並保留輸入

算術方法皆遵循先前所確立的運作慣例:各方法皆就地修改來源影像,並回傳同一張影像以供串接。呼叫後來源的像素即不復存在——已被該運算對照第二運算元所傳入內容的結果所取代。

當應用程式需要保留兩個輸入時,安全的做法是先複製其中之一:

diff = current.copy()       # leaves current intact
diff.difference(reference)  # diff now holds the absolute difference

這種模式——先複製,再運算——是任何影格差分管線的骨幹,其中參考影格必須在比較中存留下來,才能在下一張擷取的影格上重複使用。

有了六種合併運算、兩種單影像運算、一項絕對差主力運算,以及用於限定範圍的 mask 關鍵字,像素算術工具組便涵蓋了經典機器視覺所需的亮度與通道組合。表面上其餘類似算術的工具則是逐位元而非逐值運作。