Восстановление исходного изображения после размытия - PullRequest
0 голосов
/ 22 декабря 2018

У меня есть следующий код, который применяет фильтр Гаусса к произвольному изображению 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.2 с Matlab R2016a.

Мне потребовалось некоторое время, чтобы понять, как задать граничные условия для сверток, поскольку convolve.m DIPimage принимает только аргументы image_in и kernel.Для этого я использовал dip_setboundary ( Руководство пользователя DIPimage раздел 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:

after convolution

Ответы [ 2 ]

0 голосов
/ 24 декабря 2018

Давайте посмотрим на код в вопросе для одного канала, предполагая, что 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. Википедия имеет хорошее описание деконволюции Винера.

Демо

Я использую MATLAB с DIPimage 3, чтобы сделать быструю демонстрацию (гораздо меньше усилий для меня, чем стрельбадо 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.xa необходимо внести несколько изменений:

  1. Граничное условие должно быть установлено с помощью dip_setboundary, вместо того, чтобы передавать его непосредственно в convolveфункция.Строки 'add zeros' и 'periodic' являются граничными условиями.

  2. Функции ft и ift используют симметричную нормализацию, каждая из которых умножает свой вывод на 1/sqrt(prod(imsize(image))), тогда какв DIPimage 3 нормализация - это более распространенное умножение на 1/prod(imsize(image)) для ift и 1 для ft.Это означает, что преобразование Фурье kernel должно быть умножено на sqrt(prod(imsize(kernel))), чтобы соответствовать результату DIPimage 3:

    c = real(ift(ft(b)/((ft(kernel)*sqrt(prod(imsize(kernel))))+1e-6)));
    
0 голосов
/ 22 декабря 2018

Вы не можете - размытие приводит к потере информации при усреднении.

Рассмотрим 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 пикселей и 2-мерные свертки по 3x3,5x5,7x7, ... матрицы, делающие реверсирование непрактичным.

Сделайте это двумерным, вы могли бы быть в состоянии решить его математически - но чащетогда вы не получите мириады решений и очень сложных ограничений для их решения, если применить это к двумерной свертке и 3000 * 2000 пикселей.

...