Frequency Domain Filtering
Implement image filtering in the frequency domain using FFT.
Convolution in spatial domain = multiplication in frequency domain: F(f∗g)=F(f)⋅F(g)
Algorithm:
- Compute FFT of image: F=F(I)
- Create frequency domain filter H (same size as image)
- Multiply: G=F⋅H
- Inverse FFT: Ifiltered=F−1(G)
Common filters:
- Low-pass: Keep center (low frequencies) → blur
- High-pass: Keep edges (high frequencies) → sharpen
- Band-pass: Keep ring of frequencies
Example:
image = 64×64 image with edges filter_type = 'lowpass' cutoff = 0.1
Blurred image (edges smoothed)
- FFT shifts image to frequency domain
- Create circular mask: 1 inside radius cutoff*max_freq, 0 outside
- Multiply FFT by mask (kills high frequencies)
- Inverse FFT → blurred image
Low cutoff = more blur (fewer frequencies retained)
Constraints:
- image: 2D grayscale array
- filter_type: 'lowpass', 'highpass', or 'bandpass'
- cutoff: Cutoff frequency (0-1, fraction of max frequency)
- Return: Filtered image (real part)
- Background Knowledge
In image processing, a 2D image can be seen as a discrete function I(x,y) defined on a grid. The 2D Discrete Fourier Transform (DFT) represents this image in terms of spatial frequencies: low frequencies describe smooth, slowly varying regions; high frequencies describe rapid changes such as edges and fine details. The Fast Fourier Transform (FFT) is an efficient algorithm to compute the DFT in O(NlogN) time instead of O(N2).
A key property is the convolution theorem: convolution in the spatial domain corresponds to pointwise multiplication in the frequency domain:
F(f∗g)=F(f)⋅F(g)This is why we can implement filtering either by convolving with a kernel in the image domain, or by multiplying with a filter in the frequency domain. Low-pass filters keep low frequencies (central region in the spectrum) and suppress high ones, leading to blurring; high-pass filters do the opposite, enhancing edges and fine details. Band-pass filters keep only a ring of frequencies, removing both very low and very high components.
In practice, for discrete images, we work with the 2D FFT and its inverse (IFFT). Because the DFT assumes periodic extension, boundaries can create artifacts, and because the zero-frequency component is at the corner of the FFT output, we often shift the spectrum so that the DC/low frequencies are in the center (using operations like fftshift / ifftshift) to make filter design more intuitive.
- Algorithm / Approach Pattern
General pattern for frequency-domain filtering:
- Transform image to frequency domain with 2D FFT.
- Construct a filter mask H(u,v) (same size as the image) that specifies which frequencies to keep/attenuate.
- Multiply the spectrum and the filter elementwise.
- Transform back with inverse 2D FFT and take the real part.
You can vary only the design of H to implement low-pass, high-pass, or band-pass filters while keeping the same overall pipeline.
- Step-by-Step Strategy
Assume a grayscale image I of size (H, W):
- Convert to floating point
I = I.astype(np.float32)
- Compute 2D FFT
F = np.fft.fft2(I)
F_shifted = np.fft.fftshift(F) # optional but helpful for designing H
- Create frequency grid
- Get indices:
rows, cols = I.shape
crow, ccol = rows // 2, cols // 2
- Create a distance map from the center (for circular filters):
u = np.arange(rows) - crow
v = np.arange(cols) - ccol
V, U = np.meshgrid(v, u)
D = np.sqrt(U**2 + V**2)
- Design filter H (same size as image)
- Ideal low-pass with cutoff radius D0:
H = (D <= D0).astype(np.float32)
- Ideal high-pass:
H = (D >= D0).astype(np.float32)
- Band-pass between D1 and D2:
H = ((D >= D1) & (D <= D2)).astype(np.float32)
- Apply filter
G_shifted = F_shifted * H
G = np.fft.ifftshift(G_shifted)
- Inverse FFT and post-process
I_filtered_complex = np.fft.ifft2(G)
I_filtered = np.real(I_filtered_complex)
- Optionally normalize or clip:
I_filtered = np.clip(I_filtered, 0, 255).astype(np.uint8)
- (Optional) For colored images
- Apply the same process per channel (R, G, B) or convert to another color space and filter only luminance.
- Common Pitfalls
- Not matching shapes: H must be exactly the same size as the FFT of the image; mismatches cause broadcasting errors.
- Forgetting fftshift / ifftshift consistency:
- If you design H assuming low frequencies are at the center, you must apply it to the shifted spectrum and then unshift before IFFT.
- Ignoring complex output:
- After IFFT, results are complex due to numerical error; you should take np.real(...) before displaying or saving.
- Ringing artifacts:
- Ideal (sharp cutoff) filters often cause ringing (Gibbs phenomenon). In practice, smoother filters (Gaussian, Butterworth) reduce artifacts.
- Border artifacts & padding:
- Convolution via DFT is circular. If you want linear convolution, zero-pad image and filter appropriately before FFT.
- Scaling issues:
- Some libraries scale FFT/IFFT differently. Stick to the same FFT/IFFT pair from one library to avoid extra scaling factors.
- Time & Space Complexity
Let the image size be N=H×W.
-
Time complexity
-
2D FFT: O(NlogN)
-
Elementwise multiplication with H: O(N)
-
2D inverse FFT: O(NlogN) Overall: O(NlogN)
-
Space complexity
-
Need space for the image, the complex spectrum, the filter H, and the filtered spectrum. Each is O(N). Overall: O(N) additional space (with a constant factor for complex arrays and the mask).