通过拍摄和录制的图片或视频,因外界环境和在传输过程中等诸多因素影响不可避免的会产生噪声,通过图像平滑技术可对噪声进行抑制和去除,其中具备保边作用的平滑技术得到了更多关注。
可分离卷积核
如果一个卷积核至少有两个尺寸比它小的卷积和完全卷积而成,并且在计算过程中在所有边界处均进行扩充零的操作,且满足
K
e
r
n
e
l
=
k
e
r
n
e
l
1
★
k
e
r
n
e
l
2
★
⋅
⋅
⋅
★
k
e
r
n
e
l
n
Kernel = kernel_1★kernel_2★···★kernel_n
Kernel=kernel1★kernel2★⋅⋅⋅★kerneln
且其中的 k e r n e l i kernel_i kerneli 的尺寸均比Kernel小, 1 ≤ i ≤ n 1\le i \le n 1≤i≤n,则称该卷积核是可分离的。
import numpy as np
from scipy import signal
kernel1 = np.array([[1,2,3]],np.float32)
kernel2 = np.array([[4],[5],[6]],np.float32)
# 计算两个核的完全卷积
kernel = signal.convolve2d(kernel1,kernel2,mode = "full")
print("kernel1,kernel2",kernel)
kernel = signal.convolve2d(kernel2,kernel1,mode = "full")
print("kernel2,kernel1",kernel)
运行结果:
kernel1,kernel2 [[ 4. 8. 12.]
[ 5. 10. 15.]
[ 6. 12. 18.]]
kernel2,kernel1 [[ 4. 8. 12.]
[ 5. 10. 15.]
[ 6. 12. 18.]]
Full卷积的性质
如果卷积核Kernel是可分离的,且Kernel = kernel1★kernel2,则有:
I ★ K e r n e l = I ★ ( k e r n e l 1 ★ k e r n e l 2 ) = ( I ★ k e r n e l 1 ) ★ k e r n e l 2 I★Kernel = I★(kernel1★kernel2) = (I★kernel1)★kernel2 I★Kernel=I★(kernel1★kernel2)=(I★kernel1)★kernel2
import numpy as np
from scipy import signal
# 离散卷积
I = [[1,2,3,10,12],
[32,43,12,4,190],
[12,234,78,0,12],
[43,90,32,8,90],
[71,12,4,98,123]]
I = np.array(I,np.float32)
Kernel = np.array([[1,0,-1],[1,0,-1],[1,0,-1]])
I_K = signal.convolve2d(I,Kernel,mode="full",boundary="fill",fillvalue=0)
print(I_K)
kernel1 = np.array([[1],[1],[1]],np.float32)
kernel2 = np.array([[1,0,-1]],np.float32)
I_k1 = signal.convolve2d(I,kernel1,mode="full",boundary="fill",fillvalue=0)
I_k1_k2 = signal.convolve2d(I_k1,kernel2,mode="full",boundary="fill",fillvalue=0)
print(I_k1_k2)
运行结果:
print(I_K)
[[ 1. 2. 2. 8. 9. -10. -12.]
[ 33. 45. -18. -31. 187. -14. -202.]
[ 45. 279. 48. -265. 121. -14. -214.]
[ 87. 367. 35. -355. 170. -12. -292.]
[ 126. 336. -12. -230. 111. -106. -225.]
[ 114. 102. -78. 4. 177. -106. -213.]
[ 71. 12. -67. 86. 119. -98. -123.]]
print(I_k1_k2)
[[ 1. 2. 2. 8. 9. -10. -12.]
[ 33. 45. -18. -31. 187. -14. -202.]
[ 45. 279. 48. -265. 121. -14. -214.]
[ 87. 367. 35. -355. 170. -12. -292.]
[ 126. 336. -12. -230. 111. -106. -225.]
[ 114. 102. -78. 4. 177. -106. -213.]
[ 71. 12. -67. 86. 119. -98. -123.]]
Same卷积的性质
如果卷积核Kernel是可分离的,高为H2,宽为W2,可分离为1×W2的水平方向的卷积核和H2×1的垂直方向的卷积核,即Kernel = kernel1★kernel2,则有:
I ★ K e r n e l = ( I ★ k e r n e l 1 ) ★ k e r n e l 2 I★Kernel = (I★kernel1)★kernel2 I★Kernel=(I★kernel1)★kernel2
通过以下三种扩充方式(常数扩充、反射扩充、平铺扩充)展示:
print("为0的常数扩充,计算结果完全相同")
I_K = signal.convolve2d(I,Kernel,mode="same",boundary="fill",fillvalue=0)
print(I_K)
I_k1 = signal.convolve2d(I,kernel1,mode="same",boundary="fill",fillvalue=0)
I_k1_k2 = signal.convolve2d(I_k1,kernel2,mode="same",boundary="fill",fillvalue=0)
print(I_k1_k2)
print("不为0的常数扩充,计算结果不完全相同,中心区域完全相同")
I_K = signal.convolve2d(I,Kernel,mode="same",boundary="fill",fillvalue=1)
print(I_K)
I_k1 = signal.convolve2d(I,kernel1,mode="same",boundary="fill",fillvalue=1)
I_k1_k2 = signal.convolve2d(I_k1,kernel2,mode="same",boundary="fill",fillvalue=1)
print(I_k1_k2)
运行结果:
为0的常数扩充,计算结果完全相同
[[ 45. -18. -31. 187. -14.]
[ 279. 48. -265. 121. -14.]
[ 367. 35. -355. 170. -12.]
[ 336. -12. -230. 111. -106.]
[ 102. -78. 4. 177. -106.]]
[[ 45. -18. -31. 187. -14.]
[ 279. 48. -265. 121. -14.]
[ 367. 35. -355. 170. -12.]
[ 336. -12. -230. 111. -106.]
[ 102. -78. 4. 177. -106.]]
不为0的常数扩充,计算结果不完全相同,中心区域完全相同
[[ 43. -18. -31. 187. -12.]
[ 276. 48. -265. 121. -11.]
[ 364. 35. -355. 170. -9.]
[ 333. -12. -230. 111. -103.]
[ 100. -78. 4. 177. -104.]]
[[ 45. -18. -31. 187. -14.]
[ 278. 48. -265. 121. -13.]
[ 366. 35. -355. 170. -11.]
[ 335. -12. -230. 111. -105.]
[ 102. -78. 4. 177. -106.]]
print("以下是反射扩充")
I_K = signal.convolve2d(I,Kernel,mode="same",boundary="wrap")
print(I_K)
I_k1 = signal.convolve2d(I,kernel1,mode="same",boundary="wrap")
I_k1_k2 = signal.convolve2d(I_k1,kernel2,mode="same",boundary="wrap")
print(I_k1_k2)
print("以下是平铺扩充")
I_K = signal.convolve2d(I,Kernel,mode="same",boundary="wrap")
print(I_K)
I_k1 = signal.convolve2d(I,kernel1,mode="same",boundary="wrap")
I_k1_k2 = signal.convolve2d(I_k1,kernel2,mode="same",boundary="wrap")
print(I_k1_k2)
运行结果:
以下是反射扩充
[[-268. -85. 55. 306. -8.]
[ 65. 48. -265. 121. 31.]
[ 75. 35. -355. 170. 75.]
[ 111. -12. -230. 111. 20.]
[-121. -76. 12. 186. -1.]]
[[-268. -85. 55. 306. -8.]
[ 65. 48. -265. 121. 31.]
[ 75. 35. -355. 170. 75.]
[ 111. -12. -230. 111. 20.]
[-121. -76. 12. 186. -1.]]
以下是平铺扩充
[[-268. -85. 55. 306. -8.]
[ 65. 48. -265. 121. 31.]
[ 75. 35. -355. 170. 75.]
[ 111. -12. -230. 111. 20.]
[-121. -76. 12. 186. -1.]]
[[-268. -85. 55. 306. -8.]
[ 65. 48. -265. 121. 31.]
[ 75. 35. -355. 170. 75.]
[ 111. -12. -230. 111. 20.]
[-121. -76. 12. 186. -1.]]
高斯平滑
-
第一步计算高斯矩阵:
g a u s s M a t r i x H × W = [ g a u s s ( r , c , σ ) ] 0 ≤ r ≤ H − 1 , 0 ≤ c ≤ W − 1 , r , c ∈ N gaussMatrix_{H\times W} = [gauss(r,c,\sigma)]_{0 \le r\le H-1,0 \le c\le W-1,r,c \in N} gaussMatrixH×W=[gauss(r,c,σ)]0≤r≤H−1,0≤c≤W−1,r,c∈N
其中:
g a u s s ( r , c , σ ) = 1 2 π σ 2 e − ( r − H − 1 2 ) 2 + ( c − W − 1 2 ) 2 2 σ 2 gauss(r,c,\sigma) = \frac{1}{2\pi\sigma^2}e^{-\frac{(r-\frac{H-1}{2})^2+(c-\frac{W-1}{2})^2}{2\sigma^2}} gauss(r,c,σ)=2πσ21e−2σ2(r−2H−1)2+(c−2W−1)2
r,c代表位置索引,其中 0 ≤ c ≤ W − 1 0\le c \le W-1 0≤c≤W−1, 0 ≤ r ≤ H − 1 0\le r \le H-1 0≤r≤H−1,且r,c均为整数。 -
第二步:计算高斯矩阵和。
s u m ( g a u s s M a t r i x H × W ) sum(gaussMatrix_{H\times W}) sum(gaussMatrixH×W) -
第三步:高斯矩阵初一其本身的和,即归一化,得到的便是高斯卷积算子。
g a u s s K e r n e l H × W = g a u s s M a t r i x / s u m ( g a u s s M a t r i x ) gaussKernel_{H\times W} = gaussMatrix/sum(gaussMatrix) gaussKernelH×W=gaussMatrix/sum(gaussMatrix)利用上述三个步骤构建高斯卷积算子的python代码如下:
import numpy as np
import math
def get_gauss_kernel(sigma,H,W):
"""
@description :计算高斯核
---------
@param :高斯函数的方差,钢丝核的高和宽
-------
@Returns :高斯核
-------
"""
# 第一步:构建高斯矩阵
gauss_matrix = np.zeros([H,W],np.float32)
# 得到中心点位置
cH = (H-1)/2
cW = (W-1)/2
# 计算gauss(sigma,r,c)
for r in range(H):
for c in range(W):
nrom2 = math.pow(r-cH,2) + math.pow(c-cW,2)
gauss_matrix[r][c] = math.exp(-nrom2/(2*math.pow(sigma,2)))
# 第二部计算高斯矩阵的和
sum_gm = np.sum(gauss_matrix)
# 第三步:归一化
gauss_kernel = gauss_kernel/sum_gm
return gauss_kernel
因为最后要归一化,所以在代码实现中可以取代哦高斯函数中的系数 1 2 π σ 2 \frac{1}{2\pi\sigma^2} 2πσ21。
高斯卷积算子是可分离卷积核
因为 e − ( r − H − 1 2 ) 2 + ( c − W − 1 2 ) 2 2 σ 2 = e − ( r − H − 1 2 ) 2 2 σ 2 ∗ e − ( c − W − 1 2 ) 2 2 σ 2 e^{-\frac{(r-\frac{H-1}{2})^2+(c-\frac{W-1}{2})^2}{2\sigma^2}} = e^{-\frac{(r-\frac{H-1}{2})^2}{2\sigma^2}}*e^{-\frac{(c-\frac{W-1}{2})^2}{2\sigma^2}} e−2σ2(r−2H−1)2+(c−2W−1)2=e−2σ2(r−2H−1)2∗e−2σ2(c−2W−1)2,所以高斯卷积核可以分离成一位水平方向上的高斯核和一维垂直方向上的高斯核。
高斯卷积核的二项式近似
一维高斯分布函数
g ( u , μ , σ ) = 1 2 π σ e − ( x − μ ) 2 2 σ 2 g(u,\mu,\sigma)= \frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}} g(u,μ,σ)=2πσ1e−2σ2(x−μ)2
二项是为:
( x + y ) n = ∑ k = 0 n C n k x k y n − k (x+y)^n = \sum_{k=0}^{n}C_{n}^{k}x^ky^{n-k} (x+y)n=k=0∑nCnkxkyn−k
展开式系数:
C n k = m ! ( k ! ) ∗ ( n − k ) ! , k = 0 , 1 , 2 , 3 ⋅ ⋅ ⋅ n C_{n}^{k} = \frac{m!}{(k!)*(n-k)!},k=0,1,2,3···n Cnk=(k!)∗(n−k)!m!,k=0,1,2,3⋅⋅⋅n
在二项式展开式中,,令x=1,y=1,则 2 n = ∑ k = 0 n C n k 2^n = \sum_{k=0}^{n}C_{n}^{k} 2n=∑k=0nCnk,即 1 = ∑ k = 0 n C n k 2 n 1=\sum_{k=0}^{n}\frac{C_{n}^{k}}{2^n} 1=∑k=0n2nCnk,令 f ( k ) = C n k 2 n f(k) =\frac{C_{n}^{k}}{2^n} f(k)=2nCnk ,则有:
∑ k = 0 n f ( k ) = 1 \sum_{k=0}^{n}f(k)=1 k=0∑nf(k)=1
二项分布 X B i n o m i a l ( n , q ) X~Binomial(n,q) X Binomial(n,q),该分布的期望值为μ=年轻,方差 σ 2 = n p ( 1 − p ) \sigma^2 = np(1-p) σ2=np(1−p),当 p = 1 2 p=\frac{1}{2} p=21时, μ = n 2 , σ 2 = n 4 \mu=\frac{n}{2},\sigma^2=\frac{n}{4} μ=2n,σ2=4n
一维高斯函数为:
g
(
u
,
μ
,
σ
)
=
1
2
π
σ
e
−
(
x
−
μ
)
2
2
σ
2
g(u,\mu,\sigma)= \frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}}
g(u,μ,σ)=2πσ1e−2σ2(x−μ)2
因当二项分布的n足够大时,二项分布近似于正态分布,所以有:
f ( k ) ≈ g ( k , n 2 , n 4 ) f(k)\approx g(k,\frac{n}{2},\frac{n}{4}) f(k)≈g(k,2n,4n)
import cv2 as cv
import numpy as np
from scipy import signal
# 实现步骤,
# 第一步首先进行水平方向上的高斯卷积,然后在进行垂直方向上的高斯卷积
def gauss_blur(image,sigma,H,W,_boundary='fill',_fillvalue=0):
# 构建水平方向上的高斯卷积核
gauss_kernel_x = cv.getGaussianKernel(sigma,W,cv.CV_64F)
# 转置
gauss_kernel_x = np.transpose(gauss_kernel_x)
# 图像矩阵与水平方向高斯和卷积
gauss_blur_x = signal.convolve2d(image,gauss_kernel_x,mode='same',boundary=_boundary,fillvalue=_fillvalue)
# 构建垂直方向上的高斯卷积核
gauss_kernel_y = cv.getGaussianKernel(sigma,H,cv.CV_64F)
# 与垂直方向上的高斯核卷积
gauss_blur_xy = signal.convolve2d(gauss_blur_x,gauss_kernel_y,mode='same',boundary=_boundary,fillvalue=_fillvalue)
# 对得到的图像进行灰度级显示
gauss_blur_xy = np.round(gauss_blur_xy)
gauss_blur_xy = gauss_blur_xy.astype(np.unit8)
return gauss_blur_xy
均值平滑
均值平滑,顾名思义,图像中每一个位置的邻域的平均值作为该位置的输出值,代码实现与分离的高斯卷积是类似的,只需将高斯算子替换成均值算子即可。
- 图像积分,即任意一个位置的积分等于该位置上左上角所有值的和。
i n t e g r a l ( r , c ) = ∑ i = 0 i = r ∑ j = 0 j = c I ( r , c ) , 0 ≤ r ≤ R , 0 ≤ c ≤ C integral(r,c) = \sum_{i=0}^{i=r}\sum_{j=0}^{j=c}I(r,c),0 \le r \le R,0 \le c \le C integral(r,c)=i=0∑i=rj=0∑j=cI(r,c),0≤r≤R,0≤c≤C
利用矩阵积分,可与i计算出矩阵中任意举行区域的和:
∑
i
=
r
T
o
p
r
B
o
t
t
o
m
∑
j
=
c
L
e
f
t
c
R
I
g
h
t
I
(
r
,
c
)
=
I
n
t
e
g
r
a
l
(
r
B
o
t
t
o
m
,
c
R
I
g
h
t
)
+
I
n
t
e
g
r
a
l
(
r
T
o
p
−
1
,
c
L
e
f
t
−
1
)
−
I
n
t
e
g
r
a
l
(
r
B
o
t
t
o
m
,
c
L
e
f
t
−
1
)
−
I
n
t
e
g
r
a
l
(
r
T
o
p
−
1
,
c
R
I
g
h
t
)
\sum_{i=rTop}^{rBottom}\sum_{j=cLeft}^{cRIght}I(r,c) = Integral(rBottom,cRIght)+Integral(rTop-1,cLeft-1)-Integral(rBottom,cLeft-1)-Integral(rTop-1,cRIght)
i=rTop∑rBottomj=cLeft∑cRIghtI(r,c)=Integral(rBottom,cRIght)+Integral(rTop−1,cLeft−1)−Integral(rBottom,cLeft−1)−Integral(rTop−1,cRIght)
利用上式求出邻域内的和,再乘以系数 r a t i o = 1.0 / ( w i n s i z e . c o l s ∗ w i n s i z e . r o w s ) ratio = 1.0/(win_size.cols*win_size.rows) ratio=1.0/(winsize.cols∗winsize.rows), 即可得到均值平滑的结果,代码如下:
import numpy as np
import cv2 as cv
def integral(image):
"""
图像积分
"""
rows,cols = image.shape
# 行积分运算
inteImageC = np.zeros((rows,cols),np.float32)
for r in range(rows):
for c in range(cols):
if c ==0:
inteImageC[r][c] = image[r][c]
else:
inteImageC[r][c]= inteImageC[r][c-1]+image[r][c]
# 列积分运算
inteImage = np.zeros(image.shape,np.float32)
for c in range(cols):
for r in range(rows):
if r ==0:
inteImage[r][c] = inteImageC[r][c]
else:
inteImage[r][c] = inteImage[r-1][c]+inteImageC[r][c]
# 上边和夏斌啊进行补零
inteImage_0 = np.zeros((rows+1,cols+1),np.float32)
inteImage_0[1:rows+1,1:cols+1] = inteImage
return inteImage_0
def fast_mean_blur(image,win_size,border_type = cv.BORDER_DEFAULT):
"""
均值平滑
第一步:先对图像进行图像积分
第二步:计算win_size区域内的面积和
第三步:除以win_size的像素数量
"""
half_h = (win_size[0]-1)/2
half_w = (win_size[1]-1)/2
ratio = 1.0/(win_size[0]*win_size[1])
# 边界扩充
padd_image = cv.copyMakeBorder(image,half_h,half_h,half_w,half_w,border_type)
# 图像积分
padd_integral = integral(padd_image)
# 图像的高、宽
rows,cols = image.shape
# 均值滤波后的结果
mean_blur_image = np.zeros(image.shape,np.float32)
r,c = 0,0
for h in range(half_h,half_h+rows,1):
for w in range(half_w,half_w+cols,1):
mean_blur_image[r][c] = (padd_integral[h+half_h+1][w+half_w+1]+padd_integral[h-half_h][w-half_w]-padd_integral[h+half_h][w-half_w]-padd_integral[h-half_h][w+half_w])*ratio
c+=1
r+=1
c=0
return mean_blur_image
注:文中所书内容来自张平的《opencv算法精解》,作为自己所学的输出,如若撰写过程中出现错误欢迎指出。