Обратная свертка изображения

У меня есть изображение источника и результата. Я знаю, что некоторая матрица свертки была использована на источнике для получения результата. Можно ли вычислить эту матрицу свертки ? Или, по крайней мере, не точная, но очень похожая.

5 ответов


в принципе, да. Просто преобразуйте оба изображения в частотное пространство с помощью ФФТ и разделить БПФ результирующего изображения на БПФ исходного изображения. Затем примените обратный БПФ, чтобы получить аппроксимацию ядра свертки.

чтобы понять, почему это работает, обратите внимание, что свертка в пространственной области соответствует умножению в частотной области, и поэтому деконволюция аналогично соответствует делению в частотной области. В обычной деконволюции, чтобы восстановить исходное изображение, нужно разделить БПФ свернутого изображения на БПФ ядра. Однако, поскольку свертка (как и умножение) является коммутативной операцией, роли ядра и источника могут произвольно меняться: свертывание источника ядром точно такое же, как свертывание ядра источником.

тем не менее, если ваше исходное ядро имеет ограниченный размер( поддержка), реконструированное ядро обычно должно иметь один острый пик вокруг начала координат, приближаясь к исходному ядро, окруженное низким уровнем шума. Даже если вы не знаете точного размера исходного ядра, не должно быть слишком сложно извлечь этот пик и отбросить остальную часть реконструкции.

пример:

здесь Ленна тестовое изображение в оттенках серого, уменьшенных до 256×256 пикселей и свернутых с ядром 5×5 в GIMP:

OriginalKernelResult

Я буду использовать 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 kernelZoom of reconstructed kernel

сравнивая реконструкцию с исходным ядром, вы можете видеть, что они выглядят довольно похожими; действительно, применение среза где-то между 0.5 и 0.68 к реконструкции восстановит исходное ядро. Слабые пульсации, окружающие пик в реконструкции, являются шумом из-за округление и краевые эффекты.

Я не совсем уверен, что вызывает крестообразный артефакт, который появляется в реконструкции (хотя я уверен, что кто-то с большим опытом работы с этими вещами может сказать вам), но с моей головы, я предполагаю, что это имеет какое-то отношение к краям изображения. Когда я свернул исходное изображение в GIMP, я сказал ему обрабатывать края, расширяя изображение( по существу копируя внешние пиксели), тогда как деконволюция FFT предполагает что края изображения обернуты вокруг. Это может также ввести паразитные корреляции вдоль осей x и y при реконструкции.


ну, оценка может быть получена, если известна верхняя граница для размера матрицы свертки. Если это N, выберите n * n точек и попытайтесь решить систему линейных уравнений с коэффициентами свертки на основе данных источника и назначения. Учитывая округление цветовых компонентов, система не будет решать, но с помощью линейного программирования вы сможете минимизировать общее смещение от ожидаемых значений небольшими изменениями этих коэффициентов.


вы можете попробовать выполнить деконволюции с исходным изображением в качестве ядра. Но результаты могут быть непредсказуемыми - деконволюция - очень неустойчивый процесс из-за шума, краевых эффектов, ошибок округления и т. д.


я переписал @Ilmari Karonen ответ на C / C++ с помощью fftw3 для кого-то, кто может найти его удобным:

круговая функция сдвига

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

код не имеет управления памятью, поэтому вы должны очистить свою память !


Это классическая задача деконволюции. То, что вы назвали матрицей свертки, обычно называется "ядром". Операция свертки часто обозначается звездой ' * ' (не путать с умножением!). Используя эту нотацию

Result = Source * Kernel

ответы выше, используя БПФ, верны, но вы не можете использовать деконволюцию на основе БПФ в присутствии шума. Правильный способ сделать это-использовать деконволюцию Ричардсона-Люси (см. https://en.wikipedia.org/wiki/Richardson%E2%80%93Lucy_deconvolution)

Это довольно просто реализовать. Этот ответ также предоставляет пример реализации Matlab:будет ли деконволюция Ричардсона–Люси работать для восстановления скрытого ядра?