5.21. 縮放、翻轉與裁切

前面各小節處理的像素都維持在它們一開始所在的位置。transform 系列則改變了這一點。縮放會將每個輸入像素送到不同的輸出位置,可能一次送往數個輸出位置(放大時),或送往一個與其他數個輸入像素共用的位置(縮小時)。翻轉與旋轉透過不同的映射做相同的事情。裁切則保留輸入像素的某個矩形子集,並捨棄其餘部分。

image 模組透過三個方法來呈現這個系列,它們共用大部分的引數與大部分的行為:

  • copy() ——產生影像的一份副本,可能經過縮放、裁切或重新定向。

  • crop() ——與 copy 相同的操作,但預期應用程式會從來源中挑選出一個子矩形。

  • scale() ——同樣的操作,但預期應用程式會調整結果的大小。

這三者共用相同的引數與相同的轉換機制;差別在於結果預設落在哪裡。copy() 會產生一張新影像,而 crop()scale() 則就地修改來源。

5.21.1. 共用的引數

單次呼叫可結合應用程式所要求的任何縮放、裁切、定向與通道擷取的組合:

x_scaley_scale 分別沿水平與垂直軸獨立縮放輸入。兩者都預設為 1.0(不縮放)。為各軸指定不同的值會產生非等比縮放——例如,將影格拉伸成寬度是高度兩倍的樣子。

roi 將輸入限制在來源影像的某個矩形範圍內,僅取那些像素進入後續的轉換流程。這就是該操作中的「裁切」部分:傳入一個 roi 以擷取子區域。

hint 是一個旗標的位元欄位,用來選擇插值方法以及任何定向翻轉。多個旗標透過位元 OR 結合(hint=image.BILINEAR | image.HMIRROR)。這些旗標分為兩組——插值系列與定向系列——兩者彼此無關,但共用同一個位元欄位。

rgb_channel 選擇 RGB565 來源的單一通道。0 表示紅色,1 表示綠色,2 表示藍色;結果會輸出為僅包含該通道的灰階影像。例如,這對於僅在紅色通道上進行閾值化很有用。

color_palettealpha_palette 在輸出途中透過查找表重新映射像素值,做法與轉換方法 to_rainbow()to_ironbow() 相同。

copy=Truecopy_to_fb=True 遵循其他所有會產生結果的方法所使用的相同慣例——預設就地處理,copy=True 配置一個獨立的結果,copy_to_fb=True 則將結果放入影格緩衝區供 IDE 預覽。

5.21.2. 插值:AREA、BILINEAR、BICUBIC

當縮放將每個輸出像素送到一個無法與任何單一輸入像素對齊的位置時,該方法必須決定要寫入什麼值。有三個旗標控制其方式:

image.BILINEAR 在最鄰近的四個輸入像素之間,依其與輸出位置的距離加權進行插值。結果比最鄰近法更平滑,在對角線上不會出現可見的鋸齒,但額外的運算成本約為最鄰近法的四倍。對於大多數放大工作以及任何非整數縮放倍率而言,這是正確的選擇。

image.BICUBIC 在最鄰近的十六個輸入像素之間使用三次曲線進行插值,能產生更為平滑的結果,但代價是又增加了更多運算。對於有此需求且對成本敏感的應用而言,這是最佳品質;對於 IDE 僅會顯示的即時影格而言,這額外的運算量很少值得。

image.AREA 對落在輸出像素覆蓋範圍內的每個輸入像素取平均——這是縮小的正確演算法。雙線性與雙三次都是插值器:它們估算來源像素之間的值,這正是放大所需要的,但在縮小時每個輸出像素涵蓋許多來源像素,而插值器只讀取其中最鄰近的少數幾個——它略過的細節會以混疊的形式出現。image.AREA 則改為將每個被涵蓋的像素都納入平均之中。

不帶任何 hint 時的預設縮放演算法是最鄰近法,當來源已處於目的地的像素解析度時,它是最便宜也最正確的答案。

5.21.3. 定向:翻轉與旋轉

定向旗標是一小組布林轉換,它們彼此之間以及與插值旗標之間都能自由組合:

  • image.VFLIP 將影像垂直翻轉(上方變成下方)。

  • image.HMIRROR 將它水平鏡像(左方變成右方)。

  • image.TRANSPOSE 交換 x 軸與 y 軸(列變成欄)。

大多數旋轉都來自組合這三者。模組也提供了具名的快捷方式:

  • image.ROTATE_90(= VFLIP | TRANSPOSE

  • image.ROTATE_180(= HMIRROR | VFLIP

  • image.ROTATE_270(= HMIRROR | TRANSPOSE

在程式碼中:

img.copy(hint=image.ROTATE_90, copy_to_fb=True)

5.21.4. 長寬比處理

當來源的長寬比與其要被繪入的矩形不相符時,有三個旗標決定如何處理這個不相符:

image.SCALE_ASPECT_KEEP 保留來源的長寬比並對結果進行信箱式留邊——將來源縮放到剛好可放進目的地內,並以空白(零值)像素填滿目的地的其餘部分。當保持來源不變形比填滿整個輸出更重要時,這是正確的選擇。

image.SCALE_ASPECT_EXPAND 保留來源的長寬比並對它進行裁切——將來源縮放到填滿目的地,超出目的地的部分則被切除。當填滿整個輸出比看見來源的每一部分更重要時,這是正確的選擇。

image.SCALE_ASPECT_IGNORE 忽略長寬比並將來源拉伸以填滿目的地,接受由此引入的任何變形。當應用程式已將該變形納入考量時——例如,當目的地的尺寸實際上並非同一場景的某個矩形時——這是正確的選擇。

預設(未設定任何長寬比旗標)與 SCALE_ASPECT_IGNORE 相同:拉伸以填滿。在意長寬比的應用程式會明確指定這三者之一。

5.21.5. 何時選用哪一個

大多數縮放使用 scale(),並搭配一組 x_scale / y_scale 以及一個插值 hint:

img.scale(x_scale=0.5, y_scale=0.5, hint=image.AREA)

大多數旋轉使用相同的呼叫,搭配 hint=image.ROTATE_90 或類似旗標。

裁切使用 crop(),並搭配一個非預設的 roi

img.crop(roi=(40, 30, 200, 150))

當來源必須在操作後留存下來時——例如擷取一個參考影格、為一個即將被破壞性處理的影格製作縮圖——copy() 會將結果產生為一張新影像,並保持來源不受影響:

thumbnail = img.copy(x_scale=0.25, y_scale=0.25, hint=image.AREA)

那個預設值正是這三個名稱背後真正的差異:scalecrop 就地轉換,copy 則進行配置。結果放置關鍵字則彌合了這個落差:在 scalecrop 上使用 copy=True 會將結果配置為一個獨立的堆積緩衝區而非覆寫來源,而在這三者中任一者上使用 copy_to_fb=True 則會將它放入影格緩衝區供 IDE 預覽。