为什么cv2和skimage的高斯滤波器不同?

10
我有一张图像,使用cv2.GaussianBlurskimage.gaussian_filter库都应用了高斯模糊,但结果显着不同。我想知道为什么,并且可以做些什么使skimage看起来更像cv2。我知道skimage.gaussian_filterscipy.scipy.ndimage.filters.gaussian_filter的包装器。明确地说,为什么这两个函数不同,如何使它们更相似?以下是我的测试图像:Original Image
这是cv2版本(看起来更模糊):cv2 image
这是skimage/scipy版本(看起来更锐利):skimage version
细节:sigma为2,滤波器大小足够大,不应该有太大差异。Imagemagick的convert -gaussian-blur 0x2cv2视觉上是一致的。

版本:cv2=2.4.10,skimage=0.11.3,scipy=0.13.3


看起来 skimage 的版本只在垂直方向上模糊。这可能是 skimage 的那个版本中的一个错误,因为现在它已经不再这样做了。 - undefined
5个回答

6

如果有人想知道如何让skimage.gaussian_filter()与Matlab的等效imgaussfilt()匹配(我找到这个问题的原因),可以将参数“truncate=2”传递给skimage.gaussian_filter()。Matlab和skimage都会根据sigma计算内核大小。Matlab的默认值为2,而skimage的默认值为4,因此默认情况下内核显着更大。


3
好知道,但这并不是真正的答案。有时我会滥用该网站,将评论发布为答案,因为文本无法适应评论,或者因为评论包含代码,而评论系统无法处理更长的代码片段 - 但我想你的文本适合作为评论。 - Paulo Scardine

4

这两个是相等的:

gau_img = cv2.GaussianBlur(img, (5,5), 10.0) # 5*5 kernal, 2 on each side. 2 = 1/5 * 10 = 1/5 * sigma
gau_img = skimage.filters.gaussian(img, sigma=10, truncate=1/5)

整个高斯核只由sigma定义。但是哪部分高斯核用于模糊图像由truncate(在skimage中)或ksize(在opencv中)定义。

如果我使用上述方法,如果截断值为4.0,标准差为5.0,这样可能行不通,对吧?那么看起来核大小应该是(0.25,0.25)?但是OpenCV要求ksize为正奇数。 - dhamechaSpeaks
@dhamechaSpeaks ksize 必须是正奇数,因为核必须对称。如果 ksize=5,则每侧有 2 个像素。在这种情况下,skimage 中的截断参数应设置为 (ksize-1)/2/sigma = 2/sigma。 - Haotao Wang
如果你在1/5的标准差处截断高斯分布,那么你就不再拥有高斯分布了。为了保留一些高斯分布的特性,truncate至少应该设置为2.0,但是只有将其设置为至少3.0,你才能获得完整的高斯分布。请参考此链接:https://www.crisluengo.net/archives/695/ - undefined

3
对于GaussianBlur,您使用的是相当大的内核(size=33),这会导致很多平滑。平滑将严重依赖于您的内核大小。在您的参数中,每个新像素值都在33*33像素“窗口”中“平均”。
cv2.GaussianBlur的定义可以在此处找到http://docs.opencv.org/3.1.0/d4/d13/tutorial_py_filtering.html#gsc.tab=0 相比之下,skimage.filters.gaussian似乎使用较小的内核。在skimage中,“大小”由与内核大小相关的sigma定义,如此处所述:https://en.wikipedia.org/wiki/Gaussian_filter 定义可以在此处找到:http://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian 为了获得相应的结果,您需要使用更小的内核来处理OpenCV。
此外,对于这两个库,我强烈建议使用最新的库版本。

