−
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∑at=−b∑bw(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⋮111⋮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⋮111⋮1⋯⋯⋮⋯11⋮1
α={ksize.width∗ksize.height11,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πσ21e−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×gmf⋆g=σg2+σf2mfσ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)ww=ws∗wrws=e−[(i−k)2+(j−l)2]/2σs2wr=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)=[gxgy]=[∂f/∂x∂f/∂x]
梯度指出了像素值的最大变化率的方向,经常用于工业检测中产品缺陷检测和自动检测的预处理。
图像梯度提取方法简单直接,能够有效的描述图像的原始状态,因此发展出多种图像梯度算子:Roberts、Prewitt、Sobel、Laplacian、Scharr。
3.2 钝化掩蔽
简单地,从原始图像中减去一幅平滑处理的钝化图像,也可以实现图像锐化效果,称为钝化掩蔽。
令
f
~
(
x
,
y
)
\tilde{f}(x,y)
f~(x,y) 表示平滑图像,则:
g
m
a
s
k
(
x
,
y
)
=
f
(
x
,
y
)
−
f
~
(
x
,
y
)
g
(
x
,
y
)
=
f
(
x
,
y
)
k
∗
g
m
a
s
k
(
x
,
y
)
,
k
0
\begin{align} g_{mask} (x,y) &= f(x,y) - \tilde{f}(x,y) \ g(x,y) &= f(x,y) + k * g_{mask}(x,y), k>0 \end{align}
gmask(x,y)g(x,y)=f(x,y)−f~(x,y)=f(x,y)+k∗gmask(x,y),k>0
当 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
最后
🍅 硬核资料:关注即可领取PPT模板、简历模板、行业经典书籍PDF。
🍅 技术互助:技术群大佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!