在影像設計與處理中,我們經常面臨需要建立或分析不同尺寸圖像的挑戰,此時靈活調整圖像的大小和樣式都是至關重要的。然而,過度地調整圖像尺寸往往會導致細節丟失或失真,如何在不同尺度下盡可能保留細節資訊,並方便進行多尺度的成果比對,金字塔的概念應運而生。我們又如何透過 OpenCV 與 numpy 協助處理這項任務呢?
素材取用
圖片:<雲端下載> | 清明 by DaylightAllure
概念釐清
金字塔比對是一種影像處理技術,在不同尺度對影像進行比對。其是由一系列影像組成的集合體,通過對原始影像進行 Downsampling (下採樣,縮小) 或 Upsampleing (上採樣,放大) 得到,因此每層具有不同的解析度,而第一層 (0) 則為原圖尺寸。
但這種事情不是透過調整圖片大小就能辦到嗎?重點在於金字塔的優勢在於它能盡量保留不同尺度下的細節資訊,避免單次過多的放大導致失真嚴重,並方便圖片在倍率間的交互比對,而不用手動調整多次照片。而在 OpenCV 的函式庫中則採用 2 倍的倍率進行處理。
那普遍來說設為兩個大類,分別是負責縮放的高斯金字塔,還有用來比對細節流失的拉普拉斯金字塔。我們稍後會來看下兩者之間的差異,並探討實際成效。
實際演練
在實際操作之前,我們需要記得上下採樣是不可逆的動作,這是由於像素的增減一定會造成程度上的失真。然而在後續拉普拉斯圖層比對的製作中,我們需要同等大小的圖片相減,因此再次用上調整大小的高斯金字塔,以此計算細節差異的損失數量。
稍後高斯與拉普拉斯都會詳細介紹各位,我們這邊先提供程式碼的方法給各位,方便一起進行實作驗證。那由於圖檔本身像素極高,以及考量範例的視覺效果,因此筆者將以下採樣為主題進行示範。
def getPyramid(image, level, mode="gaussian", fn="down"):
"""Displays Gaussian or Laplacian pyramid levels."""
pyramidGaussian = [image]
# 控制放大縮小
if fn=="down":
func=cv2.pyrDown
revfunc=cv2.pyrUp
elif fn == "up":
func=cv2.pyrUp
revfunc=cv2.pyrDown
# 建立高斯金字塔
for i in range(level):
pyramidGaussian.append(func(pyramidGaussian[-1]))
# 檢查是否建立拉普拉斯金字塔
#
# 回傳高斯
if mode == "gaussian":
pyramid = pyramidGaussian
# 回傳拉普拉斯
elif mode == "laplacian":
pyramidLaplacian = []
# 計算前後差異
for i in range(level):
laplacian = cv2.subtract(pyramidGaussian[i], revfunc(
pyramidGaussian[i + 1], dstsize=(pyramidGaussian[i].shape[1], pyramidGaussian[i].shape[0])))
pyramidLaplacian.append(laplacian)
pyramid = pyramidLaplacian
return pyramid
高斯金字塔
其主要通過對原始影像進行高斯模糊而成,在後續進行的採樣操作時,可以透過先前製造的暈染減少鋸齒效應的影響。主要用途則是在圖片的縮放,以及圖片間的縫合。
pyramid_g = getPyramid(img, 8, "gaussian", fn="down")
for i,layer in enumerate(pyramid_g):
cv2.imwrite(f"g{i}.png", cv2.cvtColor(layer,cv2.COLOR_RGB2BGR))
我們回來看一下關於前面的提問,這邊是縮小到 1/64 的結果,先用經過不同處理的圖片做個比較。此時注意右上的圖片,左臉側髮的髮絲因為銳化效果而變得刺刺的,但高斯金字塔還保留若隱若現形態。雖然現今的圖像軟體通常會自動修正這塊,其實也不用太計較這塊,但當然我們還是以 OpenCV 的處理過程做主題。




另外普遍來說我們鮮少使用上採樣,首先是很容易製造過大的圖檔,比如範例原圖單單第一層就有的 (5488, 4715) 大圖,從原始的 5 MB 跳到 28 MB 的大小。除非你有 8K 大螢幕或特別的處理需求,否則這個特大尺寸幾乎毫無意義。再來就是因為不可回逆性,縮到很小的相片重新放大後,視覺效果通常都不會非常漂亮。
所以向上採樣通常只有非不得已的 Upscale 調整才會用上,但同樣跟 cv2.resize 的比較結果相同,就是因為模糊所帶來的色彩變化自然柔和些許,也較少看出明顯的雜訊,但是比起原圖貌似就有些不同,因此誕生下方另外的概念。
拉普拉斯金字塔
從原理上來說它是無法獨立存在的方法,必須透過高斯金字塔間的變化,同步大小來進行比對來找出失真的部分,而這些就是每次處理付出的代價。那由於是兩層高斯模糊採樣後的比對,因此就層數來說會少最後一層 (沒得比對) 。
當然這些遺失的細節通常靠近外圍,此時我們會得到類似於線稿的結果,因此這種技術通常被用來輔助檔案壓縮與邊線強化,或者幫助上採樣進行影像的重建。
pyramid_l = getPyramid(img, 8, "laplacian", fn="down")
for i,layer in enumerate(pyramid_l):
cv2.imwrite(f"l{i}.png", cv2.cvtColor(layer,cv2.COLOR_RGB2BGR))
那由於圖檔的畫素過高,在前幾層我們很難發現所標記的差異。這邊我分別取出最明顯的 4 跟 5 層出來調整到等大進行觀察,各位仔細觀察能看到 RGB 之下散落的三種顏色,所以說當像素越少通常也代表損失最嚴重。


後話
我們必須認識到上下採樣的過程是不可逆的,像素的增減必然會帶來一定程度的失真。因此我們需要在保留細節與控制失真之間取得平衡。但金字塔比對的應用遠不止於此,在影像辨識、影像縫合等領域,金字塔的概念都扮演著特別的步驟,同時適合倍率圖片組的製作。

順道一提,只有當不同大小的圖片堆疊起來的圖片組才叫做金字塔,先前的範例只是為求觀察,方便筆者都是挑出單純幾張,還幫忙先調整過圖片大小。而透過下列的圖,我們則能看出明顯的階層變化,但很多時候因為比例關係,圖片是小到觀察不到。

Gaussian and Laplacian Pyramid Construction by University of Toronto
參考
[1] Image Pyramids — OpenCV
https://docs.opencv.org/4.x/dc/dff/tutorial_py_pyramids.html
[2] OpenCV 影像金字塔 — ITHome (by VincentYeh)
https://ithelp.ithome.com.tw/articles/10327108
額外讀物
[1] Gaussian and Laplacian Pyramid Construction — University of Toronto
https://www.cs.toronto.edu/~mangas/teaching/320/slides/CSC320L10.pdf