5.32. 儲存與壓縮

到目前為止的每一頁都是處理相機上的影像:擷取至影格緩衝區或配置在 MicroPython 堆積上,透過 image 模組的方法加以操作,然後在 IDE 預覽中顯示,或在同一份指令碼中送入下游階段。大多數應用程式在某個時間點都需要做相反的事:取出目前位於 RAM 中的影像,並將它放到某個持久化的位置——SD 卡上、USB 主機上、或透過網路傳出——讓相機以外的東西能夠讀取它。

image 模組為這項工作提供了兩條途徑。儲存 途徑會將影像寫入檔案系統上的檔案,檔案格式由副檔名決定,編碼細節則由該方法處理。轉換格式 途徑會回傳一個包含已編碼位元組串流的 Image 物件,適合交給串流或網路呼叫使用,完全不需要碰到檔案系統。每一種途徑適用於不同的應用;兩者底層都建構在同一個壓縮引擎之上。

5.32.1. 儲存至檔案

save() 會將影像寫入檔案系統中的某個路徑:

img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)

格式由副檔名挑選。系統可辨識五種副檔名:.bmp 會寫出 Windows 點陣圖(無損、無壓縮、與所擷取像素逐位元組相同);.pgm 會寫出 可攜式灰階圖(無損,僅限灰階);.ppm 會寫出 可攜式像素圖(無損,RGB);.jpg.jpeg 都會寫出 JPEG(有損、已壓縮)。接收端影像必須已經是所選容器對應的正確色彩格式——將彩色影像存成 .pgm 會是錯誤。

roi 會將儲存範圍限制在影像的某個子矩形內,作法與其他每個 image 模組方法的 roi 關鍵字相同。預設為整張影像。在儲存 JPEG 壓縮影像時此關鍵字會被忽略,因為磁碟上的形式已經涵蓋了整個影格,透過裁切再重新編碼會違背儲存現有壓縮位元組的本意。

quality 是 JPEG 壓縮品質,範圍從 0100,且只有在輸出為 JPEG 時才有意義(對於無損格式此關鍵字會被忽略)。預設值 50大多數 應用程式而言是恰當的平衡;7085 是追求較高視覺品質的區間,3050 是小型縮圖與頻寬受限傳輸的適當範圍,90 以上則保留給影像將被人工檢視、或交由對壓縮假影敏感的下游演算法處理的情況。

接收端影像會被回傳,因此呼叫可以串接:img.save("/sdcard/x.jpg").draw_string(0, 0, "saved")。回傳的物件就是同一張記憶體中的影像;儲存只是一個副作用。

一個典型用途是 擷取並記錄 模式。某個觸發事件發生(偵測到色塊、按下按鈕、計時器逾時);指令碼擷取一個影格;它在檔名後附加時間戳記;然後呼叫 save() 將影像推送到 SD 卡。IDE 預覽持續執行,下一個觸發事件發生,已儲存的檔案便逐漸累積。

5.32.2. 編碼至記憶體

當目的地不是檔案系統,而是網路連線、序列埠或另一個模組的輸入時,應用程式需要的是位於 記憶體中 而非磁碟上的已編碼位元組串流。to_jpeg()to_png() 正是產生這樣的結果:

encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)

預設行為是就地轉換:接收端會被轉換為 JPEG(或 PNG)影像,並回傳同一個物件。使用 copy=True 時,轉換會寫入一個新配置的堆積物件;使用 copy_to_fb=True 時,輸出會落在影格緩衝區中。這個選擇與其他任何轉換方法所提供的相同——預設就地轉換,當之後仍需要原始影像時則複製。

qualitysubsampling 就是儲存途徑所提供的相同 JPEG 調校旋鈕。subsampling 選擇色度次取樣方案:image.JPEG_SUBSAMPLING_AUTO 會為所選品質挑選最佳值,image.JPEG_SUBSAMPLING_444 保持色度為完整解析度(檔案最大,色彩準確度最佳),image.JPEG_SUBSAMPLING_422image.JPEG_SUBSAMPLING_420 會將色度解析度沿一個或兩個軸減半(檔案較小,色彩略微柔化,在一般觀看距離下不可見)。除非應用程式有特定需求,否則預設值 AUTO 是正確的選擇。

透過 to_png() 產生的 PNG 是 無損 的,但編碼較慢,且對於攝影內容而言檔案比 JPEG 更大(攝影內容在 PNG 的預測方案下壓縮效果不佳)。當影像是線條圖、螢幕截圖,或在擷取的影格上繪製了硬邊圖形時,請使用 PNG——無損編碼能保留 JPEG 會柔化掉的銳利邊緣。否則 JPEG 就是正確的預設選擇。

to_jpeg()to_png() 都接受其他轉換方法所採用的相同繪製風格位置參數與縮放關鍵字——x_scaley_scaleroirgb_channelalphacolor_palettealpha_palettehint——因此同一個呼叫可以在單一步驟中編碼出來源的縮放、裁切或調色盤對應版本。compress()to_jpeg() 的舊式寫法;兩者接受相同的引數並產生相同的結果。

5.32.3. 壓縮帶來的好處

JPEG 與原始格式取捨背後的數字值得徹底推算一遍。

一個 320×240 的 RGB565 影格為 153,600 位元組(QVGA 下的單一擷取影格)。一個 640×480 的影格為 614,400 位元組;一個 1280×960 的影格為 2,457,600 位元組。相較於桌上型或手機顯示器,這些都不算大,但在一台總共只有幾 MB RAM 的相機、寫入頻寬有限的 SD 卡,以及通常以 USB CDC、UART 或無線模組在不高的速度下運作的主機連結的脈絡下,這些就相當可觀了。

quality=50 的 JPEG 通常會將攝影擷取影格壓縮 10 倍到 20 倍:那個 614 KB 的 640×480 影格會變成 30 到 60 KB 的已編碼位元組串流。在 quality=85 時壓縮率降為 5 倍到 10 倍(同一影格為 60 到 120 KB)。在 quality=10 時——充滿假影但仍可辨識——壓縮率可達 30 倍到 50 倍(12 到 20 KB)。

這些數字決定了對已儲存影格實際上能 什麼。一條可維持 10 MB/s 的 SD 卡途徑能輕鬆處理每秒 30 個 quality=50 JPEG 編碼的 VGA 內容影格(約 1 到 2 MB/s);若以未壓縮方式儲存相同內容則需要超過 18 MB/s,已超出相機檔案系統途徑對該卡所能維持的速度。一台以 CDC 在 1 MB/s 下拉取 JPEG 編碼影格的 USB 主機,會以大約每秒 15 到 30 個影格接收 30 到 60 KB 的影格;若以相同速率拉取原始影格,則每秒只能取得一兩個影格。

簡而言之:壓縮方法不只是儲存時的便利功能。它們正是讓擷取影格在相機之外、以應用程式所在意的影格率下變得 可用 的關鍵。選擇正確的壓縮——一般記錄用 JPEG 品質 50、品質工作用 80、線條圖擷取用 PNG——是任何稍具規模的相機應用程式日常工作的一部分。