高斯模糊在圖像處理中的用途及其廣泛,除了常規(guī)的模糊效果外,還可用于圖像金字塔分解、反走樣、高低頻分解、噪聲壓制、發(fā)光效果等等等等。正因?yàn)楦咚鼓:A(chǔ),應(yīng)用太廣泛,所以需要盡可能深入認(rèn)識(shí)這個(gè)能力,避免在實(shí)際應(yīng)用中無意采坑。
一維高斯核函數(shù)
公式
G ( x ) = 1 2 π σ e ? ( x ? μ ) 2 2 σ 2 G(x) = \frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}} G(x)=2π?σ1?e?2σ2(x?μ)2?
其中 μ \mu μ是對(duì)稱軸,圖像處理中通常不會(huì)考慮 μ ≠ 0 \mu \neq0 μ=0的情況,所以該參數(shù)可以忽略(本文自此向下均忽略 μ \mu μ)。
σ \sigma σ決定函數(shù)的高矮胖瘦, σ \sigma σ越小越高瘦,越大越矮胖。如果用于模糊的話,高瘦意味著中心像素對(duì)于計(jì)算結(jié)果的貢獻(xiàn)大,而遠(yuǎn)處的像素貢獻(xiàn)相對(duì)較小,也就是說模糊結(jié)果更多受中心像素支配,因此模糊程度就低;反之矮胖意味著模糊程度高。所以其他條件不變的情況下,增大 σ \sigma σ會(huì)加強(qiáng)模糊,反之會(huì)減弱模糊。
e指數(shù)前的系數(shù)保證了核函數(shù)在 ( ? ∞ , + ∞ ) (-\infty, +\infty) (?∞,+∞)的積分為1,不過在圖像處理中,我們顯然不可能用無窮大的半徑,所以會(huì)對(duì)高斯核的系數(shù)做歸一化,那么e指數(shù)前的系數(shù)在歸一化中就被約去了,所以干脆就不用計(jì)算。
曲線形態(tài)
下圖是一維高斯核函數(shù)的曲線形態(tài)。
注意下圖仍然計(jì)算了e指數(shù)前的系數(shù),并驗(yàn)證了核函數(shù)的積分,表明無論 σ \sigma σ怎么變化,其積分總是為1。
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
def gaussian_1d(x, sigma):
coef = 1. / (np.sqrt(2 * np.pi) * sigma)
return coef * np.exp(-x * x / (2 * sigma * sigma))
def plot_gaussian_1d():
dx = 0.01
xx = np.arange(-10, 10, dx)
plt.figure(0, figsize=(8, 6))
for sigma in [0.5, 1, 2]:
yy = gaussian_1d(xx, sigma)
ss = np.sum(yy * dx)
plt.plot(xx, yy, label="sigma = %0.1f, integral = %0.2f" % (sigma, ss))
plt.legend(loc='best')
plt.savefig('gaussian_1d.png', dpi=300)
plt.close(0)
if __name__ == '__main__':
plot_gaussian_1d()
有效模糊半徑
一般而言,增大模糊半徑會(huì)加強(qiáng)模糊效果。但從曲線形態(tài)可以看到,遠(yuǎn)離對(duì)稱中心時(shí),高斯核的系數(shù)將會(huì)變得非常非常小,在做圖像模糊時(shí),意味著這些遠(yuǎn)處的像素對(duì)于模糊的貢獻(xiàn)將會(huì)變得非常小,此時(shí)增大半徑幾乎不會(huì)帶來模糊程度的增加,但是計(jì)算量卻絲毫沒有減少。
所以這里應(yīng)當(dāng)注意一個(gè)有效模糊半徑的概念。因?yàn)?span id="n5n3t3z" class="katex--inline">
σ
\sigma
σ會(huì)影響曲線的高矮胖瘦,所以容易知道有效模糊半徑受
σ
\sigma
σ影響。為了定義有效模糊半徑,我們必須定義一個(gè)標(biāo)準(zhǔn),這個(gè)標(biāo)準(zhǔn)應(yīng)當(dāng)具有相對(duì)性概念。比如簡(jiǎn)單來說我們針對(duì)曲線某個(gè)點(diǎn)的數(shù)值與最大值的比來進(jìn)行定義:
arg?max
?
x
(
G
(
x
,
σ
)
G
(
0
,
σ
)
)
>
l
i
m
i
t
\argmax_x(\frac{G(x, \sigma)}{G(0,\sigma)}) > limit
xargmax?(G(0,σ)G(x,σ)?)>limit
如果我們把滿足上式的最大x稱為有效有效模糊半徑,那么就可以去找一下其與
σ
\sigma
σ的關(guān)系了。
搞清楚有效模糊半徑對(duì)于圖像處理的實(shí)踐很重要,避免浪費(fèi)了算力卻并沒有得到相應(yīng)的模糊效果。然而這個(gè)概念很少被討論。
下面是不同的limit情況下sigma與有效模糊半徑的關(guān)系,很容易看出來這是個(gè)線性關(guān)系。
因此,假如我們要開發(fā)一個(gè)“模糊”的功能,只開放一個(gè)模糊程度參數(shù),那么一般而言這個(gè)參數(shù)需要跟半徑掛鉤,但為了讓半徑能真正起到作用,就需要適配的sigma,這個(gè)sigma可以使用
s
i
g
m
a
=
r
a
d
i
u
s
/
a
sigma = radius / a
sigma=radius/a 來計(jì)算得到。針對(duì)圖像模糊,根據(jù)經(jīng)驗(yàn),a使用[2, 2.5]
之間的數(shù)字均可。
def effective_radius():
dx = 0.01
xx = np.arange(0, 100, dx)
limits = [0.1, 0.01, 0.001]
colors = ['red', 'green', 'blue']
sigmas = np.arange(0.1, 10, 0.1)
plt.figure(0, figsize=(8, 6))
for i, limit in enumerate(limits):
effective_r = []
for sigma in sigmas:
yy = gaussian_1d(xx, sigma)
yy = yy / np.max(yy)
radius = np.max(np.argwhere(yy >= limit))
effective_r.append(radius * dx)
plt.plot(sigmas, effective_r, color=colors[i],
label='limit=%0.3f' % limit)
plt.legend(loc='best')
plt.grid()
plt.xlabel('sigma')
plt.ylabel('effective radius')
plt.savefig('effective radius', dpi=300)
plt.close(0)
二維高斯核函數(shù)
公式
G ( x , y ) = 1 2 π σ x σ y e ? ( ( x ? μ x ) 2 2 σ x 2 + ( y ? μ y ) 2 2 σ y 2 ) G(x,y) = \frac{1}{2\pi\sigma_x\sigma_y}e^{-(\frac{(x-\mu_x)^2 }{2\sigma_x^2} + \frac{(y-\mu_y)^2 }{2\sigma_y^2})} G(x,y)=2πσx?σy?1?e?(2σx2?(x?μx?)2?+2σy2?(y?μy?)2?)
對(duì)于圖像模糊來說,我們不關(guān)心平均值
μ
\mu
μ和前面的系數(shù),所以公式可以簡(jiǎn)化為:
G
(
x
,
y
)
=
e
?
(
x
2
2
σ
x
2
+
y
2
2
σ
y
2
)
G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})}
G(x,y)=e?(2σx2?x2?+2σy2?y2?)
下面是生成kernel的代碼和結(jié)果
# -*- coding: utf-8 -*-
import cv2
import numpy as np
def gaussian_2d(radius, sigma_x, sigma_y):
coord = np.arange(-radius, radius, 0.01)
xx, yy = np.meshgrid(coord, coord)
res = np.exp(
-(xx * xx / (sigma_x * sigma_x) + yy * yy / (sigma_y * sigma_y)) / 2)
res = res / np.sum(res)
return res
def plot_gaussian_2d():
radius = 10
sigma_x = 1.0
sigma_y = 1.0
kernel = gaussian_2d(radius, sigma_x, sigma_y)
kernel = kernel / np.max(kernel)
kernel = np.clip(np.round(kernel * 255), 0, 255)
kernel = np.uint8(kernel)
cv2.imwrite('radius=%d, sigma=(%d, %d).png' % (radius, sigma_x, sigma_y),
kernel)
行列嵌套循環(huán)的高斯模糊
# -*- coding: utf-8 -*-
import cv2
import numpy as np
def gaussian_2d(radius, sigma_x, sigma_y, d_radius=1):
coord = np.arange(-radius, radius + d_radius, d_radius)
xx, yy = np.meshgrid(coord, coord)
res = np.exp(-(xx * xx / (sigma_x * sigma_x) +
yy * yy / (sigma_y * sigma_y)) / 2)
res = res / np.sum(res)
return res
def convolve_2d(image, kernel, border='transparent'):
image_height, image_width = image.shape[:2]
kernel_height, kernel_width = kernel.shape[:2]
radius_h = kernel_height // 2
radius_w = kernel_width // 2
# check: kernel_height and kernel_width must be odd
if radius_h * 2 + 1 != kernel_height or radius_w * 2 + 1 != kernel_width:
raise ValueError('kernel_height and kernel_width must be odd')
res = np.zeros_like(image, dtype=np.float32)
for row in range(image_height):
# print(row)
for col in range(image_width):
pix = 0.0
weight = 0
for i in range(kernel_height):
for j in range(kernel_width):
row_k = row + i - radius_h
col_k = col + j - radius_w
if border == 'transparent':
if 0 <= row_k < image_height and \
0 <= col_k < image_width:
pix += image[row_k, col_k] * kernel[i, j]
weight += kernel[i, j]
elif border == 'zero':
if 0 <= row_k < image_height and \
0 <= col_k < image_width:
pix += image[row_k, col_k] * kernel[i, j]
weight += kernel[i, j]
elif border == 'copy':
if row_k < 0:
row_k = 0
elif row_k >= image_height:
row_k = image_height - 1
if col_k < 0:
col_k = 0
elif col_k >= image_width:
col_k = image_width - 1
pix += image[row_k, col_k] * kernel[i, j]
weight += kernel[i, j]
elif border == 'reflect':
if row_k < 0:
row_k = np.abs(row_k)
elif row_k >= image_height:
row_k = 2 * (image_height - 1) - row_k
if col_k < 0:
col_k = np.abs(col_k)
elif col_k >= image_height:
col_k = 2 * (image_width - 1) - col_k
pix += image[row_k, col_k] * kernel[i, j]
weight += kernel[i, j]
else:
raise ValueError('border must be one of '
'[transparent, zero, copy, reflect]')
res[row, col] = np.float32(pix / weight)
res = np.uint8(np.clip(np.round(res), 0, 255))
return res
def main_gaussian_blur():
radius = 20
sigma_x = 10.0
sigma_y = 10.0
borders = ['transparent', 'zero', 'copy', 'reflect']
image = cv2.imread('lena_std.bmp')
kernel = gaussian_2d(radius, sigma_x, sigma_y, d_radius=1)
for border in borders:
blur_image = convolve_2d(image, kernel, border)
cv2.imwrite('blur_radius=%d,sigma=(%0.1f,%0.1f),border=%s.png' % (
radius, sigma_x, sigma_y, border), blur_image)
上面代碼僅用于示例計(jì)算流程,因?yàn)閜ython的for循環(huán)特別慢,所以只要kernel稍微大那么一些,上面的代碼就要運(yùn)行非常非常久。由于圖像需要在兩個(gè)方向循環(huán),kernel也需要在兩個(gè)方向循環(huán),所以整個(gè)下來計(jì)算量是o(n^4),特別慢。
除了常規(guī)的圖像高斯模糊外,上面代碼還示例了幾種邊界條件:
- transparent:超出邊界的索引不參與計(jì)算,此種邊界條件必需累加weight,最后用累加得到的weight做歸一化。這種邊界條件因?yàn)楸仨氁獙?duì)weight做累加,所以計(jì)算量略大一些些,但效果是幾種邊界條件中最好最穩(wěn)定的。
- zero:超出邊界的索引用0代替。因?yàn)?代表黑色,隨著kernel增大,越來越多的0參與到了邊界的模糊中,所以這種邊界條件會(huì)引起黑邊,效果最差。但是對(duì)于并行加速來講,其實(shí)現(xiàn)是比較容易的。在神經(jīng)網(wǎng)絡(luò)的卷積中使用非常多,但是傳統(tǒng)圖像處理中盡量避免用這種邊界條件。
- copy:超出邊界的索引使用最近的邊緣像素值,效果中規(guī)中矩,實(shí)現(xiàn)也簡(jiǎn)單。
- reflect:超出邊界的索引,以邊界為對(duì)稱中心取圖像內(nèi)部對(duì)應(yīng)位置的像素值,索引的計(jì)算會(huì)有些麻煩,特別是當(dāng)kernel尺寸非常大時(shí),以至于鏡像的索引超過了對(duì)面的邊界。但是當(dāng)kernel尺寸不那么極端時(shí),這種邊界條件的效果一般還不錯(cuò)。
下圖是4中邊界條件的效果,radius=20,sigma=10。
左上:transparent,右上:zero
左下:copy, 右下:reflect
行列分離的高斯模糊
高斯模糊使用嵌套循環(huán)顯然是很差勁的寫法。在實(shí)踐中,一般我們會(huì)寫成行列分離的形式,此時(shí)計(jì)算量從o(n^4) 降低到 o(n^3)。
上面我們寫過二維高斯核函數(shù)為:
G
(
x
,
y
)
=
e
?
(
x
2
2
σ
x
2
+
y
2
2
σ
y
2
)
G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})}
G(x,y)=e?(2σx2?x2?+2σy2?y2?)
注意此函數(shù)可以拆開寫成x,y分離的形式。
記
G
(
x
)
=
e
?
x
2
2
σ
x
2
G
(
y
)
=
e
?
y
2
2
σ
y
2
G(x) = e^{-\frac{x^2 }{2\sigma_x^2}} \\[2ex] G(y) = e^{-\frac{y^2 }{2\sigma_y^2}}
G(x)=e?2σx2?x2?G(y)=e?2σy2?y2?
那么有:
G
(
x
,
y
)
=
e
?
x
2
2
σ
x
2
?
e
?
y
2
2
σ
y
2
=
G
(
x
)
G
(
y
)
G(x,y) = e^{-\frac{x^2 }{2\sigma_x^2}} \cdot e^{-\frac{y^2 }{2\sigma_y^2}}=G(x)G(y)
G(x,y)=e?2σx2?x2??e?2σy2?y2?=G(x)G(y)
然后一個(gè)高斯模糊的計(jì)算過程為:
b
l
u
r
(
x
,
y
)
=
∫
∫
I
(
x
,
y
)
G
(
x
,
y
)
d
x
d
y
=
∫
∫
I
(
x
,
y
)
G
(
x
)
G
(
y
)
d
x
d
y
=
∫
[
∫
I
(
x
,
y
)
G
(
x
)
d
x
]
?
G
(
y
)
d
y
(
寫成離散形式
)
=
∑
y
i
[
∑
x
i
I
(
x
i
,
y
i
)
G
(
x
i
)
]
G
(
y
i
)
\begin{aligned} blur(x,y) &= \int \int I(x,y)G(x,y)dxdy \\[2ex] &= \int \int I(x,y)G(x)G(y)dxdy \\[2ex] &=\int [\int I(x,y)G(x)dx] \cdot G(y)dy \\[2ex] & (寫成離散形式) \\[2ex] & =\sum_{y_i} [\sum_{x_i} I(x_i,y_i)G(x_i)]G(y_i) \end{aligned}
blur(x,y)?=∫∫I(x,y)G(x,y)dxdy=∫∫I(x,y)G(x)G(y)dxdy=∫[∫I(x,y)G(x)dx]?G(y)dy(寫成離散形式)=yi?∑?[xi?∑?I(xi?,yi?)G(xi?)]G(yi?)?
也就是說在x方向進(jìn)行模糊,然后對(duì)這個(gè)中間結(jié)果在y方向再做模糊即可,每次只在一個(gè)方向做循環(huán),并且這兩個(gè)循環(huán)不是嵌套,而是分離的。下面代碼沒有再實(shí)現(xiàn)多種邊界條件,只寫了transparent類型的。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
def gaussian_1d(radius, sigma, d_radius=1):
xx = np.arange(-radius, radius + d_radius, d_radius)
res = np.exp(-xx * xx / (sigma * sigma) / 2)
res = res / np.sum(res)
return res
def convolve_2d_xy(image, kernel_x, kernel_y):
image_height, image_width = image.shape[:2]
kernel_x_len = len(kernel_x)
kernel_y_len = len(kernel_y)
radius_x = kernel_x_len // 2
radius_y = kernel_y_len // 2
# check: kernel_height and kernel_width must be odd
if radius_x * 2 + 1 != kernel_x_len or radius_y * 2 + 1 != kernel_y_len:
raise ValueError('kernel size must be odd')
res = np.zeros_like(image, dtype=np.float32)
# convolve in x direction
for row in range(image_height):
for col in range(image_width):
pix = 0.0
weight = 0
for j in range(kernel_x_len):
col_k = col + j - radius_x
if 0 <= col_k < image_width:
pix += image[row, col_k] * kernel_x[j]
weight += kernel_x[j]
res[row, col] = pix / weight
# convolve in y direction
image = res.copy()
for col in range(image_width):
for row in range(image_height):
pix = 0.0
weight = 0
for i in range(kernel_y_len):
row_k = row + i - radius_y
if 0 <= row_k < image_height:
pix += image[row_k, col] * kernel_y[i]
weight += kernel_y[i]
res[row, col] = pix / weight
res = np.uint8(np.clip(np.round(res), 0, 255))
return res
def main_gaussian_blur_xy():
radius = 20
sigma_x = 10.0
sigma_y = 10.0
image = cv2.imread('lena_std.bmp')
kernel_x = gaussian_1d(radius, sigma_x, d_radius=1)
kernel_y = gaussian_1d(radius, sigma_y, d_radius=1)
blur_image = convolve_2d_xy(image, kernel_x, kernel_y)
cv2.imwrite('xy_blur_radius=%d,sigma=(%0.1f,%0.1f).png' % (
radius, sigma_x, sigma_y), blur_image)
行列分離的耗時(shí)在我電腦上大概是45s,嵌套循環(huán)大概需要1020秒。盡管python的循環(huán)特別慢,這個(gè)時(shí)間對(duì)比可能做不得數(shù),但是量級(jí)上的差異還是比較能說明問題了。
下面左圖是之前嵌套循環(huán)得到的結(jié)果,中間是行列分離得到的結(jié)果,右圖是兩者的差的絕對(duì)值再乘以50,結(jié)果來看兩個(gè)模糊的結(jié)果完全一樣。
加上step的高斯模糊
當(dāng)模糊的radius非常大時(shí)候,可以適當(dāng)加上一些step來節(jié)省計(jì)算。
此時(shí)應(yīng)當(dāng)注意,為了達(dá)到預(yù)想中的模糊效果,不僅image patch與kernel乘加時(shí)的索引需要加上step,kernel系數(shù)的計(jì)算也需要加上step,不然與不加step時(shí)候的系數(shù)會(huì)有很大差異。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import time
def gaussian_1d(radius, sigma, d_radius=1.0):
xx = np.arange(-radius, radius + d_radius, d_radius)
res = np.exp(-xx * xx / (sigma * sigma) / 2)
res = res / np.sum(res)
return res
def convolve_2d_xy_step(image, kernel_x, kernel_y, step):
image_height, image_width = image.shape[:2]
kernel_x_len = len(kernel_x)
kernel_y_len = len(kernel_y)
radius_x = kernel_x_len // 2
radius_y = kernel_y_len // 2
# check: kernel_height and kernel_width must be odd
if radius_x * 2 + 1 != kernel_x_len or radius_y * 2 + 1 != kernel_y_len:
raise ValueError('kernel size must be odd')
res = np.zeros_like(image, dtype=np.float32)
# convolve in x direction
for row in range(image_height):
for col in range(image_width):
pix = 0.0
weight = 0
for j in range(kernel_x_len):
col_k = int(round(col + (j - radius_x) * step))
if 0 <= col_k < image_width:
pix += image[row, col_k] * kernel_x[j]
weight += kernel_x[j]
res[row, col] = pix / weight
# convolve in y direction
image = res.copy()
for col in range(image_width):
for row in range(image_height):
pix = 0.0
weight = 0
for i in range(kernel_y_len):
row_k = int(round(row + (i - radius_y) * step))
if 0 <= row_k < image_height:
pix += image[row_k, col] * kernel_y[i]
weight += kernel_y[i]
res[row, col] = pix / weight
res = np.uint8(np.clip(np.round(res), 0, 255))
return res
def main_gaussian_blur_xy():
radius = 20
sigma_x = 10.0
sigma_y = 10.0
step = 4.0
image = cv2.imread('lena_std.bmp')
kernel_x = gaussian_1d(radius, sigma_x, d_radius=step)
kernel_y = gaussian_1d(radius, sigma_y, d_radius=step)
blur_image = convolve_2d_xy_step(image, kernel_x, kernel_y, step)
cv2.imwrite('xy_blur_radius=%d,sigma=(%0.1f,%0.1f)_step.png' % (
radius, sigma_x, sigma_y), blur_image)
下面左圖是之前嵌套循環(huán)得到的結(jié)果,中間是行列分離+step=4得到的結(jié)果,右圖是兩者的差的絕對(duì)值再乘以50。
此時(shí)計(jì)算出來的diff已經(jīng)不再是全零,如果仔細(xì)看中間的結(jié)果,顯示出了一些塊狀紋理,表明模糊的品質(zhì)已經(jīng)開始受到影響。
任意二維高斯核的生成
這里任意高斯核指的是任意長(zhǎng)寬比,且存在旋轉(zhuǎn)角度的高斯核。
公式推導(dǎo)的思路是,把高斯核G(x,y)看做是一個(gè)圖像,然后用圖像旋轉(zhuǎn)的思路來改造二維高斯核的公式。
下面我們?cè)俅伟迅咚购斯綌[在這里。
G
(
x
,
y
)
=
e
?
(
x
2
2
σ
x
2
+
y
2
2
σ
y
2
)
G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})}
G(x,y)=e?(2σx2?x2?+2σy2?y2?)
根據(jù)圖像旋轉(zhuǎn)的原理,當(dāng)圖像中某個(gè)點(diǎn)(x, y)以圓心作為旋轉(zhuǎn)中心,沿逆時(shí)針旋轉(zhuǎn)后,新的點(diǎn)(x’, y’)的坐標(biāo)公式為:
{
x
′
=
x
?
c
o
s
θ
?
y
?
s
i
n
θ
y
′
=
x
?
s
i
n
θ
+
y
?
c
o
s
θ
\begin{cases} x' = x*cos\theta - y*sin\theta \\ y' = x*sin\theta + y*cos\theta \end{cases} \\[4ex]
{x′=x?cosθ?y?sinθy′=x?sinθ+y?cosθ?
將其代入高斯核公式,并且方便起見仍以(x, y)來表示,那么有:
G
(
x
,
y
)
=
e
?
(
(
x
?
c
o
s
θ
?
y
?
s
i
n
θ
)
2
2
σ
x
2
+
(
x
?
s
i
n
θ
+
y
?
c
o
s
θ
)
2
2
σ
y
2
)
=
e
?
(
x
2
?
c
o
s
2
θ
+
y
2
?
s
i
n
2
θ
?
2
x
y
?
s
i
n
θ
c
o
s
θ
2
σ
x
2
+
x
2
s
i
n
2
θ
+
y
2
?
c
o
s
2
θ
+
2
x
y
?
s
i
n
θ
c
o
s
θ
2
σ
y
2
)
=
e
?
(
x
2
(
c
o
s
2
θ
2
σ
x
2
+
s
i
n
2
θ
2
σ
y
2
)
+
x
y
(
?
s
i
n
2
θ
2
σ
x
2
+
s
i
n
2
θ
2
σ
y
2
)
+
y
2
(
s
i
n
2
θ
2
σ
x
2
+
c
o
s
2
θ
2
σ
y
2
)
)
\begin{aligned} G(x,y) &= e^{-(\frac{(x*cos\theta - y*sin\theta)^2 }{2\sigma_x^2} + \frac{(x*sin\theta + y*cos\theta)^2 }{2\sigma_y^2})} \\[2ex] &=e^{-(\frac{x^2*cos^2\theta + y^2*sin^2\theta -2xy*sin\theta cos\theta}{2\sigma_x^2} + \frac{x^2sin^2\theta + y^2*cos^2\theta + 2xy*sin\theta cos\theta }{2\sigma_y^2})} \\[2ex] &=e^{-(x^2(\frac{cos^2\theta}{2\sigma_x^2} + \frac{sin^2\theta}{2\sigma_y^2}) + xy(-\frac{sin2\theta}{2\sigma_x^2} + \frac{sin2\theta}{2\sigma_y^2}) + y^2( \frac{sin^2\theta}{2\sigma_x^2} + \frac{cos^2\theta}{2\sigma_y^2} ))} \end{aligned}
G(x,y)?=e?(2σx2?(x?cosθ?y?sinθ)2?+2σy2?(x?sinθ+y?cosθ)2?)=e?(2σx2?x2?cos2θ+y2?sin2θ?2xy?sinθcosθ?+2σy2?x2sin2θ+y2?cos2θ+2xy?sinθcosθ?)=e?(x2(2σx2?cos2θ?+2σy2?sin2θ?)+xy(?2σx2?sin2θ?+2σy2?sin2θ?)+y2(2σx2?sin2θ?+2σy2?cos2θ?))?文章來源:http://www.zghlxwxcb.cn/news/detail-640131.html
上述公式就是任意二維高斯核的樣子。
下面是python的實(shí)現(xiàn)代碼和圖,將一個(gè)沿橫向比較長(zhǎng)的kernel逆時(shí)針旋轉(zhuǎn)了20度。文章來源地址http://www.zghlxwxcb.cn/news/detail-640131.html
# -*- coding: utf-8 -*-
import cv2
import numpy as np
def generalized_gaussian_kernel(ksize,
sigma_x,
sigma_y=None,
angle=0.0):
"""
Generate generalized gaussian kernel.
Parameters
----------
ksize: kernel size, one integer or a list/tuple of two integers, must be
odd
sigma_x: standard deviation in x direction
sigma_y: standard deviation in y direction
angle: rotate angle, anti-clockwise
Returns
-------
kernel: generalized gaussian blur kernel
"""
# check parameters
if not isinstance(ksize, (tuple, list)):
ksize = [ksize, ksize]
else:
ksize = list(ksize)
ksize[0] = ksize[0] // 2 * 2 + 1
ksize[1] = ksize[1] // 2 * 2 + 1
if sigma_y is None:
sigma_y = sigma_x
# meshgrid coordinates
radius_x = ksize[0] // 2
radius_y = ksize[1] // 2
x = np.arange(-radius_x, radius_x + 1, 1)
y = np.arange(-radius_y, radius_y + 1, 1)
xx, yy = np.meshgrid(x, y)
# coefficients of coordinates
angle = angle / 180 * np.pi
cos_square = np.cos(angle) ** 2
sin_square = np.sin(angle) ** 2
sin_2 = np.sin(2 * angle)
alpha = 0.5 / (sigma_x * sigma_x)
beta = 0.5 / (sigma_y * sigma_y)
a = cos_square * alpha + sin_square * beta
b = -sin_2 * alpha + sin_2 * beta
c = sin_square * alpha + cos_square * beta
# generate and normalize kernel
kernel = np.exp(-(a * xx * xx + b * xx * yy + c * yy * yy))
kernel = kernel / np.sum(kernel)
return kernel
if __name__ == '__main__':
ksize = (511, 511)
sigma_x = 100.0
sigma_y = 20.0
angle = 20.0
gaussian_kernel = generalized_gaussian_kernel(ksize=ksize,
sigma_x=sigma_x,
sigma_y=sigma_y,
angle=angle)
gaussian_kernel = gaussian_kernel / np.max(gaussian_kernel)
gaussian_kernel = np.uint8(np.round(gaussian_kernel * 255))
cv2.imwrite('generalized_gaussian_kernel_1.png', gaussian_kernel)
到了這里,關(guān)于高斯模糊與圖像處理(Gaussian Blur)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!