將粗糙不平的衣物表面進行平滑處理,或是透過模糊效果來模擬窗外朦朧的景物。這種視覺處理方式已成為生活中隨處可見的技巧,同樣時常應用於資料分析的前處理過程。其中每種運算方式都有其獨特的特性和適用場景,需要根據實際需求來選擇最適合的處理方式,就讓我們用 OpenCV 來一探究竟。
素材取用
圖片:<雲端下載> | 清明 by DaylightAllure
概念釐清
模糊化的本質概念是減少影像中的高頻成分,這樣的處理過程能夠達到平滑影像、抹除雜訊的效果。在數位影像處理中,高頻成分通常代表了影像中的細節和邊緣資訊。然而因為過高的數值影響反而會導致柔邊,使得影像的細節和輪廓變得不夠銳利。
因此普遍觀念上,平滑化之技術則包括於模糊化之中,而兩者事實上是相近的概念。但是人們主觀的設計一套分類方式,其中平滑化的特點是處理後仍舊保持邊線的清晰度,而模糊化則會使整體影像變得較為混和模糊。
實際演練
針對 OpenCV 套件討論,其已經內建四種功能達到模糊化的效果。單就分類來說,我們可以依照運算是否具有「線性」特質。但保有分類之外,我也依據這些功能的用途性質以及其原理難易來進行依序討論,讓各位更容易循序漸進地理解這些技術。
線性處理
線性處理的優點主要在於依賴簡便的社學運算,因此處理速度相對快速,在需要快速處理大量影像資料的場景中特別實用。但也也存在一些限制,在需要保留細節的場景中,我們需要謹慎使用線性模糊,或考慮稍後提到的非線性運算。
線性模糊
平均模糊的概念在處理方式上較為暴力,其運作原理是透過對捲積核內所有像素值進行簡單的算術平均計算,並將得出的平均值賦予給中心像素點。這種方法雖然在實作上相當直觀且容易理解,但由於其處理過程過於簡化,降噪的功力並不突出,還往往導致整體影像在細節上的大量損失。
然而基於這樣的特性,此種模糊方式在製作柔和背景效果時特別實用,藉由降低影像中的視覺細節,能讓觀者更容易聚焦在主體上,或者呈現一種朦朧美。
# 平均模糊
avgBlur = cv2.blur(img, (ksize, ksize))
# :: avgBlur = cv2.blur(img, (31, 31))
高斯模糊
高斯模糊使用高斯函數來計算權重,類似於鐘形曲線,使得中心像素的權重最高,周圍像素的權重逐漸減小。透過這個方式可以減小像素點被重複運算導致的變形,獲得更自然的變化。
其捲積的寬高必須為奇數,而在後方還有 x 與 y 軸的標準差,設置越大會導致更模糊,而當 sigmaY=0 則會自動同步 sigmaX 之數值。
# 高斯模糊
gaussianBlur = cv2.GaussianBlur(img, (ksize, ksize), sigmaX, sigmaY=0)
# :: gaussianBlur = cv2.GaussianBlur(img, (31, 31), 0)
非線性運算
由於複雜的技術混和,非線性運算通常需要更多的時間,尤其用來判斷遮罩的權重與形狀。但可以根據影像內容進行自適應調整,提供更靈活的處理方式,針對需要精細處理的應用中更為常見。
雙邊濾波
雙邊濾波是一種能夠在平滑影像的同時,保留影像邊緣的濾波技術。其透過高斯捲積核來計算空間域,並額外施加值域比對像素之間的色彩差異,以此減少處理色彩變化較大的外圍區域,等同預期邊線不會受到過多處理。
這邊的參數比較複雜一些,d 所代表的是像素臨域的直徑 (影響的色彩範圍),接者 sigmaColour 則是顏色差值的範圍,一般偏大,代表越多的顏色能參與運算,如果 d=0 則會從這邊推算出空間範圍。最後的 sigmaSpace 則是座標空間的距離差異,一般偏小,避免過多的資料處理量,但注意當 d>0 時這個參數就不會被使用。
# 雙邊濾波
bilBlur = cv2.bilateralFilter(img, d, sigmaColour, sigmaSpace)
# :: bilBlur = cv2.bilateralFilter(img, 0, 50, 10)

中值模糊
雖然沒有使用任何捲積,透過替代周圍像素的中值,可以想像成同化周遭的顏色。這樣做可以有效地消除孤立的雜訊點,可以很好的去除 椒鹽雜訊 與 微小斑點。而由於沒有進行捲積的加權平均,因此一定程度上也能保留邊線。
而為了實作這個案例,我們首先需要先設計一個方法來添加雜訊。下方範例我們則使用 numpy 進行隨機的噪點添加,並追求泛用而分別針對單頻道與多頻道進行處理。
# 添加椒鹽雜訊
def addSaltPepperNoise(image, prob):
output = np.copy(image)
h, w = image.shape[:2]
# 白色數量
num_salt = int(np.ceil(prob * h * w * 0.5))
# 像素改造
for i in range(num_salt):
x = random.randint(0, w - 1)
y = random.randint(0, h - 1)
# 單通道
if len(image.shape) == 2:
output[y, x] = 255
# 多通道
else:
output[y, x] = [255] * image.shape[2]
# 黑色數量
num_pepper = int(np.ceil(prob * h * w * 0.5))
# 像素改造
for i in range(num_pepper):
x = random.randint(0, w - 1)
y = random.randint(0, h - 1)
# 單通道
if len(image.shape) == 2:
output[y, x] = 0
# 多通道
else:
output[y, x] = [0] * image.shape[2]
return output
這邊為了讓效果明顯一些,我刻意設 prod 為 0.2 多灑一些白鹽與胡椒,讓圖片看起來更好吃 (?) 一些。此時跟先前的平均模糊相比一下,整體的潔淨感和清晰度立即變得更加突出和明顯。
# 中值模糊
mdnBlur = cv2.medianBlur(img, ksize)
# :: mdnBlur = cv2.medianBlur(img, 31)

後話
從社交媒體的照片修圖,到醫學影像的雜訊處理,影像的平滑化技術實際上已深深地融入我們的日常生活之中。無論是美化自拍照片,還是提升醫療診斷的準確度,這些技術都扮演著重要的角色。
而透過實際的實作與比較,並從基本原理出發進行深入思考,我們不僅能更好地理解各種平滑化方法的特點,還能更準確地運用這些技術來達到我們想要的效果。
參考
[1] Smoothing Images — OpenCV
https://docs.opencv.org/4.x/d4/d13/tutorial_py_filtering.html
[2] Grayscale Salt-and-Pepper Noise — Geeks-for-Geeks
https://www.geeksforgeeks.org/add-a-salt-and-pepper-noise-to-an-image-with-python/
額外讀物
[1] 台大資訊工程學系 — Bilateral Filters
https://www.csie.ntu.edu.tw/~cyy/courses/vfx/10spring/lectures/handouts/lec14_bilateral_4up.pdf