Enhancing Newspaper Photos and Decoding Noisy Number Plates with Frequency Filters and Spatial Techniques

Prakhar Patel
7 min readJun 14, 2023

A Journey into Image Restoration and Optical Deciphering for Clearer Newspaper Images and Legible Number Plates

What is main goal of this task or project

we have a noisy image of a car with an unreadable license plate, and we want to improve the license plate visibility using image processing techniques.
Image processing involves manipulating digital images to enhance their quality, extract useful information, and correct distortions caused by image acquisition or transmission.
The ultimate-goal of this image processing task is to make the license plate readable so that we can extract useful information from it. This could be useful in various applications, such as traffic surveillance, law enforcement, or parking management.

Let’s gets started………

1. Import necessary Libraries for given task

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
import cv2
from numpy.fft import fft2, fftshift, ifft2,ifftshift
from scipy import signal

Here, we are going to use OpenCV libraries for computer vision affiliated tasks.

2. Read image as gray-scale image

  • The reason for doing Grayscale conversion is to simplify image processing tasks such as edge detection, segmentation and enhancing contrast.
image = cv2.imread('car.png',cv2.IMREAD_GRAYSCALE)
image= cv2.resize(image,(500,333))
imshow(image,cmap='gray')

3. Calculate the Discrete Fourier transform of the car image.

  • The discrete Fourier transform (DFT) is a mathematical algorithm used to transform a time-domain signal into its frequency-domain representation. It decomposes a signal into its constituent frequencies, which can be useful for analyzing the frequency content of a signal and for filtering out unwanted frequencies.
  • For this step, we calculated the 2D discrete Fourier transform (DFT) of the input grayscale image of the car using the Fast Fourier Transform (FFT) algorithm.
#apply 2d DFT
f=np.fft.fft2(image)

4. Shift the origin of the image domain to the center, and calculate the magnitude of the Fourier transform.

  • we calculated the magnitude spectrum of the Fourier transform using the np.abs() function and applied a logarithmic transformation to suppress the large dynamic range of the magnitude values and make it more visible

#shift to center
fshift=np.fft.fftshift(f)

#magnitude
magnitude_spectrum=20*np.log(np.abs(fshift))
#visualizing
plt.subplot(121), plt.imshow(image, cmap='gray')
plt.title('Input image'), plt.xticks([]),plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude'), plt.xticks([]),plt.yticks([])
plt.show()

5. Notice that white peaks in the Fourier domain capture periodic noise present in the original image.

  • To remove the periodic noise, we used the “peak_local_max” function to find the coordinates of the local maxima, in the log-transformed magnitude of the Fourier transform. We set the “min_distance” parameter to 6, which is a reasonable estimate based on the image of the magnitude. This means we are only selecting peaks that are at least 6 pixels apart. This helps to avoid selecting false peaks that may be caused by noise or other artifacts in the image.
  • The shifted Fourier transform at each point (u,v) is the value of the Fourier transform after shifting it so that the center of the frequency domain corresponds to the zero frequency. This is necessary for taking the inverse Fourier transform and obtaining the filtered image.
# coordinates of the white peaks in the image the center is [150, 175]
from skimage.feature import peak_local_max
coordinates = peak_local_max(magnitude_spectrum, min_distance= 6, exclude_border=0)

coordinates

7. You want to clean the original image in the Fourier domain such that its magnitude only has a single bright “star” in the center. Given below is a script that helps you experiment with the width of the filter (2L+1 x 2L+1) that covers frequency components of the periodic noise that we want to remove.

#clean the image to get single star in the center
L = 3
dx,dy=np.shape(image)[0], np.shape(image)[1]
new= magnitude_spectrum.copy()

for coord in coordinates:
i=coord[0]
j=coord[1]
if i==dx//2 and j==dy//2:
continue
else:
for k1 in np.arange(-L,L,1):
for k2 in np.arange(-L,L,1):
if i+k1>=0 and j+k2>=0 and i+k1<dx and j+k2<dy:
new[i+k1,j+k2]=0
fshift[i+k1,j+k2]=0 # shifted DFT of car image
imshow(new, cmap='gray')
plt.title("The size of the neighbourhood is "+str(2*L+1)+"x"+str(2*L+1))

8. Perform the inverse Fourier transform (using modified frequency components) to get the image back. Visualize your result.

  • · The Inverse Fourier Transform is the reverse operation of the Fourier Transform. It is used to convert a signal that has been transformed into the frequency domain back to the time domain. This is useful when you want to analyze or manipulate a signal in the frequency domain, but then need to convert it back to the time domain to perform operations on it.
