PIL.ImageFilter.GaussianBlur使用什么内核?半径参数与标准差有什么关系?

6
在使用PIL读取图像后,我通常会使用scipy.ndimage执行高斯滤波,如下所示。
import PIL
from scipy import ndimage

PIL_image = PIL.Image.open(filename)
data = PIL_image.getdata()
array = np.array(list(data)).reshape(data.size[::-1]+(-1,))
img = array.astype(float)
fimg = ndimage.gaussian_filter(img, sigma=sigma, mode='mirror', order=0)

PIL 中有高斯模糊函数,如下所示(参考这个答案),但我不知道它是如何工作的,使用了什么内核:

from PIL import ImageFilter
fimgPIL = PIL_image.filter(ImageFilter.GaussianBlur(radius=r)

此文档未提供细节信息

PIL.ImageFilter.GaussianBlur 相关问题:

  1. 半径参数到底是什么,是否相当于标准差 σ?
  2. 对于给定的半径,它计算卷积核的范围有多远? 2σ? 3σ? 6σ?

这个评论 是关于 Gaussian Blur - standard deviation, radius and kernel size 答案的,但我还没有找到PIL的信息。

OpenCV 使用的卷积核半径为 (sigma * 3),而scipy.ndimage.gaussian_filter 使用的卷积核半径为 int(4 * sigma + 0.5)

2个回答

8
根据源代码,看起来PIL.ImageFilter.GaussianBlur使用了PIL.ImageFilter.BoxBlur 。但是我无法弄清楚半径和sigma之间的关系。
我编写了一个脚本来检查scipy.ndimage.gaussian_filterPIL.ImageFilter.GaussianBlur之间的区别。
import numpy as np
from scipy import misc
from scipy.ndimage import gaussian_filter
import PIL
from PIL import ImageFilter
import matplotlib.pyplot as plt


# Load test color image
img = misc.face()

# Scipy gaussian filter
sigma = 5
img_scipy = gaussian_filter(img, sigma=(sigma,sigma,0), mode='nearest')

# PIL gaussian filter
radius = 5
PIL_image = PIL.Image.fromarray(img)
img_PIL = PIL_image.filter(ImageFilter.GaussianBlur(radius=radius))
data = img_PIL.getdata()
img_PIL = np.array(data).reshape(data.size[::-1]+(-1,))
img_PIL = img_PIL.astype(np.uint8)

# Image difference
img_diff = np.abs(np.float_(img_scipy) - np.float_(img_PIL))
img_diff = np.uint8(img_diff)

# Stats
mean_diff = np.mean(img_diff)
median_diff = np.median(img_diff)
max_diff = np.max(img_diff)

# Plot results
plt.subplot(221)
plt.imshow(img_scipy)
plt.title('SciPy (sigma = {})'.format(sigma))
plt.axis('off')

plt.subplot(222)
plt.imshow(img_PIL)
plt.title('PIL (radius = {})'.format(radius))
plt.axis('off')

plt.subplot(223)
plt.imshow(img_diff)
plt.title('Image difference \n (Mean = {:.2f}, Median = {:.2f}, Max = {:.2f})'
          .format(mean_diff, median_diff, max_diff))
plt.colorbar()
plt.axis('off')

# Plot histogram
d = img_diff.flatten()
bins = list(range(int(max_diff)))

plt.subplot(224)
plt.title('Histogram of Image difference')

h = plt.hist(d, bins=bins)
for i in range(len(h[0])):
    plt.text(h[1][i], h[0][i], str(int(h[0][i])))


sigma=5, radius=5 的输出结果为:enter image description here

sigma=30, radius=30 的输出结果为:enter image description here

scipy.ndimage.gaussian_filterPIL.ImageFilter.GaussianBlur 的输出结果非常相似,差异可以忽略不计。超过95%的差异值小于等于2。

PIL版本:7.2.0,SciPy版本:1.5.0


1
好的,我猜想问题出在高斯核截断的大小和/或形状上。最快的测试方法可能是处理一个除了中心有一个像素之外全是零的图像。 - uhoh
好主意!记得告诉我你的发现。 - Nirmal
我已经发布了一份补充分析,感谢您的帮助! - uhoh

4

这是对@Nimal的已接受答案的补充回答。

基本上,半径参数就像sigma。我不会深入探讨,但我认为高斯核在内部略有不同,以便在舍入回整数后保持归一化,因为PIL方法返回0到255个整数级别。

下面的脚本生成一个图像,在左侧为1,在右侧为0,然后使用两种方法进行sigma = 10像素模糊处理,然后绘制每个中心水平线及其差异。我做两次差异,因为log只能显示正差异。

第一个面板是PIL和SciPy浮点结果之间的差异,第二个是截断的整数SciPy结果,第三个是四舍五入的SciPy结果。

enter image description here

import numpy as np
import matplotlib.pyplot as plt
import PIL
from scipy.ndimage import gaussian_filter
from PIL import ImageFilter

import PIL

sigma = 10.0
filename = 'piximg.png'

# Save a PNG with a central pixel = 1
piximg = np.zeros((101, 101), dtype=float)
piximg[:, :50] = 1.0
plt.imsave(filename, piximg, cmap='gray')

# Read with PIL
PIL_image = PIL.Image.open(filename)

# Blur with PIL
img_PIL = PIL_image.filter(ImageFilter.GaussianBlur(radius=sigma)) 
data = img_PIL.getdata()
img_PIL = np.array(list(data)).reshape(data.size[::-1]+(-1,))
g1 = img_PIL[..., 1]

# Blur with SciPy
data = PIL_image.getdata()
array = np.array(list(data)).reshape(data.size[::-1]+(-1,))
img = array.astype(float)
fimg = gaussian_filter(img[...,:3], sigma=sigma, mode='mirror', order=0)
g2 = fimg[..., 1]
g2u = np.uint8(g2)
g2ur = np.uint8(g2+0.5)

if True:
    plt.figure()
    plt.subplot(3, 1, 1)
    plt.plot(g1[50])
    plt.plot(g2[50])
    plt.plot(g2[50] - g1[50])
    plt.plot(g1[50] - g2[50])
    plt.yscale('log')
    plt.ylim(0.1, None)
    plt.subplot(3, 1, 2)
    plt.plot(g1[50])
    plt.plot(g2u[50])
    plt.plot(g2u[50] - g1[50])
    plt.plot(g1[50] - g2u[50])
    plt.yscale('log')
    plt.ylim(0.1, None)
    plt.subplot(3, 1, 3)
    plt.plot(g1[50])
    plt.plot(g2ur[50])
    plt.plot(g2ur[50] - g1[50])
    plt.plot(g1[50] - g2ur[50])
    plt.yscale('log')
    plt.ylim(0.1, None)
    plt.show()

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