5.5. 區域與遮罩¶
image 模組中的每一項操作預設都會處理其來源影像的每一個像素。這是最容易描述的行為,而且當演算法的工作確實涵蓋整個影格時,這也是正確的做法——例如均勻的色彩校正、全域直方圖,或是用於傳輸的編碼程序。但在實務上,大多數演算法想要處理的範圍其實更小。一個盯著彩色標記的色塊追蹤器,只在乎場景中標記可能出現的部分,而不在乎後方的牆壁。一個形態學清理程序只有在處理早期階段標記為候選的像素時才是安全的。一個臉部偵測器可能只會在較粗略的偵測器已縮小範圍的邊界框內執行。image 模組透過兩種機制來支援這類工作,將操作限定在像素的子集合上:矩形的感興趣區域,以及二值遮罩。它們可以自由組合,而且幾乎每一個會觸及像素的方法都接受其中之一——或兩者——作為關鍵字引數。
5.5.1. 感興趣區域¶
感興趣區域是由座標頁面所介紹的 (x, y, w, h) 四元組所指定的矩形像素區域。介面上大約有三十個方法接受 roi 關鍵字引數;當其存在時,操作只會在該矩形內的像素上執行,而保持影像其餘部分不變。當 roi 為 None 或省略時,操作會在整張影像上執行——效果如同傳入了 roi=(0, 0, width, height)。
在程式碼中,這個關鍵字會與操作所接受的其他引數並列:
# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))
ROI 帶來的第一項好處是偽陽性控制。一個只觀察桌面的色彩追蹤器,永遠不會被走過旁邊的襯衫觸發;一個只在已定義的工作區域內執行的邊緣偵測器,永遠不會回報相機支架本身的邊緣。將搜尋範圍縮減至場景中演算法實際在乎的部分,是一條管線能對自身可靠性所做的最廉價改善。
它們帶來的第二項好處是由粗到細的管線。偵測結果物件——例如 blob、rect、apriltag 等等——以與 roi 所接受相同的 (x, y, w, h) 四元組形式公開其邊界框。因此,粗略的第一階段可以回傳一個邊界框,該邊界框直接放入下一階段的 roi,而第二階段便在這個更窄的範圍上執行。每一次漸進的縮小都既加快了下一階段的速度,又使其結果更可靠,因為搜尋空間已經先過濾過了。
5.5.2. 二值遮罩¶
當感興趣的區域是軸對齊的時候,矩形是正確的形式。當它不是時——例如曲線區域、非凸區域,或是某個早期階段分類為「相符」的像素——就必須告訴操作將自己限定在任意的像素圖樣上。實現這點的機制是二值遮罩:一個獨立的 Image,與來源具有相同的尺寸,用作逐像素的開/關開關。遮罩中的非零像素表示「納入相對應的來源像素」;零像素則表示「保持來源像素不變」。
遮罩通常是一張 BINARY 影像——正是為此目的而存在的每像素一位元格式——但任何單通道影像都可以使用,因為消費端會將任何非零值視為開啟。
濾波、閾值處理與算術方法都接受 mask 關鍵字引數。其形式在每個方法上都相同:傳入一張獨立配置的二值影像,與來源具有相同的尺寸。
ROI 與遮罩可以組合。同時傳入兩者,操作便只會在位於 ROI 內且在遮罩中為開啟的像素上執行。這兩種機制給了應用程式碼各自獨立的調節桿——一個用於矩形的感興趣區域,一個用於其中的任意圖樣——而不會讓任一種形式繼承另一種的限制。
ROI 將操作限定於一個軸對齊的矩形。遮罩則進一步將其縮小至任意的像素圖樣。兩者組合:只有位於 ROI 內且在遮罩中為開啟的像素才會被修改。¶
5.5.3. 建立遮罩¶
三個 Image 方法透過將所選區域之外的像素歸零,就地建立常見的遮罩幾何形狀:
mask_rectangle()保留一個矩形。mask_circle()保留一個圓形。mask_ellipse()保留一個橢圓形。
每個方法都接受 (x, y, w, h)(用於矩形與橢圓)或 (x, y, radius)(用於圓形)。不帶任何引數呼叫其中任一個,會將幾何形狀置中,並調整大小以填滿影像,這正是當應用程式的目標是一個只遮住四角、別無他物的簡單全影像橢圓或圓形時所會採用的形式。
mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear() # start from all zeros
mask.mask_ellipse() # centred, full-size oval
有趣的遮罩很少單獨來自 mask_* 方法。它們來自管線的早期階段:一次閾值處理程序會產生一張二值影像,其非零像素標示出相符之處,正是適合饋入下一階段 mask= 引數的形式。一次形態學清理程序會在不改變其形式的情況下精修該遮罩。任何最終成為單通道影像的東西,本身都是一個有效的遮罩。
5.5.4. 操作如何修改影像¶
前幾頁每個程式碼片段中都可見的一種模式——操作回傳同一個 img 以供串接——值得明確地提出來,這樣每次介紹新方法時就不必重述。Image 介面上出現三大類方法,每一類對來源影像的處理方式各不相同:
操作型方法會就地修改來源的像素,並回傳同一張影像以供串接。繪製、算術、閾值與濾波各系列都以這種方式運作。
img.gaussian(1)會模糊img並回傳同一個img;重新指派——img = img.gaussian(1)——無害但沒有必要。轉換型方法預設會像操作型方法一樣就地運作,但它們接受
copy=True與copy_to_fb=True,以便在需要保留來源時配置一張獨立的結果影像。格式轉換與幾何複製是這一類的主要成員。檢查型方法會讀取像素並回傳一個結果物件——一份偵測到的特徵清單、一張直方圖、一組統計資料——而完全不修改來源影像。
這套三分法在整個介面上都是一致的。知道一個方法屬於哪一類,就能告訴應用程式對一次呼叫該有什麼預期:來源的像素是否會原封不動地存留、是否會配置一張獨立的結果影像,以及回傳值是來源本身還是其他東西。