Python 大白從零開始 OpenCV 學習課-7. 空間域影像濾波
本系列面向小白,從零開始實戰解說 OpenCV 專案實戰,
影像濾波是在盡可能保留影像細節特征的條件下對目標影像的噪聲進行抑制,是常用的影像預處理操作,
空間域影像增強的方法很多,各有不同的特點和作用,本節介紹空間域濾波的平滑(低通濾波)和銳化(高通濾波)方法,常用的平滑演算法有高斯平滑、均值平滑、中值平滑、雙邊濾波、導向濾波等;常用的銳化演算法有鈍化掩蔽、拉普拉斯算子、Sobel梯度算子和 Scharr 梯度算子,
本文提供上述各種演算法的完整例程和運行結果,并通過一個應用案例示范綜合使用多種影像增強方法,
文章目錄
- Python 大白從零開始 OpenCV 學習課-7. 空間域影像濾波
- 1. 影像的相關與卷積運算
- 1.1 相關與卷積運算
- 1.2 影像的邊界擴充
- 例程 1.65:影像的邊界擴充
- 1.3 Scipy 實作二維離散卷積(sp.convolve2d)
- 例程 1.66:scipy.signal 實作影像的二維卷積
- 1.4 cv2 實作二維離散卷積(flip 和 filter2D)
- 例程 1.67:cv2 實作影像的二維卷積
- 1.5 可分離卷積核
- 例程 1.68:可分離核的卷積操作
- 例程 1.69:可分離核的影像卷積
- 2. 空間域平滑濾波(低通濾波)
- 2.1 低通盒式濾波器
- 例程 1.70:影像的低通濾波—盒式濾波器
- 2.2 低通高斯濾波器
- 例程 1.71:影像的低通濾波—高斯濾波器
- 2.3 非線性濾波—中值濾波(Median filter)
- 例程 1.73:影像的非線性濾波—中值濾波器
- 2.4 非線性濾波—雙邊濾波(Bilateral filter)
- 例程 1.74:影像的非線性濾波—雙邊濾波器
- 2.5 非線性濾波—聯合雙邊濾波(Joint bilateral filter)
- 2.6 非線性濾波—導向濾波(Guided filter)
- 3. 空間域銳化濾波(高通濾波)
- 3.1 影像的梯度算子
- 3.2 鈍化掩蔽
- 例程 1.77:影像銳化: 鈍化掩蔽
- 3.3 拉普拉斯卷積核(Laplacian)
- 例程 1.78:影像銳化:Laplacian 算子
- 3.4 Sobel 梯度算子
- 例程 1.79:影像銳化:Sobel 算子
- 3.5 Scharr 算子
- 例程 1.80:影像銳化:Scharr 算子
- 4. 低通、高通、帶阻、帶通
- 5. 空間域影像增強技術的綜合應用
1. 影像的相關與卷積運算
濾波通常是指對影像中特定頻率的分量進行過濾或抑制,影像濾波是在盡可能保留影像細節特征的條件下對目標影像的噪聲進行抑制,是常用的影像預處理操作,
資料采集都會帶有一定的噪聲,影像的噪聲可以理解為灰度值的隨機變化,對影像在空間域存在的隨機噪聲,可以通過平滑技術進行抑制或去除,稱為空間域影像濾波,
頻率域濾波是通過傅里葉變換方法實作的,而空間域濾波則是通過相關與卷積運算實作,常用的平滑處理演算法有基于二維離散卷積的高斯平滑、均值平滑,基于統計方法的中值平滑,保留邊緣資訊的雙邊濾波、導向濾波等,
空間濾波器是由鄰域和定義的操作構成的,濾波器規定了濾波時采用的鄰域形狀及該區域內像素值的處理方法,濾波器也被稱為 “核”、“模板”、“視窗”、“掩模”、“算子”,一般在信號處理中稱為 “濾波器”,在數學領域稱為 “核”,線性濾波器就是指基于線性核的濾波,也就是卷積運算,
1.1 相關與卷積運算
濾波器核是指像素周圍某一大小的矩形鄰域,也稱為模板、滑動視窗,
**相關運算(Correlation operation)**是利用模板對影像進行鄰域操作:將濾波器模板的中心移動到待處理的像素點,對模板區域的各點加權相乘后求和,
大小為 m*n 的核(模板) w 與影像 f(x,y) 的相關運算 ( w ? f ) ( x , y ) (w \diamond f)(x,y) (w?f)(x,y) 的數學描述為:
(
w
?
f
)
(
x
,
y
)
=
∑
s
=
?
a
a
∑
t
=
?
b
b
w
(
s
,
t
)
?
f
(
x
+
s
,
y
+
t
)
(w \diamond f)(x,y) = \sum_{s=-a}^a \sum_{t=-b}^b w(s,t) * f(x+s,y+t)
(w?f)(x,y)=s=?a∑a?t=?b∑b?w(s,t)?f(x+s,y+t)
相關運算的計算步驟如下:
(1)將模板在影像中逐點移動,模板中心移動到被處理的像素點上;
(2)將模板區域中的各點的系數(權值)與影像的像素值相乘,對乘積求和,即加權求和;
(3)將加權求和結果賦值給模板中心的像素,
注意, “相關運算” 中的 “相關” 不是 “有關的”,而是一種特定的數學運算方式,
**卷積運算(Convolution operation)**也是利用模板對影像進行鄰域操作,只是把相關運算的模板旋轉了 180度,
大小為 m*n 的核(模板) w 與影像 f(x,y) 的卷積運算
(
w
★
f
)
(
x
,
y
)
(w \bigstar f)(x,y)
(w★f)(x,y) 的數學描述為:
(
w
★
f
)
(
x
,
y
)
=
∑
s
=
?
a
a
∑
t
=
?
b
b
w
(
s
,
t
)
?
f
(
x
?
s
,
y
?
t
)
(w \bigstar f)(x,y) = \sum_{s=-a}^a \sum_{t=-b}^b w(s,t) * f(x-s,y-t)
(w★f)(x,y)=s=?a∑a?t=?b∑b?w(s,t)?f(x?s,y?t)
卷積運算子合交換律、結合律和分配律,即:
f
★
g
=
g
★
f
f
★
(
g
★
h
)
=
(
f
★
g
)
★
h
f
★
(
g
+
h
)
=
(
f
★
g
)
+
(
f
★
h
)
f \bigstar g = g \bigstar f \\ f \bigstar (g \bigstar h) = (f \bigstar g) \bigstar h \\ f \bigstar (g + h) = (f \bigstar g) + (f \bigstar h)
f★g=g★ff★(g★h)=(f★g)★hf★(g+h)=(f★g)+(f★h)

(本圖片來自 “小黑鴨” 《OpenCV學習+常用函式記錄②:影像卷積與濾波》,特此致謝,)
1.2 影像的邊界擴充
相關和卷積運算都要對影像的邊界點要進行特殊處理,就需要將邊界進行適當擴充,
函式說明:
OpenCV 中提供了函式 cv.copyMakeBorder 進行邊界擴充方式,也可以為影像設定邊框,
cv.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]]) → dst
引數說明:
- src:進行邊界擴充的影像
- top, bottom, left, right:上側、下側、左側、右側邊界擴充的的寬度(像素數)
- value:當 borderType 為 BORDER_CONSTANT 時,以常量(value)填充擴充的邊界,默認值為 (0,0,0)
- borderType 邊界擴充的型別
- cv2.BORDER_REPLICATE:復制,復制最邊緣像素進行填充(aa | abcdefg | gg),中值濾波采用復制法
- cv2.BORDER_REFLECT:對稱法,以影像邊緣為軸進行對稱填充(cba| abcdefg | gfe)
- cv2.BORDER_REFLECTT_101:倒映法,以影像最邊緣像素為軸進行對稱填充(dcb| abcdefg | fed),函式 filter2D, blur, GaussianBlur, bilateralFilter 中默認的邊界處理方法
- cv2.BORDER_WRAP:用另一側元素來填充這一側的擴充邊界(efg| abcdefg | ab)
- cv2.BORDER_CONSTANT:以常數(value)作為像素值進行擴充(vv | abcdefg | vv)
例程 1.65:影像的邊界擴充
# 1.65 影像的邊界擴充
img = cv2.imread("../images/imgRose1.jpg") # 讀取彩色影像(BGR)
top = bottom = left = right = 50
imgReplicate = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REPLICATE)
imgReflect = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT)
imgReflect101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT_101)
imgWrap = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_WRAP)
imgConstant = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(200,200,200))
plt.figure(figsize=(9, 6))
plt.subplot(231), plt.axis([-50,562,-50,562]), plt.title('ORIGINAL'), plt.axis('off')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(232), plt.axis('off'), plt.title('REPLICATE')
plt.imshow(cv2.cvtColor(imgReplicate, cv2.COLOR_BGR2RGB))
plt.subplot(233), plt.axis('off'), plt.title('REFLECT')
plt.imshow(cv2.cvtColor(imgReflect, cv2.COLOR_BGR2RGB))
plt.subplot(234), plt.axis('off'), plt.title('REFLECT_101')
plt.imshow(cv2.cvtColor(imgReflect101, cv2.COLOR_BGR2RGB))
plt.subplot(235), plt.axis('off'), plt.title('WRAP')
plt.imshow(cv2.cvtColor(imgWrap, cv2.COLOR_BGR2RGB))
plt.subplot(236), plt.axis('off'), plt.title('CONSTANT')
plt.imshow(cv2.cvtColor(imgConstant, cv2.COLOR_BGR2RGB))
plt.show()

