模糊后恢复原始图像

时间:2018-12-22 18:31:48

标签: python matlab image-processing scipy convolution

我有以下代码将高斯滤波器应用于任意图像25次。每次应用滤镜时,生成的图像都会被标准化。

kernel = np.array([[1.0,2.0,1.0],
                  [2.0,4.0,2.0],
                  [1.0,2.0,1.0]])

for i in range(25):
    # handle each component separately
    img[:,:,0] = convolve(img[:,:,0], kernel, mode='same')
    img[:,:,1] = convolve(img[:,:,1], kernel, mode='same')
    img[:,:,2] = convolve(img[:,:,2], kernel, mode='same')
    img = img / 16 # normalize

逆转此过程的最佳方法是什么?即如果我的图像模糊(执行上述代码的结果),并且想要获取原始图像。

编辑1:

示例

原始内容:original

模糊:blurred

编辑2:

试图重现克里斯的答案

我安装了dipimage_2.9。我正在将macOS 10.14.2Matlab R2016a一起使用。

花了我一段时间才能指定卷积的边界条件,因为DIPimage的convolve.m仅接受image_inkernel args。我最终为此使用了dip_setboundaryDIPimage User Manual第9.2节)。

这是代码(我只是简单地添加了dip_setboundarycut的作物区域的起源):

% Get data
a = readim('https://i.stack.imgur.com/OfSx2.png'); % using local path in real code
a = a{1}; % Keep only red channel

%% Create kernel
kernel = [1.0,2.0,1.0
          2.0,4.0,2.0
          1.0,2.0,1.0] / 16;
tmp = deltaim((size(kernel)-1)*25+1);
dip_setboundary('add_zeros');
for ii=1:25
    tmp = convolve(tmp,kernel);
end
kernel = tmp;

%% Apply convolution
dip_setboundary('periodic');
b = convolve(a,kernel);
dip_setboundary('symmetric'); % change back to default
% Find inverse operation
%   1- pad stuff so image and kernel have the same size
%      we maintain the periodic boundary condition for image b
b = repmat(b,ceil(imsize(kernel)./imsize(b)));
kernel = extend(kernel,imsize(b));
%   2- apply something similar to Wiener deconvolution
c = real(ift(ft(b)/(ft(kernel)+1e-6))); % Not exactly Wiener, but there's no noise!
%   3- undo padding
c = cut(c,imsize(a), [0, 0]); % upper left corner

这是生成的图像c

after convolution

2 个答案:

答案 0 :(得分:1)

您不能-通过求平均模糊模糊信息。

考虑一维示例:

[1  2  1]   on    [1,2,3,4,5,6,7]  assuming 0 for missing "pixel" on convolution

产生[4, 8, 12, 16, 20, 24, 20] 8 可能来自[1,2,3],也可能来自[2,1,4]-因此您已经有了2种不同的解决方案。无论您采用哪种方法,都会影响 12 的来源。

这是一个过于简单的示例-您可以解决这个问题-但在图像处理中,您可能需要处理3000 * 2000像素和2x 3x3、5x5、7x7等的2维卷积,从而使反转无法实现。 >

使这二维可以可能用数学方法求解-但是,如果将其应用于二维,则通常不会得到许多解决方案和非常复杂的约束来求解它卷积和3000 * 2000像素。

答案 1 :(得分:1)

假设img是一个灰度图像,让我们看一下单个通道中的代码-这里的所有内容都可以在每个通道中应用,因此我们不需要重复三遍:

for i in range(25):
    img = ndimage.convolve(img, kernel)
    img = img / 16 # normalize

我们将在一分钟内消除卷积。首先,让我们简化所应用的操作。

简化处理

上面与(在数字精度内)相同:

kernel = kernel / 16 # Normalize
for i in range(25):
    img = ndimage.convolve(img, kernel)

只要img不是发生裁剪和/或舍入的某种整数类型,这是正确的。通常,使用*卷积和C一些常数,

g = C (f * h) = f * (C h)

接下来,我们知道应用25次卷积与对复合内核应用一次卷积相同,

g = (((f * h) * h) * h) * h = f * (h * h * h * h)

我们如何获得复合内核?将卷积应用于全为零且中间像素为1的图像时,将再次产生核,因此

