The trick is to properly compensate for the fact that Frequency Domain multiplication applies a convolution with the circular boundary conditions in the spatial domain.
You may use the following code:
gaussianKernelStd = 2;
gaussianKernelRadius = ceil(5 * gaussianKernelStd);
mI = im2double(imread('cameraman.tif'));
mI = mI(:, :, 1);
numRows = size(mI, 1);
numCols = size(mI, 2);
vX = [-gaussianKernelRadius:gaussianKernelRadius].';
vK = exp(-(vX .* vX) ./ (2 * gaussianKernelStd * gaussianKernelStd));
mK = vK * vK.';
mK = mK ./ sum(mK(:)); %<! The Gaussian Kernel
mIPad = padarray(mI, [gaussianKernelRadius, gaussianKernelRadius], "replicate", 'both'); %<! For the replicate array
mKC = CircularExtension2D(mK, size(mIPad, 1), size(mIPad, 2)); %<! Circular extension for the 2D Kernel
startIdx = gaussianKernelRadius + 1;
mIFiltered = ifft2(fft2(mIPad) .* fft2(mKC), 'symmetric');
mIFiltered = mIFiltered(startIdx:(startIdx + numRows - 1), startIdx:(startIdx + numCols - 1)); %<! Removing the padding
mIFilteredRef = imfilter(mI, mK, 'replicate', 'same', 'conv'); %<! Reference
max(abs(mIFilteredRef(:) - mIFiltered(:))) %<! Should be very very low
CircularExtension2D() is given In my StackExchange Signal Processing Q38542 GitHub Repository. It was taken from my answer to Applying Image Filtering (Circular Convolution) in Frequency Domain.
The steps the code implements are as following:
- Pad the image in order to have Replicate boundary condition convolution.
- Convert the spatial domain kernel into a form which matches the image in frequency domain. We assume top left of the image is
(0, 0) in spatial domain. So we need the
(0, 0) of the kernel to also be in the top left corner.
- Apply circular convolution using frequency domain.
As you can see, the result is prefect.
In my answer to How Much Zero Padding Do We Need to Perform Filtering in the Fourier Domain? I implemented a MATLAB Function,
ImageFilteringFrequencyDomain(), to apply Frequency Domain Convolution with the border conditions supported in
In your case, the kernel is
5x5 which is very small.
For small kernels and a single image it is better to apply the convolution in the spatial domain. It will be much faster.