10

I've got an image that I apply a Gaussian Blur to using both cv2.GaussianBlur and skimage.gaussian_filter libraries, but I get significantly different results. I'm curious as to why, and what can be done to make skimage look more like cv2. I know skimage.gaussian_filter is a wrapper around scipy.scipy.ndimage.filters.gaussian_filter. To clearly state the question, why are the two functions different and what can be done to make them more similar?

Here is my test image:

Original Image

Here is the cv2 version (appears blurrier):

cv2 image

Here is the skimage/scipy version (appears sharper):

skimage version

Details:

skimage_response = skimage.filters.gaussian_filter(im, 2, multichannel=True, mode='reflect')

cv2_response = cv2.GaussianBlur(im, (33, 33), 2)

So sigma=2 and the size of the filter is big enough that it shouldn't make a difference. Imagemagick covnert -gaussian-blur 0x2 visually agrees with cv2.

Versions: cv2=2.4.10, skimage=0.11.3, scipy=0.13.3

Community
  • 1
  • 1
waldol1
  • 1,841
  • 2
  • 18
  • 22

5 Answers5

7

If anyone is curious about how to make skimage.gaussian_filter() match Matlab's equivalent imgaussfilt() (the reason I found this question), pass the parameter 'truncate=2' to skimage.gaussian_filter(). Both skimage and Matlab calculate the kernel size as a function of sigma. Matlab's default is 2. Skimage's default is 4, resulting in a significantly larger kernel by default.

user2348114
  • 159
  • 1
  • 7
  • 3
    Good to know but this is not really an answer. Sometimes I abuse the site posting a comment as an answer because the text would not fit a comment or because the comment includes code and the comment system does not handle well longer snippets of code - but I guess your text fits a comment. – Paulo Scardine Jan 23 '18 at 19:39
5

These two are equal:

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)

The whole Gaussian kernel is defined by sigma only. But which part of gaussian kernel do you use to blur the image is defined by truncate (in skimage) or ksize (in opencv).

SDJ
  • 4,083
  • 1
  • 17
  • 35
Haotao Wang
  • 341
  • 2
  • 7
  • If I use the above, if truncate is 4.0 and sigma is 5.0, this may not work, right? Then it looks like the kernel size should be (0.25, 0.25)? But opencv requires the ksize to be positive and odd. – dhamechaSpeaks Sep 25 '20 at 23:46
  • @dhamechaSpeaks The ksize is required to be positive and odd because the kernel has to be symmetric. If ksize=5, then there are 2 pixels on each side. In this case, the truncate parameter in skimage should be set to (ksize-1)/2/sigma = 2/sigma – Haotao Wang Oct 01 '20 at 19:50
4

For GaussianBlur, you are using a rather large kernel (size=33), which causes a lot of smoothing. Smoothing will depend drastically on you kernel size. With your parameters each new pixel value is "averaged" in a 33*33 pixel "window".

A definition of cv2.GaussianBlur can be found here http://docs.opencv.org/3.1.0/d4/d13/tutorial_py_filtering.html#gsc.tab=0

In contrast, skimage.filters.gaussian seems to work on a smaller kernel. In skimage, the "size" is defined by sigma which is related to kernel size as described here: https://en.wikipedia.org/wiki/Gaussian_filter

Definition can be found here: http://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian

In order to get corresponding results, you'd have to work with a smaller kernel for OpenCV.

Furthermore, for both libraries, I'd strongly recommend to use up to date library versions.

tfv
  • 6,016
  • 4
  • 36
  • 67
  • 2
    The amount of smoothing is controlled by sigma, not the size. Pixels are not straight averaged, they are weighted averaged by a Gaussian kernel. Size just truncates computation and skimage computes a size that's 4*sigma. The versions shouldn't be the issue. This is old and basic functionality. – waldol1 Mar 28 '16 at 18:14
  • As far as I can read here, you are using a deprecated funtion gaussian_filter http://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian_filter , but you are correct that this is basic functionality. You are also corrrect that sigma correlates to kernel size. However, I do not understand why a siga of 2 you are using in scikit should be equivalent to a kernel size of 33 in OpenCV. – tfv Mar 28 '16 at 18:24
  • I am no mathematician, but the way I read https://en.wikipedia.org/wiki/Gaussian_filter , [quote] "A gaussian kernel requires 6{\sigma}-1 values, e.g. for a {\sigma} of 3 it needs a kernel of length 17". This would mean that your sima=2 is equivalent to a kernel of size 6*2-1=11. Sorry, I am no expert on this, but you may want to review your size assumtion. – tfv Mar 28 '16 at 18:24
  • 2
    Mathematically, a Gaussian kernel has infinite size, just the values far away from the center are so small that they can be ignored. The function gaussian_filter is deprecated, but I suspect that it is a name change only because they both just wrap the scipy filter. Scipy makes the size 8 * sigma + 1 (or 4 * sigma * 2 sides + 1 center), and opencv does something similar, but adding precision by a larger size shouldn't make it more or less blurry visually. – waldol1 Mar 28 '16 at 20:53
  • Yes, this answer is not correct. You can have as large a kernel as you like, but sigma controls the weights in the kernel. A sigma value of 1.0 and a kernel size of, say 99, should give the same result as sigma=1.0 and a kernel size of 9. It's just that the larger kernel will contain mostly zeroes. – Andrew Marshall Dec 09 '21 at 15:44
1

According to [Scipy0.15.1 API][1]:

scipy.ndimage.filters(img, sigma=sigma, truncate = 4.0)

It setsthe Gauss filter with the kernel size in truncate * sigma. In this understanding, the following two fuctionc will give the same results on gray scale image:

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)

Some Test results: trunc_val = 3; sigma_val = 3 enter image description here

trunc_val = 3; sigma_val = 1 enter image description here

trunc_val = 3; sigma_val = 9 enter image description here

Zimeng Zhao
  • 115
  • 1
  • 11
  • Double check my eyes, but the last pair of images don't appear to be identical. Try it with my test image found in the OP. – waldol1 Aug 18 '22 at 17:12
1

Both opencv and scipy allow specifying sigma which has identical meaning in both libraries. The kernel size is determined differently:

So to get identical results you need to explicitly specify both the kernel size and 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

The remaining differences (see 4th plot) are caused by floating point calcuation and different resulting datatype (uint8 vs float64).

Stef
  • 28,728
  • 2
  • 24
  • 52