delta = np.zeros(kernel.shape)
delta[delta.shape[0]//2, delta.shape[1]//2] = 1
kernel2 = ndimage.convolve(delta, kernel)
kernel2 == kernel # is true everywhere, up to numerical precision

因此,以下代码找到用于平滑问题图像的内核:

kernel = np.array([[1.0,2.0,1.0],
                  [2.0,4.0,2.0],
                  [1.0,2.0,1.0]]) / 16
delta = np.zeros(((kernel.shape[0]-1)*25+1, (kernel.shape[1]-1)*25+1))
delta[delta.shape[0]//2, delta.shape[1]//2] = 1
for i in range(25):
    delta = ndimage.convolve(delta, kernel)
kernel = delta

由于中心极限定理,该内核与高斯内核非常相似。

现在我们可以通过一次卷积获得与问题相同的输出:

output = ndimage.convolve(img, kernel)

反转卷积

逆滤波的过程称为反卷积。从理论上讲,这是一个非常琐碎的过程,但是在实践中,由于噪音,内核知识不准确等原因,这非常困难。

我们知道我们可以通过傅立叶域来计算卷积:

output = np.convolve(img, kernel, mode='wrap')

相同
output = np.real(np.fft.ifft2( np.fft.fft2(img) * np.fft.fft2(np.fft.ifftshift(kernel)) ))

(假设kernelimg的大小相同,我们通常必须先用零填充)。当使用convolve时,图像如何扩展到其边界之外,这导致了空间和频域运算结果之间的任何差异。傅里叶方法假设周期性边界条件,这就是为什么我在这里使用'wrap'模式进行卷积的原因。

逆运算只是傅立叶域中的除法运算

img = np.real(np.fft.ifft2( np.fft.fft2(output) / np.fft.fft2(np.fft.ifftshift(kernel)) ))

要执行此操作,我们需要知道kernel的确切值,并且在此过程中不应添加任何噪声。对于如上所述计算的output,这应该在理论上给出确切的结果

但是,对于某些频率分量,某些内核可能恰好为零(即np.fft.fft2(np.fft.ifftshift(kernel))包含零)。这些频率无法恢复,除以0将导致NaN值在逆变换中扩展到整个图像,逆图像将全部为NaN。

对于高斯内核,不存在零,因此不应发生这种情况。但是,会有许多频率几乎为零。 output的傅立叶变换因此对于这些元素也将具有很小的价值。然后,逆过程就是将一个很小的值除以另一个很小的值,从而导致数值精度问题。

您将看到,如果只有很少的噪声,此过程将如何大大增强这种噪声,从而几乎完全由该噪声提供输出。

维纳反卷积包括正则化,以防止出现噪声和数值不精确性这些问题。基本上,您可以通过向kernel的傅里叶变换中添加正值来防止极小数的除法。 Wikipedia很好地描述了维纳反卷积。

演示

我在这里使用带有DIPimage 3的MATLAB进行快速演示(与启动Python并弄清楚如何完成所有这些工作相比,对我来说要少得多)。这是代码:

% Get data
a = readim('https://i.stack.imgur.com/OfSx2.png');
a = a{1}; % Keep only red channel

% Create kernel
kernel = [1.0,2.0,1.0
          2.0,4.0,2.0
          1.0,2.0,1.0] / 16;
tmp = deltaim((size(kernel)-1)*25+1);
for ii=1:25
    tmp = convolve(tmp,kernel,'add zeros');
end
kernel = tmp;

% Apply convolution
b = convolve(a,kernel,'periodic');

% Find inverse operation
%   1- pad stuff so image and kernel have the same size
%      we maintain the periodic boundary condition for image b
b = repmat(b,ceil(imsize(kernel)./imsize(b)));
kernel = extend(kernel,imsize(b));
%   2- apply something similar to Wiener deconvolution
c = ift(ft(b)/(ft(kernel)+1e-6),'real'); % Not exactly Wiener, but there's no noise!
%   3- undo padding
c = cut(c,imsize(a),'top left');

这是输出,顶部三分之一是输入图像,中间三分之一是模糊图像,底部三分之一是输出图像:

output of code above

这里要注意的是,我对初始卷积使用了周期性边界条件,该条件与傅立叶变换中发生的情况相匹配。其他边界条件将在边缘附近的逆变换中导致伪影。由于内核大小大于映像,因此整个映像将是一个很大的伪像,您将无法恢复任何内容。还要注意的是,要用零填充内核到图像的大小,我必须复制图像,因为内核大于图像。复制图像再次匹配由傅立叶变换造成的周期性边界条件。如果输入图像比卷积内核大得多,这两种技巧都可以忽略,就像您在正常情况下所期望的那样。

还请注意,如果不对卷积进行正则化,则输出全为NaN,因为我们将非常小的值除以非常小的值。内核的傅里叶变换中有很多接近零的值,因为模糊非常重。

最后,请注意,即使在模糊图像上添加少量噪点,也将不可能以可读文本的方式对图像进行解卷积。逆变换看起来非常好,但是文本笔画会失真得足以使字母不再容易识别:

same as above, but with a little bit of noise added after blurring


上面的代码使用DIPimage 3,它尚未安装官方二进制文件,需要从source构建。要使用DIPimage 2.x运行代码,需要进行一些更改:

  1. 必须使用dip_setboundary设置边界条件,而不是直接将边界条件传递给convolve函数。字符串'add zeros''periodic'是边界条件。

  2. ftift函数使用对称归一化,每个函数将其输出乘以1/sqrt(prod(imsize(image))),而在DIPimage 3中,归一化是{{1 }}代表1/prod(imsize(image)),而ift代表1。这意味着ft的傅立叶变换必须乘以kernel才能匹配DIPimage 3的结果:

    sqrt(prod(imsize(kernel)))