We often scan papers to convert them into images. We have various tools available online to enhance those images to make them brighter and remove any shading in those images. What if we can do that shading removal manually? We could just load any image as a gray-scale image into our code and obtain a output within seconds without the help of any app.
This can be achieved using basic Numpy manipulations and a few open CV functions. To explain the process the following image is used which is clicked from a phone.
There is a definite shading which needs to be removed. Let’s get started.
- Import the necessary packages into your environment. For easy display of images Jupyter Notebook is being used.
import numpy as np
import matplotlib.pyplot as plt
2. There are 2 things to note while performing the shading removal. As the image is a gray-scale image, if the image has a light background and darker objects, we have to perform max-filtering first, followed by min-filtering. If the image has a dark background and light objects, we can perform min-filtering first and then proceed with max-filtering.
So what exactly is max-filtering and min-filtering?
3. Max-filtering: Let us assume we have an image I of a certain size. The algorithm that we write should go through the pixels of I one by one, and for each pixel (x,y) it must find the maximum gray value in a neighborhood (a window of size N x N) around that pixel, and write that maximum gray value in the corresponding pixel location (x,y) in A. The resulting image A is called a max-filtered image of input image I.
Lets implement this concept in code.
- max_filtering() function takes in the input image and the window size N.
- It initially creates a ‘wall’ (pads with -1) around the input array which would help us when we iterate through edge pixels.
- We then create a ‘temp’ variable to copy our calculated max values into it.
- We then iterate through the array and create a window around the current pixel of size N x N.
- We then calculate the max value in that window using ‘amax()’ function and write that value in the temp array.
- We the copy this temp array into the main array A and return this as the output.
- A is the max-filtered image of input I.
def max_filtering(N, I_temp):
wall = np.full((I_temp.shape+(N//2)*2, I_temp.shape+(N//2)*2), -1)
wall[(N//2):wall.shape-(N//2), (N//2):wall.shape-(N//2)] = I_temp.copy()
temp = np.full((I_temp.shape+(N//2)*2, I_temp.shape+(N//2)*2), -1)
for y in range(0,wall.shape):
for x in range(0,wall.shape):
window = wall[y-(N//2):y+(N//2)+1,x-(N//2):x+(N//2)+1]
num = np.amax(window)
temp[y,x] = num
A = temp[(N//2):wall.shape-(N//2), (N//2):wall.shape-(N//2)].copy()
4. Min-filtering: This algorithm is exactly the same as max-filtering but instead of finding the maximum gray values in the neighborhood, we find the minimum values in the N x N neighborhood around that pixel, and write that minimum gray value in (x,y) in B. The resulting image B is called a min-filtered image of the image I.
Lets code this function.
def min_filtering(N, A):
wall_min = np.full((A.shape+(N//2)*2, A.shape+(N//2)*2), 300)
wall_min[(N//2):wall_min.shape-(N//2), (N//2):wall_min.shape-(N//2)] = A.copy()
temp_min = np.full((A.shape+(N//2)*2, A.shape+(N//2)*2), 300)
for y in range(0,wall_min.shape):
for x in range(0,wall_min.shape):
window_min = wall_min[y-(N//2):y+(N//2)+1,x-(N//2):x+(N//2)+1]
num_min = np.amin(window_min)
temp_min[y,x] = num_min
B = temp_min[(N//2):wall_min.shape-(N//2), (N//2):wall_min.shape-(N//2)].copy()
5. So, if a image has a lighter background, we want to perform max-filtering first which will give us a enhanced background and pass that max-filtered image into the min-filtering function which will take care of the actual content enhancement.
6. So, after performing the min-max filtering the values we obtain are not in the range of 0–255. So, we have to normalize the final array obtained using the background subtraction method which is subtracting the min-max filtered image with the original image to obtain the final image with the shading removed.
#B is the filtered image and I is the original image
def background_subtraction(I, B):
O = I - B
norm_img = cv2.normalize(O, None, 0,255, norm_type=cv2.NORM_MINMAX)
7. The variable N which is the window size for filtering is to be altered for the size of the particles or content in your image. For the test image the size N=20 was chosen. The final output image after being enhanced looks like:
The output image is more enhanced than the original image. The code implemented is a humble attempt to manually implement some of the pre-existing functions in open CV to enhance images. The entire notebook with the images can be found in the Github link.