3
平滑度的程度由sigma控制,而不是大小。像素不是直接平均,而是通过高斯核进行加权平均。大小只是截断计算,skimage计算的大小为4 * sigma。版本不应该是问题。这是旧的和基本的功能。 - waldol1
据我所看,您在使用一个已弃用的函数gaussian_filter http://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian_filter,但您是正确的,这是基本功能。您也正确地指出sigma与内核大小相关。然而,我不明白为什么在scikit中使用2的sigma应该等同于OpenCV中33的内核大小。 - tfv
我不是数学家,但我阅读 https://en.wikipedia.org/wiki/Gaussian_filter 的方式是这样的:[引用]“高斯核需要6{\sigma}-1个值,例如对于{\sigma}为3,它需要长度为17的核”。这意味着您的sima=2相当于大小为6*2-1=11的核。很抱歉,我对此不是专家,但您可能需要重新审查您的大小假设。 - tfv
3
从数学上讲,高斯核具有无限大小,只是远离中心的值太小,可以忽略不计。函数gaussian_filter已被弃用,但我怀疑这只是一个名称更改,因为它们都只是包装了scipy过滤器。Scipy将大小设置为8 * sigma + 1(或4 * sigma * 2边+1中心),opencv做类似的事情,但通过增加精度来增加大小不应使其在视觉上更模糊或更清晰。 - waldol1
是的,这个答案不正确。你可以有任意大的核,但是sigma控制着核中的权重。例如,sigma值为1.0和核大小为99的结果应该与sigma=1.0和核大小为9的结果相同。只是较大的核将包含大部分零。 - Andrew Marshall

2
"opencv"和"scipy"都允许指定"sigma",在两个库中具有相同的含义。但是内核大小的确定方式不同:
- 在"scipy"中,它是从"truncate"参数派生的,如下所示:int(truncate * sigma + 0.5) - 在"opencv"中,它可以独立于"sigma"指定(如果省略,则从sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8计算)。
因此,为了获得相同的结果,您需要显式指定内核大小和sigma。
import cv2
from skimage.filters import gaussian
import matplotlib.pyplot as plt

img = cv2.imread('bg4dZ.png', cv2.IMREAD_GRAYSCALE)

truncate = 4
sigma = 2
radius = int(truncate * sigma + 0.5)
ksize = 2 * radius + 1

opencv = cv2.GaussianBlur(img, (ksize, ksize), sigma, borderType=cv2.BORDER_REFLECT)
scipy = gaussian(img, sigma, truncate=truncate, preserve_range=True, mode='reflect')

fig, axs = plt.subplots(ncols=4, layout='constrained', figsize=(16, 4))
axs[0].imshow(img, cmap='gray')
axs[1].imshow(opencv, cmap='gray')
axs[2].imshow(scipy, cmap='gray')
diff = opencv - scipy
diff = axs[3].imshow(diff, cmap='seismic', vmin=diff.min(), vmax=-diff.min())
fig.colorbar(diff, shrink=.95)
for ax in axs:
    ax.set_axis_off()

enter image description here

剩余的差异(见第4个图)是由浮点计算和不同的结果数据类型(uint8 vs float64)引起的。

0
根据 [Scipy0.15.1 API][1] 的说明:
scipy.ndimage.filters(img, sigma=sigma, truncate = 4.0)

它使用截断 * 标准差的核大小设置高斯滤波器。在这种理解下,以下两个函数将在灰度图像上产生相同的结果:
trunc_val = 3
sigma_val = 3
k_size = int(sigma_val * trunc_val)
gau_img1 = cv2.GaussianBlur(img, (k_size,k_size), sigma_val)
gau_img2 = gaussian_filter(img, sigma = sigma_val, truncate = trunc_val) 

cv2.imshow("cv2 res", gau_img1)
cv2.imshow("scipy res", gau_img2)
cv2.waitKey(-1)

一些测试结果: trunc_val = 3; sigma_val = 3 输入图像描述

trunc_val = 3; sigma_val = 1 输入图像描述

trunc_val = 3; sigma_val = 9 输入图像描述


请再仔细看一下,但最后一对图像似乎不完全相同。请使用我在原始帖子中提供的测试图像进行尝试。 - waldol1
这个ksize的大小远远不足以包含完整的高斯函数。skimage使用ksize = 2 * int(truncate * sigma + 0.5) + 1 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接