1.3 Scipy 實作二維離散卷積(sp.convolve2d)
Scipy 中提供了函式 sp.convolve2d 實作二維離散卷積的計算,
對于二維離散卷積的運算,Python的科學計算包Scipy提供了函式實作該功能:
convolve2d(in1, in2, mode="full", boundary="fill", fillvalue=0) → dst
引數說明:
- in1:進行卷積運算的影像,二維陣列——只能處理單通道影像,如灰度影像
- in2:卷積操作的模板(卷積核),二維陣列
- mode:卷積型別,‘full’、‘valid’、‘same’,默認值為 ‘full’
- boundary:邊界擴充方式,‘fill’、‘wrap’、‘symm’,默認值為 ‘fill’
- ‘fill’:以常數(fillvalue)作為像素值進行擴充(vv | abcdefg | vv)
- ‘symm’:對稱法,以影像邊緣為軸進行對稱填充(cba| abcdefg | gfe)
- ‘wrap’:用另一側元素來填充這一側的擴充邊界(efg| abcdefg | ab)
- fillvalue:當 boundary=‘fill’ 時,以以常數(fillvalue)作為像素值進行擴充
例程 1.66:scipy.signal 實作影像的二維卷積
# 1.66 scipy.signal 實作影像的二維卷積
img = cv2.imread("../images/imgLena.tif", flags=0) # # flags=0 讀取為灰度影像
kernel = np.array([[-3-3j,0-10j,+3-3j], [-10+0j,0+0j,+10+0j], [-3+3j,0+10j,+3+3j]]) # Gx + j*Gy
# scipy.signal 實作卷積運算
from scipy import signal
convFull = signal.convolve2d(img, kernel, boundary='symm', mode='full') # full 卷積
convValid = signal.convolve2d(img, kernel, boundary='symm', mode='valid') # valid 卷積
convSame = signal.convolve2d(img, kernel, boundary='symm', mode='same') # same 卷積
print(img.shape, convFull.shape, convValid.shape, convSame.shape) # 輸出影像大小有區別
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title('Original'), plt.axis('off')
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.axis('off'), plt.title('Convolve (full)')
plt.imshow(np.absolute(convFull), cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.axis('off'), plt.title('Convolve (same)')
plt.imshow(np.absolute(convSame), cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()
注意事項:
-
signal.convolve2d 只能對二維矩陣進行卷積操作,因此只能處理灰度影像,如果需要處理彩色影像,可以分別對每一通道進行卷積操作來實作,
-
signal.convolve2d 選擇不同卷積型別 ‘full’、‘valid’、‘same’ 時,影像卷積效果的差別并不明顯,但影像尺寸大小有區別,這與不同型別時采用不同的邊界處理方式有關,
img.shape: (512, 512)
convFull.shape: (514, 514)
convValid.shape: (510, 510)
convSame.shape: (512, 512)

1.4 cv2 實作二維離散卷積(flip 和 filter2D)
使用 OpenCV 中的 cv.flip 和 cv.filter2D 函式也可以實作影像的卷積運算,
函式 cv.flip 實作圍繞軸線翻轉二維陣列,將影像沿軸線進行軸對稱變換,可以將影像沿水平方向、垂直方向、或水平/垂直方向同時進行翻轉,例程 1.38 介紹了影像的翻轉(鏡像)的使用方法,
函式 cv.filter2D 對影像與核(模板)進行相關計算,與函式 cv.flip 共同實作卷積運算,
函式說明:
cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) → dst
引數說明:
- src:卷積處理的輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:卷積處理的輸出影像,大小和型別與 src 相同
- ddepth:目標影像每個通道的深度(資料型別),ddepth=-1 表示與輸入影像的資料型別相同
- kernel:卷積操作的模板(卷積核),二維實型陣列
- anchor:卷積核的錨點位置,默認值 (-1, -1) 表示以卷積核的中心為錨點
- delta:輸出影像的偏移量,可選項,默認值為 0
- borderType:邊界擴充的型別
cv.filter2D 可以處理灰度影像,也可以直接處理彩色影像,不需要對每一色彩通道分別操作,
例程 1.67:cv2 實作影像的二維卷積
# 1.67:cv2 實作影像的二維卷積
img = cv2.imread("../images/imgGaia.tif", flags=0) # # flags=0 讀取為灰度影像
kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) # Gx + j*Gy
kFlip = cv2.flip(kernel, -1) # 將卷積核旋轉180度
# 使用函式filter2D算出same卷積
imgConv1 = cv2.filter2D(img, -1, kFlip,
anchor=(0,0), borderType=cv2.BORDER_CONSTANT)
imgConv2 = cv2.filter2D(img, -1, kFlip,
anchor=(0,0), borderType=cv2.BORDER_REFLECT)
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title('Original'), plt.axis('off')
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.axis('off'), plt.title('cv2.filter2D (BORDER_CONSTANT)')
plt.imshow(np.absolute(imgConv1), cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.axis('off'), plt.title('cv2.filter2D (BORDER_REFLECT)')
plt.imshow(np.absolute(imgConv2), cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

1.5 可分離卷積核
如果卷積核 w 可以被分解為兩個或多個較小尺寸卷積核 w1、w2…,即: w = w 1 ★ w 2 w = w1 \bigstar w2 w=w1★w2,則成為可分離卷積核,
秩為 1 的矩陣可以分解為一個列向量與一個行向量的乘積,因此秩為 1 的卷積核是可分離卷積核,
可分離卷積核 w 與影像 f 的卷積(same 卷積),等于先用 f 與 w1 卷積,再用 w2 對結果進行卷積:
w
★
f
=
(
w
1
★
w
2
)
★
f
=
w
2
★
(
w
1
★
f
)
=
(
w
1
★
f
)
★
w
2
w \bigstar f = (w_1 \bigstar w_2)\bigstar f = w_2 \bigstar (w_1 \bigstar f) = (w_1 \bigstar f)\bigstar w_2
w★f=(w1?★w2?)★f=w2?★(w1?★f)=(w1?★f)★w2?
隨著影像尺寸與卷積核尺寸的增大,用分離的卷積核依次對影像進行卷積操作,可以有效地提高運算速度,因此,在二維影像處理中,經常將一個可分離卷積核分解為一維水平核 kernalX 和一維垂直核 kernalY 的乘積,
函式 sepFilter2D 實作可分離核(模板)對影像進行線性濾波,
函式說明:
cv.sepFilter2D( src, ddepth, kernelX, kernelY[, dst[, anchor[, delta[, borderType]]]]) → dst # OpenCV4
該函式先用一維水平核 kernalX 對影像的行進行濾波,再用一維垂直核 kernalY 對影像的列進行濾波,
引數說明:
- src:卷積處理的輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:卷積處理的輸出影像,大小和型別與 src 相同
- ddepth:目標影像每個通道的深度(資料型別),ddepth=-1 表示與輸入影像的資料型別相同
- kernelX:水平卷積核向量,一維實型陣列
- kernelY:垂直卷積核向量,一維實型陣列
- anchor:卷積核的錨點位置,默認值 (-1, -1) 表示以卷積核的中心為錨點
- delta:輸出影像的偏移量,可選項,默認值為 0
- borderType:邊界擴充的型別
例程 1.68:可分離核的卷積操作
# 1.68:可分離核的卷積操作
imgList = list(range(0, 36))
imgTest = np.array(imgList).reshape(6, 6)
# 可分離卷積核: kernXY = kernX * kernY
kernX = np.array([[-1, 3, -1]], np.float32) # (1,3)
kernY = np.transpose(kernX) # (3,1)
kernXY = kernX * kernY
print(kernX.shape, kernY.shape, kernXY.shape)
from scipy import signal
# 二維卷積核直接對影像進行卷積操作
imgConv_XY = signal.convolve2d(imgTest, kernXY, mode='same', boundary='fill')
# 可分離卷積核分解為一維水平核 kernalX 和一維垂直核 kernalY 分別進行卷積操作
imgConv_X = signal.convolve2d(imgTest, kernX, mode='same', boundary='fill')
imgConv_X_Y = signal.convolve2d(imgConv_X, kernY, mode='same', boundary='fill')
print("\n比較 imgConv_XY 與 imgConv_X_Y 是否相等:\t", (imgConv_XY == imgConv_X_Y).all())
print("\nimgConv_kernXY:\n", imgConv_XY)
print("\nimgConv_kernX_kernY:\n", imgConv_X_Y)
運行結果如下:
(1, 3) (3, 1) (3, 3)
比較 imgConv_XY 與 imgConv_X_Y 是否相等: True
imgConv_kernXY:
[[-14. -4. -2. 0. 2. 10.]
[ 11. 7. 8. 9. 10. 23.]
[ 23. 13. 14. 15. 16. 35.]
[ 35. 19. 20. 21. 22. 47.]
[ 47. 25. 26. 27. 28. 59.]
[130. 68. 70. 72. 74. 154.]]
imgConv_kernX_kernY:
[[-14. -4. -2. 0. 2. 10.]
[ 11. 7. 8. 9. 10. 23.]
[ 23. 13. 14. 15. 16. 35.]
[ 35. 19. 20. 21. 22. 47.]
[ 47. 25. 26. 27. 28. 59.]
[130. 68. 70. 72. 74. 154.]]
例程 1.69:可分離核的影像卷積
# 1.69:可分離核的影像卷積
img = cv2.imread("../images/imgGaia.tif", flags=1)
# 可分離卷積核: kernXY = kernX * kernY
kernX = np.array([[-1, 3, -1]], np.float32) # (1,3)
kernY = np.transpose(kernX) # (3,1)
kernXY = kernX * kernY
# (1) 可分離卷積核分解為一維水平核 kernalX 和一維垂直核 kernalY 分步進行卷積操作
imgConvY = cv2.filter2D(img, -1, kernY,
anchor=(0,0), borderType=cv2.BORDER_CONSTANT)
imgConv_X_Y = cv2.filter2D(imgConvY, -1, kernX,
anchor=(0,0), borderType=cv2.BORDER_CONSTANT)
# (2) 二維卷積核 kernXY 直接對影像進行卷積操作
imgConv_XY = cv2.filter2D(img, -1, kernXY,
anchor=(0, 0), borderType=cv2.BORDER_CONSTANT)
# (3) 一維水平核 kernalX 和一維垂直核 kernalY 進行可分離卷積核的卷積操作
imgConvSep_XY = cv2.sepFilter2D(img, -1, kernX, kernY,
anchor=(0,0), borderType=cv2.BORDER_CONSTANT)
print("\n比較 imgConv_XY 與 imgConv_X_Y 是否相等:\t", (imgConv_XY == imgConv_X_Y).all())
print("\n比較 imgConvSep_XY 與 imgConv_XY 是否相等:\t", (imgConvSep_XY == imgConv_XY).all())
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title('cv2.filter2D(kernXY)')
plt.imshow(cv2.cvtColor(imgConv_XY, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.axis('off'), plt.title('cv2.filter2D (kernX->kernY)')
plt.imshow(cv2.cvtColor(imgConv_X_Y, cv2.COLOR_BGR2RGB))
plt.subplot(133), plt.axis('off'), plt.title('cv2.sepFilter2D(kernX,kernY)')
plt.imshow(cv2.cvtColor(imgConvSep_XY, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()
運行結果如下:
比較 imgConv_XY 與 imgConv_X_Y 是否相等: False
比較 imgConvSep_XY 與 imgConv_XY 是否相等: True

2. 空間域平滑濾波(低通濾波)
影像濾波是在盡可能保留影像細節特征的條件下對目標影像的噪聲進行抑制,是常用的影像預處理操作,
平滑濾波也稱為低通濾波,可以抑制影像中的灰度突變,使影像變得模糊,是低頻增強的空間域濾波技術,平滑濾波常用于:
- 模糊影像和影像降噪,
- 在影像重取樣前平滑影像以減少混淆
- 減少影像中無關的細節
- 平滑因灰度級不足所導致的影像的偽輪廓
線性空間濾波是指影像與濾波器核(卷積核)進行卷積計算,平滑卷積核與影像的卷積類似于積分運算,對影像的鄰域進行加權求和,可以實作空間域平滑濾波,
2.1 低通盒式濾波器
盒式核是最簡單的可分離低通濾波器核,盒式核的模板區域中各像素點的系數相同,因此也是可分離核,
盒式濾波器結構簡單,便于快速實作和實驗,但盒式濾波器對透鏡模糊特性的近似能力較差,而且往往會沿垂直方向模糊影像,
OpenCV 提供了 cv.blur 函式和 cv.boxFilter 函式實作盒式濾波器核低通濾波,
函式說明:
cv.blur(src, ksize[, dst[, anchor[, borderType]]]) → dst
函式 cv.blur 使用的濾波器核的運算式為:
K
=
1
k
s
i
z
e
.
w
i
d
t
h
?
k
s
i
z
e
.
h
e
i
g
h
t
[
1
1
?
1
1
1
?
1
?
?
?
?
1
1
?
1
]
K= \frac{1}{ksize.width * ksize.height} \begin{bmatrix} 1 & 1 &\cdots &1\\ 1 & 1 &\cdots &1\\ \vdots &\vdots &\vdots &\vdots\\ 1 & 1 &\cdots &1 \end{bmatrix}
K=ksize.width?ksize.height1???????11?1?11?1??????11?1???????
引數說明:
- src:低通濾波輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:低通濾波輸出影像,大小和型別與 src 相同
- ksize:模糊核的大小,元組 (width, height),寬度、高度應設為正奇數
- anchor:卷積核的錨點位置,默認值 (-1, -1),表示以卷積核的中心為錨點
- borderType:邊界擴充的型別
函式說明:
cv.boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) → dst
函式 cv.blur 使用的濾波器核的運算式為:
K
=
α
[
1
1
?
1
1
1
?
1
?
?
?
?
1
1
?
1
]
α
=
{
1
k
s
i
z
e
.
w
i
d
t
h
?
k
s
i
z
e
.
h
e
i
g
h
t
,
if normalize=True
1
,
if normalize=False
K= \alpha \begin{bmatrix} 1 & 1 &\cdots &1 \\1 & 1 &\cdots &1\\ \vdots &\vdots &\vdots &\vdots\\ 1 & 1 &\cdots &1 \end{bmatrix}\\ \alpha = \begin{cases} \frac{1}{ksize.width * ksize.height}&, \text{if normalize=True}\\ 1 &, \text{if normalize=False}\\ \end{cases}
K=α??????11?1?11?1??????11?1???????α={ksize.width?ksize.height1?1?,if normalize=True,if normalize=False?
顯然,當 normalize=True 時,函式 cv.blur() 等價于函式 cv.boxFilter(normalize=True) ,
引數說明:
- src:低通濾波輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:低通濾波輸出影像,大小和型別與 src 相同
- ddepth:輸出影像每個通道的深度(資料型別),ddepth=-1 表示與輸入影像的資料型別相同
- ksize:模糊核的大小,元組 (width, height),寬度、高度應設為正奇數
- anchor:卷積核的錨點位置,默認值 (-1, -1),表示以卷積核的中心為錨點
- normalize:歸一化選項,默認值 normalize=True 時進行歸一化,否則不作歸一化處理
- borderType:邊界擴充的型別
例程 1.70:影像的低通濾波—盒式濾波器
# 1.70:影像的低通濾波 (盒式濾波器核)
img = cv2.imread("../images/Fig0515a.tif", flags=0) # # flags=0 讀取為灰度影像
kSize = (5, 5)
kernel1 = np.ones(kSize, np.float32) / (kSize[0]*kSize[1]) # 生成歸一化盒式核
imgConv1 = cv2.filter2D(img, -1, kernel1) # cv2.filter2D 方法
imgConv2 = cv2.blur(img, kSize) # cv2.blur 方法
imgConv3 = cv2.boxFilter(img, -1, kSize) # cv2.boxFilter 方法 (默認normalize=True)
print("比較 cv2.filter2D 與 cv2.blur 方法結果相同嗎?\t", (imgConv1 == imgConv2).all())
print("比較 cv2.blur 與 cv2.boxFilter 方法結果相同嗎?\t", (imgConv2 == imgConv3).all())
kSize = (11, 11)
imgConv11 = cv2.blur(img, kSize) # cv2.blur 方法
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Original")
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.axis('off'), plt.title("cv2.blur (kSize=[5,5])")
plt.imshow(imgConv2, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.axis('off'), plt.title("cv2.blur (kSize=[11,11])")
plt.imshow(imgConv11, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()
運行結果如下:
比較 cv2.filter2D 與 cv2.blur 方法結果相同嗎? True
比較 cv2.blur 與 cv2.boxFilter 方法結果相同嗎? True

2.2 低通高斯濾波器
實際應用中要求卷積核是各向同性的(圓對稱),其回應與方向無關,高斯核是唯一可分離的圓對稱核,因此非常適合影像處理,對于去除影像中的隨機噪聲非常有效,
高斯核的數學運算式為:
w
(
s
,
t
)
=
G
(
s
,
t
)
=
1
2
π
σ
2
e
?
r
2
/
2
σ
2
w(s,t) = G(s,t) = \frac{1}{2\pi\sigma^2} e^{- {r^2}/{2\sigma ^2}}
w(s,t)=G(s,t)=2πσ21?e?r2/2σ2
兩個一維高斯函式 f 和 g 的乘積和卷積的均值與標準差如下:
m
f
×
g
=
m
f
σ
g
2
+
m
g
σ
f
2
σ
g
2
+
σ
f
2
,
σ
f
×
g
=
σ
f
2
?
σ
g
2
σ
g
2
+
σ
f
2
m
f
?
g
=
m
f
+
m
g
,
σ
f
?
g
2
=
σ
f
2
+
σ
g
2
\begin{aligned} m_{f \times g} &= \frac{m_f \sigma _g^2 + m_g \sigma _f^2}{\sigma _g^2 + \sigma _f^2} &,\sigma_{f \times g} &= \frac{\sigma _f^2 * \sigma _g^2}{\sigma _g^2 + \sigma _f^2}\\ m_{f \star g} &= m_f + m_g &,\sigma_{f \star g} ^2 &= \sigma _f^2 + \sigma _g^2 \end{aligned}
mf×g?mf?g??=σg2?+σf2?mf?σg2?+mg?σf2??=mf?+mg??,σf×g?,σf?g2??=σg2?+σf2?σf2??σg2??=σf2?+σg2??
OpenCV 提供了 cv.GaussianBlur 函式實作高斯核低通濾波器,cv.getGaussianKernel 函式可以計算一維高斯濾波器的系數,
函式說明:
cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) → dst
cv.getGaussianKernel(ksize, sigma[, ktype]) → retval
引數說明:
- src:低通濾波輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:低通濾波輸出影像,大小和型別與 src 相同
- ksize:模糊核的大小,元組 (width, height),寬度、高度應設為正奇數
- sigmaX:x 軸方向的高斯核標準差
- sigmaY:y 軸方向的高斯核標準差,可選項
- borderType:邊界擴充的型別
- sigma:高斯核的標準差
- retval:回傳值,高斯濾波器的系數
注意事項:
-
sigmaY 預設時 sigmaY=sigmaX;sigmaY=sigmaX=0 時,由 ksize 自動計算并設定 sigmaY, sigmaX 的值,
-
如 sigma 為負值,由 ksize 自動計算并設定 sigma 的值:
sigma = 0.3*((ksize-1)/2 - 1) + 0.8,
例程 1.71:影像的低通濾波—高斯濾波器
# 1.71:影像的低通濾波 (高斯濾波器核)
img = cv2.imread("../images/imgLena.tif", flags=1)
kSize = (5, 5)
imgGaussBlur1 = cv2.GaussianBlur(img, (5,5), sigmaX=10)
imgGaussBlur2 = cv2.GaussianBlur(img, (11,11), sigmaX=20)
# 計算高斯核
gaussX = cv2.getGaussianKernel(5, 0)
gaussXY = gaussX * gaussX.transpose(1, 0)
print("gaussX:\n", gaussX)
print("gaussXY:\n", gaussXY)
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Original")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.axis('off'), plt.title("ksize=5, sigma=10")
plt.imshow(cv2.cvtColor(imgGaussBlur1, cv2.COLOR_BGR2RGB))
plt.subplot(133), plt.axis('off'), plt.title("ksize=11, sigma=20")
plt.imshow(cv2.cvtColor(imgGaussBlur2, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()
運行結果如下:
gaussX:
[[0.0625]
[0.25 ]
[0.375 ]
[0.25 ]
[0.0625]]
gaussXY:
[[0.00390625 0.015625 0.0234375 0.015625 0.00390625]
[0.015625 0.0625 0.09375 0.0625 0.015625 ]
[0.0234375 0.09375 0.140625 0.09375 0.0234375 ]
[0.015625 0.0625 0.09375 0.0625 0.015625 ]
[0.00390625 0.015625 0.0234375 0.015625 0.00390625]]

2.3 非線性濾波—中值濾波(Median filter)
中值濾波是一種非線性濾波方法,是基于統計排序方法的濾波器 ,中值濾波法將像素點的鄰域內的所有像素點灰度值的中值作為該像素點的灰度值,
注意中值不是平均值,而是按大小排序的中間值,由于需要排序操作,中值濾波消耗的運算時間很長,
中值濾波處理后像素點的灰度值,可能保持不變,也可能改變為鄰域內其它像素點的灰度值,
中值濾波對于消除影像中的椒鹽噪聲非常有效,椒鹽噪聲也稱為脈沖噪聲,是隨機出現的白點或者黑點,通常是由于影像訊號受到干擾而產生,如脈沖干擾、影像掃描,
OpenCV 提供了 cv.medianBlur 函式實作中值濾波演算法,
函式說明:
cv.medianBlur(src, ksize[, dst]) → dst
引數說明:
- src:輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:輸出影像,大小和型別與 src 相同
- ksize:模糊核的線性大小,大于 1 的奇數
例程 1.73:影像的非線性濾波—中值濾波器
# 1.73:影像的非線性濾波 (中值濾波器)
img = cv2.imread("../images/Fig0335a.tif", flags=1)
imgMedianBlur1 = cv2.medianBlur(img, 3)
imgMedianBlur2 = cv2.medianBlur(img, 7)
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Original")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.axis('off'), plt.title("cv2.medianBlur(size=3)")
plt.imshow(cv2.cvtColor(imgMedianBlur1, cv2.COLOR_BGR2RGB))
plt.subplot(133), plt.axis('off'), plt.title("cv2.medianBlur(size=7)")
plt.imshow(cv2.cvtColor(imgMedianBlur2, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()

2.4 非線性濾波—雙邊濾波(Bilateral filter)
雙邊濾波是一種非線性濾波方法,是結合影像的空間鄰近度和像素值相似度的一種折衷處理,同時考慮空域資訊和灰度相似性,在去除噪聲的同時有效地保持邊緣清晰銳利,對于人像處理具有美顏功能,
邊緣的灰度變化較大,高斯濾波會明顯地模糊邊緣,對于高頻細節的保護較弱,雙邊濾波器在空間中也采用高斯濾波器,但增加了一個反映像素強度差異的高斯方差 σ d \sigma_d σd? ,在邊緣附近離的較遠的像素對邊緣上的像素值影響很小,從而保證了邊緣附近的像素值,實作邊緣保存(edge preserving),雙邊濾波器的權值是空間臨近度權值和像素值相似度權值的乘積,因此輸出像素依賴于當前被卷積像素的鄰域,又取決于被卷積像素的灰度值和鄰域像素的灰度值的差,
雙邊濾波器核的數學運算式為:
g
(
i
,
j
)
=
∑
f
(
k
,
l
)
w
∑
w
w
=
w
s
?
w
r
w
s
=
e
?
[
(
i
?
k
)
2
+
(
j
?
l
)
2
]
/
2
σ
s
2
w
r
=
e
?
∥
(
f
(
i
,
j
)
?
f
(
k
,
l
)
∥
2
/
2
σ
r
2
g(i,j) = \frac{\sum f(k,l) w}{\sum w}\\ w = ws * wr\\ ws = e^{- [{(i-k)^2+(j-l)^2}]/{2\sigma _s^2}}\\ wr = e^{- \lVert {(f(i,j)-f(k,l)} \rVert ^2/{2\sigma _r^2}}
g(i,j)=∑w∑f(k,l)w?w=ws?wrws=e?[(i?k)2+(j?l)2]/2σs2?wr=e?∥(f(i,j)?f(k,l)∥2/2σr2?
雙邊濾波器對于低頻資訊的濾波效果較好,但不能干凈地過濾彩色影像里的高頻噪聲,
OpenCV 提供了 cv. bilateralFilter 函式可以實作影像的雙邊濾波,
函式說明:
cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) → dst
引數說明:
- src:輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:輸出影像,大小和型別與 src 相同
- d:濾波核的像素鄰域直徑,如 d<=0 ,則由從 sigmaSpace 計算得到,
- sigmaColor:濾波器核在顏色空間的方差,反映產生顏色影響的顏色強度區間的大小
- sigmaSpace:濾波器核在坐標空間的方差,反映產生顏色影響的影響空間的大小
- borderType:邊界擴充的型別
例程 1.74:影像的非線性濾波—雙邊濾波器
# 1.74:影像的非線性濾波—雙邊濾波器
img = cv2.imread("../images/imgFabricNoise.png", flags=1)
imgBiFilter = cv2.bilateralFilter(img, d=0, sigmaColor=100, sigmaSpace=10)
imgMeanFilter = cv2.pyrMeanShiftFiltering(img, sp=15, sr=20)
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Original")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.axis('off'), plt.title("cv2.bilateralFilter")
plt.imshow(cv2.cvtColor(imgBiFilter, cv2.COLOR_BGR2RGB))
plt.subplot(133), plt.axis('off'), plt.title("cv2.pyrMeanShiftFiltering")
plt.imshow(cv2.cvtColor(imgMeanFilter, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()

2.5 非線性濾波—聯合雙邊濾波(Joint bilateral filter)
聯合雙邊濾波是在雙邊濾波基礎上對相似性權重模板進行優化,對于紋理影像的處理效果較好,
聯合雙邊濾波與雙邊濾波的區別在于:雙邊濾波是根據影像中不同位置的灰度值差異建立相似性權重模板,再與距離權重模板相乘計算卷積核;聯合濾波是先對影像進行高斯平滑,然后根據高斯平滑影像的灰度值差異建立相似性模板,再計算卷積核,即聯合雙邊濾波是根據原始影像的高斯平滑作為引導圖片來建立相似性模板,進一步地,如果使用其它引導圖片來建立相似性模板,還可以實作其它功能,
OpenCV 在 ximgproc 模塊提供了 cv.ximgproc.jointBilateralFilter 函式實作聯合雙邊濾波演算法,
函式說明:
cv.ximgproc.jointBilateralFilter(joint, src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) → dst
引數說明:
- src:輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- joint:聯合濾波的導向影像,大小和型別與 src 相同
- dst:輸出影像,大小和型別與 src 相同
- d:濾波核的像素鄰域直徑
- sigmaColor:濾波器核在顏色空間的方差,反映產生顏色影響的顏色強度區間的大小
- sigmaSpace:濾波器核在坐標空間的方差,反映產生顏色影響的影響空間的大小
- borderType:邊界擴充的型別
注意事項:
- OpenCV3 中的 ximgproc 模塊提供聯合雙邊濾波演算法,ximgproc 屬于擴展模塊,因此需要安裝擴展包(opencv-contrib-python)提供支持,
- 除了主模塊,還引入了contrib,其中的ximgproc模塊包括了聯合雙邊濾波的演算法,因此如果需要使用opencv的聯合雙邊濾波,需要安裝opencv-contrib-python包,
參考例程:
(略)

2.6 非線性濾波—導向濾波(Guided filter)
導向濾波又稱引導濾波,通過一張引導圖片反映邊緣、物體等資訊,對輸入影像進行濾波處理,使輸出影像的內容由輸入影像決定,但紋理與引導圖片相似,
導向濾波的原理是區域線性模型,在保持雙邊濾波的優勢(有效保持邊緣,非迭代計算)的同時計算速度很快,
而克服雙邊濾波速度慢的缺點,
導向濾波(導向濾波)不僅能實作雙邊濾波的邊緣平滑,而且在檢測到邊緣附近有很好的表現,可應用在影像增強、HDR壓縮、影像摳圖及影像去霧等場景,
在進行保持邊緣濾波時,可以采用原始影像自身或其預處理后的影像作為導向圖片,
OpenCV 在 ximgproc 模塊提供了 cv.ximgproc.guidedFilter 函式實作導向濾波演算法,
函式說明:
cv.ximgproc_guidedFilter.filter(guide, src, d[, eps[, dDepth]) → dst
引數說明:
- src:輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- guide:導向影像,大小和型別與 src 相同
- dst:輸出影像,大小和型別與 src 相同
- d:濾波核的像素鄰域直徑
- eps:規范化引數, eps 的平方類似于雙邊濾波中的 sigmaColor
- dDepth:輸出圖片的資料深度
參考例程:
(略)

3. 空間域銳化濾波(高通濾波)
3.1 影像的梯度算子
影像模糊通過平滑(加權平均)來實作,類似于積分運算,影像銳化則通過微分運算(有限差分)實作,使用一階微分或二階微分都可以得到影像灰度的變化值,
影像銳化的目的是增強影像的灰度跳變部分,使模糊的影像變得清晰,影像銳化也稱為高通濾波,通過和增強高頻,衰減和抑制低頻,影像銳化常用于電子印刷、醫學成像和工業檢測,
- 恒定灰度區域,一階導數為零,二階導數為零;
- 灰度臺階或斜坡起點區域,一階導數非零,,二階導數非零;
- 灰度斜坡區域,一階導數非零,二階導數為零,
一階導數、二階導數的有限差分公式為:
? f ? x = f ( x + 1 ) ? f ( x ) ? 2 f ? x 2 = f ( x + 1 ) ? 2 f ( x ) + f ( x ? 1 ) \begin{aligned} \dfrac{\partial f}{\partial x} &= f(x+1) - f(x) \\ \dfrac{\partial ^2 f}{\partial x ^2} &= f(x+1) - 2f(x) + f(x-1) \end{aligned} ?x?f??x2?2f??=f(x+1)?f(x)=f(x+1)?2f(x)+f(x?1)?
在影像處理中,一階導數用梯度(二維列向量)表示:
?
f
=
g
r
a
d
(
f
)
=
[
g
x
g
y
]
=
[
?
f
/
?
x
?
f
/
?
x
]
\nabla f = grad(f)=\begin{bmatrix}g_x\\g_y\end{bmatrix}=\begin{bmatrix}\partial f /\partial x \\\partial f /\partial x \end{bmatrix}
?f=grad(f)=[gx?gy??]=[?f/?x?f/?x?]
梯度指出了像素值的最大變化率的方向,經常用于工業檢測中產品缺陷檢測和自動檢測的預處理,
影像梯度提取方法簡單直接,能夠有效的描述影像的原始狀態,因此發展出多種影像梯度算子:Roberts、Prewitt、Sobel、Laplacian、Scharr,
3.2 鈍化掩蔽
簡單地,從原始影像中減去一幅平滑處理的鈍化影像,也可以實作影像銳化效果,稱為鈍化掩蔽,
令
f
~
(
x
,
y
)
\tilde{f}(x,y)
f~?(x,y) 表示平滑影像,則:
KaTeX parse error: No such environment: align at position 8: \begin{?a?l?i?g?n?}? g_{mask} (x,y)…
當 k>1 時,實作高提升濾波;當 k=1 時,實作鈍化掩蔽;k<1時,減弱鈍化掩蔽,
因此,鈍化掩蔽的實作程序是:
(1)對原始影像進行平滑處理,得到平滑影像;
(2)從原始影像中減去平滑影像,產生掩蔽模板;
(3)將原始影像與掩蔽模板加權相加,得到鈍化掩蔽,
原圖減去模糊圖的結果為模板,輸出影像等于原圖加上加權后的模板,當權重為1得到非銳化掩蔽,當權重大于1成為高提升濾波,
鈍化掩蔽沒有直接計算和使用梯度算子,但減法運算具有微分運算的特征,因此本質上是梯度演算法,可以實作銳化濾波的效果,
例程 1.77:影像銳化: 鈍化掩蔽
# 1.77:影像銳化: 鈍化掩蔽
img = cv2.imread("../images/Fig0338a.tif", flags=0)
# 對原始影像進行平滑,GaussianBlur(img, size, sigmaX)
imgGauss = cv2.GaussianBlur(img, (5,5), sigmaX=5)
imgGaussNorm = cv2.normalize(imgGauss,dst=None,alpha=0,beta=255,norm_type=cv2.NORM_MINMAX)
# 掩蔽模板:從原始影像中減去平滑影像
imgMask = img - imgGaussNorm
passivation1 = img + 0.6 * imgMask # k<1 減弱鈍化掩蔽
imgPas1 = cv2.normalize(passivation1, None, 0, 255, cv2.NORM_MINMAX)
passivation2 = img + imgMask # k=1 鈍化掩蔽
imgPas2 = cv2.normalize(passivation2, None, 0, 255, cv2.NORM_MINMAX)
passivation3 = img + 2 * imgMask # k>1 高提升濾波
imgPas3 = cv2.normalize(passivation3, None, 0, 255, cv2.NORM_MINMAX)
plt.figure(figsize=(10, 7))
titleList = ["1. Original", "2. GaussSmooth", "3. MaskTemplate",
"4. Passivation(k=0.5)", "5. Passivation(k=1.0)", "6. Passivation(k=2.0)"]
imageList = [img, imgGauss, imgMask, imgPas1, imgPas2, imgPas3]
for i in range(6):
plt.subplot(2,3,i+1), plt.title(titleList[i]), plt.axis('off')
plt.imshow(imageList[i], 'gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()
鈍化掩蔽的影像銳化效果如下圖所示,注意當 k>1 時,實作高提升濾波;當 k=1 時,實作鈍化掩蔽;而當 k<1時則會減弱鈍化掩蔽,

3.3 拉普拉斯卷積核(Laplacian)
各向同性卷積核的回應與方向無關,最簡單的各向同性導數算子(卷積核)是拉普拉斯算子(Laplace):
? 2 f = ? 2 f ? x 2 + ? 2 f ? y 2 ? 2 f ? x 2 = f ( x + 1 , y ) ? 2 f ( x , y ) + f ( x ? 1 , y ) ? 2 f ? y 2 = f ( x , y + 1 ) ? 2 f ( x , y ) + f ( x , y ? 1 ) ? 2 f ( x , y ) = f ( x + 1 , y ) + f ( x ? 1 , y ) + f ( x , y + 1 ) + f ( x , y ? 1 ) ? 4 f ( x , y ) \begin{aligned} \nabla ^2 f &= \dfrac{\partial ^2 f}{\partial x ^2} + \dfrac{\partial ^2 f}{\partial y ^2} \\ \dfrac{\partial ^2 f}{\partial x ^2} &= f(x+1,y) - 2f(x,y) + f(x-1,y) \\ \dfrac{\partial ^2 f}{\partial y ^2} &= f(x,y+1) - 2f(x,y) + f(x,y-1) \\ \nabla ^2 f(x,y) &= f(x+1,y) + f(x-1,y) + f(x,y+1) + f(x,y-1) - 4f(x,y) \end{aligned} ?2f?x2?2f??y2?2f??2f(x,y)?=?x2?2f?+?y2?2f?=f(x+1,y)?2f(x,y)+f(x?1,y)=f(x,y+1)?2f(x,y)+f(x,y?1)=f(x+1,y)+f(x?1,y)+f(x,y+1)+f(x,y?1)?4f(x,y)?
由此可以得到拉普拉斯核 K1,類似地,考慮對角項后可以得到拉普拉斯核 K2,
K 1 = [ 0 1 0 1 ? 4 1 0 1 0 ] , K 2 = [ 1 1 1 1 ? 8 1 1 1 1 ] , K 3 = [ 0 ? 1 0 ? 1 4 ? 1 0 ? 1 0 ] , K 4 = [ ? 1 ? 1 ? 1 ? 1 8 ? 1 ? 1 ? 1 ? 1 ] K1= \begin{bmatrix} 0 & 1 &0\\ 1 & -4 &1\\ 0 & 1 &0\\ \end{bmatrix}, \ K2= \begin{bmatrix} 1 & 1 &1\\ 1 & -8 &1\\ 1 & 1 &1\\ \end{bmatrix}, \ K3= \begin{bmatrix} 0 & -1 &0\\ -1 & 4 &-1\\ 0 & -1 &0\\ \end{bmatrix}, \ K4= \begin{bmatrix} -1 & -1 &-1\\ -1 & 8 &-1\\ -1 & -1 &-1\\ \end{bmatrix} K1=???010?1?41?010????, K2=???111?1?81?111????, K3=???0?10??14?1?0?10????, K4=????1?1?1??18?1??1?1?1????
Laplace 是導數算子,會突出影像中的急劇灰度變化,抑制灰度緩慢變化區域,往往會產生暗色背景下的灰色邊緣和不連續影像,將拉普拉斯影像與原圖疊加,可以得到保留銳化效果的影像,
拉普拉斯卷積核很容易通過卷積操作 cv. filter_2d 實作,OpenCV 也提供了拉普拉斯算子 cv.Laplacian 來實作,
函式說明:
cv.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) → dst
引數說明:
-
src:輸入影像,可以是灰度影像,也可以是多通道的彩色影像
-
ddepth:輸出圖片的資料深度:
-
dst:輸出影像,大小和型別與 src 相同
-
ksize:計算二階導數濾波器的孔徑大小,必須為正奇數,可選項
-
scale:縮放比例因子,可選項,默認值為 1
-
delta:輸出影像的偏移量,可選項,默認值為 0
-
borderType:邊界擴充的型別,注意不支持對側填充(BORDER_WRAP)
例程 1.78:影像銳化:Laplacian 算子
# 1.78:影像銳化:拉普拉斯算子 (Laplacian)
img = cv2.imread("../images/Fig0338a.tif", flags=0) # NASA 月球影像圖
# 使用函式 filter2D 實作 Laplace 卷積算子
kernLaplace = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]]) # Laplacian kernel
imgLaplace1 = cv2.filter2D(img, -1, kernLaplace, borderType=cv2.BORDER_REFLECT)
# 使用 cv2.Laplacian 實作 Laplace 卷積算子
imgLaplace2 = cv2.Laplacian(img, -1, ksize=3)
imgRecovery = cv2.add(img, imgLaplace2) # 恢復原影像
# 二值化邊緣圖再卷積
ret, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_TRIANGLE)
imgLaplace3 = cv2.Laplacian(binary, cv2.CV_64F)
imgLaplace3 = cv2.convertScaleAbs(imgLaplace3)
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Original")
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.axis('off'), plt.title("cv.Laplacian")
plt.imshow(imgLaplace2, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.axis('off'), plt.title("thresh-Laplacian")
plt.imshow(imgLaplace3, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()
由于拉普拉斯卷積核很敏感,可以先進行閾值化處理,再進行拉普拉斯卷積,例程對比了直接進行拉普拉斯卷積,與閾值化處理后進行拉普拉斯卷積,結果如下圖所示,

3.4 Sobel 梯度算子
Sobel 算子是一種離散的微分算子,是高斯平滑和微分求導的聯合運算,抗噪聲能力強,
Sobel 梯度算子利用區域差分尋找邊緣,計算得到梯度的近似值,先計算水平、垂直方向的梯度 G x = k x ? s r c G_x = k_x * src Gx?=kx??src, G y = k y ? s r c G_y = k_y * src Gy?=ky??src,再求總梯度 $ G = \sqrt{G_x2+G_y2}$ ,編程實作時,可以用絕對值近似平方根: G = ∣ G x ∣ + ∣ G y ∣ G = |G_x| + |G_y| G=∣Gx?∣+∣Gy?∣,
Sobel 梯度算子是由平滑算子和差分算子卷積得到,Sobel 梯度算子的卷積核為:
K
x
=
[
?
1
0
1
?
2
0
2
?
1
0
1
]
,
K
y
=
[
?
1
?
2
?
1
0
0
0
1
2
1
]
K_x = \begin{bmatrix} -1 & 0 &1\\ -2 & 0 &2\\ -1 & 0 &1\\ \end{bmatrix}, \ K_y = \begin{bmatrix} -1 &-2 &-1\\ 0 &0 &0\\ 1 &2 &1\\ \end{bmatrix}
Kx?=????1?2?1?000?121????, Ky?=????101??202??101????
Sobel 梯度算子很容易通過卷積操作 cv.filter2D 實作,OpenCV 也提供了函式 cv.Sobel 實作 Sobel 梯度算子,
函式說明:
cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) → dst
引數說明:
- src:輸入影像,灰度影像,不適用彩色影像
- dst:輸出影像,大小和型別與 src 相同
- ddepth:輸出圖片的資料深度,由輸入影像的深度進行選擇
- dx:x 軸方向導數的階數,1 或 2
- dy:y 軸方向導數的階數,1 或 2
- ksize:Sobel 卷積核的大小,可選的取值為:1/3/5/7,ksize=-1 時使用 Scharr 算子運算
- scale:縮放比例因子,可選項,默認值為 1
- delta:輸出影像的偏移量,可選項,默認值為 0
- borderType:邊界擴充的型別,注意不支持對側填充(BORDER_WRAP)
注意事項:
- ddepth 的設定比較復雜,而且容易出錯,限于篇幅本文不做展開,
此外,為了處理微分運算導致的資料例外(超出 [0,255]),OpenCV 提供了 cv.convertScaleAbs 進行飽和運算(saturate): d s t = s a t u r a t e ( s r c ? α + b e t a ) dst = saturate(src * \alpha + beta) dst=saturate(src?α+beta),
函式說明:
cv.convertScaleAbs(src[, alpha[, beta]]) → dst
引數說明:
- src:輸入影像,可以是灰度影像,也可以是多通道的彩色影像
- dst:輸出影像,大小和型別與 src 相同
- alpha:調節系數,可選項,默認值為 1
- beta:亮度調節,可選項,默認值為 0
例程 1.79:影像銳化:Sobel 算子
# 1.79:影像銳化:Sobel 算子
# img = cv2.imread("../images/Fig0338a.tif", flags=0) # NASA 月球影像圖
img = cv2.imread("../images/imgGaia.tif", flags=0)
# 使用函式 filter2D 實作 Sobel 算子
kernSobelX = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # SobelX kernel
kernSobelY = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) # SobelY kernel
imgSobelX = cv2.filter2D(img, -1, kernSobelX, borderType=cv2.BORDER_REFLECT)
imgSobelY = cv2.filter2D(img, -1, kernSobelY, borderType=cv2.BORDER_REFLECT)
# 使用 cv2.Sobel 實作 Sobel 算子
SobelX = cv2.Sobel(img, cv2.CV_16S, 1, 0) # 計算 x 軸方向
SobelY = cv2.Sobel(img, cv2.CV_16S, 0, 1) # 計算 y 軸方向
absX = cv2.convertScaleAbs(SobelX) # 轉回 uint8
absY = cv2.convertScaleAbs(SobelY) # 轉回 uint8
SobelXY = cv2.addWeighted(absX, 0.5, absY, 0.5, 0) # 用絕對值近似平方根
plt.figure(figsize=(10, 6))
plt.subplot(141), plt.axis('off'), plt.title("Original")
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.axis('off'), plt.title("SobelX")
plt.imshow(SobelX, cmap='gray', vmin=0, vmax=255)
# plt.imshow(imgSobelX, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.axis('off'), plt.title("SobelY")
plt.imshow(SobelY, cmap='gray', vmin=0, vmax=255)
# plt.imshow(imgSobelY, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.axis('off'), plt.title("SobelXY")
plt.imshow(SobelXY, cmap='gray')
plt.tight_layout()
plt.show()

3.5 Scharr 算子
Scharr 算子也稱為 Scharr 濾波器,計算 x 軸或 y 軸方向的影像差分,
Scharr 算子是 Soble 算子在 ksize=3 時的優化,與 Soble 的速度相同,且精度更高,Scharr 算子與 Sobel 算子的不同點是在平滑部分,其中心元素占的權重更重,相當于使用較小標準差的高斯函式,也就是更瘦高的模板,
Scharr 算子的卷積核為:
G
x
=
[
?
3
0
3
?
10
0
10
?
3
0
3
]
,
G
y
=
[
?
3
10
?
3
0
0
10
3
10
3
]
G_x = \begin{bmatrix} -3 & 0 &3\\ -10 & 0 &10\\ -3 & 0 &3\\ \end{bmatrix}, \ G_y = \begin{bmatrix} -3 &10 &-3\\ 0 &0 &10\\ 3 &10 &3\\ \end{bmatrix}
Gx?=????3?10?3?000?3103????, Gy?=????303?10010??3103????
Scharr 算子很容易通過卷積操作 cv.filter2D 實作,OpenCV 也提供了函式 cv.Scharr 實作 Scharr 算子,
函式說明:
cv.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) → dst
引數說明:
- src:輸入影像
- dst:輸出影像,大小和型別與 src 相同
- ddepth:輸出圖片的資料深度,由輸入影像的深度進行選擇
- dx:x 軸方向導數的階數
- dy:y 軸方向導數的階數
- scale:縮放比例因子,可選項,默認值為 1
- delta:輸出影像的偏移量,可選項,默認值為 0
- borderType:邊界擴充的型別,注意不支持對側填充(BORDER_WRAP)
例程 1.80:影像銳化:Scharr 算子
# 1.80:影像銳化:Scharr 算子
# img = cv2.imread("../images/Fig0338a.tif", flags=0) # NASA 月球影像圖
img = cv2.imread("../images/imgGaia.tif", flags=0)
# 使用函式 filter2D 實作 Scharr 算子
kernScharrX = np.array([[-3, 0, 3], [-10, 0, 10], [-3, 0, 3]]) # ScharrX kernel
kernScharrY = np.array([[-3, 10, -3], [0, 0, 10], [3, 10, 3]]) # ScharrY kernel
# 使用 cv2.Scharr 實作 Scharr 算子
ScharrX = cv2.Scharr(img, cv2.CV_16S, 1, 0) # 計算 x 軸方向
ScharrY = cv2.Scharr(img, cv2.CV_16S, 0, 1) # 計算 y 軸方向
absX = cv2.convertScaleAbs(ScharrX) # 轉回 uint8
absY = cv2.convertScaleAbs(ScharrY) # 轉回 uint8
ScharrXY = cv2.addWeighted(absX, 0.5, absY, 0.5, 0) # 用絕對值近似平方根
plt.figure(figsize=(10, 6))
plt.subplot(141), plt.axis('off'), plt.title("Original")
plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.axis('off'), plt.title("ScharrX")
plt.imshow(ScharrX, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.axis('off'), plt.title("ScharrY")
plt.imshow(ScharrY, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.axis('off'), plt.title("ScharrXY")
plt.imshow(ScharrXY, cmap='gray')
plt.tight_layout()
plt.show()

4. 低通、高通、帶阻、帶通
影像濾波是在盡可能保留影像細節特征的條件下對目標影像的噪聲進行抑制,是常用的影像預處理操作,
空間域和頻率域線性濾波器可以分為四類:低通濾波器、高通濾波器、帶通濾波器和帶阻濾波器,后三類濾波器都可以由低通濾波器構造:
- 低通濾波器: l p ( x , y ) lp(x,y) lp(x,y)
- 高通濾波器: h p ( x , y ) = δ ( x , y ) ? l p ( x , y ) hp(x,y) = \delta(x,y) - lp(x,y) hp(x,y)=δ(x,y)?lp(x,y)
- 帶通濾波器: b r ( x , y ) = l p 1 ( x , y ) + h p 2 ( x , y ) br(x,y) = lp_1(x,y) + hp_2(x,y) br(x,y)=lp1?(x,y)+hp2?(x,y)
- 帶阻濾波器: b p ( x , y ) = δ ( x , y ) ? b r ( x , y ) bp(x,y) = \delta(x,y) - br(x,y) bp(x,y)=δ(x,y)?br(x,y)
這些傳遞函式都可以由一個低通濾波器傳遞函式通過線性變換得到,
本案例中建立一維低通濾波器傳遞函式,進而可以生成空間濾波器核,
以一幅由以下公式描述的同心圓反射板為例,測驗濾波方法的特性:
z
(
x
,
y
)
=
[
1
+
c
o
s
(
x
2
+
y
2
)
]
/
2
,
x
,
y
∈
[
?
8.2
,
8.2
]
z(x,y) = [1 + cos(x^2+y2)]/2,\quad x,y \in [-8.2,8.2]
z(x,y)=[1+cos(x2+y2)]/2,x,y∈[?8.2,8.2]
首先,設計一個一維空間低通濾波器函式
x
=
s
i
n
(
x
)
/
x
,
x
∈
[
?
6
π
,
6
π
]
x = sin(x) / x,\quad x \in [-6\pi,6\pi]
x=sin(x)/x,x∈[?6π,6π]
例程 1.81:低通/高通,帶阻/帶通
# 1.81:低通/高通,帶阻/帶通
# 同心圓反射板
height, width = 597, 597
m = int((height-1) / 2) # 298
n = int((width-1) / 2) # 298
X = np.linspace(-8.2, 8.2, height) # 長度:597
Y = np.linspace(-8.2, 8.2, width)
x, y = np.meshgrid(X, Y)
circle = 0.5 * (1 + np.cos(x**2 + y**2))
for i in range(circle.shape[0]):
for j in range(circle.shape[1]):
if np.sqrt((i-m)**2 + (j-n)**2) > m:
circle[i,j] = 0
# 一維低通濾波器函式
hFilter, wFilter = 128, 128
mFilter = int((hFilter-1) / 2) # 63
nFilter = int((wFilter-1) / 2) # 63
x = np.linspace(-6*np.pi, 6*np.pi, hFilter) # 長度:128
y = np.linspace(-6*np.pi, 6*np.pi, wFilter) # 長度:128
scale = 1 # 濾波器縮放因子
xFilter = np.sin(x*scale) / x # 128
# 二維低通濾波器函式
X, Y = np.meshgrid(x, y) # 生成x、y網格化資料
Z = np.sin(np.sqrt(X**2+Y**2)) / np.sqrt(X**2+Y**2)
plt.figure(figsize=(10, 3))
plt.subplot(131), plt.axis('off'), plt.title("Concentric circles(597*597)"), plt.imshow(circle, 'gray')
plt.subplot(132), plt.title("1D low-pass filter"), plt.plot(x, xFilter), plt.axis([-20,20,-0.25,1.25])
ax = plt.subplot(133, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=plt.get_cmap("rainbow"), linewidth=0, antialiased=False)
ax.set_title("2D low-pass filter")
plt.show()

5. 空間域影像增強技術的綜合應用
空間域影像增強的方法很多,各有不同的特點和作用,對于一幅具體影像,往往要根據影像的實際情況,綜合使用幾種影像增強的方法,以便達到較為理想的結果,
本節以人體骨骼掃描影像(來自G.E.MedicalSystem)為例,要求對影像進行銳化以顯示更多的骨骼細節,原始影像的灰度級比較狹窄,噪聲含量大,簡單使用一種影像增強方法難以達到理想的結果,需要綜合應用空間域影像增強技術:首先使用拉普拉斯變換突出細節,然后使用梯度算子增強突出的邊緣,再使用低通濾波器降低噪聲,以此為模板得到需要的銳化影像,最后用伽馬校正調整灰度級的動態范圍,具體步驟如下:
(1)拉普拉斯變換,突出原始影像的細節;
(2)原始影像疊加拉普拉斯變換影像,恢復背景特征;
(3)Sobel 梯度算子,增強突出的邊緣;
(4)用盒式濾波器平滑梯度影像;
(5)拉普拉斯與平滑梯度相乘得到掩蔽模板;
(6)原始影像與掩蔽模板疊加,得到銳化影像;
(7)Gamma 變換,增大灰度級的動態范圍,
例程 1.82 空間域影像增強技術的綜合應用
# 1.82 空間域影像增強技術的綜合應用
# 原始影像,人體骨骼掃描影像
img = cv2.imread("../images/bonescan.tif", flags=0) # 人體骨骼掃描影像
# 圖 2:拉普拉斯變換,突出細節
kernLaplace = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]], np.int8) # Laplacian kernel
# kernLaplaceD = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], np.int8) # Diagonal Laplacian kernel
Laplacian = cv2.filter2D(img, ddepth=-1, kernel=kernLaplace)
imgLaplacian = np.uint8(cv2.normalize(Laplacian, None, 0, 255, cv2.NORM_MINMAX))
# 圖 3:原始影像 + 拉普拉斯變換,恢復背景特征
AddLap = img + imgLaplacian
imgAddLap = np.uint8(cv2.normalize(AddLap, None, 0, 255, cv2.NORM_MINMAX))
# 圖 4:Sobel 梯度算子,增強突出的邊緣
SobelX = cv2.Sobel(img, cv2.CV_16S, 1, 0) # 計算 x 軸方向
SobelY = cv2.Sobel(img, cv2.CV_16S, 0, 1) # 計算 y 軸方向
absX = cv2.convertScaleAbs(SobelX) # 轉回 uint8
absY = cv2.convertScaleAbs(SobelY) # 轉回 uint8
SobelXY = cv2.addWeighted(absX, 0.5, absY, 0.5, 0) # 用絕對值近似平方根
imgSobel = np.uint8(cv2.normalize(SobelXY, None, 0, 255, cv2.NORM_MINMAX))
# 圖 5:用 (5,5) 盒式濾波器平滑梯度影像
kernelBox = np.ones(5, np.float32) / (5 * 5) # 生成歸一化盒式核
SobelBox = cv2.filter2D(img, -1, kernelBox) # cv2.filter2D 方法
imgSobelBox = cv2.normalize(SobelBox, None, 0, 255, cv2.NORM_MINMAX)
# 圖 6:圖2 與 圖5 相乘得到模板 mask,突出了強邊緣,相對較低了噪聲
mask = imgLaplacian * imgSobelBox
imgMask = np.uint8(cv2.normalize(mask, None, 0, 255, cv2.NORM_MINMAX))
# 圖7:原始影像與圖 6 相加,得到銳化影像,大部分細節更清晰
passivation = img + imgMask * 0.3
imgPassi = np.uint8(cv2.normalize(passivation, None, 0, 255, cv2.NORM_MINMAX))
# 圖8: 冪律變換(Gamma 變換),增大灰度級的動態范圍
epsilon = 1e-5 # 非常小的值以防出現除0的情況
# Gamma = np.zeros_like(imgPassi, dtype=np.float)
Gamma = np.power(imgPassi + epsilon, 0.5)
imgGamma = np.uint8(cv2.normalize(Gamma, None, 0, 255, cv2.NORM_MINMAX))
# 繪圖
plt.figure(figsize=(10, 7))
titleList = ["1. Original", "2. Laplacian", "3. Original + Laplacian", "4. Sobel",
"5. Sobel Box5", "6. Sobel mask", "7. Passivation", "8. Gamma correction"]
imageList = [img, imgLaplacian, imgAddLap, imgSobel, imgSobelBox, imgMask, imgPassi, imgGamma]
for i in range(8):
plt.subplot(2,4,i+1), plt.title(titleList[i]), plt.axis('off')
plt.imshow(imageList[i], 'gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

著作權宣告:
歡迎關注『Python 小白從零開始 OpenCV 學習課 @ youcans』 原創作品
本文中部分原始圖片來自 Rafael C. Gonzalez “Digital Image Processing, 4th.Ed.”,特此致謝,
原創作品,轉載必須標注原文鏈接:https://blog.csdn.net/youcans/article/details/121422046
Copyright 2021 youcans, XUPT
Crated:2021-12-05
歡迎關注 『Python小白從零開始 OpenCV 學習課』 系列,持續更新
Python 大白從零開始 OpenCV 學習課-1.安裝與環境配置
Python 大白從零開始 OpenCV 學習課-2.影像讀取與顯示
Python 大白從零開始 OpenCV 學習課-3.影像的創建與修改
Python 大白從零開始 OpenCV 學習課-4.影像的疊加與混合
Python 大白從零開始 OpenCV 學習課-5.影像的幾何變換
Python 大白從零開始 OpenCV 學習課-6. 灰度變換與直方圖處理
Python 大白從零開始 OpenCV 學習課-7. 空間域影像濾波
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/374766.html
標籤:其他
