我有以下代码将高斯滤波器应用于任意图像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:
示例
编辑2:
试图重现克里斯的答案
我安装了dipimage_2.9
。我正在将macOS 10.14.2
与Matlab R2016a
一起使用。
花了我一段时间才能指定卷积的边界条件,因为DIPimage的convolve.m
仅接受image_in
和kernel
args。我最终为此使用了dip_setboundary
(DIPimage User Manual第9.2节)。
这是代码(我只是简单地添加了dip_setboundary
和cut
的作物区域的起源):
% 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
:
答案 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)) ))
(假设kernel
与img
的大小相同,我们通常必须先用零填充)。当使用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');
这是输出,顶部三分之一是输入图像,中间三分之一是模糊图像,底部三分之一是输出图像:
这里要注意的是,我对初始卷积使用了周期性边界条件,该条件与傅立叶变换中发生的情况相匹配。其他边界条件将在边缘附近的逆变换中导致伪影。由于内核大小大于映像,因此整个映像将是一个很大的伪像,您将无法恢复任何内容。还要注意的是,要用零填充内核到图像的大小,我必须复制图像,因为内核大于图像。复制图像再次匹配由傅立叶变换造成的周期性边界条件。如果输入图像比卷积内核大得多,这两种技巧都可以忽略,就像您在正常情况下所期望的那样。
还请注意,如果不对卷积进行正则化,则输出全为NaN,因为我们将非常小的值除以非常小的值。内核的傅里叶变换中有很多接近零的值,因为模糊非常重。
最后,请注意,即使在模糊图像上添加少量噪点,也将不可能以可读文本的方式对图像进行解卷积。逆变换看起来非常好,但是文本笔画会失真得足以使字母不再容易识别:
上面的代码使用DIPimage 3,它尚未安装官方二进制文件,需要从source构建。要使用DIPimage 2.x运行代码,需要进行一些更改:
必须使用dip_setboundary
设置边界条件,而不是直接将边界条件传递给convolve
函数。字符串'add zeros'
和'periodic'
是边界条件。
ft
和ift
函数使用对称归一化,每个函数将其输出乘以1/sqrt(prod(imsize(image)))
,而在DIPimage 3中,归一化是{{1 }}代表1/prod(imsize(image))
,而ift
代表1
。这意味着ft
的傅立叶变换必须乘以kernel
才能匹配DIPimage 3的结果:
sqrt(prod(imsize(kernel)))