I'm working on an image processing problem and wondering if DFT(rotation(image)) == rotation(DFT(image)) (1). My final goal is to apply rotations in the Fourier domain then do an inverse Fourier transform and get the rotated image.
This question is pretty close to what I'm looking for. That question links to ressources saying that (1) is true in theory (for infinite signals and infinite frequency bins), and the accepted answer shows that it is so mathematically. However, the answers also point out that it may be actually hard to reproduce in the "real world" with DFT algorithms, because of artefacts.
I've tried to reproduce (1) but have so far failed to do so, and I'm under the impression that something is wronger than just "artefacts". My experiments can be summed up in the following image :
The images I'm showing here in the Fourier domain are zooms of the imaginary parts of the FTs, with normalized intensity to enhance visualization. However, the point stands for the real part, and also for magnitude and phase spectrums if you choose to visualize them like so.
As you can see, I am not getting results consistent with (1), as the two images of the last column are not equal (at all). And I feel like the problem is in the rotation of the Fourier image. The rotated Fourier image looks nothing like the Fourier of the rotated. I actually don't really get how the latter is computed: even with a 45 degrees rotation, the middle part stays about the same for instance (look at the 4 black neighbours of the central white pixels, they are the same in both images of column 2).
To emphasize my point, I made an animation showing (left) the FT of the original image, (middle) the rotation of the Fourier, (right) the Fourier of the rotation, for angles varying between 0 and 90
I've tried more severe padding of the original image, am careful with bit depths, have tried rotation magnitude and phase instead of real and imaginary parts, with no luck so far. Is there something I'm doing very wrong ? How can I achieve my goal to "correctly" apply rotation in the Fourier domain ?
Here is the code to reproduce the bulk of the experiments (in Python, using OpenCV and Scipy) as well as the original image
import numpy as np
import cv2
from scipy import ndimage
# Helper function to save an image with a normalized range for visualization
def normalize_and_cvt_to_uint8(im):
min_ = np.min(im)
max_ = np.max(im)
im_norm = (im - min_) / (max_ - min_) * 255
return im_norm.astype(np.uint8)
# Rotate a complex image
def rot(im_tot, angle):
# Get real and imaginary part
im_real = im_tot[:,:,0]
im_imag = im_tot[:,:,1]
# Rotate each part individually
im_real_rot = ndimage.rotate(im_real, angle, reshape = False)
im_imag_rot = ndimage.rotate(im_imag, angle, reshape = False)
# Recreate complex image
im_rot = np.dstack((im_real_rot, im_imag_rot))
return im_rot
# Helper function to pad an image to a given shape
def pad_to_shape(im, goal_h, goal_w):
[h, w] = im.shape[:2]
delta_w = goal_w - w
delta_h = goal_h - h
top, bottom = delta_h // 2, delta_h - (delta_h // 2)
left, right = delta_w // 2, delta_w - (delta_w // 2)
return cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=0)
path_im = "poivrons.png"
im = cv2.imread(path_im, 0)
im_shape = 801
im = pad_to_shape(im, im_shape, im_shape)
cv2.imwrite("starting_im.png", im)
# Compute DFT of original image
dft1 = cv2.dft(np.float32(im), flags=cv2.DFT_COMPLEX_OUTPUT)
dft1 = np.fft.fftshift(dft1)
cv2.imwrite("dft1.png", normalize_and_cvt_to_uint8(dft1[:,:,1]))
# Rotate original image
angle = 45
im_rot = ndimage.rotate(im, angle, reshape=False)
cv2.imwrite("rotated_img.png", im_rot)
# Compute DFT of rotated image
dft2 = cv2.dft(np.float32(im_rot), flags=cv2.DFT_COMPLEX_OUTPUT)
dft2 = np.fft.fftshift(dft2)
cv2.imwrite("dft2.png", normalize_and_cvt_to_uint8(dft2[:,:,1]))
# Rotate DFT of original image
dft3 = rot(dft1, angle)
dft3 = np.fft.fftshift(dft3)
# Compute IDFT of rotated DFT
cv2.imwrite("dft3.png", normalize_and_cvt_to_uint8(dft3[:,:,1]))
im_back = cv2.idft(np.fft.ifftshift(dft3), flags=cv2.DFT_SCALE | cv2.DFT_REAL_OUTPUT)
cv2.imwrite("back_img.png", im_back)