图像的逆卷积

时间:2013-05-14 11:13:41

标签: image algorithm convolution

我有源图片和结果图片。我知道,在源上使用了一些卷积矩阵来得到结果。可以计算这个卷积矩阵吗?或者至少不是一个,但非常相似。

5 个答案:

答案 0 :(得分:21)

原则上,是的。只需使用FFT将两个图像转换为频率空间,然后将结果图像的FFT除以源图像的FFT。然后应用逆FFT得到卷积核的近似值。

要了解其工作原理,请注意空间域中的卷积对应于频域中的乘法,因此解卷积类似地对应于频域中的除法。在普通的去卷积中,可以将卷积图像的FFT除以内核的FFT以恢复原始图像。但是,由于卷积(如乘法)是一种可交换操作,因此内核和源的角色可以任意交换:内核对源进行卷积与源对卷内核进行卷积完全相同。

然而,正如其他答案所指出的那样,这不太可能产生内核的精确重建,原因与普通反卷积通常不会精确重建原始图像的原因相同:舍入和削波会在过程中引入噪声,并且卷积可以完全消除某些频率(通过将它们乘以零),在这种情况下,这些频率无法重建。

也就是说,如果你的原始内核尺寸有限(支持),重建内核通常应该在原点附近有一个尖峰,接近原始内核,被低级噪声包围。即使您不知道原始内核的确切大小,也不应该太难以提取此峰值并丢弃其余的重建。

实施例

这是灰度的Lenna test image,缩小为256×256像素,并在GIMP中与5×5内核进行卷积:

Original * KernelResult

我将使用Python和numpy / scipy进行反卷积:

from scipy import misc
from numpy import fft

orig = misc.imread('lena256.png')
blur = misc.imread('lena256blur.png')
orig_f = fft.rfft2(orig)
blur_f = fft.rfft2(blur)

kernel_f = blur_f / orig_f         # do the deconvolution
kernel = fft.irfft2(kernel_f)      # inverse Fourier transform
kernel = fft.fftshift(kernel)      # shift origin to center of image
kernel /= kernel.max()             # normalize gray levels
misc.imsave('kernel.png', kernel)  # save reconstructed kernel

内核的256×256图像以及中心周围7×7像素区域的缩放如下所示:

Reconstructed kernel Zoom of reconstructed kernel

将重建与原始内核进行比较,可以看出它们看起来非常相似;实际上,在重建中应用0.5到0.68之间的截止值将恢复原始内核。由于圆角和边缘效应,重建过程中峰值周围的微弱波纹是噪声。

我不完全确定是什么导致了重建中出现的十字形神器(尽管我确信有这些东西的经验的人可以告诉你),但是我的猜测是是因为它与图像边缘有关。当我在GIMP中卷积原始图像时,我告诉它通过扩展图像来处理边缘(基本上复制最外面的像素),而FFT反卷积假设图像边缘环绕。这可能会在重建过程中引入沿x和y轴的虚假相关。

答案 1 :(得分:2)

这是解卷积的经典问题。你所谓的卷积矩阵通常被称为“核心”。卷积运算通常用星号'*'表示(不要混淆乘法!)。使用这种表示法

Result = Source * Kernel

使用FFT的上述答案是正确的,但在噪声存在的情况下,您无法真正使用基于FFT的反卷积。正确的方法是使用Richardson-Lucy反卷积(见https://en.wikipedia.org/wiki/Richardson%E2%80%93Lucy_deconvolution

实施非常简单。这个答案还提供了一个示例Matlab实现:Would Richardson–Lucy deconvolution work for recovering the latent kernel?

答案 2 :(得分:1)

嗯,如果已知卷积矩阵大小的上限,则可以产生估计。如果它是N,则选择N * N个点并尝试基于源和目的地的数据来针对卷积系数求解线性方程系统。考虑到颜色分量的舍入,系统将无法求解,但通过线性编程,您可以通过对这些系数进行小的改动来最小化与预期值的总偏移。

答案 3 :(得分:1)

您可以尝试使用源图像作为内核执行deconvolution。但结果可能无法预测 - 由于噪声,边缘效应,舍入误差等原因,反卷积是一个非常不稳定的过程。

答案 4 :(得分:1)

我已经使用fftw3将@Ilmari Karonen answer重写为C / C ++给某些人,他可能会觉得它很方便:

循环移位功能

template<class ty>
void circshift(ty *out, const ty *in, int xdim, int ydim, int xshift, int yshift)
{
  for (int i =0; i < xdim; i++) 
  {
    int ii = (i + xshift) % xdim;
    for (int j = 0; j < ydim; j++) 
    {
      int jj = (j + yshift) % ydim;
      out[ii * ydim + jj] = in[i * ydim + j];
    }
  }
}

现在主要代码

int width = 256;
int height = 256;

int index = 0;

MyStringAnsi imageName1 = "C://ka4ag.png";    
MyStringAnsi imageName2 = "C://KyPu2.png";

double * in1 = new double[width * height];
fftw_complex * out1 = new fftw_complex[width * height]; 

double * in2 = new double[width * height];
fftw_complex * out2 = new fftw_complex[width * height]; 

MyUtils::MyImage * im1 = MyUtils::MyImage::Load(imageName1, MyUtils::MyImage::PNG);
MyUtils::MyImage * im2 = MyUtils::MyImage::Load(imageName2, MyUtils::MyImage::PNG);

for (int i = 0; i < width * height; i++)
{
    in1[i] = ((im1->Get(i).r / (255.0 * 0.5)) - 1.0);
    in2[i] = ((im2->Get(i).r / (255.0 * 0.5)) - 1.0);
}


fftw_plan dft_plan1 = fftw_plan_dft_r2c_2d(width, height, in1, out1, FFTW_ESTIMATE);    
fftw_execute(dft_plan1);
fftw_destroy_plan(dft_plan1);

fftw_plan dft_plan2 = fftw_plan_dft_r2c_2d(width, height, in2, out2, FFTW_ESTIMATE);    
fftw_execute(dft_plan2);
fftw_destroy_plan(dft_plan2);

fftw_complex * kernel = new fftw_complex[width * height];   

for (int i = 0; i < width * height; i++)
{
    std::complex<double> c1(out1[i][0], out1[i][1]);
    std::complex<double> c2(out2[i][0], out2[i][1]);

    std::complex<double> div = c2 / c1;

    kernel[i][0] = div.real();
    kernel[i][1] = div.imag();
}

double * kernelOut = new double[width * height];

fftw_plan dft_planOut = fftw_plan_dft_c2r_2d(width, height, kernel, kernelOut, FFTW_ESTIMATE);
fftw_execute(dft_planOut);
fftw_destroy_plan(dft_planOut);

double * kernelShift = new double[width * height];

circshift(kernelShift, kernelOut, width, height, (width/2), (height/2));

double maxKernel = kernelShift[0];
for (int i = 0; i < width * height; i++)
{
    if (maxKernel < kernelShift[i]) maxKernel = kernelShift[i]; 
}

for (int i = 0; i < width * height; i++)
{
    kernelShift[i] /= maxKernel; 
}

uint8 * res = new uint8[width * height];
for (int i = 0; i < width * height; i++)
{                   
   res[i] = static_cast<uint8>((kernelShift[i]+ 1.0) * (255.0 * 0.5));
}

//now in res is similar result as in @Ilmari Karonen, but shifted by +128

代码没有内存管理,所以你必须清理内存!