# Perform inverse Fourier transform to obtain the filtered image
filtered_image = np.fft.ifftshift(fshift)
filtered_image = ifft2(filtered_image)

# Take the real part of the filtered image (imaginary part is negligible)
filtered_image = np.real(filtered_image)
image_back= (filtered_image-np.min(filtered_image))*255.0/(np.max(filtered_image)-np.min(filtered_image))

# Visualize the filtered image
plt.imshow(image_back, cmap='gray')
plt.title('Filtered Image')

9. Slice image to get only number plate region.

import skimage
from skimage import transform
#scaled= transform.rescale(image_back,1.2, anti_aliasing=False)
image_back = image_back[150:280,100:300]
plt.imshow(image_back,cmap='gray')

10. Apply gaussian filter

  • Because it is renowned for its capacity to eliminate localized noise in the Fourier domain.
  • The Gaussian filter is a low-pass filter that keeps low-frequency components, which are linked to the structure of the image, while attenuating high-frequency components, which are often connected to noise or minute details.
from scipy import signal

# create a low-pass Gaussian filter
kernel = np.outer(signal.gaussian(image_back.shape[0], 1), signal.gaussian(image_back.shape[1], 1))
# find Fourier transform of the image f(x,y)
freq = fft2(image_back)
# generate a kernel whose origin is in the top-left corner
kern = ifftshift(kernel) # h(x,y)
# calculate FFT of the kernel
freq_kernel = fft2(kern)
# multiply in the frequency domain
low_pass_product = freq * freq_kernel
# compute the low-pass filtered image
low_pass_im = ifft2(low_pass_product).real

plt.imshow(low_pass_im, cmap='gray')

11. Apply gamma correction to improve contrast.

  • Gamma correction is a nonlinear adjustment that is applied to the intensity values of an image, which can result in darker areas becoming darker and brighter areas becoming brighter, depending on the gamma value used.
  • A gamma value less than 1 will make the dark areas of the image brighter, while leaving the bright areas relatively unchanged. This is sometimes referred to as “gamma compression.” Conversely, a gamma value greater than 1 will make the dark areas of the image darker and the bright areas brighter. This is sometimes referred to as “gamma expansion.”
from skimage import exposure,img_as_float

fig,ax = plt.subplots(nrows=2, ncols=2, figsize=(10,5))
# Gamma corrected
gamma_corrected = exposure.adjust_gamma(image_back, 2)

# logarithmic corrected
logarithmic_corrected = exposure.adjust_log(image_back, 1)

ax[0,0].imshow(gamma_corrected,cmap='gray')
ax[0,1].imshow(logarithmic_corrected,cmap='gray')
ax[1,0].hist(gamma_corrected.ravel(), bins=30, histtype='step', color='b' )
ax[1,1].hist(logarithmic_corrected.ravel(), bins=30, histtype='step', color='b' )

12. Choose a midpoint around which you want to stretch contrast and normalize the previous output

  • Contrast stretching is a technique in image processing used to adjust the contrast of an image. It is often used to improve the visual quality of images that appear washed out or have low contrast. Contrast stretching involves rescaling the intensity values of an image to spread the range of values across the full dynamic range of the image. This can help to reveal more detail in the image and make it appear more visually appealing.
def normalize(intensity, m, E):
I = intensity
dx, dy = np.shape(intensity)[0], np.shape(intensity)[1]
eps = 0.001
cs = np.zeros((dx, dy))
for i in range(dx):
for j in range(dy):
cs[i, j] = (I[i, j] - m) * (E / (1 - m - eps))
return cs

# choose a midpoint of 128 and maximum intensity value of 255
m = 100
E = 200

# apply contrast stretching to the Fourier-reconstructed image
g = normalize(gamma_corrected, m, E)

# display the resulting image
plt.imshow(g, cmap='gray')

Findings

After normalizing, we got the image segmented region to a certain clarity but not fully though. It could be as BHT 042 / BHT 012. We could see the number plate clearly in gamma and logarithmic corrections but not that clear when normalized.

Thanks for Reading

If you like my work and want to support me…

  1. The BEST way to support me is by following me on Medium here.
  2. Be one of the FIRST to follow me on Instagram here.
  3. Follow me on LinkedIn here.
  4. Follow me on GitHub here.

--

--

Prakhar Patel

Hello, I’m a computer student passionate about data science. I believe the best way to broaden our knowledge is to share it with people.